Browse Source

* Patch from Dokkie884 to implement inheritance

Michaël Van Canneyt 3 years ago
parent
commit
82df643b74

+ 3 - 2
packages/fcl-mustache/src/fpexmustache.pp

@@ -36,7 +36,7 @@ Type
     Procedure SetData(const aValue : TMustacheString) ; override;
   Public
     Destructor Destroy; override;
-    Procedure Render(aContext : TMustacheContext; aOutput : TMustacheOutput; const aPrefix : String = ''; aLast : Boolean = False); override;
+    Procedure Render(aContext : TMustacheContext; aOutput : TMustacheOutput; aBlockOverrides : TMustacheBlockOverrideList; const aPrefix : String = ''; aLast : Boolean = False); override;
     Property Node : TFPExprNode Read FNode;
   end;
 
@@ -126,7 +126,8 @@ begin
 end;
 
 procedure TMustacheExprElement.Render(aContext: TMustacheContext;
-  aOutput: TMustacheOutput; const aPrefix: String; aLast: Boolean);
+  aOutput: TMustacheOutput; aBlockOverrides : TMustacheBlockOverrideList;
+  const aPrefix: String; aLast: Boolean);
 
 Var
   Res : TFPExpressionResult;

+ 162 - 33
packages/fcl-mustache/src/fpmustache.pp

@@ -28,6 +28,7 @@ Type
   TMustacheString = UTF8String;
   TMustacheChar = AnsiChar;
   TMustacheContext = class;
+  TMustacheBlockOverrideList = class;
 
   TMustacheOutput = Class(TObject)
   Public
@@ -51,7 +52,7 @@ Type
 
   { TMustacheElement }
 
-  TMustacheElementType = (metRoot,metComment,metText,metVariable,metSection,metInvertedSection,metPartial);
+  TMustacheElementType = (metRoot,metComment,metText,metVariable,metSection,metInvertedSection,metPartial,metParametricPartial,metBlock);
 
   TMustacheElement = Class(TObject)
   private
@@ -71,7 +72,7 @@ Type
     // Add a child. Parent always owns child
     Procedure AddChild(aChild : TMustacheElement); virtual;
     // Render the text for this element
-    Procedure Render(aContext : TMustacheContext; aOutput : TMustacheOutput; const aPrefix : String = ''; aLast : Boolean = False); virtual; abstract;
+    Procedure Render(aContext : TMustacheContext; aOutput : TMustacheOutput; aBlockOverrides : TMustacheBlockOverrideList = Nil; const aPrefix : String = ''; aLast : Boolean = False); virtual; abstract;
     // Position in template
     Property Position : Integer Read FPosition;
     // Parent element
@@ -118,9 +119,15 @@ Type
   Public
     Destructor Destroy; override;
     Procedure AddChild(aChild : TMustacheElement); override;
-    Procedure Render(aContext : TMustacheContext; aOutput : TMustacheOutput; const aPrefix : String = ''; aLast : Boolean = False); override;
+    Procedure Render(aContext : TMustacheContext; aOutput : TMustacheOutput; aBlockOverrides : TMustacheBlockOverrideList = Nil; const aPrefix : String = ''; aLast : Boolean = False); override;
   end;
 
+  { TMustacheBlockElement }
+
+  TMustacheBlockElement = Class(TMustacheParentElement)
+  Public
+    Procedure Render(aContext : TMustacheContext; aOutput : TMustacheOutput; aBlockOverrides : TMustacheBlockOverrideList = Nil; const aPrefix : String = ''; aLast : Boolean = False); override;
+  end;
 
   { TMustacheTextElement }
 
@@ -131,7 +138,7 @@ Type
     Procedure SetData(Const aData : TMustacheString) ; override;
     Function GetData : TMustacheString; override;
   Public
-    Procedure Render(aContext : TMustacheContext; aOutput : TMustacheOutput; const aPrefix : String = ''; aLast : Boolean = False); override;
+    Procedure Render(aContext : TMustacheContext; aOutput : TMustacheOutput; aBlockOverrides : TMustacheBlockOverrideList = Nil; const aPrefix : String = ''; aLast : Boolean = False); override;
   end;
 
   { TMustacheVariableElement }
@@ -142,7 +149,7 @@ Type
   Protected
     Procedure SetData(Const aData : TMustacheString); override;
   Public
