2
votes

(Sort of) Short and (maybe?) sweet

How do I put conditional logic (the same XPATH as would go into an <xsl:if>'s test attribute) into an attribute of an XML sheet that is being read via document('layoutsheet.xml') without using either saxon:evaluate or the XSLT 3 <xsl:evaluate> to actually evaluate the logic in the calling XSL sheet? (and dyn:evaluate seems to be out, too)


Clarification: what I'm mostly looking for is if there is some pattern using node representations of the logic and related transforms that would get me to similar behavior. I don't mind creating a representative structure within the XML sheet that handles layout behavior. I'm just drawing a blank right now on anything that wouldn't just evolve into writing essentially a full interpreter within XSL, which seems like probably a horrendous duplication of effort and waste of time for inferior results.

Because I'm not calling the XSL parser myself (it's called by software I don't maintain and can't directly change), I'm essentially restricted to what's native to Saxon. I mostly want to arrive at a portable answer for both our own future security but also potential sharing if it turns out to be of any value elsewhere. If this is the best I'm going to manage with the tools at hand, I'm ok with hearing that.


I would ideally like this logic to be fully encapsulated in the XML sheet, rather than relying on knowing particular aspects of it in the XSL sheet. That is, I don't want to assume that this will always be a two part comparison using a particular function call to evaluate a single element against a value (e.g. storing both sides of the logic evaluation as separate attributes that I would then have to know in the XSL sheet to evaluate against each other would not be ideal). I also don't want to make the match pick logic based on a particular layout structure: the idea is to make it as structure agnostic as possible, and instead include the related structural logic in the XML sheet.

While I could load the XML sheet with deeper structure to use as matching conditionals (which in turn would either continue their identity transform structure deeper in when a match evaluates as true, or otherwise not), I would ideally still prefer something flexible to more complicated conditional logic than just a straightforward two value equality test, such as allowed by full XPATH statements.


Longer explanation and associated code snippets

I'm working within an XSLT based CMS and trying to create a dynamic version target system using a separate XML sheet to determine document layout. So far, that's been a bit of work but it's coming along nicely. To make a long story short, we needed something more runtime dynamic than simply overriding via import/include nesting.

The original system relied on named template calls to pull in different regions of the page, and then identity transform to finish including the relevant portions. I've essentially replaced that with an XML layout sheet that uses namespaced template "calls" which are matched on within my XSL sheets. This is an initial iteration meant to be as quickly implementable as possible while offering deep future flexibility, hence staying close to that named call paradigm.

One issue that I've run into is how to handle conditional placement without having to make specific matches and corresponding "calls" and attach the associated logic in my XSL sheet, rather than being able to modularize that logic out into the XML.

I've found a currently working solution using saxon:evaluate, but would like any input on other solutions (XSLT 2) which don't rely on vendor specific extensions. Because this is within a third party CMS (OmniUpdate's OU Campus) I can't load extensions myself: I'm basically stuck with whatever Saxon already has loaded.

Basic layoutsheet: (XML with XHTML to define document structure, which is then merged into via XSLT from the calling XML document sheet containing actual document content)

<mus:layoutsheet org="msu" version="2"
xmlns:mus="http://www.montana.edu/web/xsl/variables"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ou="http://omniupdate.com/XSL/Variables"
exclude-result-prefixes="xsl mus ou">

    <mus:call-template name="pre-processing-pub" />     
    <mus:call-template name="doctype" />
    <html>

        <mus:call-template name="pre-processing"/>

        <mus:call-template name="head" />
        <body class="responsive">
            <mus:call-template name="header" />

            <div class="container">
                <div class="row">
                    <mus:call-template name="titles-region" />
                    <mus:call-template name="breadcrumb" />
                </div>
            </div>

            <div class="container" id="content">

                <!-- this is the full width feature region -->
                <mus:call-template name="banners-region" cond="ou:pcfparam('full-width-feature-area') = 'show'"/>
                <!-- ok how do we do conditional placement? because this needs to get called in here or in content -->
                <!-- how about having that as an attribute here, then we can react to that in the match-->

                <div class="row">

                    <mus:call-template name="nested-nav" />
                    <!--wrap in an if statement for handling the different locations when tabbed -->                        
                    <div id="rightpane" class="col-xs-12 pull-right page-content sec">
                        <mus:call-template name="banners-region" cond="not(ou:pcfparam('full-width-feature-area') = 'show')"/>

                        <mus:call-template name="content" />


                    </div>                          

                    <mus:call-template name="contact-info" /> 

                </div>
            </div>
            <!--<xsl:call-template name="bottom-span-bar" />    -->

            <mus:call-template name="footer" />
        </body>
    </html>
</mus:layoutsheet>

The elements in question are <mus:call-template name="banners-region" cond="ou:pcfparam('full-width-feature-area') = 'show'"/> and <mus:call-template name="banners-region" cond="not(ou:pcfparam('full-width-feature-area') = 'show')"/>

@cond is the attribute loaded with the XPATH statements which otherwise would have been the contents of an xsl:if test surrounding this template "call" in each case.

Note that while this version of Saxon does have XSLT 3 elements available, including <xsl:evaluate>, I've been running into namespace issues when trying to make that work here with the function call to ou:pcfparam (all of the correct namespaces are included in the calling xsl sheet, so that's not it). While I could do away with the function call and use pure xpath, I would have to use a // to avoid another namespace issue with part of the related element structure, and for now that's been breaking too (probably because it references an element in the xml sheet the transformation process is called on, rather than the xml sheet the layout is stored in). I'd also prefer to avoid XSLT 3 where possible, for now, to keep some of the basic methodology I'm working on more portable.

