开发者

XML with inline XSLT

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 -->
  &开发者_如何学Clt;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>


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">
    <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>


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>


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">
  <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>
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