-    Procedure Render(aContext : TMustacheContext; aOutput : TMustacheOutput; const aPrefix : String = ''; aLast : Boolean = False); override;
+    Procedure Render(aContext : TMustacheContext; aOutput : TMustacheOutput; aBlockOverrides : TMustacheBlockOverrideList = Nil; const aPrefix : String = ''; aLast : Boolean = False); override;
     Property NoUnescape : Boolean Read FNoUnescape;
   end;
 
@@ -150,12 +157,12 @@ Type
 
   TMustacheSectionElement = Class(TMustacheParentElement)
   Public
-    Procedure Render(aContext : TMustacheContext; aOutput : TMustacheOutput; const aPrefix : String = ''; aLast : Boolean = False); override;
+    Procedure Render(aContext : TMustacheContext; aOutput : TMustacheOutput; aBlockOverrides : TMustacheBlockOverrideList = Nil; const aPrefix : String = ''; aLast : Boolean = False); override;
   end;
 
   { TMustachePartialElement }
 
-  TMustachePartialElement = Class(TMustacheElement)
+  TMustachePartialElement = Class(TMustacheParentElement)
   Private
     FPrefix : TMustacheString;
     FPartialName : TMustacheString;
@@ -169,7 +176,7 @@ Type
   Public
     Destructor Destroy; override;
     Procedure AddChild(aChild: TMustacheElement); override;
-    Procedure Render(aContext : TMustacheContext; aOutput : TMustacheOutput; const aPrefix : String = ''; aLast : Boolean = False); override;
+    Procedure Render(aContext : TMustacheContext; aOutput : TMustacheOutput; aBlockOverrides : TMustacheBlockOverrideList = Nil; const aPrefix : String = ''; aLast : Boolean = False); override;
     Property Partial : TMustacheElement Read FPartial;
   end;
 
@@ -180,6 +187,18 @@ Type
     Function FindPartial(aName : TMustacheString) : TMustacheElement;
   end;
 
+  { TMustacheBlockOverrideList }
+
+  TMustacheBlockOverrideList = Class
+  Private
+    FBlocks: TMustacheElementArray;
+    FCount: Integer;
+  Public
+    Procedure Push(aBlock: TMustacheElement);
+    Procedure Pop;
+    Function FindFirst(aBlockName: TMustacheString): TMustacheElement;
+  end;
+
   { TMustacheParser }
   TGetTextValueEvent = Procedure (Const aName : TMustacheString; var aHandled : Boolean; var aValue : TMustacheString) of Object;
 
@@ -330,6 +349,7 @@ Resourcestring
   SErrInvalidDelimiter = 'Invalid set delimiter: %s';
   SErrInvalidDelimiterValue = 'Invalid set delimiter %s value: %s in "%s"';
   SErrNoPartials = 'No partials list';
+  SErrNotABlockType = 'Class %s is not a block type.';
 
   // SErrPartialNotFound = 'Partial "%s" not found.';
   SStartTag = 'Start';
@@ -354,6 +374,40 @@ begin
     end;
 end;
 
+{ TMustacheBlockOverrideList }
+
+procedure TMustacheBlockOverrideList.Push(aBlock: TMustacheElement);
+var
+  Len: Integer;
+begin
+  if aBlock.ElementType <> metBlock then
+    Raise EMustache.CreateFmt(SErrNotABlockType, [aBlock.ClassName]);
+  Len := Length(FBlocks);
+  if (FCount >= Len) then
+    SetLength(FBlocks, Len + ListGrowCount);
+  FBlocks[FCount] := aBlock;
+  Inc(FCount);
+end;
+
+procedure TMustacheBlockOverrideList.Pop;
+begin
+  Dec(FCount);
+  FBlocks[FCount] := nil; // do not free; does not own blocks
+end;
+
+function TMustacheBlockOverrideList.FindFirst(aBlockName: TMustacheString): TMustacheElement;
+var
+  I: Integer;
+begin
+  Result := nil;
+  for I:=0 to FCount-1 do
+    if FBlocks[I].Data = aBlockName then
+    begin
+      Result := FBlocks[I];
+      Break;
+    end;
+end;
+
 { TMustachePartialElement }
 
 function TMustachePartialElement.GetData: TMustacheString;
