Browse Source

* Patch by Dokkie8844, better whitespace handling: fix issue #39557

Michaël Van Canneyt 3 years ago
parent
commit
b3e079c5da
1 changed files with 77 additions and 107 deletions
  1. 77 107
      packages/fcl-mustache/src/fpmustache.pp

+ 77 - 107
packages/fcl-mustache/src/fpmustache.pp

@@ -202,11 +202,8 @@ Type
     Procedure DoParse(aParent : TMustacheElement; Const aTemplate, aStart, aStop : TMustacheString); virtual;
     Procedure DoParse(aParent : TMustacheElement; Const aTemplate, aStart, aStop : TMustacheString); virtual;
     // Called to get the template of a partial. The template is parsed, and the result added to the partials list.
     // Called to get the template of a partial. The template is parsed, and the result added to the partials list.
     Function GetPartial(const aName : TMustacheString) : TMustacheString; virtual;
     Function GetPartial(const aName : TMustacheString) : TMustacheString; virtual;
-    // Auxuliary functions for the peculiar whitespace handling of Mustache specs...
-    function EndsOnWhiteSpace(aElement: TMustacheElement): Boolean; virtual;
-    function GetEndingWhiteSpace(aElement: TMustacheElement): TMustacheString; virtual;
+    // Extract new start/stop tag markers
     procedure ExtractStartStop(const aName: TMustacheString; out aStart, aStop: TMustacheString); virtual;
     procedure ExtractStartStop(const aName: TMustacheString; out aStart, aStop: TMustacheString); virtual;
-    procedure TrimEndingWhiteSpace(aElement: TMustacheElement); virtual;
   Public
   Public
     // Create a new parser.
     // Create a new parser.
     Constructor Create(aTemplate : TMustacheString = '';aStart: TMustacheString='';aStop: TMustacheString = ''); virtual;
     Constructor Create(aTemplate : TMustacheString = '';aStart: TMustacheString='';aStop: TMustacheString = ''); virtual;
@@ -961,55 +958,6 @@ begin
   DoParse(aParent,FTemplate,StartTag, StopTag);
   DoParse(aParent,FTemplate,StartTag, StopTag);
 end;
 end;
 
 