The related matching template:

<xsl:template match="mus:call-template[@name='banners-region']">
    <xsl:if test="saxon:evaluate(@cond)">
        <xsl:call-template name="slider" /> 
    </xsl:if>
</xsl:template>

I feel like maybe I've overlooked something here, possibly something that should otherwise be obvious (maybe because I'm in the middle of trying to not get bronchitis, blah). While the saxon:evaluate is drop dead simple for me to use, I'd prefer something more portable because this approach in general allows for some very nice modularization along MVC architecture concepts for processing otherwise unstructured XML based content sheets using a relatively straightforward XHTML+light XML layout sheet to handle most of the view structuring and presentation, making it easier for people not familiar with XSLT to revise related structure.

Notes: EXSLT's dyn:evaluate is not implemented in Saxon, so that's not a cross-compatible option, although if it's functionally equivalent then I guess I can stick to saxon:evaluate and just tell people to replace it with dyn:evaluate if not on saxon, which might be the best way to go here.


Abbreviated example of the format of the actually calling XML sheet (I've reduced the number of elements, but the structure is the same, it's essentially an options and related content sheet, and is where the editor within the CMS actually saves changes):

<?xml version="1.0" encoding="utf-8"?>
<?pcf-stylesheet path="/multi-standard-new.xsl" site="XSL" extension="html"?>
<!DOCTYPE document SYSTEM "/data/staging/oucampus/MSU-Bozeman/XSL/_resources/dtd/standard.dtd">
<document xmlns:ouc="http://omniupdate.com/XSL/Variables">
    <parameter name="page-type">multi-feature</parameter>


    <ouc:properties label="metadata">
        <meta name="description" content="Description" />
        <meta name="keywords" content="Some keywords here" />
    </ouc:properties>


    <ouc:properties label="config">
        <title>The title</title>

        <!-- +++++++++++++++++++++++ -->
        <!-- Processing Instructions -->
        <!-- +++++++++++++++++++++++ -->

        <parameter
            name="page-subtitle"
            group="Everyone"
            type="checkbox"
            prompt="Show Subtitle"
            alt="Check to display the subtitle region."
        >
<option value="show" selected="false">Yes</option>
</parameter>
        <parameter section="Processing Instructions"
            name="page-preprocessing"
            group="tmplSettings_Processing"
            type="checkbox"
            prompt="Pre-processing"
            alt="Check to enable the pre-processing region. This region is for server side instructions which must run before any HTML."
        >
<option value="show" selected="true">Yes</option>
</parameter>
    </ouc:properties>
    <ouc:div label="page-subtitle"  group="Everyone"  button-class="oucEditButton" button-text="Subtitle" break="break" >
        <ouc:editor wysiwyg-class="page-subtitle" csspath="/_resources/ou/editor/maincontent.css" cssmenu="/_resources/ou/editor/menu.txt" width="955" />
Hark! A Subtitle!
    </ouc:div>
<ouc:div label="main-content"  group="Everyone"  button-class="oucEditButton" button-text="Main Content" break="break" ><ouc:editor csspath="/_resources/ou/editor/maincontent.css" cssmenu="/_resources/ou/editor/menu.txt" width="955" wysiwyg-class="main-content"/><div>testing stuff:</div>
<div></div>
<div></div>
<hr class="tabs-start" />
<h2>STUFF TITLE</h2>
<p>Stuff</p>
</ouc:div>
</document>
2
You may be interested to know that XSLT 3.0 has a new instruction: <xsl:evaluate>, and it may be useful in your case. More information here: w3.org/TR/xslt-30/#dynamic-xpathDimitre Novatchev
Thanks for mentioning it, @DimitreNovatchev! I actually did try using xsl:evaluate, but ran into namespace issues: it didn't seem to recognize any of my declared prefixes, no matter where I declared them. I'd REALLY love if there were some elegant pattern I was overlooking that would more natively work on succinct node representations of the logic and (non-specific to each comparison) related transforms, but I'm getting the sense that that's a definite no :)taswyn
taswyn, you can specify this in the namespace-context attribute.Dimitre Novatchev
I'd actually previously tried that, with everything I tried resulting in "An empty sequence is not allowed as the @namespace-context attribute of xsl:analyze-string." Today I went back and played with xsl:evaluate and tried using '.', and that worked, as did leaving it off. Which leaves me wondering what was going on.taswyn
Nevermind, it's still failing when I try to cast the variable holding the evaluate to boolean, so it doesn't work for the conditional since it always evaluates as true.taswyn

2 Answers

3
votes

Two Suggestions

  1. Just write XSLT directly. Needing to dynamically evaluate XPath expressions is a sign you've hit a complexity wall. Continued head banging is not advised.
  2. Or, if you really must invent your own XML-based template language, then write XSLT to translate your XML templates into XSLT, and then execute this generated XSLT directly. With XSLT as your target language, you'll have its full power (including XPath expression evaluation which you can dynamically invoke) at your disposal without having to rely on processor-specific extensions.
2
votes

I would seriously look at @kjhughes' second suggestion. I haven't really followed your problem description in all its detail, but it does seem to me that you are designing a miniature transformation language for manipulating your content, and I've found that when you find yourself doing that, translating your language to XSLT and then running the generated XSLT is often easier (and faster, and more portable) than interpreting the language with an interpreter written in XSLT and invoking xsl:evaluate to evaluate the path expressions, which is in effect what you are currently doing.

Using a pipeline processor such as Orbeon or Calabash can help to manage the overall control flow.