@@ -368,17 +422,21 @@ end;
 
 procedure TMustachePartialElement.AddChild(aChild: TMustacheElement);
 begin
-  If (FPartial<>Nil) and (aChild<>Nil) then
-    Raise EMustache.Create('Cannot set partial twice');
-  FPartial:=aChild;
+  if FPartial = nil then
+    FPartial := aChild
+  else
+    inherited AddChild(aChild);
 end;
 
 procedure TMustachePartialElement.Dump(aList: Tstrings;
   aIndent: TMustacheString; aDumpChildren: Boolean);
+var
+  PartialIndex: Integer;
 begin
+  PartialIndex := aList.Count;  // save index, because inherited Dump may dump children
   inherited Dump(aList, aIndent, aDumpChildren);
   if Prefix<>'' then
-    aList[aList.Count-1]:=aList[aList.Count-1]+' Prefix: "'+Prefix+'"';
+    aList[PartialIndex]:=aList[PartialIndex]+' Prefix: "'+Prefix+'"';
 end;
 
 function TMustachePartialElement.GetPrefix: TMustacheString;
@@ -392,10 +450,31 @@ begin
 end;
 
 procedure TMustachePartialElement.Render(aContext: TMustacheContext;
-  aOutput: TMustacheOutput; const aPrefix : String = ''; aLast : Boolean = False);
+  aOutput: TMustacheOutput; aBlockOverrides : TMustacheBlockOverrideList = Nil;
+  const aPrefix : String = ''; aLast : Boolean = False);
+
+Var
+  I,
+  OverrideCount: Integer;
 
 begin
-  FPartial.Render(aContext,aOutput,Prefix);
+  OverrideCount := 0;
+  if ElementType = metParametricPartial then
+  begin
+    if aBlockOverrides = Nil then
+      raise EMustache.Create('Parametric Partial elements need an instance of TMustacheBlockOverrideList');
+    for I:=0 to ChildCount-1 do
+      if Children[I].ElementType = metBlock then
+      begin
+        aBlockOverrides.Push(Children[I]);
+        Inc(OverrideCount);
+      end;
+  end;
+
+  FPartial.Render(aContext,aOutput,aBlockOverrides,Prefix);
+
+  for I:=1 to OverrideCount do
+    aBlockOverrides.Pop;
 end;
 
 destructor TMustachePartialElement.Destroy;
@@ -403,7 +482,6 @@ begin
   inherited Destroy;
 end;
 
-
 { TMustache }
 
 function TMustache.CreateParser(aTemplate : TMustacheString): TMustacheParser;
@@ -478,10 +556,18 @@ end;
 
 procedure TMustache.Render(aContext: TMustacheContext; aOutput: TMustacheOutput);
 
+Var
+  BlockOverrides: TMustacheBlockOverrideList;
+
 begin
   if not Assigned(Compiled) then
     Compile;
-  Compiled.Render(aContext,aOutput);
+  BlockOverrides := TMustacheBlockOverrideList.Create;
+  try
+    Compiled.Render(aContext,aOutput,BlockOverrides);
+  finally
+    BlockOverrides.Free;
+  end;
 end;
 
 function TMustache.Render(aContext: TMustacheContext): TMustacheString;
@@ -701,7 +787,8 @@ end;
 { TMustacheSectionElement }
 
 procedure TMustacheSectionElement.Render(aContext: TMustacheContext;
-  aOutput: TMustacheOutput; const aPrefix: String; aLast : Boolean = False);
+  aOutput: TMustacheOutput; aBlockOverrides : TMustacheBlockOverrideList = Nil;
+  const aPrefix: String = ''; aLast : Boolean = False);
 
 Var
   L : TMustacheSectionType;
@@ -711,15 +798,15 @@ begin
    if ElementType=metInvertedSection then
      begin
      if L=mstNone then
-       inherited Render(aContext, aOutput,aPrefix);
+       inherited Render(aContext, aOutput, aBlockOverrides, aPrefix);
      end
    else
      Case L of
      mstSingle :
-        inherited Render(aContext, aOutput);
+        inherited Render(aContext, aOutput, aBlockOverrides);
      mstList :
         while aContext.MoveNextSectionItem(Name) do
