views:

111

answers:

2

I'm trying to create a site which (among other things) will display data which is contained in xml files. I'm using xsl stylesheets to format everything, but some of the pages have similar content. Rather than have to make multiple xml sheets with duplicate data, is there a way to tell the xsl where the data is being displayed and have it determine which layout to use.

Example:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
<xsl:template match="/">

<xsl:choose>
  <xsl:if test="something">
    <!-- Format data one way -->
  </xsl:if>
  <xsl:otherwise>
    <!-- Format data another way -->
  </xsl:otherwise>
</xsl:choose>

</xsl:template>
</xsl:stylesheet>

The site is being hosted on a larger site which doesn't allow its microsites to use any server side scripting so my options are severely limited here.

A: 

You could use Client Side XSLT. Provide a PI into your XML documents and in the specific stylesheet include the master layout stylesheet.

Be free to check and use http://www.aranedabienesraices.com.ar as example.

EDIT 3: Almost full example with recursion.

XML document "layoutA.xml":

<html xmlns:inc="include">
    <body>
        <h1>Birthday</h1>
        <dl inc:in-iter="person">
            <dt inc:path="name"></dt>
            <dd inc:path="date"></dd>
        </dl>
    </body>
</html>

Input XML document:

<data>
    <person>
        <name>Bob</name>
        <date>2010-02-23</date>
        <link>http://example.org/bob&lt;/link&gt;
    </person>
    <person>
        <name>Alex</name>
        <date>2010-02-23</date>
        <link>http://example.org/alex&lt;/link&gt;
    </person>
</data>

Stylesheet:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:inc="include">
    <xsl:param name="pLayout" select="'layoutA.xml'"/>
    <xsl:template match="/">
        <xsl:apply-templates select="document($pLayout)/*">
            <xsl:with-param name="context" select="*"/>
        </xsl:apply-templates>
    </xsl:template>
    <xsl:template match="@*|node()">
        <xsl:param name="context"/>
        <xsl:copy>
            <xsl:apply-templates select="@*|node()">
                <xsl:with-param name="context" select="$context"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="*[@inc:path]">
        <xsl:param name="context"/>
        <xsl:copy>
            <xsl:apply-templates select="@*">
                <xsl:with-param name="context" select="$context"/>
            </xsl:apply-templates>
            <xsl:value-of select="$context/*[name()=current()/@inc:path]"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="*[@inc:in-iter]" priority="1">
        <xsl:param name="context"/>
        <xsl:variable name="me" select="."/>
        <xsl:copy>
            <xsl:apply-templates select="@*">
                <xsl:with-param name="context" select="$context"/>
            </xsl:apply-templates>
            <xsl:for-each select="$context/*[name()=current()/@inc:in-iter]">
                <xsl:apply-templates select="$me/node()">
                    <xsl:with-param name="context" select="."/>
                </xsl:apply-templates>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="*[@inc:out-iter]" priority="1">
        <xsl:param name="context"/>
        <xsl:variable name="me" select="."/>
        <xsl:for-each select="$context/*[name()=current()/@inc:out-iter]">
            <xsl:element name="{name($me)}" namespace="{namespace-uri($me)}">
                <xsl:apply-templates select="$me/@*|$me/node()">
                    <xsl:with-param name="context" select="."/>
                </xsl:apply-templates>
            </xsl:element>
        </xsl:for-each>
    </xsl:template>
    <xsl:template match="@inc:path|@inc:in-iter|@inc:out-iter" priority="1"/>
    <xsl:template match="@inc:*">
        <xsl:param name="context"/>
        <xsl:attribute name="{local-name()}">
            <xsl:value-of select="$context/*[name()=current()]"/>
        </xsl:attribute>
    </xsl:template>
</xsl:stylesheet>

Output:

<html xmlns:inc="include">
    <body>
        <h1>Birthday</h1>
        <dl>
            <dt>Bob</dt>
            <dd>2010-02-23</dd>
            <dt>Alex</dt>
            <dd>2010-02-23</dd>
        </dl>
    </body>
</html>

