CmpareMode.cpp 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097
  1. #include "CmpareMode.h"
  2. #include "Encode.h"
  3. #include "rcglobal.h"
  4. #include <QFile>
  5. #include <QFileDevice>
  6. #include <QVector>
  7. #include <QCryptographicHash>
  8. #include <functional>
  9. #include <QDataStream>
  10. #include <QtConcurrent>
  11. CmpareMode::CmpareMode()
  12. {
  13. }
  14. CmpareMode::~CmpareMode()
  15. {
  16. }
  17. //识别文字编码,并将文字按照原始编码格式,转换为QString。如果失败,默认按照utf8的格式进行转换;
  18. bool CmpareMode::recognizeTextCode(QByteArray & text, LineFileInfo &lineInfo, QString &outUnicodeText)
  19. {
  20. int lineNums = lineInfo.lineNums;
  21. int length = text.count();
  22. int result = false;
  23. //第一行时,检测一下文件编码,返回值也是文件的编码
  24. if (0 == lineNums)
  25. {
  26. int skip = 0;
  27. lineInfo.code = Encode::DetectEncode((uchar*) text.data(), length, skip);
  28. //根据编码跳过第一行前面的几个字符编码标识字段
  29. if (skip > 0)
  30. {
  31. text = text.mid(skip);
  32. }
  33. return Encode::tranStrToUNICODE((CODE_ID)lineInfo.code, text.data(), text.count(), outUnicodeText);
  34. }
  35. else
  36. {
  37. /*对于头部没有标识的行,需要每行进行详细检查,比较耗时
  38. *对于第一行已经是GBK的编码,标识出所有的确是GBK的行号
  39. *严格来说,如果以后要做国际版,不应该只考虑GBK,而是要考虑本地ASNI编码。
  40. *对中国而言,本地ASNI编码是GBK,对其它国家,比如日本/韩国而言,这些ASNI是它们本国
  41. *对应的本地编码。
  42. */
  43. //#if 0
  44. // //全部都在ascii范围以内,就作为ascii码。注意ASCII处理时其它地方时按照UTF8进行编码的
  45. // if (Encode::CheckTextIsAllAscii((uchar*)text.data(), length))
  46. // {
  47. // lineInfo.code = CODE_ID::ASCII;
  48. // return Encode::tranStrToUNICODE((CODE_ID)lineInfo.code, text.data(), length, outUnicodeText);
  49. // }
  50. // else
  51. // {
  52. //#endif
  53. CODE_ID actualCode = Encode::CheckUnicodeWithoutBOM((uchar*)text.data(), length, outUnicodeText);
  54. if (CODE_ID::UTF8_NOBOM == actualCode)
  55. {
  56. lineInfo.code = CODE_ID::UTF8_NOBOM;
  57. result = true;
  58. }
  59. else if (CODE_ID::GBK == actualCode)
  60. {
  61. //如果发现存在GBK,则要以GBK作为字符编码。这里识别gbk是因为显示的时候,需要转化gbk进行显示
  62. lineInfo.code = CODE_ID::GBK;
  63. result = true;
  64. }
  65. else if (CODE_ID::ANSI == actualCode)
  66. {
  67. lineInfo.code = CODE_ID::UNKOWN; //这里就是乱码了。即不是utf8也不是GBK,也不能说乱码,目前其它国家未处理的码
  68. result = false;
  69. }
  70. //#if 0
  71. // }
  72. //#endif
  73. }
  74. return result;
  75. }
  76. //LE编码要特殊对待。
  77. bool CmpareMode::isUnicodeLeBomFile(uchar* fileFpr, int fileLength)
  78. {
  79. if (fileLength >= 2 && fileFpr[0] == 0xFF && fileFpr[1] == 0xFE)
  80. {
  81. return true;
  82. }
  83. return false;
  84. }
  85. //isCheckHead:是否检测头。只有文件开头,才有。如果是分块加载,中间打开的文件,则不需要检测。默认检测
  86. CODE_ID CmpareMode::getTextFileEncodeType(uchar* fileFpr, int fileLength, QString filePath, bool isCheckHead)
  87. {
  88. if (isCheckHead)
  89. {
  90. if (fileLength >= 2 && fileFpr[0] == 0xFF && fileFpr[1] == 0xFE)
  91. {
  92. return CODE_ID::UNICODE_LE; //skip 2
  93. }
  94. else if (fileLength >= 2 && fileFpr[0] == 0xFE && fileFpr[1] == 0xFF)
  95. {
  96. return CODE_ID::UNICODE_BE; //skip 2
  97. }
  98. else if (fileLength >= 3 && fileFpr[0] == 0xEF && fileFpr[1] == 0xBB && fileFpr[2] == 0xBF)
  99. {
  100. return CODE_ID::UTF8_BOM; //skip 3 with BOM
  101. }
  102. }
  103. //走到这里说明没有文件头BOM,进行全盘文件扫描
  104. if (!filePath.isEmpty())
  105. {
  106. return scanFileRealCode(filePath);
  107. }
  108. return CODE_ID::UNKOWN;
  109. }
  110. //20230201新增:把Unicode_LE的字节流,转换为QString 发现如果是CODE_ID::UNICODE_LE,\r\n变成了\r\0\n\0,读取readLine遇到\n就结束了,而且toUnicode也会变成乱码失败
  111. //所以UNICODE_LE需要单独处理。该函数只处理Unicode_LE编码文件,事先一定要检查文件编码
  112. //返回字符数,不是编码类型,注意是字符数,不是bytes
  113. //这里有个问题,当初读取文件分块是,是无条件读取到\n就结束,则可能最后不是\n\0,而是\n。这种情况要特殊处理。标记为A。
  114. //是否跳过前面的LE头。默认不跳过。只有文件块开头第一块,才需要跳过。
  115. bool CmpareMode::tranUnicodeLeToUtf8Bytes(uchar* fileFpr, const int fileLength, QString &outUtf8Bytes, bool isSkipHead)
  116. {
  117. int lineNums = 0;
  118. CODE_ID code = CODE_ID::UNICODE_LE;
  119. int lineStartPos = (isSkipHead ? 2:0); //uicode_le前面有2个特殊标识,故跳过2
  120. //获取一行在文件中
  121. auto getOneLineFromFile = [fileFpr](int& startPos, const int fileLength, QByteArray& ret)->bool {
  122. if (startPos < fileLength)
  123. {
  124. ret.clear();
  125. int lineLens = 0;
  126. bool isFindLine = false;
  127. for (int i = startPos; i < fileLength; ++i, ++lineLens)
  128. {
  129. //遇到换行符号
  130. if (fileFpr[i] == 0x0A)
  131. {
  132. //lineLens需要加2,因为当前这个没有加,而且后面还有一个\0,这是le格式规定的。
  133. //特殊对待A。如果后续还要一个\0,及长度足够,则+2,否则只能加1
  134. if (startPos + lineLens + 2 < fileLength)
  135. {
  136. ret.append((char*)(fileFpr + startPos), lineLens + 2);
  137. startPos += lineLens + 2;
  138. }
  139. else
  140. {
  141. //这里就是特殊情况,尾部后面没有\0,只能前进1个。
  142. ret.append((char*)(fileFpr + startPos), lineLens + 1);
  143. //必须手动补上一个\0。避免格式残缺
  144. ret.append('\0');
  145. startPos += lineLens + 1;
  146. }
  147. isFindLine = true;
  148. break;
  149. }
  150. }
  151. //没有找到一行
  152. if (!isFindLine)
  153. {
  154. //最后一行,可能没有带\r\0直接返回
  155. ret.append((char*)(fileFpr + startPos), fileLength - startPos);
  156. startPos = fileLength;
  157. }
  158. return true;
  159. }
  160. return false;
  161. };
  162. QByteArray line;
  163. QByteArray tempUtf8Bytes;
  164. tempUtf8Bytes.reserve(fileLength+1);
  165. while (getOneLineFromFile(lineStartPos, fileLength, line)) {
  166. tempUtf8Bytes.append(line);
  167. }
  168. return Encode::tranStrToUNICODE(code, tempUtf8Bytes.data(), tempUtf8Bytes.count(), outUtf8Bytes);
  169. }
  170. //20210802:发现如果是CODE_ID::UNICODE_LE,\r\n变成了\r\0\n\0,读取readLine遇到\n就结束了,而且toUnicode也会变成乱码失败
  171. //所以UNICODE_LE需要单独处理。该函数只处理Unicode_LE编码文件,事先一定要检查文件编码
  172. //返回字符数,不是编码类型,注意是字符数,不是bytes
  173. quint32 CmpareMode::readLineFromFileWithUnicodeLe(uchar* m_fileFpr, const int fileLength, QList<LineFileInfo>& lineInfoVec, QList<LineFileInfo>& blankLineInfoVec,int mode, int &maxLineSize)
  174. {
  175. QCryptographicHash md4(QCryptographicHash::Md4);
  176. int lineNums = 0;
  177. CODE_ID code = CODE_ID::UNICODE_LE;
  178. int lineStartPos = 2; //uicode_le前面有2个特殊标识,故跳过2
  179. //获取一行在文件中
  180. auto getOneLineFromFile = [m_fileFpr](int& startPos, const int fileLength, QByteArray& ret)->bool{
  181. if (startPos < fileLength)
  182. {
  183. ret.clear();
  184. int lineLens = 0;
  185. bool isFindLine = false;
  186. for (int i = startPos; i < fileLength; ++i,++lineLens)
  187. {
  188. //遇到换行符号
  189. if (m_fileFpr[i] == 0x0A)
  190. {
  191. if (startPos + lineLens + 2 < fileLength)
  192. {
  193. //lineLens需要加2,因为当前这个没有加,而且后面还有一个\0,这是le格式规定的
  194. ret.append((char*)(m_fileFpr + startPos), lineLens + 2);
  195. startPos += lineLens + 2;
  196. }
  197. else
  198. {
  199. //这里就是特殊情况,尾部后面没有\0,只能前进1个。
  200. //其实是容错,可能最后一个没有\0
  201. ret.append((char*)(m_fileFpr + startPos), lineLens + 1);
  202. //必须手动补上一个\0。避免格式残缺
  203. ret.append('\0');
  204. startPos += lineLens + 1;
  205. }
  206. isFindLine = true;
  207. break;
  208. }
  209. }
  210. //没有找到一行
  211. if (!isFindLine)
  212. {
  213. //最后一行,可能没有带\r\0直接返回
  214. ret.append((char*)(m_fileFpr + startPos), fileLength - startPos);
  215. startPos = fileLength;
  216. }
  217. return true;
  218. }
  219. return false;
  220. };
  221. QByteArray line;
  222. quint32 charNums = 0;
  223. auto work = [mode, &md4](LineFileInfo& lineInfo, const int n) {
  224. if (mode == 0)
  225. {
  226. md4.addData(lineInfo.unicodeStr.trimmed().toUtf8());
  227. }
  228. else if (mode == 1)
  229. {
  230. md4.addData(lineInfo.unicodeStr.left(lineInfo.unicodeStr.length() - n).toUtf8());
  231. }
  232. else if (mode == 2)
  233. {
  234. QString temp = lineInfo.unicodeStr;
  235. md4.addData(temp.replace(QRegExp("\\s"), QString("")).toUtf8());
  236. }
  237. };
  238. while (getOneLineFromFile(lineStartPos, fileLength,line)) {
  239. LineFileInfo lineInfo;
  240. lineInfo.lineNums = lineNums;
  241. /* 这种方式读取文件会包含后面的行尾 */
  242. int length = line.length();
  243. if (maxLineSize < length)
  244. {
  245. maxLineSize = length;
  246. }
  247. //如果是头部有标识的格式,则后续不用详细检查每行编码,直接按照头部标识走
  248. Encode::tranStrToUNICODE(code, line.data(), line.count(), lineInfo.unicodeStr);
  249. lineInfo.code = code;
  250. charNums += lineInfo.unicodeStr.size();
  251. if (lineInfo.unicodeStr.endsWith("\r\r\n"))
  252. {
  253. //这里是一种错误,但确实可能出现
  254. if (length > 3)
  255. {
  256. work(lineInfo, 3);
  257. }
  258. else
  259. {
  260. //空白行
  261. lineInfo.isLcsExist = false;
  262. lineInfo.isEmptyLine = true;
  263. }
  264. lineInfo.lineEndFormat = RC_LINE_FORM::DOS_LINE;
  265. }
  266. else if (lineInfo.unicodeStr.endsWith("\r\n"))
  267. {
  268. if (length > 2)
  269. {
  270. work(lineInfo, 2);
  271. }
  272. else
  273. {
  274. //空白行
  275. lineInfo.isLcsExist = false;
  276. lineInfo.isEmptyLine = true;
  277. }
  278. lineInfo.lineEndFormat = RC_LINE_FORM::DOS_LINE;
  279. }
  280. else if (lineInfo.unicodeStr.endsWith("\n"))
  281. {
  282. if (length > 1)
  283. {
  284. work(lineInfo, 1);
  285. }
  286. else
  287. {
  288. lineInfo.isLcsExist = false;
  289. lineInfo.isEmptyLine = true;
  290. }
  291. lineInfo.lineEndFormat = RC_LINE_FORM::UNIX_LINE;
  292. }
  293. else if (lineInfo.unicodeStr.endsWith("\r"))
  294. {
  295. if (length > 1)
  296. {
  297. work(lineInfo, 1);
  298. }
  299. else
  300. {
  301. lineInfo.isLcsExist = false;
  302. lineInfo.isEmptyLine = true;
  303. }
  304. lineInfo.lineEndFormat = RC_LINE_FORM::MAC_LINE;
  305. }
  306. else
  307. {
  308. if (length > 0)
  309. {
  310. work(lineInfo, 0);
  311. }
  312. else
  313. {
  314. lineInfo.isLcsExist = false;
  315. lineInfo.isEmptyLine = true;
  316. }
  317. lineInfo.lineEndFormat = RC_LINE_FORM::UNKNOWN_LINE;
  318. }
  319. if (lineInfo.isEmptyLine)
  320. {
  321. blankLineInfoVec.append(lineInfo);
  322. }
  323. else
  324. {
  325. lineInfo.md4 = md4.result();
  326. //qDebug() << lineInfo.md4;
  327. md4.reset();
  328. lineInfoVec.append(lineInfo);
  329. }
  330. ++lineNums;
  331. }
  332. return charNums;
  333. }
  334. //读取每一行,将空白行和非空白行分开。非空白行取他们的行md4值(不包含尾部的换行符)
  335. //返回值:文件扫描出来的字符编码
  336. //在对比行的md5值时,忽略了后面的行尾类型。即只对比字符内容,忽略了行尾。
  337. //20210802:发现如果是CODE_ID::UNICODE_LE,\r\n变成了\r\0\n\0,读取readLine遇到\n就结束了,而且toUnicode也会变成乱码失败
  338. //所以UNICODE_LE需要单独处理。注意UNICODE_BE没有这个问题,因为BE是\0\r\0\n,0在前面就没有这个问题
  339. //20210901 发现使用readLine的方式来读取一行不可靠。因为有些文件中一行中间有个\r,这种情况没有识别为多行。readLine是根据\n来识别的。
  340. //进而导致中间的\r没有识别为多行,但是在编辑器中却多一行,导致对比错误。还是要自己来识别行。不依赖于readLine
  341. //CODE_ID fileCode 事先预判定的编码
  342. CODE_ID CmpareMode::readLineFromFile(uchar* m_fileFpr, const int fileLength, const CODE_ID fileCode, QList<LineFileInfo>&lineInfoVec, QList<LineFileInfo>&blankLineInfoVec, int mode, int &maxLineSize)
  343. {
  344. QCryptographicHash md4(QCryptographicHash::Md4);
  345. int lineNums = 0;
  346. CODE_ID code = fileCode;
  347. bool isExistGbk = false;
  348. bool isExistUnKownCode = false;
  349. bool isExistUtf8 = false;
  350. int lineStartPos = 0;
  351. //跳过前面的BOM头部。LE不在这里处理,在外面
  352. if (fileCode == CODE_ID::UNICODE_BE || fileCode == CODE_ID::UNICODE_LE)
  353. {
  354. lineStartPos = 2;
  355. }
  356. else if (fileCode == CODE_ID::UTF8_BOM)
  357. {
  358. lineStartPos = 3;
  359. }
  360. //获取一行在文件中
  361. auto getOneLineFromFile = [m_fileFpr](int& startPos, const int fileLength, const CODE_ID fileCode, QByteArray& ret)->bool {
  362. if (startPos < fileLength)
  363. {
  364. ret.clear();
  365. int lineLens = 0;
  366. bool isFindLine = false;
  367. for (int i = startPos; i < fileLength; ++i, ++lineLens)
  368. {
  369. //遇到符号CR
  370. if (m_fileFpr[i] == 0x0D)
  371. {
  372. //后一个是LF,即以CRLF结尾
  373. if ((i + 1 < fileLength) && (m_fileFpr[i+1] == 0x0A))
  374. {
  375. //lineLens需要加2,因为当前这个没有加,而且后面还有一个\n
  376. ret.append((char*)(m_fileFpr + startPos), lineLens + 2);
  377. startPos += lineLens + 2;
  378. isFindLine = true;
  379. break;
  380. }
  381. else if ((fileCode == UNICODE_BE)&&((i>0) && m_fileFpr[i-1] == '\0'))
  382. {
  383. //事先发现就是BE格式,以\0\r\0\n为结尾的
  384. if ((i + 2 < fileLength) && (m_fileFpr[i + 1] == 0x0) && (m_fileFpr[i + 2] == 0x0A))
  385. {
  386. //lineLens需要加3,因为当前这个没有加,而且后面还有一个\0\n
  387. ret.append((char*)(m_fileFpr + startPos), lineLens + 3);
  388. startPos += lineLens + 3;
  389. isFindLine = true;
  390. break;
  391. }
  392. else
  393. {
  394. //虽然说是BE格式,但是后面没有以\0\n结尾,而是以\r结尾。这种多半就是错误。直接按\0\r结尾
  395. //lineLens需要加1,因为当前这个没有加
  396. ret.append((char*)(m_fileFpr + startPos), lineLens + 1);
  397. startPos += lineLens + 1;
  398. isFindLine = true;
  399. break;
  400. }
  401. }
  402. else
  403. {
  404. //直接以\r结尾了,后面没有\n或者\0\n。符合MAC格式,windows可能编码只有\r,没有\n的错误情况。
  405. //lineLens需要加1,因为当前这个没有加
  406. ret.append((char*)(m_fileFpr + startPos), lineLens + 1);
  407. startPos += lineLens + 1;
  408. isFindLine = true;
  409. break;
  410. }
  411. }
  412. else if(m_fileFpr[i] == 0x0A)
  413. {
  414. //没有先遇到\r,直接遇到\n.20210903发现忘记处理该情况le
  415. //lineLens需要加1,因为当前这个没有加
  416. ret.append((char*)(m_fileFpr + startPos), lineLens + 1);
  417. startPos += lineLens + 1;
  418. isFindLine = true;
  419. break;
  420. }
  421. }
  422. //没有找到一行
  423. if (!isFindLine)
  424. {
  425. //最后一行,可能没有带\r\0直接返回
  426. ret.append((char*)(m_fileFpr + startPos), fileLength - startPos);
  427. startPos = fileLength;
  428. }
  429. return true;
  430. }
  431. return false;
  432. };
  433. QByteArray line;
  434. auto work = [mode,&md4](LineFileInfo& lineInfo, const int n) {
  435. if (mode == 0)
  436. {
  437. md4.addData(lineInfo.unicodeStr.trimmed().toUtf8());
  438. }
  439. else if (mode == 1)
  440. {
  441. md4.addData(lineInfo.unicodeStr.left(lineInfo.unicodeStr.length() - n).toUtf8());
  442. }
  443. else if (mode == 2)
  444. {
  445. QString temp = lineInfo.unicodeStr;
  446. md4.addData(temp.replace(QRegExp("\\s"), QString("")).toUtf8());
  447. }
  448. };
  449. while (getOneLineFromFile(lineStartPos, fileLength, code, line)) {
  450. LineFileInfo lineInfo;
  451. lineInfo.lineNums = lineNums;
  452. /* 这种方式读取文件会包含后面的行尾 */
  453. int length = line.length();
  454. if (maxLineSize < length)
  455. {
  456. maxLineSize = length;
  457. }
  458. //外面必须把code先检测好了
  459. //if (fileCode == CODE_ID::UNICODE_BE /*|| fileCode == CODE_ID::UNICODE_LE */ || fileCode == CODE_ID::UTF8_BOM)
  460. if(fileCode != CODE_ID::UNKOWN)
  461. {
  462. //如果是头部有标识的格式,则后续不用详细检查每行编码,直接按照头部标识走
  463. Encode::tranStrToUNICODE(code, line.data(), line.count(), lineInfo.unicodeStr);
  464. lineInfo.code = fileCode;
  465. }
  466. else if(fileCode == CODE_ID::UNKOWN)
  467. {
  468. /*对于头部没有标识的行,需要每行进行详细检查,比较耗时
  469. *对于第一行已经是GBK的编码,标识出所有的确是GBK的行号
  470. *严格来说,如果以后要做国际版,不应该只考虑GBK,而是要考虑本地ASNI编码。
  471. *对中国而言,本地ASNI编码是GBK,对其它国家,比如日本/韩国而言,这些ASNI是它们本国
  472. *对应的本地编码。
  473. */
  474. recognizeTextCode(line, lineInfo, lineInfo.unicodeStr);
  475. if (CODE_ID::UTF8_NOBOM == lineInfo.code)
  476. {
  477. isExistUtf8 = true;
  478. }
  479. else if (CODE_ID::GBK == lineInfo.code)
  480. {
  481. //如果发现存在GBK,则要以GBK作为字符编码。这里识别gbk是因为显示的时候,需要转化gbk进行显示
  482. isExistGbk = true;
  483. }
  484. else if (CODE_ID::UNKOWN == lineInfo.code)
  485. {
  486. isExistUnKownCode = true;
  487. }
  488. }
  489. if (lineInfo.unicodeStr.endsWith("\r\r\n"))
  490. {
  491. //这里是一种错误,但确实可能出现
  492. if (length > 3)
  493. {
  494. work(lineInfo,3);
  495. }
  496. else
  497. {
  498. //空白行
  499. lineInfo.isLcsExist = false;
  500. lineInfo.isEmptyLine = true;
  501. }
  502. lineInfo.lineEndFormat = RC_LINE_FORM::DOS_LINE;
  503. }
  504. else if (lineInfo.unicodeStr.endsWith("\r\n"))
  505. {
  506. if (length > 2)
  507. {
  508. work(lineInfo, 2);
  509. }
  510. else
  511. {
  512. //空白行
  513. lineInfo.isLcsExist = false;
  514. lineInfo.isEmptyLine = true;
  515. }
  516. lineInfo.lineEndFormat = RC_LINE_FORM::DOS_LINE;
  517. }
  518. else if (lineInfo.unicodeStr.endsWith("\n"))
  519. {
  520. if (length > 1)
  521. {
  522. work(lineInfo, 1);
  523. }
  524. else
  525. {
  526. lineInfo.isLcsExist = false;
  527. lineInfo.isEmptyLine = true;
  528. }
  529. lineInfo.lineEndFormat = RC_LINE_FORM::UNIX_LINE;
  530. }
  531. else if (lineInfo.unicodeStr.endsWith("\r"))
  532. {
  533. if (length > 1)
  534. {
  535. work(lineInfo, 1);
  536. }
  537. else
  538. {
  539. lineInfo.isLcsExist = false;
  540. lineInfo.isEmptyLine = true;
  541. }
  542. lineInfo.lineEndFormat = RC_LINE_FORM::MAC_LINE;
  543. }
  544. else
  545. {
  546. if (length > 0)
  547. {
  548. work(lineInfo, 0);
  549. }
  550. else
  551. {
  552. lineInfo.isLcsExist = false;
  553. lineInfo.isEmptyLine = true;
  554. }
  555. lineInfo.lineEndFormat = RC_LINE_FORM::UNKNOWN_LINE;
  556. }
  557. if (lineInfo.isEmptyLine)
  558. {
  559. blankLineInfoVec.append(lineInfo);
  560. }
  561. else
  562. {
  563. lineInfo.md4 = md4.result();
  564. md4.reset();
  565. lineInfoVec.append(lineInfo);
  566. }
  567. ++lineNums;
  568. }
  569. //如果外部指定了格式,则直接返回外部格式
  570. if (fileCode != CODE_ID::UNKOWN)
  571. {
  572. return fileCode;
  573. }
  574. return judgeFinalTextCode(code, isExistUnKownCode, isExistGbk, isExistUtf8);
  575. }
  576. CODE_ID CmpareMode::judgeFinalTextCode(CODE_ID code, bool isExistUnKownCode, bool isExistGbk, bool isExistUtf8)
  577. {
  578. //如果是三种有明确标识的字符编码,则严格按照标识的逻辑去读取。哪怕里面存在错误编码,也只能按照头部标识为准
  579. if (CODE_ID::UNICODE_LE == code || CODE_ID::UNICODE_BE == code || CODE_ID::UTF8_BOM == code || code == CODE_ID::GBK)
  580. {
  581. return code;
  582. }
  583. //剩下的是在文件头没有严格标识编码的文件
  584. //存在不能识别的编码,则应该是ASNI,需要用户指定编码
  585. if (isExistUnKownCode)
  586. {
  587. return CODE_ID::UNKOWN;
  588. }
  589. if (isExistGbk)
  590. {
  591. //如果没有错误码,而且发现gbk,则是gbk编码
  592. return CODE_ID::GBK;
  593. }
  594. //如果不存在错误和gbk,就是纯粹的ut8_nobom
  595. if (isExistUtf8)
  596. {
  597. return CODE_ID::UTF8_NOBOM;
  598. }
  599. return code;
  600. }
  601. //读取用于纯输出,不做比较。bool &isMaybeHexFile 是否是hex文件,不一定准确,做一个推测
  602. // int& charsNums 输出字符个数
  603. CODE_ID CmpareMode::readLineFromFile(uchar* m_fileFpr, const int fileLength, const CODE_ID fileCode, QList<LineFileInfo>&lineInfoVec, int& maxLineSize, int& charsNums, bool &isMaybeHexFile)
  604. {
  605. int lineNums = 0;
  606. CODE_ID code = fileCode;
  607. bool isExistGbk = false;
  608. bool isExistUnKownCode = false;
  609. bool isExistUtf8 = false;
  610. int lineStartPos = 0;
  611. int errorCodeLines = 0;
  612. charsNums = 0;
  613. if (fileCode == CODE_ID::UNICODE_BE || fileCode == CODE_ID::UNICODE_LE)
  614. {
  615. lineStartPos = 2;
  616. }
  617. else if (fileCode == CODE_ID::UTF8_BOM)
  618. {
  619. lineStartPos = 3;
  620. }
  621. //获取一行在文件中
  622. auto getOneLineFromFile = [m_fileFpr](int& startPos, const int fileLength, const CODE_ID fileCode, QByteArray& ret)->bool {
  623. if (startPos < fileLength)
  624. {
  625. ret.clear();
  626. int lineLens = 0;
  627. bool isFindLine = false;
  628. for (int i = startPos; i < fileLength; ++i, ++lineLens)
  629. {
  630. //遇到符号CR
  631. if (m_fileFpr[i] == 0x0D)
  632. {
  633. //后一个是LF,即以CRLF结尾
  634. if ((i + 1 < fileLength) && (m_fileFpr[i + 1] == 0x0A))
  635. {
  636. //lineLens需要加2,因为当前这个没有加,而且后面还有一个\n
  637. ret.append((char*)(m_fileFpr + startPos), lineLens + 2);
  638. startPos += lineLens + 2;
  639. isFindLine = true;
  640. break;
  641. }
  642. else if ((fileCode == UNICODE_BE) && ((i > 0) && m_fileFpr[i - 1] == '\0'))
  643. {
  644. //事先发现就是BE格式,以\0\r\0\n为结尾的
  645. if ((i + 2 < fileLength) && (m_fileFpr[i + 1] == 0x0) && (m_fileFpr[i + 2] == 0x0A))
  646. {
  647. //lineLens需要加3,因为当前这个没有加,而且后面还有一个\0\n
  648. ret.append((char*)(m_fileFpr + startPos), lineLens + 3);
  649. startPos += lineLens + 3;
  650. isFindLine = true;
  651. break;
  652. }
  653. else
  654. {
  655. //虽然说是BE格式,但是后面没有以\0\n结尾,而是以\r结尾。这种多半就是错误。直接按\0\r结尾
  656. //lineLens需要加1,因为当前这个没有加
  657. ret.append((char*)(m_fileFpr + startPos), lineLens + 1);
  658. startPos += lineLens + 1;
  659. isFindLine = true;
  660. break;
  661. }
  662. }
  663. else
  664. {
  665. //直接以\r结尾了,后面没有\n或者\0\n。符合MAC格式,windows可能编码只有\r,没有\n的错误情况。
  666. //lineLens需要加1,因为当前这个没有加
  667. ret.append((char*)(m_fileFpr + startPos), lineLens + 1);
  668. startPos += lineLens + 1;
  669. isFindLine = true;
  670. break;
  671. }
  672. }
  673. else if (m_fileFpr[i] == 0x0A)
  674. {
  675. //没有先遇到\r,直接遇到\n.20210903发现忘记处理该情况le
  676. //lineLens需要加1,因为当前这个没有加
  677. ret.append((char*)(m_fileFpr + startPos), lineLens + 1);
  678. startPos += lineLens + 1;
  679. isFindLine = true;
  680. break;
  681. }
  682. }
  683. //没有找到一行
  684. if (!isFindLine)
  685. {
  686. //最后一行,可能没有带\r\0直接返回
  687. ret.append((char*)(m_fileFpr + startPos), fileLength - startPos);
  688. startPos = fileLength;
  689. }
  690. return true;
  691. }
  692. return false;
  693. };
  694. QByteArray line;
  695. while (getOneLineFromFile(lineStartPos, fileLength, code, line)) {
  696. LineFileInfo lineInfo;
  697. lineInfo.lineNums = lineNums;
  698. /* 这种方式读取文件会包含后面的行尾 */
  699. int length = line.length();
  700. if (maxLineSize < length)
  701. {
  702. maxLineSize = length;
  703. }
  704. //外面必须把code先检测好了
  705. //if (fileCode == CODE_ID::UNICODE_BE /*|| fileCode == CODE_ID::UNICODE_LE */ || fileCode == CODE_ID::UTF8_BOM)
  706. if(fileCode != CODE_ID::UNKOWN)
  707. {
  708. //如果是头部有标识的格式,则后续不用详细检查每行编码,直接按照头部标识走
  709. Encode::tranStrToUNICODE(code, line.data(), line.count(), lineInfo.unicodeStr);
  710. lineInfo.code = fileCode;
  711. }
  712. else if (fileCode == CODE_ID::UNKOWN)
  713. {
  714. /*对于头部没有标识的行,需要每行进行详细检查,比较耗时
  715. *对于第一行已经是GBK的编码,标识出所有的确是GBK的行号
  716. *严格来说,如果以后要做国际版,不应该只考虑GBK,而是要考虑本地ASNI编码。
  717. *对中国而言,本地ASNI编码是GBK,对其它国家,比如日本/韩国而言,这些ASNI是它们本国
  718. *对应的本地编码。
  719. */
  720. recognizeTextCode(line, lineInfo, lineInfo.unicodeStr);
  721. if (CODE_ID::UTF8_NOBOM == lineInfo.code)
  722. {
  723. isExistUtf8 = true;
  724. }
  725. else if (CODE_ID::GBK == lineInfo.code)
  726. {
  727. //如果发现存在GBK,则要以GBK作为字符编码。这里识别gbk是因为显示的时候,需要转化gbk进行显示
  728. isExistGbk = true;
  729. }
  730. else if (CODE_ID::UNKOWN == lineInfo.code)
  731. {
  732. isExistUnKownCode = true;
  733. //增加错误行的计数
  734. errorCodeLines++;
  735. }
  736. }
  737. if (lineInfo.unicodeStr.endsWith("\r\r\n"))
  738. {
  739. //这里是一种错误,但确实可能出现
  740. if (length > 3)
  741. {
  742. }
  743. else
  744. {
  745. //空白行
  746. lineInfo.isLcsExist = false;
  747. lineInfo.isEmptyLine = true;
  748. }
  749. lineInfo.lineEndFormat = RC_LINE_FORM::DOS_LINE;
  750. }
  751. else if (lineInfo.unicodeStr.endsWith("\r\n"))
  752. {
  753. if (length > 2)
  754. {
  755. }
  756. else
  757. {
  758. //空白行
  759. lineInfo.isLcsExist = false;
  760. lineInfo.isEmptyLine = true;
  761. }
  762. lineInfo.lineEndFormat = RC_LINE_FORM::DOS_LINE;
  763. }
  764. else if (lineInfo.unicodeStr.endsWith("\n"))
  765. {
  766. if (length > 1)
  767. {
  768. }
  769. else
  770. {
  771. lineInfo.isLcsExist = false;
  772. lineInfo.isEmptyLine = true;
  773. }
  774. lineInfo.lineEndFormat = RC_LINE_FORM::UNIX_LINE;
  775. }
  776. else if (lineInfo.unicodeStr.endsWith("\r"))
  777. {
  778. if (length > 1)
  779. {
  780. }
  781. else
  782. {
  783. lineInfo.isLcsExist = false;
  784. lineInfo.isEmptyLine = true;
  785. }
  786. lineInfo.lineEndFormat = RC_LINE_FORM::MAC_LINE;
  787. }
  788. else
  789. {
  790. if (length > 0)
  791. {
  792. }
  793. else
  794. {
  795. lineInfo.isLcsExist = false;
  796. lineInfo.isEmptyLine = true;
  797. }
  798. lineInfo.lineEndFormat = RC_LINE_FORM::UNKNOWN_LINE;
  799. }
  800. lineInfoVec.append(lineInfo);
  801. charsNums += lineInfo.unicodeStr.size();
  802. ++lineNums;
  803. }
  804. //如果超过一半的行都是错误的,则考虑为hex文件。
  805. if (lineNums >= 10 && (errorCodeLines * 100 / lineNums > 50))
  806. {
  807. isMaybeHexFile = true;
  808. }
  809. else
  810. {
  811. isMaybeHexFile = false;
  812. //如果前面三行中含有\0字符,也可能是二进制文件
  813. if (lineNums > 3)
  814. {
  815. for (int i = 0; i < 3; ++i)
  816. {
  817. if (lineInfoVec.at(i).unicodeStr.contains(QChar('\0')))
  818. {
  819. isMaybeHexFile = true;
  820. break;
  821. }
  822. }
  823. }
  824. }
  825. //如果用户外部强制编码,则直接按改编码返回
  826. if (fileCode != CODE_ID::UNKOWN)
  827. {
  828. return fileCode;
  829. }
  830. return judgeFinalTextCode(code, isExistUnKownCode, isExistGbk, isExistUtf8);
  831. }
  832. //扫描文件的字符编码,不输出文件
  833. CODE_ID CmpareMode::scanFileRealCode(QString filePath)
  834. {
  835. QFile file(filePath);
  836. file.open(QIODevice::ReadOnly);
  837. CODE_ID code = CODE_ID::UNKOWN;
  838. int lineNums = 0;
  839. bool isExistGbk = false;
  840. bool isExistUnKownCode = false;
  841. bool isExistUtf8 = false;
  842. while (!file.atEnd()) {
  843. LineFileInfo lineInfo;
  844. QByteArray line = file.readLine();
  845. lineInfo.lineNums = lineNums;
  846. /* 这种方式读取文件会包含后面的行尾 */
  847. //int length = line.length();
  848. //第一行时,检测一下文件编码,返回值也是文件的编码
  849. if (0 == lineNums)
  850. {
  851. recognizeTextCode(line, lineInfo, lineInfo.unicodeStr);
  852. code = (CODE_ID)lineInfo.code;
  853. //已经找到文本的标签,相信标签,之前返回
  854. if (code == CODE_ID::UNICODE_BE || code == CODE_ID::UNICODE_LE || code == CODE_ID::UTF8_BOM || code == CODE_ID::GBK)
  855. {
  856. break;
  857. }
  858. }
  859. else
  860. {
  861. /*对于头部没有标识的行,需要每行进行详细检查,比较耗时
  862. *对于第一行已经是GBK的编码,标识出所有的确是GBK的行号
  863. *严格来说,如果以后要做国际版,不应该只考虑GBK,而是要考虑本地ASNI编码。
  864. *对中国而言,本地ASNI编码是GBK,对其它国家,比如日本/韩国而言,这些ASNI是它们本国
  865. *对应的本地编码。
  866. */
  867. recognizeTextCode(line, lineInfo, lineInfo.unicodeStr);
  868. if (CODE_ID::UTF8_NOBOM == lineInfo.code)
  869. {
  870. isExistUtf8 = true;
  871. }
  872. else if (CODE_ID::GBK == lineInfo.code)
  873. {
  874. //如果发现存在GBK,则要以GBK作为字符编码。这里识别gbk是因为显示的时候,需要转化gbk进行显示
  875. isExistGbk = true;
  876. }
  877. else if (CODE_ID::UNKOWN == lineInfo.code)
  878. {
  879. isExistUnKownCode = true;
  880. //20220127一旦发现错误编码,或者说不能识别的编码,则直接跳出。
  881. //因为肯定是不能识别的编码ASNI
  882. break;
  883. }
  884. }
  885. ++lineNums;
  886. //最多扫描200行,加块速度。速度与精确性的权衡
  887. if (lineNums >= 200)
  888. {
  889. break;
  890. }
  891. }
  892. file.close();
  893. return judgeFinalTextCode(code, isExistUnKownCode, isExistGbk, isExistUtf8);
  894. }
  895. //读取文件,并输出
  896. //bytescharsNums:文件字符个数,不是文件大小
  897. //20220908 自动判断是否是二进制文件。isHexFile 是输出
  898. CODE_ID CmpareMode::scanFileOutPut(CODE_ID code, QString filePath, QList<LineFileInfo>& outputLineInfoVec, int &maxLineSize, int& charsNums, bool &isHexFile)
  899. {
  900. QFile* file = new QFile(filePath);
  901. file->open(QIODevice::ReadOnly);
  902. uchar* m_fileFpr = file->map(0, file->size());
  903. if (code == UNKOWN)
  904. {
  905. code = getTextFileEncodeType(m_fileFpr, file->size(), filePath);
  906. }
  907. //UNICODE_LE格式需要单独处理
  908. if (code == UNICODE_LE)
  909. {
  910. charsNums = readLineFromFileWithUnicodeLe(m_fileFpr, file->size(), outputLineInfoVec, outputLineInfoVec, 0, maxLineSize);
  911. }
  912. else
  913. {
  914. code = readLineFromFile(m_fileFpr, file->size(), code, outputLineInfoVec, maxLineSize, charsNums, isHexFile);
  915. }
  916. file->unmap(m_fileFpr);
  917. file->close();
  918. delete file;
  919. return code;
  920. }