-          inherited Render(aContext, aOutput,aPrefix);
+          inherited Render(aContext, aOutput, aBlockOverrides, aPrefix);
      end;
   if L<>mstNone then
     aContext.PopSection(Name);
@@ -819,7 +906,8 @@ begin
 end;
 
 procedure TMustacheTextElement.Render(aContext: TMustacheContext;
-  aOutput: TMustacheOutput; const aPrefix: String; aLast : Boolean = False);
+  aOutput: TMustacheOutput; aBlockOverrides : TMustacheBlockOverrideList = Nil;
+  const aPrefix: String = ''; aLast : Boolean = False);
 
 Var
   S : String;
@@ -867,7 +955,8 @@ begin
 end;
 
 procedure TMustacheVariableElement.Render(aContext: TMustacheContext;
-  aOutput: TMustacheOutput; const aPrefix: String; aLast : Boolean = False);
+  aOutput: TMustacheOutput; aBlockOverrides : TMustacheBlockOverrideList = Nil;
+  const aPrefix: String = ''; aLast : Boolean = False);
 
 Var
   aValue : TMustacheString;
@@ -979,7 +1068,7 @@ Var
 
   function IsPartialTag: Boolean;
   begin
-    Result := (NewPos + lStart <= Total) and (aTemplate[NewPos + lStart] = '>');
+    Result := (NewPos + lStart <= Total) and (aTemplate[NewPos + lStart] in ['>','<']);
   end;
 
   // check if the current tag occurs standalone on a line
@@ -1006,7 +1095,7 @@ Var
     repeat
       pStop := Pos(cStop, aTemplate, I + lStart + 1);
       if (Copy(aTemplate, I, lStart) = cStart) and (pStop > 0)
-        and (aTemplate[I + lStart] in ['=','#','!','^','>','/']) then
+        and (aTemplate[I + lStart] in ['=','#','!','^','>','/','<','$']) then
         I := pStop + lStop
       else
         Break;
@@ -1087,7 +1176,7 @@ begin
       if (aName='') then
         Raise EMustache.CreateFmt(SErrEmptyTag,[cStart,Current]);
       C:=aName[1];
-      if C in ['=','#','^','/','!','>'] then
+      if C in ['=','#','^','/','!','>','<','$'] then
         aName:=Copy(aName,2,Length(aName)-1);
       clStop:=Lstop; // Can change.
       case C of
@@ -1122,7 +1211,7 @@ begin
           CurrParent:=CreateElement(metInvertedSection,currParent,Current);
           CurrParent.SetData(aName);
           end;
-        '>' :
+        '>','<' :
           begin
           // Find or create compiled partial;
           aName:=Trim(aName);
@@ -1136,16 +1225,32 @@ begin
             DoParse(Partial,GetPartial(aName),FStartTag,FStopTag);
             end;
           // Create reference and insert into current tree
-          With CreateElement(metPartial,currParent,Current) do
+          if C='>' then // normal partial, no children, no end tag
+            With CreateElement(metPartial,currParent,Current) do
+              begin
+              AddChild(Partial);
+              Data:=aName;
+              Prefix := PartialPrefix;
+              end
+          else // parametric partial, may have children, end tag must follow
             begin
-            AddChild(Partial);
-            Data:=aName;
-            Prefix := PartialPrefix;
+            CurrParent:=CreateElement(metParametricPartial,currParent,Current);
+            With CurrParent do
+              begin
+              AddChild(Partial);
+              Data:=aName;
+              Prefix := PartialPrefix;
+              end;
             end;
           end;
+        '$' :
+          begin
+          CurrParent:=CreateElement(metBlock,currParent,Current);
+          CurrParent.SetData(aName);
+          end;
         '/' :
           begin
-          if Not (CurrParent.ElementType in [metSection,metInvertedSection]) then
+          if Not (CurrParent.ElementType in [metSection,metInvertedSection,metParametricPartial,metBlock]) then
             Raise EMustache.CreateFmt(SErrNoSectionToClose,[aName,Current])
           else if (CurrParent.Data<>Trim(aName)) then
             Raise EMustache.CreateFmt(SErrSectionClose,[currParent.Data,CurrParent.Position,aName,Current])
