//////////////////////////////////////////////////////////////////////////////// // 单元用途:身份证号码相关处理 // // 单元作者:池龙 // //////////////////////////////////////////////////////////////////////////////// // 备 注: // // 区划代码转换为名称的部分,需要从外部文件中读取区划定义的数据(如随单 // // 元发布的文件 regioncode.dat )。 // // // // 该文件为纯文本文件,每行是一个代码和名称的对应关系。代码和名称之间以 // // 逗号作分隔。以#开头的行作为注释行,空行不处理。程序中作了一定的容错处理, // // 通常情况下,不正确的数据会被略过。但在重新编排时,还是请尽量保证数据的正 // // 确性。 // // // // 另外,为了保证查找的效率,所以要求文件中的记录按区划代码由小到大的顺 // // 序排列。 // //////////////////////////////////////////////////////////////////////////////// unit ascidnum; interface uses Controls, Classes, SysUtils; type //性别 TSex = (sexUnknown, //未知 sexMale, //男性 sexFemale); //女性 //身份证号码的类型 TIDNumType = (idnum15, //15位 idnum18); //18位 //校验结果 TChecksumRst = (csNoChecksum, //无校验(15位) csChecksumError, //错误 csChecksumCorrect); //正确 //个人信息 TIDNumInfo = record IDNumType : TIDNumType; //身份证号码类型 RegionCode : string; //区划代码 RegionStr : string; //区划名称 BirthDay : TDate; //生日 Sex : TSex; //性别 ChecksumRst : TChecksumRst; //校验结果 end; //区划代码对应关系 TRegionCodeTransRec = record RegionCode: string; //代码 RegionStr : string; //名称 end; TRegionCodeTransArray = array of TRegionCodeTransRec; //分离过的区域代码 TSepRegionCode = record ProvinceCode : string; //省级代码 CityCode : string; //市级代码 CountyCode : string; //区县级代码 end; TStrArray = array of string; const //18位身份证号码的校验方式中使用的常量 //从左向右,除了末尾的校验位之外,每位的权值 cPosPowerArray : array[1..17] of integer = (7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2); //得到的校验值对应的第18位字符 cCheckSumValueArray : array[0..10] of char = ('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'); //取身份证号码中的信息 //为了加快速度,不将区划代码进行转换 //需要取得确切的区划名称,用GetRegionStr函数 function GetIDNumInfo(IDNum: string; var IDNumInfo: TIDNumInfo): boolean; //检查身份证号码的有效性 //内部仍然是利用GetIDNumInfo函数 function IsIDNumValid(IDNum: string): boolean; //将15位身份证号码扩展为18位 function ExpandIDNum(IDNum15: string; var IDNum18: string): boolean; //根据区划代码取相应的字符串 function GetRegionStr(RegionCode: string; FromFileName: string): string; overload; function GetRegionStr(RegionCode: string): string; overload; function GetRegionStr(var IDNumInfo: TIDNumInfo; FromFileName: string): boolean; overload; function GetRegionStr(var IDNumInfo: TIDNumInfo): boolean; overload; //从文件中区划代码信息 function LoadRegionDat(FromFileName: string; var RegionCodeArray: TRegionCodeTransArray): boolean; overload; //下面这个函数的作用是直接将区划代码信息读入到本单元的公用变量里 function LoadRegionDat(FromFileName: string): boolean; overload; implementation var RegionCodeTransArray : TRegionCodeTransArray; //判断这个字符是否为数字 function IsDigiChar(TestChar: Char): boolean; begin //直接判断文字 Result := TestChar in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; //当然这里也可以使用判断字符ASCII值所在区间的方法,如: // Result := ord(TestChar) in [ord('0')..ord('9')]; end; //判断一个字符串是否全部由数字组成 function IsDigiStr(TestStr: string): boolean; var Count0 : integer; begin if Length(TestStr) > 0 then begin Result := true; for Count0 := 1 to Length(TestStr) do if not IsDigiChar(TestStr[Count0]) then begin Result := false; Break; end; end else Result := false; end; //用于生成18位身份证号码中的校验字符 //注意:此函数不检查前17位的有效性以及其是否为数字 function GenIDNumCheckSumChar(IDNum18: string): char; var DataSum : integer; Count0 : integer; begin if length(IDNum18) = 18 then begin DataSum := 0; //将前17位上的各个数字加权累加 for Count0 := 1 to 17 do Inc(DataSum, StrtoInt(IDNum18[Count0]) * cPosPowerArray[Count0]); //结果取模,再从字典中得到对应的字符 Result := cCheckSumValueArray[DataSum mod 11]; end else Result := ' '; end; //将15位身份证号码扩展为18位 function ExpandIDNum(IDNum15: string; var IDNum18: string): boolean; var IDNumInfo: TIDNumInfo; TmpIDNum : string; begin Result := false; IDNum18 := ''; //先检察原有的号码是否正确 if (length(IDNum15) = 15) and GetIDNumInfo(IDNum15, IDNumInfo) then begin //行政区划代码 + 生日 + 顺序码 + 校验 TmpIDNum := IDNumInfo.RegionCode + formatdatetime('yyyymmdd', IDNumInfo.BirthDay) + Copy(IDNum15, 13, 3) + ' '; //生成校验码,补到末位 TmpIDNum[18] := GenIDNumCheckSumChar(TmpIDNum); IDNum18 := TmpIDNum; Result := true; end; end; function GetIDNumInfo(IDNum: string; var IDNumInfo: TIDNumInfo): boolean; var ToProcess : boolean; IDNumLen, Year, Month, Day : word; SexMark : char; begin Result := false; FillChar(IDNumInfo, sizeof(IDNumInfo), #0); IDNumLen := length(IDNum); if IDNumLen in [15, 18] then begin ToProcess := false; //检查字符串是否符合要求 case IDNumLen of //15位身份证号码,全部要求为数字 15 : begin ToProcess := IsDigiStr(IDNum); IDNumInfo.IDNumType := idnum15; IDNumInfo.ChecksumRst := csNoChecksum; end; //18位身份证号码,前17位必须是数字,最后一位除数字外,可能是"X" 18 : begin ToProcess := IsDigiStr(Copy(IDNum, 1, 17)) and (IsDigiChar(IDNum[18]) or (IDNum[18] in ['x', 'X'])); IDNumInfo.IDNumType := idnum18; end; end; if ToProcess then begin //取区划代码 IDNumInfo.RegionCode := Copy(IDNum, 1, 6); //取生日信息 case IDNumInfo.IDNumType of idnum15 : begin Year := strtoint('19' + Copy(IDNum, 7, 2)); Month := strtoint(Copy(IDNum, 9, 2)); Day := strtoint(Copy(IDNum, 11, 2)); end; idnum18 : begin Year := strtoint(Copy(IDNum, 7, 4)); Month := strtoint(Copy(IDNum, 11, 2)); Day := strtoint(Copy(IDNum, 13, 2)); end; else begin Year := 0; Month := 0; Day := 0; end; end; //尝试将生日信息转换成日期 try IDNumInfo.BirthDay := EncodeDate(Year, Month, Day); except ToProcess := false; end; if ToProcess then begin //取性别 SexMark := '1'; //取性别标志 case IDNumInfo.IDNumType of idnum15 : SexMark := IDNum[15]; idnum18 : SexMark := IDNum[17]; end; //判断性别 case strtoint(SexMark) mod 2 of 0 : IDNumInfo.Sex := sexFemale; 1 : IDNumInfo.Sex := sexMale; end; //如果是18位的身份证,判断校验是否正确 if IDNumInfo.IDNumType = idnum18 then begin if CompareText(GenIDNumCheckSumChar(IDNum), IDNum[18]) = 0 then begin IDNumInfo.ChecksumRst := csChecksumCorrect; Result := true; end; end else Result := true; end; end; end; end; //判断身份证号码是否正确,通过从中取信息是否成功来判定。 function IsIDNumValid(IDNum: string): boolean; var TmpIDNumInfo : TIDNumInfo; begin Result := GetIDNumInfo(IDNum, TmpIDNumInfo); end; //将区划代码转换成名称 function RegionCode2Str(RegionCode: string; RegionCodeArray: TRegionCodeTransArray): string; //在列表中搜索指定的区划代码所对应的名称 //用折半查找,所以要求输入的对应关系必须是有序的 function SearchRegionStr(SearchRegionCode: string): string; var SearchFrom, SearchTo, CheckIdx, SearchValue, CheckValue : integer; begin Result := ''; SearchFrom := low(RegionCodeArray); SearchTo := high(RegionCodeArray); SearchValue := StrToInt(SearchRegionCode); while SearchFrom <= SearchTo do begin CheckIdx := (SearchFrom + SearchTo) div 2; CheckValue := StrToInt(RegionCodeArray[CheckIdx].RegionCode); if CheckValue = SearchValue then begin Result := RegionCodeArray[CheckIdx].RegionStr; Break; end else if CheckValue > SearchValue then begin SearchTo := CheckIdx - 1; end else if CheckValue < SearchValue then begin SearchFrom := CheckIdx + 1; end; end; end; var SepRegionCode : TSepRegionCode; TmpStr, SearchRst : string; begin Result := ''; //区划代码为6位 if (length(RegionCode) = 6) and IsDigiStr(RegionCode) and (length(RegionCodeArray) > 0) then begin //分解区划代码 SepRegionCode.ProvinceCode := Copy(RegionCode, 1, 2); SepRegionCode.CityCode := Copy(RegionCode, 3, 2); SepRegionCode.CountyCode := Copy(RegionCode, 5, 2); //省级的区划代码不包括11以下的组合 if StrToInt(SepRegionCode.ProvinceCode) > 10 then begin //列表内搜索省的名称 TmpStr := ''; SearchRst := SearchRegionStr(SepRegionCode.ProvinceCode + '0000'); if length(SearchRst) > 0 then begin TmpStr := SearchRst; //在列表内搜索市的名称 SearchRst := SearchRegionStr(SepRegionCode.ProvinceCode + SepRegionCode.CityCode + '00'); if length(SearchRst) > 0 then begin if not((SearchRst = '市辖区') or (SearchRst = '县')) then TmpStr := TmpStr + SearchRst; //在列表内搜索县的名称 SearchRst := SearchRegionStr(SepRegionCode.ProvinceCode + SepRegionCode.CityCode + SepRegionCode.CountyCode); if length(SearchRst) > 0 then begin if not((SearchRst = '市辖区') or (SearchRst = '县')) then TmpStr := TmpStr + SearchRst; end; end; end; Result := TmpStr; end; end; end; //取区划名称 function GetRegionStr(RegionCode: string; FromFileName: string): string; var RegionCodeArray: TRegionCodeTransArray; begin if LoadRegionDat(FromFileName, RegionCodeArray) then Result := RegionCode2Str(RegionCode, RegionCodeArray) else Result := ''; end; //取区划名称 function GetRegionStr(RegionCode: string): string; begin Result := RegionCode2Str(RegionCode, RegionCodeTransArray); end; //取区划名称 function GetRegionStr(var IDNumInfo: TIDNumInfo; FromFileName: string): boolean; var RegionStr : string; begin RegionStr := GetRegionStr(IDNumInfo.RegionCode, FromFileName); if not (RegionStr = '') then begin IDNumInfo.RegionStr := RegionStr; Result := true; end else Result := false; end; //取区划名称 function GetRegionStr(var IDNumInfo: TIDNumInfo): boolean; var RegionStr : string; begin RegionStr := GetRegionStr(IDNumInfo.RegionCode); if not (RegionStr = '') then begin IDNumInfo.RegionStr := RegionStr; Result := true; end else Result := false; end; //根据指定分隔符分解字符串,并存入数组 function DecodeStr2Array(Source: string; SepChar: char; var Rst: TStrArray): integer; var Count, CutFrom, Len: word; procedure AddStr(Str: string); begin SetLength(Rst, length(Rst) + 1); Rst[high(Rst)] := Str; end; begin Result := 0; SetLength(Rst, 0); len := length(Source); if len > 0 then begin CutFrom := 1; for count := 1 to len do //逐字符扫描字符串,遇到分隔符时则切断串获得数据 if (Source[count] = SepChar) then begin AddStr(Copy(Source, CutFrom, count - CutFrom)); CutFrom := count + 1; if Count = Len then AddStr(''); end else if count = len then begin AddStr(Copy(Source, CutFrom, count - CutFrom + 1)); Break; end; Result := length(Rst); end; end; //把区划代码和名称的对应关系从文件读到变量中 function LoadRegionDat(FromFileName: string; var RegionCodeArray: TRegionCodeTransArray): boolean; var Count0 : integer; RegionDatFile : TStringList; StrArray : TStrArray; begin Result := false; SetLength(RegionCodeArray, 0); if FileExists(FromFileName) then begin RegionDatFile := TStringList.Create; //读入文件 try RegionDatFile.LoadFromFile(FromFileName); except end; //处理数据 for Count0 := 0 to RegionDatFile.Count - 1 do //跳过空行 if length(RegionDatFile[Count0]) > 0 then begin //跳过以#开头的行(注释行) if not (RegionDatFile[Count0][1] = '#') then begin //分解数据 if (DecodeStr2Array(RegionDatFile[Count0], ',', StrArray) = 2) and //以,分隔的两个字段 (length(StrArray[0]) = 6) and //代码长度为6个字符 IsDigiStr(StrArray[0]) and //代码必须全部是数字 (length(StrArray[1]) > 0) then //名称长度必须大于1 begin //增加内容 SetLength(RegionCodeArray, length(RegionCodeArray) + 1); RegionCodeArray[high(RegionCodeArray)].RegionCode := StrArray[0]; RegionCodeArray[high(RegionCodeArray)].RegionStr := StrArray[1]; end; end; end; Result := length(RegionCodeArray) > 0; RegionDatFile.Free; end; end; //直接将区划代码信息读入到本单元的公用变量里 function LoadRegionDat(FromFileName: string): boolean; begin Result := LoadRegionDat(FromFileName, RegionCodeTransArray); end; initialization SetLength(RegionCodeTransArray, 0); end.