-function TMustacheParser.EndsOnWhiteSpace(aElement: TMustacheElement): Boolean;
-
-Var
-  I : Integer;
-  S : TMustacheString;
-
-begin
-  // if on standalone line, the entire line must be removed, see specs comments.standalone
-  Result:=(aElement.ElementType=metText);
-  s:=aElement.Data;
-  I:=Length(S);
-  While Result and (I>0) do
-     begin
-     if S[i] in [#13,#10] then
-       Break;
-     Result:=(S[I]=' ');
-     Dec(i);
-     end;
-  Result:=Result and ((I>0) or (aElement.Position=1));
-end;
-
-function TMustacheParser.GetEndingWhiteSpace(aElement: TMustacheElement): TMustacheString;
-
-Var
-  S : TMustacheString;
-  I : Integer;
-
-begin
-  s:=aElement.Data;
-  I:=Length(S);
-  While (I>0) and (S[I]=' ') do
-     Dec(i);
-  Result:=Copy(S,I+1);
-end;
-
-procedure TMustacheParser.TrimEndingWhiteSpace(aElement: TMustacheElement);
-
-Var
-  I : Integer;
-  S : TMustacheString;
-
-begin
-  s:=aElement.Data;
-  I:=Length(S);
-  While (I>0) and (S[I]=' ') do
-     Dec(i);
-  aElement.Data:=Copy(S,1,I);
-end;
-
 Function TMustacheParser.CreateDefault(aParent : TMustacheElement; aPosition : Integer;Const aName : String) : TMustacheElement;
 Function TMustacheParser.CreateDefault(aParent : TMustacheElement; aPosition : Integer;Const aName : String) : TMustacheElement;
 
 
 begin
 begin
@@ -1025,42 +973,74 @@ Var
   aLen,clStop, lStart,lStop, NewPos, Current, Total : Integer;
   aLen,clStop, lStart,lStop, NewPos, Current, Total : Integer;
   aName,cStart,cStop,R : TMustacheString;
   aName,cStart,cStop,R : TMustacheString;
   C: TMustacheChar;
   C: TMustacheChar;
-  IsWhiteSpace : Boolean;
-  Partial,WhiteSpaceEl : TMustacheELement;
+  IsStandalone: Boolean;
+  PartialPrefix: TMustacheString;
+  Partial : TMustacheELement;
 
 
-  Function CheckWhiteSpace : Boolean;
+  // check if the current tag is a tag that can occur standalone on a line
+  // if so, the whole line will be removed as per the specs
+  function IsStandaloneTag: Boolean;
+  begin
+    Result := (NewPos + lStart <= Total) and
+      (aTemplate[NewPos + lStart] in ['=','#','!','^','>','/']);
+  end;
 
 
+  function IsPartialTag: Boolean;
   begin
   begin
-    WhiteSpaceEl:=Nil;
-    With CurrParent do
-      begin
-      Result:=(ChildCount=0) or EndsOnWhiteSpace(Children[ChildCount-1]);
-      if Result and (ChildCount>0) then
-         WhiteSpaceEl:=Children[ChildCount-1];
-      end;
+    Result := (NewPos + lStart <= Total) and (aTemplate[NewPos + lStart] = '>');
   end;
   end;
 
 
-  Procedure FinishWhiteSpace(Full : Boolean = true);
-  Var
-    I : Integer;
+  // check if the current tag occurs standalone on a line
+  function CheckStandalone: Boolean;
+
+    function LeftOnlyWhiteSpace: Boolean;
+    var
+      I: Integer;
+    begin
+      I := NewPos - 1;
+      while (I >= 1) and (aTemplate[I] = ' ') do
+         Dec(I);
+      Result := (I < 1) or (aTemplate[I] = #10);
+    end;
+
+    function RightOnlyWhiteSpace: Boolean;
+    var
+      I: Integer;
+    begin
+      I := Pos(cStop, aTemplate, NewPos + lStart);
+      if I = 0 then
+        Exit(False);
+      Inc(I, lStop);
+      while (I <= Total) and (aTemplate[I] = ' ') do
+         Inc(I);
+      Result := (I > Total) or (aTemplate[I] = #10) or
+        (Copy(aTemplate, I, 2) = #13#10);
+    end;
+
   begin
   begin
-    I:=NewPos;
-    While IsWhiteSpace and (I+clStop<=Total) do
-      begin
-      C:=aTemplate[I+clStop];
-      if (C in [#13,#10]) then
-        Break;
-      isWhiteSpace:=aTemplate[I+clStop]=' ';
-      I:=I+1;
-      end;
-    if isWhiteSpace then
-      begin
-      While (I<=Total) and (aTemplate[I+clStop] in [#13,#10]) do
-        Inc(I);
-      NewPos:=I;
-      if Assigned(WhiteSpaceEl) and full then
-        TrimEndingWhiteSpace(WhiteSpaceEl);
-      end;
+    Result := IsStandaloneTag and LeftOnlyWhiteSpace and RightOnlyWhiteSpace;
+  end;
+
+  function WhiteSpaceRightPos(const S: TMustacheString): Integer;
+  var
+    I: Integer;
+  begin
+    I := Length(S);
+    while (I > 0) and (S[I] = ' ') do
+      Dec(I);
+
+    Result := I + 1;
+  end;
+
+  procedure SkipRestOfLine;
+  var
+    EndOfLine: Integer;
+  begin
+    EndOfLine := Pos(#10, aTemplate, Current);
+    if EndOfLine = 0 then
+      Current := Total + 1
+    else
+      Current := EndOfLine + 1;
   end;
   end;
 
 
 begin
 begin
@@ -1073,15 +1053,24 @@ begin
   Total:=Length(aTemplate);
   Total:=Length(aTemplate);
   While (Current<=Total) do
   While (Current<=Total) do
     begin
     begin
-    C:=Template[Current];
+    PartialPrefix := '';
     NewPos:=Pos(cStart,aTemplate,Current);
     NewPos:=Pos(cStart,aTemplate,Current);
+    IsStandalone := CheckStandalone;
     if NewPos=0 then
     if NewPos=0 then
       NewPos:=Total+1;
       NewPos:=Total+1;
     // Stash what we have till now.
     // Stash what we have till now.
     if NewPos>Current then
     if NewPos>Current then
       begin
       begin
       R:=Copy(aTemplate,Current,NewPos-Current);
       R:=Copy(aTemplate,Current,NewPos-Current);
-      CreateElement(metText,currParent,Current).SetData(R);
+      if IsStandalone then
+        if IsPartialTag then
+          // keep R intact; copy trailing whitespace to PartialPrefix
+          PartialPrefix := Copy(R, WhiteSpaceRightPos(R))
+        else
+          // remove trailing whitespace from R
+          R := Copy(R, 1, WhiteSpaceRightPos(R) - 1);
+      if R <> '' then
+        CreateElement(metText,currParent,Current).SetData(R);
       Current:=NewPos;
       Current:=NewPos;
       end;
       end;
     if Current<Total then
     if Current<Total then
@@ -1100,14 +1089,9 @@ begin
       case C of
       case C of
         '=' :
         '=' :
           begin
           begin
-          IsWhiteSpace:=CheckWhiteSpace;
-          if IsWhiteSpace then
-            FinishWhiteSpace;
           ExtractStartStop(aName,cStart,cStop);
           ExtractStartStop(aName,cStart,cStop);
           lStart:=Length(cStart);
           lStart:=Length(cStart);
           lStop:=Length(cStop);
           lStop:=Length(cStop);
-          //R:=Copy(aTemplate,newPos+clStop);
-          //Writeln(R);
           end;
           end;
         '{' :
         '{' :
           begin
           begin
@@ -1122,31 +1106,21 @@ begin
           end;
           end;
         '#' :
         '#' :
           begin
           begin
-          IsWhiteSpace:=CheckWhiteSpace;
           CurrParent:=CreateElement(metSection,currParent,Current);
           CurrParent:=CreateElement(metSection,currParent,Current);
           CurrParent.SetData(aName);
           CurrParent.SetData(aName);
-          if IsWhiteSpace then
-            FinishWhiteSpace;
           end;
           end;
         '!' :
         '!' :
           begin
           begin
-          IsWhiteSpace:=CheckWhiteSpace;
           CreateElement(metComment,currParent,Current).SetData(aName);
           CreateElement(metComment,currParent,Current).SetData(aName);
-          if IsWhiteSpace then
-            FinishWhiteSpace;
           end;
           end;
         '^' :
         '^' :
           begin
           begin
-          IsWhiteSpace:=CheckWhiteSpace;
           CurrParent:=CreateElement(metInvertedSection,currParent,Current);
           CurrParent:=CreateElement(metInvertedSection,currParent,Current);
           CurrParent.SetData(aName);
           CurrParent.SetData(aName);
-          if IsWhiteSpace then
-            FinishWhiteSpace;
           end;
           end;
         '>' :
         '>' :
           begin
           begin
           // Find or create compiled partial;
           // Find or create compiled partial;
-          IsWhiteSpace:=CheckWhiteSpace;
           aName:=Trim(aName);
           aName:=Trim(aName);
           if not Assigned(Partials) then
           if not Assigned(Partials) then
             Raise EMustache.Create(SErrNoPartials);
             Raise EMustache.Create(SErrNoPartials);
@@ -1162,28 +1136,24 @@ begin
             begin
             begin
             AddChild(Partial);
             AddChild(Partial);
             Data:=aName;
             Data:=aName;
-            if isWhitespace and assigned(WhiteSpaceEl) then
-              Prefix:=GetEndingWhiteSpace(WhiteSpaceEl);
+            Prefix := PartialPrefix;
             end;
             end;
-          if IsWhiteSpace then
-            FinishWhiteSpace(False);
           end;
           end;
         '/' :
         '/' :
           begin
           begin
-          IsWhiteSpace:=CheckWhiteSpace;
           if Not (CurrParent.ElementType in [metSection,metInvertedSection]) then
           if Not (CurrParent.ElementType in [metSection,metInvertedSection]) then
             Raise EMustache.CreateFmt(SErrNoSectionToClose,[aName,Current])
             Raise EMustache.CreateFmt(SErrNoSectionToClose,[aName,Current])
           else if (CurrParent.Data<>Trim(aName)) then
           else if (CurrParent.Data<>Trim(aName)) then
             Raise EMustache.CreateFmt(SErrSectionClose,[currParent.Data,CurrParent.Position,aName,Current])
             Raise EMustache.CreateFmt(SErrSectionClose,[currParent.Data,CurrParent.Position,aName,Current])
           else
           else
             currParent:=currParent.Parent;
             currParent:=currParent.Parent;
-          if IsWhiteSpace then
-            FinishWhiteSpace;
           end
           end
       else
       else
         CreateDefault(CurrParent,Current,aName);
         CreateDefault(CurrParent,Current,aName);
       end;
       end;
       Current:=NewPos+clStop;
       Current:=NewPos+clStop;
+      if IsStandalone then
+        SkipRestOfLine;
       end;
       end;
     end;
     end;
   if CurrParent<>aParent then
   if CurrParent<>aParent then