@@ -1229,14 +1334,36 @@ begin
 end;
 
 procedure TMustacheParentElement.Render(aContext: TMustacheContext;
-  aOutput: TMustacheOutput; const aPrefix: String; aLast : Boolean = False);
+  aOutput: TMustacheOutput; aBlockOverrides : TMustacheBlockOverrideList = Nil;
+  const aPrefix: String = ''; aLast : Boolean = False);
 
 Var
   I : integer;
 
 begin
   For I:=0 to ChildCount-1 do
-    Children[I].Render(aContext,aOutPut,aPrefix,I=ChildCount-1);
+    Children[I].Render(aContext,aOutPut,aBlockOverrides,aPrefix,I=ChildCount-1);
+end;
+
+{ TMustacheBlockElement }
+
+procedure TMustacheBlockElement.Render(aContext: TMustacheContext;
+  aOutput: TMustacheOutput; aBlockOverrides : TMustacheBlockOverrideList = Nil;
+  const aPrefix: String = ''; aLast : Boolean = False);
+
+Var
+  I : integer;
+  BlockToRender : TMustacheElement;
+
+begin
+  if aBlockOverrides = Nil then
+    Raise EMustache.Create('Block elements need an instance of TMustacheBlockOverrideList');
+  BlockToRender:=aBlockOverrides.FindFirst(Self.Data);
+  if BlockToRender=nil then
+    BlockToRender:=Self;
+
+  For I:=0 to BlockToRender.ChildCount-1 do
+    BlockToRender.Children[I].Render(aContext,aOutPut,aBlockOverrides,aPrefix,I=BlockToRender.ChildCount-1);
 end;
 
 { TMustacheElement }
@@ -1310,5 +1437,7 @@ begin
   TMustacheParser.SetDefaultTypeClass(metSection,TMustacheSectionElement);
   TMustacheParser.SetDefaultTypeClass(metInvertedSection,TMustacheSectionElement);
   TMustacheParser.SetDefaultTypeClass(metPartial,TMustachePartialElement);
+  TMustacheParser.SetDefaultTypeClass(metParametricPartial,TMustachePartialElement);
+  TMustacheParser.SetDefaultTypeClass(metBlock,TMustacheBlockElement);
 end.
 

File diff suppressed because it is too large
+ 2 - 0
packages/fcl-mustache/tests/spec/inheritance.json


+ 2 - 2
packages/fcl-mustache/tests/tcexmustache.pas

@@ -171,7 +171,7 @@ end;
 procedure TTestExMustacheParser.TestRenderSimple;
 begin
   TestSimple;
-  ParseResult.Children[0].Render(Nil,Output,'',False);
+  ParseResult.Children[0].Render(Nil,Output,Nil,'',False);
   AssertEquals('Correct result','3',Output.Data);
 end;
 
@@ -189,7 +189,7 @@ begin
   FExpr.Identifiers.AddVariable('age',rtInteger,@GetVar);
   Template:='{{#data}}{{{name}}}:{{[age>11]}} {{/data}}';
   CallParser;
-  ParseResult.Render(FContext,Output,'',False);
+  ParseResult.Render(FContext,Output,Nil,'',False);
   AssertEquals('Correct result','me:False you:True he:True ',Output.Data);
 end;
 

+ 55 - 4
packages/fcl-mustache/tests/tcmustache.pas

@@ -57,6 +57,9 @@ type
     procedure TestSectionErrNotStarted;
     procedure TestTextSection;
     procedure TestPartial;
+    procedure TestParametricPartial;
+    procedure TestParametricPartialWithBlock;
+    procedure TestBlock;
   end;
 
   { TTestMustacheOutput }
@@ -175,7 +178,7 @@ procedure TTestMustacheElement.TestTextElementPrefix;
 begin
   Fel:=TMustacheTextElement.Create(metText,Nil,0);
   El.Data:='me'#10'you';