Passing param pLayout as 'layoutB.xml', and this "layoutB.xml":

<html xmlns:inc="include">
    <body>
        <h1>Friends</h1>
        <ul>
            <li inc:out-iter="person">
                <a inc:href="link" inc:path="name"></a>
            </li>
        </ul>
    </body>
</html>

Output:

<html xmlns:inc="include">
    <body>
        <h1>Friends</h1>
        <ul>
            <li>
                <a href="http://example.org/bob"&gt;Bob&lt;/a&gt;
            </li>
            <li>
                <a href="http://example.org/alex"&gt;Alex&lt;/a&gt;
            </li>
        </ul>
    </body>
</html>

Note: The main problem with your requeriment is the same document restriction (so, same document URI, no diferent PI, no diferent layout URI metadata) wich leaves you only to javascript to pass the layout URI param. Until browser support XPath 2.0 fn:document-uri() so you can parse URL query. Of course, you could use some extension (MSXSL script, as example) but it would be dificult to make it work cross-browser.

Alejandro
The problem here is that each xml document can still only be styled in one way. I'm trying NOT to have a master layout stylesheet.
Skunkwaffle
@Skunkwaffle: Each XML input document could have its own stylesheet and also could keep a master (or same for same documents) stylesheet. Check my edit and my **link**
Alejandro
This still requires two different xml documents. What I want is to have A.xml, use master.xsl or b.xsl, depending on where it's being displayed.
Skunkwaffle
@Skunkwaffle: What do you mean by "depending on where it's being displayed"? Some sort of media query?
Alejandro
Sorry I should have mentioned this earlier. I'm displaying the xml pages inside an iframe.
Skunkwaffle
@Skunkwaffle: if you could only determine wich layout to apply depending on an event on the client (the URI to retrive the document is the same for each iframe) you have to use javascript to pass the layout URI to de stylesheet.
Alejandro
This is where I'm unsure. I don't know of any way for the stylesheet to read the uri, whether it's different or not. I guess this is the heart of my question.
Skunkwaffle
@Skunkwaffle: If you can't pass the data for layout definition in your document (with a PI, with metadata), you would need to pass as param to the transformation.
Alejandro
@alejandro -- I see that somebody has downvoted your answer. I believe that your answer deserves better than that and give you a +1.
Dimitre Novatchev
+1  A: 

In such situation I use layouts, each contained in a separate XML document.

The (filename of the) layout to use can be passed as a parameter to the transformation, or it can be dynamically determined within the transformation.

From this moment on, the Layout XML document can be accessed using the XSLT document() function:

<xsl:variable name="vDocLayout" select="document($pLayout)"/>

Then you can issue:

<xsl:apply-templates select="$vDocLayout"/>

This is the "fill in the blanks" XSLT design pattern.

Dimitre Novatchev
@Dimitre: Yes. I use this pattern and that is why I've posted the link to my business site as living example. But, because there is no support today for XPath 2.0 on the browser, you can't use `fn:document-uri()` as way to pass parameter on the wire. So, you must keep sending PI with diferent stylesheet, at least to just declare your `pLayout` param, or add its uri with metadata into your XML document (mostly the same task as adding the PI). Having some sort of inline mapping bettween data and layout URI is posible, but make the site less dynamic...
Alejandro
@Dimitre: Also, I call this kind of transformation (separate layout document and data document) as "population", because its more like good code practice with code-behind in .Net
Alejandro
@Alejandro: There is no restriction to use this design pattern only in client-side processing. On the server side, passing parameters to the transformation is supported on all possible platforms.
Dimitre Novatchev
@Dimitre: Yes, server side it is an option, but not for the OP: "doesn't allow its microsites to use any server side scripting"
Alejandro
@Alejandro: He knows the full URL and can put it by convention in a special element/attribute in the XSLT stylesheet.
Dimitre Novatchev
@Dimitre: Yes, you are rigth. That is what I call metadata. But involves a server process, the same as adding a PI. Unless there is multiple documents, wich is not the case as the OP wrote in the comments to my answer.
Alejandro