tags:

views:

56

answers:

3

I need an XML file to change after installation, based on some parameters.

For example:

<?xml ...?>
<root xmlns="...">

  <!-- remove this element when PARAM_MODE=1 -->
  <sometag />

  <!-- remove this element when PARAM_MODE=2 -->
  <sometag2 />

  <someothertag />
</root>

The easiest way it to use XSLT to remove the element, but I want the comment and XSL to be combined and not be duplicated.

Is there something that can do it ? Something like this:

<?xml ...?>
<root xmlns="..." xmlns:xsl="XSL_NAMESPACE">
  <!-- XSL to remove this element when PARAM_MODE=1 -->
  <xsl:remove the attribute if $PARAM_MODE=1 />
  <sometag />

  <!-- XSL to remove this element when PARAM_MODE=2 -->
  <xsl:remove the attribute if $PARAM_MODE=2 />
  <sometag2 />

  <someothertag />
</root>
+2  A: 

This stylesheet:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:my="...">
    <xsl:param name="PARAM_MODE" select="1"/>
    <xsl:template match="@*|node()" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="my:sometag">
        <xsl:if test="$PARAM_MODE!=1">
            <xsl:call-template name="identity"/>
        </xsl:if>
    </xsl:template>
    <xsl:template match="my:sometag2">
        <xsl:if test="$PARAM_MODE!=2">
            <xsl:call-template name="identity"/>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

With this input:

<root xmlns="...">
    <!-- remove this element when PARAM_MODE=1 -->
    <sometag />
    <!-- remove this element when PARAM_MODE=2 -->
    <sometag2 />
    <someothertag />
</root>

Output:

<root xmlns="...">
    <!-- remove this element when PARAM_MODE=1 -->
    <!-- remove this element when PARAM_MODE=2 -->
    <sometag2></sometag2>
    <someothertag></someothertag>
</root>

Do note that if you want a simplified syntax, from http://www.w3.org/TR/xslt#result-element-stylesheet :

A simplified syntax is allowed for stylesheets that consist of only a single template for the root node. The stylesheet may consist of just a literal result element (see 7.1.1 Literal Result Elements). Such a stylesheet is equivalent to a stylesheet with an xsl:stylesheet element containing a template rule containing the literal result element; the template rule has a match pattern of /.

So, you can add elements, but you can't strip them.

EDIT: Reversed logic for simplified syntax.

Suppose this stylesheet with... test.xsl URI:

<?xml-stylesheet type="text/xsl" href="test.xsl"?>
<root 
 xmlns="..." 
 xmlns:my="..." 
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
 xsl:version="1.0">
    <PARAM_MODE>1</PARAM_MODE>
    <!-- remove this element when PARAM_MODE=1 -->
    <xsl:if test="document('')/my:root/my:PARAM_MODE!=1">
        <sometag />
    </xsl:if>
    <!-- remove this element when PARAM_MODE=2 -->
    <xsl:if test="document('')/my:root/my:PARAM_MODE!=2">
        <sometag2 />
    </xsl:if>
    <someothertag />
</root>

Runnig with itself as input (I'm emphasizing this with the PI. Also, that makes fn:document() superfluous...), it outputs:

<root xmlns="..." xmlns:my="...">
    <PARAM_MODE>1</PARAM_MODE>
    <sometag2 />
    <someothertag />
</root>

At last, an comments driven stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:param name="PARAM_MODE" select="1"/>
    <xsl:template match="@*|node()" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="*[preceding-sibling::node()[1]
                            /self::comment()[starts-with(.,' remove ')]]">
        <xsl:if test="$PARAM_MODE != substring-after(
                                        preceding-sibling::comment()[1],
                                        'PARAM_MODE=')">
            <xsl:call-template name="identity"/>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

Output:

<root xmlns="...">
    <!-- remove this element when PARAM_MODE=1 -->
    <!-- remove this element when PARAM_MODE=2 -->
    <sometag2></sometag2>
    <someothertag></someothertag>
</root>
Alejandro
+1 for working answer and for exploring the implied possibility of using a simplified syntax. I think you showed that his request "I want the comment and XSL to be combined and not be duplicated" is probably impossible to do.
LarsH
P.S. I was thinking that @Vitaly wanted to strip elements based on the presence of the comments, rather than based on the element name. But it's not entirely clear.
LarsH
@LarsH: I agree it's not clear. `PARAM_MODE` could be also an element. About the simplified syntax: I don't think it's impossible because you can always reverse the logic like: `<root xmlns="..." xmlns:my="..." xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xsl:version="1.0"><PARAM_MODE>1</PARAM_MODE><!-- remove this element when PARAM_MODE=1 --><xsl:if test="document('')/my:root/my:PARAM_MODE!=1"><sometag /></xsl:if><!-- remove this element when PARAM_MODE=2 --><xsl:if test="document('')/my:root/my:PARAM_MODE!=2"><sometag2 /></xsl:if><someothertag /></root>`
Alejandro
@Alejandro: your last comment is the way I want it be, do you mind explaining more about it by editing your answer (the formatting in comments is really bad), I have a few more questions about this example...
Vitaly Polonetsky
+1  A: 