-  El.Render(Nil,Output,'  ');
+  El.Render(Nil,Output,Nil,'  ');
   AssertEquals('Correct output 1','me'#10'  you',Output.Data);
 end;
 
@@ -183,7 +186,7 @@ procedure TTestMustacheElement.TestTextElementPrefixNotLast;
 begin
   Fel:=TMustacheTextElement.Create(metText,Nil,0);
   El.Data:='me'#10'you'#10;
-  El.Render(Nil,Output,'  ');
+  El.Render(Nil,Output,Nil,'  ');
   AssertEquals('Correct output 2','me'#10'  you'#10'  ',Output.Data);
 end;
 
@@ -192,7 +195,7 @@ procedure TTestMustacheElement.TestTextElementPrefixLast;
 begin
   Fel:=TMustacheTextElement.Create(metText,Nil,0);
   El.Data:='me'#10'you'#10;
-  El.Render(Nil,Output,'  ',True);
+  El.Render(Nil,Output,Nil,'  ',True);
   AssertEquals('Correct output 2','me'#10'  you'#10,Output.Data);
 end;
 
@@ -447,7 +450,7 @@ begin
   Sel:=TMustacheTextElement.Create(metText,Fel,0);
   Sel.Data:='f'#10;
   Fel.AddChild(Sel);
-  Fel.Render(Context,Output,'  ');
+  Fel.Render(Context,Output,Nil,'  ');
   AssertEquals('Correct output','a'#10'  bd'#10'  ef'#10,Output.Data);
 end;
 
@@ -721,6 +724,54 @@ begin
   AssertElement(el.Partial,0,metText,'bcd',TMustacheTextElement);
 end;
 
+procedure TTestMustacheParser.TestParametricPartial;
+var
+  El: TMustachePartialElement;
+begin
+  AddPartial('part','bcd');
+  Template:='a{{<part}}{{/part}}e';
+  CallParser;
+  AssertResultCount(3);
+  AssertElement(0,metText,'a',TMustacheTextElement);
+  El:=AssertElement(1,metParametricPartial,'part',TMustachePartialElement) as TMustachePartialElement;
+  AssertElement(2,metText,'e',TMustacheTextElement);
+  AssertEquals('Correct parametric partial','part',El.Partial.Data);
+  AssertEquals('Correct parametric partial',1,El.Partial.ChildCount);
+  AssertElement(El.Partial,0,metText,'bcd',TMustacheTextElement);
+end;
+
+procedure TTestMustacheParser.TestParametricPartialWithBlock;
+var
+  El: TMustachePartialElement;
+  ElBlock: TMustacheBlockElement;
+begin
+  AddPartial('part','bcd');
+  Template:='a{{<part}}{{$x}}y{{/x}}{{/part}}e';
+  CallParser;
+  AssertResultCount(3);
+  AssertElement(0,metText,'a',TMustacheTextElement);
+  El:=AssertElement(1,metParametricPartial,'part',TMustachePartialElement) as TMustachePartialElement;
+  AssertElement(2,metText,'e',TMustacheTextElement);
+  AssertEquals('Correct parametric partial','part',El.Partial.Data);
+  AssertEquals('Correct parametric partial',1,El.Partial.ChildCount);
+  AssertElement(El.Partial,0,metText,'bcd',TMustacheTextElement);
+  AssertEquals('elements in parametric partial',1,El.ChildCount);
+  ElBlock:=AssertElement(El,0,metBlock,'x',TMustacheBlockElement) as TMustacheBlockElement;
+  AssertEquals('elements in block',1,ElBlock.ChildCount);
+  AssertElement(ElBlock,0,metText,'y',TMustacheTextElement);
+end;
+
+procedure TTestMustacheParser.TestBlock;
+var
+  El: TMustacheBlockElement;
+begin
+  Template:='{{$a}}bbb{{/a}}';
+  CallParser;
+  AssertResultCount(1);
+  El:=AssertElement(0,metBlock,'a',TMustacheBlockElement) as TMustacheBlockElement;
+  AssertEquals('No elements in block',1,El.ChildCount);
+  AssertElement(El,0,metText,'bbb');
+end;
 
 initialization
   RegisterTests([TTestMustacheParser,TTestMustacheOutput,TTestMustacheElement]);

+ 6 - 0
packages/fcl-mustache/tests/tcspecs.pas

@@ -43,6 +43,7 @@ Type
     Procedure TestInverted;
     Procedure TestPartials;
     Procedure TestSections;
+    Procedure TestInheritance;
   end;
 
 
@@ -181,6 +182,11 @@ begin
   DoTest('sections');
 end;
 
+procedure TTestMustacheSpecs.TestInheritance;
+begin
+  DoTest('inheritance');
+end;
+
 begin
   TTestMustacheSpecs.BaseDir:='spec/';
   RegisterTest(TTestMustacheSpecs);

Some files were not shown because too many files changed in this diff