pas2jsfileutils.pp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. { Author: Mattias Gaertner 2017 [email protected]
  2. Abstract:
  3. Low level file path handling.
  4. }
  5. unit Pas2jsFileUtils;
  6. {$mode objfpc}{$H+}
  7. {$i pas2js_defines.inc}
  8. interface
  9. uses
  10. {$IFDEF Unix}
  11. BaseUnix,
  12. {$ENDIF}
  13. SysUtils, Classes;
  14. function FilenameIsAbsolute(const aFilename: string):boolean;
  15. function FilenameIsWinAbsolute(const aFilename: string):boolean;
  16. function FilenameIsUnixAbsolute(const aFilename: string):boolean;
  17. function FileIsInPath(const Filename, Path: string): boolean;
  18. function ChompPathDelim(const Path: string): string;
  19. function ExpandFileNameUTF8(const FileName: string; {const} BaseDir: string = ''): string;
  20. function ExpandDirectory(const aDirectory: string): string;
  21. function TryCreateRelativePath(const Filename, BaseDirectory: String;
  22. UsePointDirectory: boolean; out RelPath: String): Boolean;
  23. function ResolveDots(const AFilename: string): string;
  24. procedure ForcePathDelims(Var FileName: string);
  25. function GetForcedPathDelims(Const FileName: string): String;
  26. function ExtractFilenameOnly(const aFilename: string): string;
  27. function GetCurrentDirUTF8: String;
  28. function CompareFilenames(const File1, File2: string): integer;
  29. function GetPhysicalFilename(const Filename: string;
  30. ExceptionOnError: boolean): string;
  31. function ResolveSymLinks(const Filename: string;
  32. {%H-}ExceptionOnError: boolean): string; // if a link is broken returns ''
  33. procedure FindMatchingFiles(Mask: string; MaxCount: integer; Files: TStrings);// find files, matching * and ?
  34. function GetEnvironmentVariableCountUTF8: Integer;
  35. function GetEnvironmentStringUTF8(Index: Integer): string;
  36. function GetEnvironmentVariableUTF8(const EnvVar: string): String;
  37. function GetNextDelimitedItem(const List: string; Delimiter: char;
  38. var Position: integer): string;
  39. type TChangeStamp = SizeInt;
  40. const InvalidChangeStamp = low(TChangeStamp);
  41. procedure IncreaseChangeStamp(var Stamp: TChangeStamp);
  42. const
  43. UTF8BOM = #$EF#$BB#$BF;
  44. EncodingUTF8 = 'UTF-8';
  45. EncodingSystem = 'System';
  46. function NormalizeEncoding(const Encoding: string): string;
  47. function IsNonUTF8System: boolean;// true if system encoding is not UTF-8
  48. function UTF8CharacterStrictLength(P: PChar): integer;
  49. function GetDefaultTextEncoding: string;
  50. function GetConsoleTextEncoding: string;
  51. {$IFDEF Windows}
  52. // AConsole - If false, it is the general system encoding,
  53. // if true, it is the console encoding
  54. function GetWindowsEncoding(AConsole: Boolean = False): string;
  55. {$ENDIF}
  56. {$IF defined(Unix) and not defined(Darwin)}
  57. function GetUnixEncoding: string;
  58. {$ENDIF}
  59. function IsASCII(const s: string): boolean; inline;
  60. function UTF8ToUTF16(const s: string): UnicodeString;
  61. function UTF16ToUTF8(const s: UnicodeString): string;
  62. function UTF8ToSystemCP(const s: string): string;
  63. function SystemCPToUTF8(const s: string): string;
  64. function ConsoleToUTF8(const s: string): string;
  65. // converts UTF8 string to console encoding (used by Write, WriteLn)
  66. function UTF8ToConsole(const s: string): string;
  67. implementation
  68. {$IFDEF Windows}
  69. uses Windows;
  70. {$ENDIF}
  71. var
  72. EncodingValid: boolean = false;
  73. DefaultTextEncoding: string = EncodingSystem;
  74. {$IFDEF Unix}
  75. {$IFNDEF Darwin}
  76. Lang: string = '';
  77. {$ENDIF}
  78. {$ENDIF}
  79. NonUTF8System: boolean = false;
  80. function FilenameIsWinAbsolute(const aFilename: string): boolean;
  81. begin
  82. Result:=((length(aFilename)>=3) and
  83. (aFilename[1] in ['A'..'Z','a'..'z']) and (aFilename[2]=':') and (aFilename[3]in AllowDirectorySeparators))
  84. or ((length(aFilename)>=2) and (aFilename[1] in AllowDirectorySeparators) and (aFilename[2] in AllowDirectorySeparators));
  85. end;
  86. function FilenameIsUnixAbsolute(const aFilename: string): boolean;
  87. begin
  88. Result:=(aFilename<>'') and (aFilename[1]='/');
  89. end;
  90. function FileIsInPath(const Filename, Path: string): boolean;
  91. var
  92. ExpFile: String;
  93. ExpPath: String;
  94. l: integer;
  95. begin
  96. if Path='' then begin
  97. Result:=false;
  98. exit;
  99. end;
  100. ExpFile:=Filename;
  101. ExpPath:=IncludeTrailingPathDelimiter(Path);
  102. l:=length(ExpPath);
  103. Result:=(l>0) and (length(ExpFile)>l) and (ExpFile[l]=PathDelim)
  104. and (AnsiCompareFileName(ExpPath,LeftStr(ExpFile,l))=0);
  105. end;
  106. function ChompPathDelim(const Path: string): string;
  107. var
  108. Len, MinLen: Integer;
  109. begin
  110. Result:=Path;
  111. if Path = '' then
  112. exit;
  113. Len:=length(Result);
  114. if (Result[1] in AllowDirectorySeparators) then begin
  115. MinLen := 1;
  116. {$IFDEF HasUNCPaths}
  117. if (Len >= 2) and (Result[2] in AllowDirectorySeparators) then
  118. MinLen := 2; // keep UNC '\\', chomp 'a\' to 'a'
  119. {$ENDIF}
  120. end
  121. else begin
  122. MinLen := 0;
  123. {$IFdef MSWindows}
  124. if (Len >= 3) and (Result[1] in ['a'..'z', 'A'..'Z']) and
  125. (Result[2] = ':') and (Result[3] in AllowDirectorySeparators)
  126. then
  127. MinLen := 3;
  128. {$ENDIF}
  129. end;
  130. while (Len > MinLen) and (Result[Len] in AllowDirectorySeparators) do dec(Len);
  131. if Len<length(Result) then
  132. SetLength(Result,Len);
  133. end;
  134. function ExpandDirectory(const aDirectory: string): string;
  135. begin
  136. Result:=aDirectory;
  137. if Result='' then exit;
  138. Result:=ExpandFileNameUTF8(Result);
  139. if Result='' then exit;
  140. Result:=IncludeTrailingPathDelimiter(Result);
  141. end;
  142. function TryCreateRelativePath(const Filename, BaseDirectory: String;
  143. UsePointDirectory: boolean; out RelPath: String): Boolean;
  144. {
  145. Returns True if it is possible to create a relative path from Source to Dest
  146. Function must be thread safe, so no expanding of filenames is done, since this
  147. is not threadsafe (at least on Windows platform)
  148. - Dest and Source must either be both absolute filenames, or relative
  149. - Dest and Source cannot contain '..' since no expanding is done by design
  150. - Dest and Source must be on same drive or UNC path (Windows)
  151. - if both Dest and Source are relative they must at least share their base directory
  152. - Double PathDelims are ignored (unless they are part of the UNC convention)
  153. - if UsePointDirectory is True and Result is True then if RelPath is Empty string, RelPath becomes '.'
  154. - if AlwaysRequireSharedBaseFolder is False then Absolute filenames need not share a basefolder
  155. - if the function succeeds RelPath contains the relative path from Source to Dest,
  156. no PathDelimiter is appended to the end of RelPath
  157. Examples:
  158. - Filename = /foo/bar BaseDirectory = /foo Result = True RelPath = bar
  159. - Filename = /foo///bar BaseDirectory = /foo// Result = True RelPath = bar
  160. - Filename = /foo BaseDirectory = /foo/bar Result = True RelPath = ../
  161. - Filename = /foo/bar BaseDirectory = /bar Result = False (no shared base directory)
  162. - Filename = foo/bar BaseDirectory = foo/foo Result = True RelPath = ../bar
  163. - Filename = foo/bar BaseDirectory = bar/foo Result = False (no shared base directory)
  164. - Filename = /foo BaseDirectory = bar Result = False (mixed absolute and relative)
  165. }
  166. function IsNameChar(c: char): boolean; inline;
  167. begin
  168. Result:=(c<>#0) and not (c in AllowDirectorySeparators);
  169. end;
  170. var
  171. UpDirCount: Integer;
  172. ResultPos: Integer;
  173. i: Integer;
  174. FileNameRestLen, SharedDirs: Integer;
  175. FileP, BaseP, FileEndP, BaseEndP: PChar;
  176. begin
  177. Result:=false;
  178. RelPath:=Filename;
  179. if (BaseDirectory='') or (Filename='') then exit;
  180. // check for different windows file drives
  181. if (CompareText(ExtractFileDrive(Filename),
  182. ExtractFileDrive(BaseDirectory))<>0)
  183. then
  184. exit;
  185. FileP:=PChar(Filename);
  186. BaseP:=PChar(BaseDirectory);
  187. //writeln('TryCreateRelativePath START File="',FileP,'" Base="',BaseP,'"');
  188. // skip matching directories
  189. SharedDirs:=0;
  190. if FileP^ in AllowDirectorySeparators then begin
  191. if not (BaseP^ in AllowDirectorySeparators) then exit;
  192. repeat
  193. while FileP^ in AllowDirectorySeparators do inc(FileP);
  194. while BaseP^ in AllowDirectorySeparators do inc(BaseP);
  195. if (FileP^=#0) or (BaseP^=#0) then break;
  196. //writeln('TryCreateRelativePath check match .. File="',FileP,'" Base="',BaseP,'"');
  197. FileEndP:=FileP;
  198. BaseEndP:=BaseP;
  199. while IsNameChar(FileEndP^) do inc(FileEndP);
  200. while IsNameChar(BaseEndP^) do inc(BaseEndP);
  201. if CompareFilenames(copy(Filename,FileP-PChar(Filename)+1,FileEndP-FileP),
  202. copy(BaseDirectory,BaseP-PChar(BaseDirectory)+1,BaseEndP-BaseP))<>0
  203. then
  204. break;
  205. FileP:=FileEndP;
  206. BaseP:=BaseEndP;
  207. inc(SharedDirs);
  208. until false;
  209. end else if (BaseP^ in AllowDirectorySeparators) then
  210. exit;
  211. //writeln('TryCreateRelativePath skipped matches File="',FileP,'" Base="',BaseP,'"');
  212. if SharedDirs=0 then exit;
  213. // calculate needed '../'
  214. UpDirCount:=0;
  215. BaseEndP:=BaseP;
  216. while IsNameChar(BaseEndP^) do begin
  217. inc(UpDirCount);
  218. while IsNameChar(BaseEndP^) do inc(BaseEndP);
  219. while BaseEndP^ in AllowDirectorySeparators do inc(BaseEndP);
  220. end;
  221. //writeln('TryCreateRelativePath UpDirCount=',UpDirCount,' File="',FileP,'" Base="',BaseP,'"');
  222. // create relative filename
  223. if (FileP^=#0) and (UpDirCount=0) then begin
  224. // Filename is the BaseDirectory
  225. if UsePointDirectory then
  226. RelPath:='.'
  227. else
  228. RelPath:='';
  229. exit(true);
  230. end;
  231. FileNameRestLen:=length(Filename)-(FileP-PChar(Filename));
  232. SetLength(RelPath,3*UpDirCount+FileNameRestLen);
  233. ResultPos:=1;
  234. for i:=1 to UpDirCount do begin
  235. RelPath[ResultPos]:='.';
  236. RelPath[ResultPos+1]:='.';
  237. RelPath[ResultPos+2]:=PathDelim;
  238. inc(ResultPos,3);
  239. end;
  240. if FileNameRestLen>0 then
  241. Move(FileP^,RelPath[ResultPos],FileNameRestLen);
  242. Result:=true;
  243. end;
  244. function ResolveDots(const AFilename: string): string;
  245. //trim double path delims and expand special dirs like .. and .
  246. //on Windows change also '/' to '\' except for filenames starting with '\\?\'
  247. var SrcPos, DestPos, l, DirStart: integer;
  248. c: char;
  249. MacroPos: LongInt;
  250. begin
  251. Result:=AFilename;
  252. {$ifdef windows}
  253. //Special case: everything is literal after this, even dots (this does not apply to '//?/')
  254. if (Pos('\\?\', AFilename) = 1) then Exit;
  255. {$endif}
  256. l:=length(AFilename);
  257. SrcPos:=1;
  258. DestPos:=1;
  259. // trim double path delimiters and special dirs . and ..
  260. while (SrcPos<=l) do begin
  261. c:=AFilename[SrcPos];
  262. {$ifdef windows}
  263. //change / to \. The WinApi accepts both, but it leads to strange effects in other places
  264. if (c in AllowDirectorySeparators) then c := PathDelim;
  265. {$endif}
  266. // check for double path delims
  267. if (c=PathDelim) then begin
  268. inc(SrcPos);
  269. {$IFDEF Windows}
  270. if (DestPos>2)
  271. {$ELSE}
  272. if (DestPos>1)
  273. {$ENDIF}
  274. and (Result[DestPos-1]=PathDelim) then begin
  275. // skip second PathDelim
  276. continue;
  277. end;
  278. Result[DestPos]:=c;
  279. inc(DestPos);
  280. continue;
  281. end;
  282. // check for special dirs . and ..
  283. if (c='.') then begin
  284. if (SrcPos<l) then begin
  285. if (AFilename[SrcPos+1]=PathDelim)
  286. and ((DestPos=1) or (AFilename[SrcPos-1]=PathDelim)) then begin
  287. // special dir ./ or */./
  288. // -> skip
  289. inc(SrcPos,2);
  290. continue;
  291. end else if (AFilename[SrcPos+1]='.')
  292. and (SrcPos+1=l) or (AFilename[SrcPos+2]=PathDelim) then
  293. begin
  294. // special dir ..
  295. // 1. .. -> copy
  296. // 2. /.. -> skip .., keep /
  297. // 3. C:.. -> copy
  298. // 4. C:\.. -> skip .., keep C:\
  299. // 5. \\.. -> skip .., keep \\
  300. // 6. ../.. -> copy because if the first '..' was not resolved, the next can't neither
  301. // 7. dir/.. -> trim dir and ..
  302. // 8. dir$macro/.. -> copy
  303. if DestPos=1 then begin
  304. // 1. .. or ../ -> copy
  305. end else if (DestPos=2) and (Result[1]=PathDelim) then begin
  306. // 2. /.. -> skip .., keep /
  307. inc(SrcPos,2);
  308. continue;
  309. {$IFDEF Windows}
  310. end else if (DestPos=3) and (Result[2]=':')
  311. and (Result[1] in ['a'..'z','A'..'Z']) then begin
  312. // 3. C:.. -> copy
  313. end else if (DestPos=4) and (Result[2]=':') and (Result[3]=PathDelim)
  314. and (Result[1] in ['a'..'z','A'..'Z']) then begin
  315. // 4. C:\.. -> skip .., keep C:\
  316. inc(SrcPos,2);
  317. continue;
  318. end else if (DestPos=3) and (Result[1]=PathDelim)
  319. and (Result[2]=PathDelim) then begin
  320. // 5. \\.. -> skip .., keep \\
  321. inc(SrcPos,2);
  322. continue;
  323. {$ENDIF}
  324. end else if (DestPos>1) and (Result[DestPos-1]=PathDelim) then begin
  325. // */.
  326. if (DestPos>3)
  327. and (Result[DestPos-2]='.') and (Result[DestPos-3]='.')
  328. and ((DestPos=4) or (Result[DestPos-4]=PathDelim)) then begin
  329. // 6. ../.. -> copy because if the first '..' was not resolved, the next can't neither
  330. end else begin
  331. // 7. xxxdir/.. -> trim dir and skip ..
  332. DirStart:=DestPos-2;
  333. while (DirStart>1) and (Result[DirStart-1]<>PathDelim) do
  334. dec(DirStart);
  335. MacroPos:=DirStart;
  336. while MacroPos<DestPos do begin
  337. if (Result[MacroPos]='$')
  338. and (Result[MacroPos+1] in ['(','a'..'z','A'..'Z']) then begin
  339. // 8. directory contains a macro -> keep
  340. break;
  341. end;
  342. inc(MacroPos);
  343. end;
  344. if MacroPos=DestPos then begin
  345. // previous directory does not contain a macro -> remove dir/..
  346. DestPos:=DirStart;
  347. inc(SrcPos,2);
  348. //writeln('ResolveDots ',DestPos,' SrcPos=',SrcPos,' File="',AFilename,'" Result="',copy(Result,1,DestPos-1),'"');
  349. if SrcPos>l then begin
  350. // '..' at end of filename
  351. if (DestPos>1) and (Result[DestPos-1]<>PathDelim) then begin
  352. // foo/dir/.. -> foo
  353. dec(DestPos);
  354. end else if (DestPos=1) then begin
  355. // foo/.. -> .
  356. Result[1]:='.';
  357. DestPos:=2;
  358. end;
  359. end else if DestPos=1 then begin
  360. // e.g. 'foo/../'
  361. while (SrcPos<=l) and (AFilename[SrcPos] in AllowDirectorySeparators) do
  362. inc(SrcPos);
  363. end;
  364. continue;
  365. end;
  366. end;
  367. end;
  368. end;
  369. end else begin
  370. // special dir . at end of filename
  371. if DestPos=1 then begin
  372. Result:='.';
  373. exit;
  374. end else begin
  375. // skip
  376. break;
  377. end;
  378. end;
  379. end;
  380. // copy directory
  381. repeat
  382. Result[DestPos]:=c;
  383. inc(DestPos);
  384. inc(SrcPos);
  385. if (SrcPos>l) then break;
  386. c:=AFilename[SrcPos];
  387. {$ifdef windows}
  388. //change / to \. The WinApi accepts both, but it leads to strange effects in other places
  389. if (c in AllowDirectorySeparators) then c := PathDelim;
  390. {$endif}
  391. if c=PathDelim then break;
  392. until false;
  393. end;
  394. // trim result
  395. if DestPos<=length(AFilename) then
  396. SetLength(Result,DestPos-1);
  397. end;
  398. procedure ForcePathDelims(Var FileName: string);
  399. var
  400. i: Integer;
  401. begin
  402. for i:=1 to length(FileName) do
  403. {$IFDEF Windows}
  404. if Filename[i]='/' then
  405. Filename[i]:='\';
  406. {$ELSE}
  407. if Filename[i]='\' then
  408. Filename[i]:='/';
  409. {$ENDIF}
  410. end;
  411. function GetForcedPathDelims(const FileName: string): String;
  412. begin
  413. Result:=FileName;
  414. ForcePathDelims(Result);
  415. end;
  416. function ExtractFilenameOnly(const aFilename: string): string;
  417. var
  418. StartPos: Integer;
  419. ExtPos: Integer;
  420. begin
  421. StartPos:=length(AFilename)+1;
  422. while (StartPos>1)
  423. and not (AFilename[StartPos-1] in AllowDirectorySeparators)
  424. {$IFDEF Windows}and (AFilename[StartPos-1]<>':'){$ENDIF}
  425. do
  426. dec(StartPos);
  427. ExtPos:=length(AFilename);
  428. while (ExtPos>=StartPos) and (AFilename[ExtPos]<>'.') do
  429. dec(ExtPos);
  430. if (ExtPos<StartPos) then ExtPos:=length(AFilename)+1;
  431. Result:=copy(AFilename,StartPos,ExtPos-StartPos);
  432. end;
  433. function CompareFilenames(const File1, File2: string): integer;
  434. begin
  435. Result:=AnsiCompareFileName(File1,File2);
  436. end;
  437. procedure FindMatchingFiles(Mask: string; MaxCount: integer; Files: TStrings);
  438. var
  439. p: Integer;
  440. Path, Filename: String;
  441. Info: TRawByteSearchRec;
  442. begin
  443. Mask:=ResolveDots(Mask);
  444. p:=1;
  445. while p<=length(Mask) do begin
  446. if Mask[p] in ['*','?'] then begin
  447. while (p<=length(Mask)) and not (Mask[p] in AllowDirectorySeparators) do inc(p);
  448. Path:=LeftStr(Mask,p-1);
  449. if FindFirst(Path,faAnyFile,Info)=0 then begin
  450. repeat
  451. Filename:=ExtractFilePath(Path)+Info.Name;
  452. if p>length(Mask) then begin
  453. // e.g. /path/unit*.pas
  454. if Files.Count>=MaxCount then
  455. raise EListError.Create('found too many files "'+Path+'"');
  456. Files.Add(Filename);
  457. end else begin
  458. // e.g. /path/sub*path/...
  459. FindMatchingFiles(Filename+copy(Mask,p,length(Mask)),MaxCount,Files);
  460. end;
  461. until FindNext(Info)<>0;
  462. end;
  463. exit;
  464. end;
  465. inc(p);
  466. end;
  467. if FileExists(Mask) then begin
  468. if Files.Count>=MaxCount then
  469. raise EListError.Create('found too many files "'+Mask+'"');
  470. Files.Add(Mask);
  471. end;
  472. end;
  473. function GetNextDelimitedItem(const List: string; Delimiter: char;
  474. var Position: integer): string;
  475. var
  476. StartPos: Integer;
  477. begin
  478. StartPos:=Position;
  479. while (Position<=length(List)) and (List[Position]<>Delimiter) do
  480. inc(Position);
  481. Result:=copy(List,StartPos,Position-StartPos);
  482. if Position<=length(List) then inc(Position); // skip Delimiter
  483. end;
  484. procedure IncreaseChangeStamp(var Stamp: TChangeStamp);
  485. begin
  486. if Stamp<High(TChangeStamp) then
  487. inc(Stamp)
  488. else
  489. Stamp:=InvalidChangeStamp+1;
  490. end;
  491. function IsNonUTF8System: boolean;
  492. begin
  493. Result:=NonUTF8System;
  494. end;
  495. function UTF8CharacterStrictLength(P: PChar): integer;
  496. begin
  497. if p=nil then exit(0);
  498. if ord(p^)<%10000000 then begin
  499. // regular single byte character
  500. exit(1);
  501. end
  502. else if ord(p^)<%11000000 then begin
  503. // invalid single byte character
  504. exit(0);
  505. end
  506. else if ((ord(p^) and %11100000) = %11000000) then begin
  507. // should be 2 byte character
  508. if (ord(p[1]) and %11000000) = %10000000 then
  509. exit(2)
  510. else
  511. exit(0);
  512. end
  513. else if ((ord(p^) and %11110000) = %11100000) then begin
  514. // should be 3 byte character
  515. if ((ord(p[1]) and %11000000) = %10000000)
  516. and ((ord(p[2]) and %11000000) = %10000000) then
  517. exit(3)
  518. else
  519. exit(0);
  520. end
  521. else if ((ord(p^) and %11111000) = %11110000) then begin
  522. // should be 4 byte character
  523. if ((ord(p[1]) and %11000000) = %10000000)
  524. and ((ord(p[2]) and %11000000) = %10000000)
  525. and ((ord(p[3]) and %11000000) = %10000000) then
  526. exit(4)
  527. else
  528. exit(0);
  529. end else
  530. exit(0);
  531. end;
  532. function GetDefaultTextEncoding: string;
  533. begin
  534. if EncodingValid then begin
  535. Result:=DefaultTextEncoding;
  536. exit;
  537. end;
  538. {$IFDEF Windows}
  539. Result:=GetWindowsEncoding;
  540. {$ELSE}
  541. {$IFDEF Darwin}
  542. Result:=EncodingUTF8;
  543. {$ELSE}
  544. Lang := GetEnvironmentVariable('LC_ALL');
  545. if Lang='' then begin
  546. Lang := GetEnvironmentVariable('LC_MESSAGES');
  547. if Lang='' then
  548. Lang := GetEnvironmentVariable('LANG');
  549. end;
  550. Result:=GetUnixEncoding;
  551. {$ENDIF}
  552. {$ENDIF}
  553. Result:=NormalizeEncoding(Result);
  554. DefaultTextEncoding:=Result;
  555. EncodingValid:=true;
  556. end;
  557. function NormalizeEncoding(const Encoding: string): string;
  558. var
  559. i: Integer;
  560. begin
  561. Result:=LowerCase(Encoding);
  562. for i:=length(Result) downto 1 do
  563. if Result[i]='-' then Delete(Result,i,1);
  564. end;
  565. function IsASCII(const s: string): boolean; inline;
  566. var
  567. p: PChar;
  568. begin
  569. if s='' then exit(true);
  570. p:=PChar(s);
  571. repeat
  572. case p^ of
  573. #0: if p-PChar(s)=length(s) then exit(true);
  574. #128..#255: exit(false);
  575. end;
  576. inc(p);
  577. until false;
  578. end;
  579. function UTF8ToUTF16(const s: string): UnicodeString;
  580. begin
  581. Result:=UTF8Decode(s);
  582. end;
  583. function UTF16ToUTF8(const s: UnicodeString): string;
  584. begin
  585. if s='' then exit('');
  586. Result:=UTF8Encode(s);
  587. // prevent UTF8 codepage appear in the strings - we don't need codepage
  588. // conversion magic
  589. SetCodePage(RawByteString(Result), CP_ACP, False);
  590. end;
  591. {$IFDEF Unix}
  592. {$I pas2jsfileutilsunix.inc}
  593. {$ENDIF}
  594. {$IFDEF Windows}
  595. {$I pas2jsfileutilswin.inc}
  596. {$ENDIF}
  597. procedure InternalInit;
  598. begin
  599. SetMultiByteConversionCodePage(CP_UTF8);
  600. // SetMultiByteFileSystemCodePage(CP_UTF8); not needed, this is the default under Windows
  601. SetMultiByteRTLFileSystemCodePage(CP_UTF8);
  602. GetDefaultTextEncoding;
  603. {$IFDEF Windows}
  604. NonUTF8System:=true;
  605. {$ELSE}
  606. NonUTF8System:=SysUtils.CompareText(DefaultTextEncoding,'UTF8')<>0;
  607. {$ENDIF}
  608. InitPlatform;
  609. end;
  610. initialization
  611. InternalInit;
  612. finalization
  613. FinalizePlatform;
  614. end.