This stylesheet uses the comment and the param values to determine what content to redact.

Inspiration for the XPATH to find the appropriate element was derived from @Dimitre Novatchev's answer for xslt and xpath: match preceding comments.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output indent="yes"/>

    <xsl:param name="PARAM_MODE" />

    <!--Match on the comments to remove elements, and the elements immediately following -->

    <xsl:template match="comment()[starts-with(.,' remove this element when PARAM_MODE=')] 
         | 
         *[self::*[
                generate-id(
                     preceding-sibling::comment()
                       [1]
                       [starts-with(.,' remove this element when PARAM_MODE=')]
                     /following-sibling::node()
                       [1]
                       [self::text() and not(normalize-space())]
                     /following-sibling::node()
                       [1]
                       [self::*]
                 )=generate-id()
            ]
         ]">

        <!--Param/Variable references are not allowed in the match expression, so we will need to do a little work inside of the template -->
        <xsl:choose>
           <!--If the PARAM_MODE value matches the comment, or it's the element immediately following, then remove it -->
            <xsl:when test="(self::comment() and substring-after(.,'=')=$PARAM_MODE) 
                or 
                (self::*[
                        generate-id(
                            preceding-sibling::comment()
                               [1]
                               [starts-with(.,' remove this element when PARAM_MODE=') 
                                  and substring-after(.,'=')=$PARAM_MODE]
                            /following-sibling::node()
                               [1]
                               [self::text() and not(normalize-space())]
                            /following-sibling::node()
                               [1]
                               [self::*]
                          )=generate-id()
                    ])" />
            <xsl:otherwise>
                <!--Otherwise, invoke the identity template and copy forward -->
                <xsl:call-template name="identity"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

<!--Identity template that copies content into the result document -->
    <xsl:template match="@*|node()" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

When applied to this XML document (with parameter PARAM_MODE=2):

<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="...">

    <!-- remove this element when PARAM_MODE=1 -->
    <sometag />

    <!-- remove this element when PARAM_MODE=2 -->
    <sometag2 />

    <someothertag />
</root>

The following output is produced:

<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="...">

    <!-- remove this element when PARAM_MODE=1 -->
    <sometag/>




    <someothertag/>
</root>
Mads Hansen
+1  A: 

Here's an approach that lets the content of the comment drive whether or not the element following the comment gets copied to the output. See the comments in the transform for details.

Using this input:

<?xml version="1.0" encoding="utf-8" ?>
<tle>
  <!-- remove this element if $param = 1-->
  <foo/>
  <dont_remove/>
  <dont_remove/>
  <!-- remove this element if $param = 2-->
  <bar/>
  <dont_remove/>
  <dont_remove/>
  <dont_remove/>
</tle>

and this transform:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
  <xsl:output method="xml" indent="yes"/>

  <xsl:variable name="param">2</xsl:variable>

  <xsl:template match="@* | node()">
      <xsl:copy>
          <xsl:apply-templates select="@* | node()"/>
      </xsl:copy> 
  </xsl:template>

  <!-- this matches only elements whose immediately preceding non-text node is
       a comment; all other elements are copied to the output by the identity
       template.  

       it ignores text nodes so that whitespace between the comment and the
       element it's tagging doesn't break their association.
  -->
  <xsl:template match="match="*[preceding-sibling::node()
                                 [not(self::text())]
                                 [1]
                                 [self::comment()]
                               ]">
    <!-- find the immediately preceding comment -->
    <xsl:variable name="comment" select="preceding-sibling::comment()[1]"/>
    <!-- don't copy this element if the text of the comment matches the 
         value of $param -->
    <xsl:choose>
      <xsl:when test="$param = 1 and contains($comment, 'param = 1')"/>
      <xsl:when test="$param = 2 and contains($comment, 'param = 2')"/>
      <xsl:otherwise>
        <xsl:copy>
          <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

</xsl:stylesheet>

produces this output:

<tle>
  <!-- remove this element if $param = 1-->
  <foo />
  <dont_remove />
  <dont_remove />
  <!-- remove this element if $param = 2-->

  <dont_remove />
  <dont_remove />
  <dont_remove />
</tle>
Robert Rossney