2
$\begingroup$

I'm interested in building a website theme with Mathematica's XMLTemplate as proposed by C. E. here.

The problem is, Mathematica's template syntax is verbose and a little clumsy, so I want to package up template routines. One common routine is checking if a template variable is defined.

I built the routine like so:

varExists=
"<wolfram:which>
 <wolfram:if test='KeyMemberQ[#,First@#var]'>
  <wolfram:slot id='if'></wolfram:slot>
 </wolfram:if>
 <wolfram:else>
  <wolfram:slot id='else' />
 </wolfram:else>
</wolfram:which>";

And it works to a degree:

dir = FileNameJoin@{$TemporaryDirectory, "xml_templates"};
CreateDirectory[dir];

(* export templates to file *)
Export[FileNameJoin@{dir, "varExists"},
 varExists,
 "Text"
 ]

temp=
 "<title>
  <wolfram:get path='varExists'>
   <wolfram:slot id='var'>longTitle</wolfram:slot>
   <wolfram:slot id='if'>A very long title</wolfram:slot>
   <wolfram:slot id='else'>A more compact title</wolfram:slot>
  </wolfram:get>
</title>";

Export[
  FileNameJoin@{dir, "template.html"},
  temp,
  "Text"
  ];

(* test template *)

Block[{
  $TemplatePath =
   Append[$TemplatePath, dir]
  },
 TemplateApply[
  XMLTemplate@File[FileNameJoin@{dir, "template.html"}],
  <|
   "longTitle" -> True
   |>
  ]
 ]

"<title>

 A very long title

</title>"

This is what we'd expect. But now let's use this template itself as a routine and pass a variable from within a different root template:

(* Export new template *)
temp2=
 "<wolfram:with longTitle='True'>
 <wolfram:get path='template.html' />
</wolfram:with>";
Export[
  FileNameJoin@{dir, "template2.html"},
  temp2,
  "Text"
  ];

(* try again *)
Block[{
  $TemplatePath =
   Append[$TemplatePath, dir]
  },
 TemplateApply[
  XMLTemplate@File[FileNameJoin@{dir, "template2.html"}],
  <||>
  ]
 ]


"<title>

 A more compact title

</title>"

So clearly that "<wolfram:with ... >" isn't passing its variables.

If we add a Print[Templating`$TemplateArguments] to our "test" we find that it remains empty. If we look at the Templating`$TemplateArgumentStack we find this:

{{<||>}, {<||>}, {<||>}, {}, {<|"longTitle" -> True|>}, {<|"var" :> 
    TemplateObject[{"longTitle"}], 
   "if" :> TemplateObject[{"A very long title"}], 
   "else" :> TemplateObject[{"A more compact title"}]|>}}

So these variables exit the stack before they can even be used. (Obviously I could screw around with the argument stack, but that seems like a pain).

So here's the question: how do I do this? If variables aren't passed, how am I supposed to build routines? I reallllly don't want to have to use the insanely verbose syntax everywhere in my templates.

$\endgroup$
1
$\begingroup$

So I have a way to get around this issue. The heart of the problem is that the template argument stack isn't appropriately inclusive to handle this case, but we can get around this by explicitly passing arguments to the routine, rather than doing it implicitly.

E.g., instead of this:

"<title>
  <wolfram:get path='varExists'>
   <wolfram:slot id='var'>longTitle</wolfram:slot>
   <wolfram:slot id='if'>A very long title</wolfram:slot>
   <wolfram:slot id='else'>A more compact title</wolfram:slot>
  </wolfram:get>
</title>"

We use this:

"<title>
  <wolfram:get path='varExists'>
   <wolfram:slot id='var'>longTitle</wolfram:slot>
   <wolfram:slot id='longTitle'><wolfram:slot id='longTitle'/></wolfram:slot>
   <wolfram:slot id='if'>A very long title</wolfram:slot>
   <wolfram:slot id='else'>A more compact title</wolfram:slot>
  </wolfram:get>
</title>"

This still requires us to tweak our varExists routine though. We'll deal with this by making a function that can handle this checking more inclusively:

varDefinedM =
  "With[{
    tempArgs=
      (Join@@
        Flatten@{
          #,
          Replace[Templating`$TemplateArgumentStack,{
              {___,a_}:>a,
              _-><||>
            }]
          })
    },
    !MatchQ[
      tempArgs@
        Replace[tempArgs[\"var\"],
          t_TemplateObject:>
            TemplateApply[t,tempArgs]
          ],
      _Missing|False|None
      ]
    ]&";
Export[FileNameJoin@{dir, "varDefined.m"},
  varDefinedM,
  "Text"
  ];

And then we rewrite our varExists routine to look like this:

varExists=
"<wolfram:which>
 <wolfram:if test='Import[\"varDefined.m\"][#]'>
  <wolfram:slot id='if'></wolfram:slot>
 </wolfram:if>
 <wolfram:else>
  <wolfram:slot id='else' />
 </wolfram:else>
</wolfram:which>";
Export[FileNameJoin@{dir, "varExists"},
  varExists,
  "Text"
  ];

And finally it works, after, making sure dir is also on $Path:

Block[{
  $TemplatePath =
   Prepend[$TemplatePath, dir],
  $Path =
   Prepend[$Path, dir]
  },
 TemplateApply[
  XMLTemplate@File[FileNameJoin@{dir, "template2.html"}],
  <||>
  ]
 ]

"<title>

 A very long title

</title>"

This is clumsy, yes, but by building up a library of routines like this some of that clumsiness can be hidden.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.