1{%MainUnit pas2jsfileutils.pas} 2{ 3 This file is part of the Free Component Library (FCL) 4 Copyright (c) 2018 Mattias Gaertner mattias@freepascal.org 5 6 Pascal to Javascript converter class. 7 8 See the file COPYING.FPC, included in this distribution, 9 for details about the copyright. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 15 ********************************************************************** 16} 17{$IF DEFINED(UTF8_RTL) AND NOT DEFINED(WINCE)} 18 {$DEFINE ArgsWAsUTF8} 19{$ENDIF} 20 21{$IFDEF OldStuff} 22//Function prototypes 23var _ParamStrUtf8: Function(Param: Integer): string; 24{$ENDIF} 25 26var 27 ArgsW: Array of WideString; 28 ArgsWCount: Integer; // length(ArgsW)+1 29 {$IFDEF ArgsWAsUTF8} 30 ArgsUTF8: Array of String; // the ArgsW array as UTF8 31 OldArgV: PPChar = nil; 32 {$IFEND} 33 34{$ifndef wince} 35{$IFDEF OldStuff} 36function ParamStrUtf8Ansi(Param: Integer): String; 37begin 38 Result:=ObjPas.ParamStr(Param); 39end; 40{$ENDIF} 41{$endif wince} 42 43{$IFDEF OldStuff} 44function ParamStrUtf8Wide(Param: Integer): String; 45begin 46 if ArgsWCount <> ParamCount then 47 begin 48 //DebugLn('Error: ParamCount <> ArgsWCount!'); 49 Result := ObjPas.ParamStr(Param); 50 end 51 else 52 begin 53 if (Param <= ArgsWCount) then 54 {$IFDEF ACP_RTL} 55 Result := String(UnicodeString(ArgsW[Param])) 56 {$ELSE} 57 Result := UTF16ToUTF8(ArgsW[Param]) 58 {$ENDIF ACP_RTL} 59 else 60 Result := ''; 61 end; 62end; 63{$ENDIF oldstuff} 64 65{$IFDEF ArgsWAsUTF8} 66procedure SetupArgvAsUtf8; 67var 68 i: Integer; 69begin 70 SetLength(ArgsUTF8,length(ArgsW)); 71 OldArgV:=argv; 72 GetMem(argv,SizeOf(Pointer)*length(ArgsW)); 73 for i:=0 to length(ArgsW)-1 do 74 begin 75 ArgsUTF8[i]:=ArgsW{%H-}[i]; 76 argv[i]:=PChar(ArgsUTF8[i]); 77 end; 78end; 79{$endif} 80 81procedure SetupCommandlineParametersWide; 82var 83 ArgLen, Start, CmdLen, i, j: SizeInt; 84 Quote : Boolean; 85 Buf: array[0..259] of WChar; // need MAX_PATH bytes, not 256! 86 PCmdLineW: PWideChar; 87 CmdLineW: WideString; 88 89 procedure AllocArg(Idx, Len:longint); 90 begin 91 if (Idx >= ArgsWCount) then 92 SetLength(ArgsW, Idx + 1); 93 SetLength(ArgsW[Idx], Len); 94 end; 95 96begin 97 { create commandline, it starts with the executed filename which is argv[0] } 98 { Win32 passes the command NOT via the args, but via getmodulefilename} 99 ArgsWCount := 0; 100 ArgLen := GetModuleFileNameW(0, @buf[0], sizeof(buf)); 101 102 //writeln('ArgLen = ',Arglen); 103 104 buf[ArgLen] := #0; // be safe, no terminating 0 on XP 105 allocarg(0,arglen); 106 move(buf[0],ArgsW[0][1],arglen * SizeOf(WChar)); 107 108 //writeln('ArgsW[0] = ',ArgsW[0]); 109 110 PCmdLineW := nil; 111 { Setup cmdline variable } 112 PCmdLineW := GetCommandLineW; 113 CmdLen := StrLen(PCmdLineW); 114 115 //writeln('StrLen(PCmdLineW) = ',CmdLen); 116 117 SetLength(CmdLineW, CmdLen); 118 Move(PCmdLineW^, CmdLineW[1], CmdLen * SizeOf(WChar)); 119 120 121 //debugln(CmdLineW); 122 //for i := 1 to CmdLen do DbgOut(DbgS(i mod 10)); debugln; 123 124 i := 1; 125 while (i <= CmdLen) do 126 begin 127 //debugln('Next'); 128 //DbgOut('i=',DbgS(i),' CmdLineW[',DbgS(i),']=');if i<=CmdLen then debugln(CmdLineW[i]) else debugln('#0'); 129 //skip leading spaces 130 while (i <= CmdLen) and (CmdLineW[i] <= #32) do Inc(i); 131 //DbgOut('After skipping spaces: i=',DbgS(i),' CmdLineW[',DbgS(i),']=');if i<=CmdLen then debugln(CmdLineW[i]) else debugln('#0'); 132 if (i > CmdLen) then Break; 133 Quote := False; 134 Start := i; 135 ArgLen := 0; 136 while (i <= CmdLen) do 137 begin //find next commandline parameter 138 case CmdLineW[i] of 139 #1..#32: 140 begin 141 if Quote then 142 begin 143 //debugln('i=',DbgS(i),': Space in Quote'); 144 Inc(ArgLen) 145 end 146 else 147 begin 148 //debugln('i=',DbgS(i),': Space in NOT Quote'); 149 Break; 150 end; 151 end; 152 '"': 153 begin 154 if (i < CmdLen) and (CmdLineW[i+1] <> '"') then 155 begin 156 //debugln('i=',DbgS(i),': Quote := not Quote'); 157 Quote := not Quote 158 end 159 else 160 begin 161 //debugln('i=',DbgS(i),': Skip Quote'); 162 Inc(i); 163 end; 164 end; 165 else Inc(ArgLen); 166 end;//case 167 Inc(i); 168 end; //find next commandline parameter 169 170 //debugln('ArgWCount=',DbgS(ArgsWCount),' Start=',DbgS(start),' ArgLen=',DbgS(arglen),' i=',DbgS(i)); 171 172 //we already have (a better) ArgW[0] 173 if (ArgsWCount > 0) then 174 begin //Process commandline parameter 175 AllocArg(ArgsWCount, ArgLen); 176 Quote := False; 177 i := Start; 178 j := 1; 179 while (i <= CmdLen) do 180 begin 181 case CmdLineW[i] of 182 #1..#32: 183 begin 184 if Quote then 185 begin 186 //if j > ArgLen then debugln('Error whitespace: j > ArgLen: j=',DbgS(j),' ArgLen=',DbgS(arglen)); 187 ArgsW[ArgsWCount][j] := CmdLineW[i]; 188 Inc(j); 189 end 190 else 191 Break; 192 end; 193 '"': 194 begin 195 if (i < CmdLen) and (CmdLineW[i+1] <> '"') then 196 Quote := not Quote 197 else 198 Inc(i); 199 end; 200 else 201 begin 202 //if j > ArgLen then debugln('Error Quote: j > ArgLen: j=',DbgS(j),' ArgLen=',DbgS(arglen)); 203 ArgsW[ArgsWCount][j] := CmdLineW[i]; 204 Inc(j); 205 end; 206 end; 207 Inc(i); 208 end; 209 210 //debugln('ArgsW[',DbgS(ArgsWCount),'] = ',ArgsW[ArgsWCount]); 211 end; // Process commandline parameter 212 Inc(ArgsWCount); 213 214 end; 215 Dec(ArgsWCount); 216 //Note: 217 //On WinCe Argsv is a static function, so we cannot change it. 218 //This might change in the future if Argsv on WinCE will be declared as a function variable 219 {$IFDEF ArgsWAsUTF8} 220 if DefaultSystemCodePage=CP_UTF8 then 221 SetupArgvAsUtf8; 222 {$IFEND} 223end; 224 225function FilenameIsAbsolute(const aFilename: string): boolean; 226begin 227 Result:=FilenameIsWinAbsolute(aFilename); 228end; 229 230procedure GetDirUtf8(DriveNr: Byte; var Dir: String); 231{This procedure may not be threadsafe, because SetCurrentDirectory isn't} 232{$ifndef WinCE} 233var 234 w, D: WideString; 235 SavedDir: WideString; 236 res : Integer; 237{$endif} 238begin 239 {$ifdef WinCE} 240 Dir := '\'; 241 // Previously we sent an exception here, which is correct, but this causes 242 // trouble with code which isnt tested for WinCE, so lets just send a dummy result instead 243 // Exception.Create('[GetCurrentDirWide] The concept of the current directory doesn''t exist in Windows CE'); 244 {$else} 245 //writeln('GetDirWide START'); 246 if not (DriveNr = 0) then 247 begin 248 res := GetCurrentDirectoryW(0, nil); 249 SetLength(SavedDir, res); 250 res:=Windows.GetCurrentDirectoryW(res, @SavedDir[1]); 251 SetLength(SavedDir,res); 252 253 D := WideChar(64 + DriveNr) + ':'; 254 if not SetCurrentDirectoryW(@D[1]) then 255 begin 256 Dir := Char(64 + DriveNr) + ':\'; 257 SetCurrentDirectoryW(@SavedDir[1]); 258 Exit; 259 end; 260 end; 261 res := GetCurrentDirectoryW(0, nil); 262 SetLength(w, res); 263 res := GetCurrentDirectoryW(res, @w[1]); 264 SetLength(w, res); 265 Dir:=UTF16ToUTF8(w); 266 if not (DriveNr = 0) then SetCurrentDirectoryW(@SavedDir[1]); 267 //writeln('GetDirWide END'); 268 {$endif} 269end; 270 271function ExpandFileNamePJ(const FileName: string; {const} BaseDir: String = ''): String; 272var 273 IsAbs, StartsWithRoot, CanUseBaseDir : Boolean; 274 {$ifndef WinCE} 275 HasDrive: Boolean; 276 FnDrive, CurDrive, BaseDirDrive: Char; 277 {$endif} 278 CurDir, Fn: String; 279begin 280 //writeln('LazFileUtils.ExpandFileNamePJ'); 281 //writeln('FileName = "',FileName,'"'); 282 //writeln('BaseDir = "',BaseDir,'"'); 283 284 Fn := FileName; 285 //if Filename uses ExtendedLengthPath scheme then it cannot be expanded 286 //AND it should not be altered by ForcePathDelims or ResolveDots 287 //See: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx 288 if (Length(Fn) > 3) and (Fn[1] = PathDelim) and (Fn[2] = PathDelim) and 289 (Fn[3] = '?') and (Fn[4] = PathDelim) //Do NOT use AllowDirectorySeparators here! 290 then Exit(FN); 291 ForcePathDelims(Fn); 292 IsAbs := FileNameIsWinAbsolute(Fn); 293 if not IsAbs then 294 begin 295 StartsWithRoot := (Fn = '\') or 296 ((Length(Fn) > 1) and 297 (Fn[1] = DirectorySeparator) and 298 (Fn[2] <> DirectorySeparator)); 299 {$ifndef WinCE} 300 HasDrive := (Length(Fn) > 1) and 301 (Fn[2] = ':') and 302 (UpCase(Fn[1]) in ['A'..'Z']); 303 304 if HasDrive then 305 begin 306 FnDrive := UpCase(Fn[1]); 307 GetDirUtf8(Byte(FnDrive)-64, CurDir{%H-}); 308 CurDrive := UpCase(GetCurrentDirPJ[1]); 309 end 310 else 311 begin 312 CurDir := GetCurrentDirPJ; 313 FnDrive := UpCase(CurDir[1]); 314 CurDrive := FnDrive; 315 end; 316 317 //writeln('HasDrive = ',HasDrive,' Fn = ',Fn); 318 //writeln('CurDir = ',CurDir); 319 //writeln('CurDrive = ',CurDrive); 320 //writeln('FnDrive = ',FnDrive); 321 322 if (Length(BaseDir) > 1) and (UpCase(BaseDir[1]) in ['A'..'Z']) and (BaseDir[2] = ':') then 323 begin 324 BaseDirDrive := BaseDir[1] 325 end 326 else 327 begin 328 if HasDrive then 329 BaseDirDrive := CurDrive 330 else 331 BaseDirDrive := #0; 332 end; 333 334 //You cannot use BaseDir if both FileName and BaseDir includes a drive and they are not the same 335 CanUseBaseDir := ((BaseDirDrive = #0) or 336 (not HasDrive) or 337 (HasDrive and (FnDrive = BaseDirDrive))) 338 and (BaseDir <> ''); 339 340 //writeln('CanUseBaseDir = ',CanUseBaseDir); 341 342 if not HasDrive and StartsWithRoot and not CanUseBaseDir then 343 begin 344 //writeln('HasDrive and StartsWithRoot'); 345 Fn := Copy(CurDir,1,2) + Fn; 346 HasDrive := True; 347 IsAbs := True; 348 end; 349 //FileNames like C:foo, strip Driveletter + colon 350 if HasDrive and not IsAbs then Delete(Fn,1,2); 351 352 //writeln('HasDrive = ',Hasdrive,' Fn = ',Fn); 353 {$else} 354 CanUseBaseDir := True; 355 {$endif WinCE} 356 end; 357 if IsAbs then 358 begin 359 //writeln('IsAbs = True -> Exit'); 360 Result := ResolveDots(Fn); 361 end 362 else 363 begin 364 if not CanUseBaseDir or (BaseDir = '') then 365 Fn := IncludeTrailingPathDelimiter(CurDir) + Fn 366 else 367 begin 368 if (Length(Fn) > 0) and (Fn[1] = DirectorySeparator) then Delete(Fn,1,1); 369 Fn := IncludeTrailingPathDelimiter(BaseDir) + Fn; 370 end; 371 372 Fn := ResolveDots(Fn); 373 //if BaseDir is something like 'z:foo\' or '\' then this needs to be expanded as well 374 if not FileNameIsAbsolute(Fn) then 375 Fn := ExpandFileNamePJ(Fn, ''); 376 Result := Fn; 377 end; 378end; 379 380function GetCurrentDirPJ: String; 381{$ifndef WinCE} 382var 383 w : UnicodeString; 384 res : Integer; 385 {$endif} 386begin 387 {$ifdef WinCE} 388 Result := '\'; 389 // Previously we sent an exception here, which is correct, but this causes 390 // trouble with code which isnt tested for WinCE, so lets just send a dummy result instead 391 // Exception.Create('[GetCurrentDirWide] The concept of the current directory doesn''t exist in Windows CE'); 392 {$else} 393 res:=GetCurrentDirectoryW(0, nil); 394 SetLength(w, res); 395 res:=Windows.GetCurrentDirectoryW(res, @w[1]); 396 SetLength(w, res); 397 Result:=UTF16ToUTF8(w); 398 {$endif} 399end; 400 401function GetPhysicalFilename(const Filename: string; ExceptionOnError: boolean 402 ): string; 403begin 404 Result:=Filename; 405 if ExceptionOnError then ; 406end; 407 408function ResolveSymLinks(const Filename: string; ExceptionOnError: boolean 409 ): string; 410begin 411 Result:=Filename; 412end; 413 414function IsUNCPath(const Path: String): Boolean; 415begin 416 Result := (Length(Path) > 2) 417 and (Path[1] in AllowDirectorySeparators) 418 and (Path[2] in AllowDirectorySeparators); 419end; 420 421function ExtractUNCVolume(const Path: String): String; 422var 423 I, Len: Integer; 424 425 // the next function reuses Len variable 426 function NextPathDelim(const Start: Integer): Integer;// inline; 427 begin 428 Result := Start; 429 while (Result <= Len) and not (Path[Result] in AllowDirectorySeparators) do 430 inc(Result); 431 end; 432 433begin 434 if not IsUNCPath(Path) then 435 Exit(''); 436 I := 3; 437 Len := Length(Path); 438 if Path[I] = '?' then 439 begin 440 // Long UNC path form like: 441 // \\?\UNC\ComputerName\SharedFolder\Resource or 442 // \\?\C:\Directory 443 inc(I); 444 if not (Path[I] in AllowDirectorySeparators) then 445 Exit(''); 446 if UpperCase(Copy(Path, I + 1, 3)) = 'UNC' then 447 begin 448 inc(I, 4); 449 if I < Len then 450 I := NextPathDelim(I + 1); 451 if I < Len then 452 I := NextPathDelim(I + 1); 453 end; 454 end 455 else 456 begin 457 I := NextPathDelim(I); 458 if I < Len then 459 I := NextPathDelim(I + 1); 460 end; 461 Result := Copy(Path, 1, I); 462end; 463 464function FileGetAttrUTF8(const FileName: String): Longint; 465begin 466 Result:=Integer(Windows.GetFileAttributesW(PWideChar(UTF8Decode(FileName)))); 467end; 468 469function FileIsWritable(const AFilename: string): boolean; 470begin 471 Result:=((FileGetAttrUTF8(AFilename) and faReadOnly) = 0); 472end; 473 474function FileIsExecutable(const AFilename: string): boolean; 475begin 476 Result:=FileExists(AFilename); 477end; 478 479function GetEnvironmentVariableCountPJ: Integer; 480var 481 hp,p : PWideChar; 482begin 483 Result:=0; 484 p:=GetEnvironmentStringsW; 485 if p=nil then exit; 486 hp:=p; 487 while hp^<>#0 do 488 begin 489 Inc(Result); 490 hp:=hp+strlen(hp)+1; 491 end; 492 FreeEnvironmentStringsW(p); 493end; 494 495function GetEnvironmentStringPJ(Index: Integer): string; 496var 497 hp,p : PWideChar; 498begin 499 Result:=''; 500 p:=GetEnvironmentStringsW; 501 if p=nil then exit; 502 hp:=p; 503 while (hp^<>#0) and (Index>1) do 504 begin 505 Dec(Index); 506 hp:=hp+strlen(hp)+1; 507 end; 508 if (hp^<>#0) then 509 Result:=UTF16ToUTF8(hp); 510 FreeEnvironmentStringsW(p); 511end; 512 513function GetEnvironmentVariablePJ(const EnvVar: string): String; 514begin 515 Result:=UTF16ToUTF8(SysUtils.GetEnvironmentVariable(UTF8ToUTF16(EnvVar))); 516end; 517 518// AConsole - If false, it is the general system encoding, 519// if true, it is the console encoding 520function GetWindowsEncoding(AConsole: Boolean = False): string; 521var 522 cp : UINT; 523{$IFDEF WinCE} 524// CP_UTF8 is missing in the windows unit of the Windows CE RTL 525const 526 CP_UTF8 = 65001; 527{$ENDIF} 528begin 529 if AConsole then cp := GetOEMCP 530 else cp := GetACP; 531 532 case cp of 533 CP_UTF8: Result := EncodingUTF8; 534 else 535 Result:='cp'+IntToStr(cp); 536 end; 537end; 538 539function GetConsoleTextEncoding: string; 540begin 541 Result:=GetWindowsEncoding(True); 542end; 543 544{$ifdef WinCe} 545function UTF8ToSystemCP(const s: string): string; inline; 546begin 547 Result := s; 548end; 549{$else} 550function UTF8ToSystemCP(const s: string): string; 551// result has codepage CP_ACP 552var 553 src: UnicodeString; 554 len: LongInt; 555begin 556 Result:=s; 557 if IsASCII(Result) then 558 begin 559 // prevent codepage conversion magic 560 SetCodePage(RawByteString(Result), CP_ACP, False); 561 exit; 562 end; 563 src:=UTF8Decode(s); 564 if src='' then 565 exit; 566 len:=WideCharToMultiByte(CP_ACP,0,PUnicodeChar(src),length(src),nil,0,nil,nil); 567 SetLength(Result,len); 568 if len>0 then 569 begin 570 WideCharToMultiByte(CP_ACP,0,PUnicodeChar(src),length(src),@Result[1],length(Result),nil,nil); 571 // prevent codepage conversion magic 572 SetCodePage(RawByteString(Result), CP_ACP, False); 573 end; 574end; 575{$endif not wince} 576 577{$ifdef WinCE} 578function SystemCPToUTF8(const s: string): string; inline; 579begin 580 Result := SysToUtf8(s); 581end; 582{$else} 583// for all Windows supporting 8bit codepages (e.g. not WinCE) 584function SystemCPToUTF8(const s: string): string; 585// result has codepage CP_ACP 586var 587 UTF16WordCnt: SizeInt; 588 UTF16Str: UnicodeString; 589begin 590 Result:=s; 591 if IsASCII(Result) then 592 begin 593 // prevent codepage conversion magic 594 SetCodePage(RawByteString(Result), CP_ACP, False); 595 exit; 596 end; 597 UTF16WordCnt:=MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, Pointer(s), length(s), nil, 0); 598 // this will null-terminate 599 if UTF16WordCnt>0 then 600 begin 601 setlength(UTF16Str, UTF16WordCnt); 602 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, Pointer(s), length(s), @UTF16Str[1], UTF16WordCnt); 603 Result:=UTF16ToUTF8(UTF16Str); 604 end; 605end; 606{$endif not wince} 607 608{$ifdef WinCe} 609function UTF8ToConsole(const s: string): string; // converts UTF8 to console string (used by Write, WriteLn) 610begin 611 Result := UTF8ToSystemCP(s); 612end; 613{$else} 614function UTF8ToConsole(const s: string): string; // converts UTF8 to console string (used by Write, WriteLn) 615var 616 Dst: PChar; 617begin 618 {$ifndef NO_CP_RTL} 619 Result := UTF8ToSystemCP(s); 620 {$else NO_CP_RTL} 621 Result := s; // Kept for compatibility 622 {$endif NO_CP_RTL} 623 Dst := AllocMem((Length(Result) + 1) * SizeOf(Char)); 624 if CharToOEM(PChar(Result), Dst) then 625 Result := StrPas(Dst); 626 FreeMem(Dst); 627 {$ifndef NO_CP_RTL} 628 SetCodePage(RawByteString(Result), CP_OEMCP, False); 629 {$endif NO_CP_RTL} 630end; 631{$endif not WinCE} 632 633{$ifdef WinCE} 634function ConsoleToUTF8(const s: string): string;// converts console encoding to UTF8 635begin 636 Result := SysToUTF8(s); 637end; 638{$else} 639function ConsoleToUTF8(const s: string): string;// converts console encoding to UTF8 640var 641 Dst: PChar; 642begin 643 Dst := AllocMem((Length(s) + 1) * SizeOf(Char)); 644 if OemToChar(PChar(s), Dst) then 645 Result := StrPas(Dst) 646 else 647 Result := s; 648 FreeMem(Dst); 649 Result := SystemCPToUTF8(Result); 650end; 651{$endif not wince} 652 653procedure InitPlatform; 654begin 655 {$ifndef WinCE} 656 if Win32MajorVersion <= 4 then 657 begin 658 {$IFDEF OldStuff} 659 _ParamStrUtf8 := @ParamStrUtf8Ansi; 660 {$ENDIF} 661 end 662 else 663 {$endif} 664 begin 665 ArgsWCount := -1; 666 {$IFDEF OldStuff} 667 _ParamStrUtf8 := @ParamStrUtf8Wide; 668 {$ENDIF} 669 SetupCommandlineParametersWide; 670 end; 671end; 672 673procedure FinalizePlatform; 674{$IFDEF ArgsWAsUTF8} 675var 676 p: PPChar; 677{$ENDIF} 678begin 679 {$IFDEF ArgsWAsUTF8} 680 // restore argv and free memory 681 if OldArgV<>nil then 682 begin 683 p:=argv; 684 argv:=OldArgV; 685 Freemem(p); 686 end; 687 {$ENDIF} 688end; 689