Using XSLT as an XML pre-processor

2019-02-27 15:10发布

问题:

This is my first time doing anything with XSLT, or XML really, so please excuse me. I've found XSLT web documentation is really terse.

I have an XML file that I want to process to selectively drop content based on an input set of defines. The behavior should be similar to a simple code pre-processor handling ifdef blocks.

I've worked out how to do it as below, but some parts such as the "contents" variable didn't seem like the best way to handle this.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="xml" />
  <xsl:param name="defines-uri" required="yes"/>

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

  <xsl:template match="ifdef">
    <xsl:variable name="contents" select="child::node()"/>
    <xsl:variable name="defines" select="document($defines-uri)/defines"/>
    <xsl:variable name="val" select="@select"/>

    <xsl:for-each select="$defines">
      <xsl:if test="def=$val">
        <xsl:apply-templates select="$contents"/>
      </xsl:if>
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet>

The main issue was the apply-templates on the case when a match was found in the defines. Without the contents, I get the defines document dumped to different degrees in the output.

Whats the best way to do pre-processing of XML without transformation?

回答1:

I've worked out how to do it as below, but some parts such as the "contents" variable didn't seem like the best way to handle this.

Well, basically you got it right. You can still improve it a little, though:

<xsl:variable name="defines" select="document($defines-uri)/defines"/>

<xsl:template match="ifdef">
  <xsl:variable name="this" select="."/>

  <xsl:for-each select="$defines[def = $this/@select]">
    <xsl:apply-templates select="$this/node()" />
  </xsl:for-each>
</xsl:template>

The <xsl:for-each> changes the context node. Within it, the . refers to the node being iterated over, not the one that had been matched by the <xsl:template>.

That means you have to preseve the "outer" context in a variable, this is standard practice.



回答2:

You're doing really well for someone who's only just beginning with XSLT and XML.

This isn't an answer to your question, but I just wanted to say that this might be a safer "copy by default" template:

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

Yours works by grace of a certain default built-in template. But you might get strange behaviour regarding text nodes (or other things that aren't elements) when you add more templates, since the default has a low priority.