Browse Source

* Patch from Mattias Gaertner
- quote object keys if needed { "1":"value" }
- use " or ' for string literals

git-svn-id: trunk@35416 -

michael 8 years ago
parent
commit
55241e283f
2 changed files with 111 additions and 11 deletions
  1. 2 0
      packages/fcl-js/src/jsbase.pp
  2. 109 11
      packages/fcl-js/src/jswriter.pp

+ 2 - 0
packages/fcl-js/src/jsbase.pp

@@ -26,6 +26,8 @@ Type
   TJSType = (jstUNDEFINED,jstNull,jstBoolean,jstNumber,jstString,jstObject,jstReference,JSTCompletion);
 
   TJSString = UnicodeString;
+  TJSChar = WideChar;
+  TJSPChar = PWideChar;
   TJSNumber = Double;
 
   { TJSValue }

+ 109 - 11
packages/fcl-js/src/jswriter.pp

@@ -83,8 +83,14 @@ Type
     Property AsUnicodeString : UnicodeString Read GetUnicodeString;
   end;
 
+  TJSEscapeQuote = (
+    jseqSingle,
+    jseqDouble,
+    jseqBoth
+    );
 
   { TJSWriter }
+
   TWriteOption = (woCompact,
                   woUseUTF8,
                   woTabIndent,
@@ -151,7 +157,7 @@ Type
     Procedure WritePrimaryExpression(El: TJSPrimaryExpression);virtual;
     Procedure WriteBinary(El: TJSBinary);virtual;
   Public
-    Function EscapeString(const S: TJSString): String;
+    Function EscapeString(const S: TJSString; Quote: TJSEscapeQuote = jseqDouble): String;
     Function JSStringToStr(const S: TJSString): string;
     Constructor Create(AWriter : TTextWriter);
     Constructor Create(Const AFileName : String);
@@ -164,7 +170,9 @@ Type
     Property IndentSize : Byte Read FIndentSize Write FIndentSize;
     Property UseUTF8 : Boolean Read GetUseUTF8;
   end;
-  EJSWriter = CLass(Exception);
+  EJSWriter = Class(Exception);
+
+function IsValidJSIdentifier(Name: TJSString; AllowEscapeSeq: boolean = false): boolean;
 
 implementation
 
@@ -172,6 +180,85 @@ Resourcestring
   SErrUnknownJSClass = 'Unknown javascript element class : %s';
   SErrNilNode = 'Nil node in Javascript';
 
+function IsValidJSIdentifier(Name: TJSString; AllowEscapeSeq: boolean): boolean;
+var
+  p: TJSPChar;
+  i: Integer;
+begin
+  Result:=false;
+  if Name='' then exit;
+  p:=TJSPChar(Name);
+  repeat
+    case p^ of
+    #0:
+      if p-TJSPChar(Name)=length(Name) then
+        exit(true)
+      else
+        exit;
+    '0'..'9':
+      if p=TJSPChar(Name) then
+        exit
+      else
+        inc(p);
+    'a'..'z','A'..'Z','_','$': inc(p);
+    '\':
+      begin
+      if not AllowEscapeSeq then exit;
+      inc(p);
+      if p^='x' then
+        begin
+        // \x00
+        for i:=1 to 2 do
+          begin
+          inc(p);
+          if not (p^ in ['0'..'9','a'..'f','A'..'F']) then exit;
+          end;
+        end
+      else if p^='u' then
+        begin
+        inc(p);
+        if p^='{' then
+          begin
+          // \u{00000}
+          i:=0;
+          repeat
+            inc(p);
+            case p^ of
+            '}': break;
+            '0'..'9': i:=i*16+ord(p^)-ord('0');
+            'a'..'f': i:=i*16+ord(p^)-ord('a')+10;
+            'A'..'F': i:=i*16+ord(p^)-ord('A')+10;
+            else exit;
+            end;
+            if i>$10FFFF then exit;
+          until false;
+          inc(p);
+          end
+        else
+          begin
+          // \u0000
+          for i:=1 to 4 do
+            begin
+            inc(p);
+            if not (p^ in ['0'..'9','a'..'f','A'..'F']) then exit;
+            end;
+          end;
+        end
+      else
+        exit; // unknown sequence
+      end;
+    #$200C,#$200D: inc(p); // zero width non-joiner/joiner
+    #$00AA..#$2000,
+    #$200E..#$D7FF:
+      inc(p); // ToDo: only those with ID_START/ID_CONTINUE see https://codepoints.net/search?IDC=1
+    #$D800..#$DBFF:
+      inc(p,2); // see above
+    else
+      exit;
+    end;
+  until false;
+end;
+
 { TBufferWriter }
 
 function TBufferWriter.GetBufferLength: Integer;
@@ -380,27 +467,30 @@ begin
     end;
 end;
 
-function TJSWriter.EscapeString(const S: TJSString): String;
+function TJSWriter.EscapeString(const S: TJSString; Quote: TJSEscapeQuote
+  ): String;
 
 Var
   I,J,L : Integer;
-  P : PWideChar;
+  P : TJSPChar;
 
 begin
   I:=1;
   J:=1;
   Result:='';
   L:=Length(S);
-  P:=PWideChar(S);
+  P:=TJSPChar(S);
   While I<=L do
     begin
-    if (P^ in ['"','/','\',#8,#9,#10,#12,#13]) then
+    if (P^ in [#0..#31,'"','''','/','\']) then
       begin
       Result:=Result+JSStringToStr(Copy(S,J,I-J));
       Case P^ of
         '\' : Result:=Result+'\\';
         '/' : Result:=Result+'\/';
-        '"' : Result:=Result+'\"';
+        '"' : if Quote=jseqSingle then Result:=Result+'"' else Result:=Result+'\"';
+        '''': if Quote=jseqDouble then Result:=Result+'''' else Result:=Result+'\''';
+        #0..#7,#11,#14..#31: Result:=Result+'\x'+hexStr(ord(P^),2);
         #8  : Result:=Result+'\b';
         #9  : Result:=Result+'\t';
         #10 : Result:=Result+'\n';
@@ -427,6 +517,7 @@ procedure TJSWriter.WriteValue(V: TJSValue);
 
 Var
   S : String;
+  JS: TJSString;
 begin
   if V.CustomValue<>'' then
     S:=JSStringToStr(V.CustomValue)
@@ -435,7 +526,14 @@ begin
       jstUNDEFINED : S:='undefined';
       jstNull : s:='null';
       jstBoolean : if V.AsBoolean then s:='true' else s:='false';
-      jstString : S:='"'+EscapeString(V.AsString)+'"';
+      jstString :
+        begin
+        JS:=V.AsString;
+        if Pos('"',JS)>0 then
+          S:=''''+EscapeString(JS,jseqSingle)+''''
+        else
+          S:='"'+EscapeString(JS,jseqDouble)+'"';
+        end;
       jstNumber :
         if Frac(V.AsNumber)=0 then // this needs to be improved
           Str(Round(V.AsNumber),S)
@@ -544,10 +642,10 @@ procedure TJSWriter.WriteRegularExpressionLiteral(
 
 begin
   Write('/');
-  Write(EscapeString(El.Pattern.AsString));
+  Write(EscapeString(El.Pattern.AsString,jseqBoth));
   Write('/');
   If Assigned(El.PatternFlags) then
-    Write(EscapeString(El.PatternFlags.AsString));
+    Write(EscapeString(El.PatternFlags.AsString,jseqBoth));
 end;
 
 procedure TJSWriter.WriteLiteral(El: TJSLiteral);
@@ -642,7 +740,7 @@ begin
   For I:=0 to C do
    begin
    S:=El.Elements[i].Name;
-   if QE then
+   if QE or not IsValidJSIdentifier(S) then
      S:='"'+S+'"';
    Write(S+': ');
    Indent;