开发者

xslt: keeping namespace declaration on root when root element is not known in advance

I have xml documents that follow a schema where most of the defined elements are allowed to be the root of a valid instance. I also have several xslt's v2.0 which translate it in various ways (put it into a normal form, a compact form, a different dialect, ...) These xslt's are all based on an identity transform with templates added to make the desired modification. The problem is that there is a proliferation of namespace attributes because there are some elements that come from outside the default namespace.

I have tried the recommended procedures for inserting the namespace on the root element, but I can't seem to get it right. The issues are: 1. the transformation may change the name, and sometimes the content of the root element, so I still need the templates for each of the global elements, and since I don't know which one will be root, I can't just insert namespace elements where needed (I don't know where they will be needed for a particular document. 2. I thought about implementing this as multi-pass, or simply an independent xslt, since I want the same result for several different xslts. In this case, what I would need is an identity transform that takes all the namespaces and prefixes from all elements in the document, and inserts them into the root. This would, I hope, automatically remove the namespace attributes from the children? However, I tried the following

<?xml version="1.0" ?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:strip-space elements="*"/>
    <xsl:output method="xml" indent="yes"/>
    <xsl:template name="start" match="/">
        <xsl:copy>
            <xsl:for-each select="*">
                <xsl:copy>
                    <xsl:for-each select="descendant::*">
                        <xsl:call-template name="add-ns">
                            <xsl:with-param name="ns-namespace">
                                <xsl:value-of select="namespace-uri()"/>
                            </xsl:with-param>
                            <xsl:with-param name="ns-prefix">
                                <xsl:value-of
                                    select=" prefix-from-QName( QName(namespace-uri(),name()))"/>
                            </xsl:with-param>
                        </xsl:call-template>
                    </xsl:for-each>
                    <xsl:apply-templates select="node() | @*"/>
                </xsl:copy>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>
    <xsl:template name="add-ns">
        <xsl:param name="ns-prefix" select="'x'"/>
        <xsl:param name="ns-namespace" select="'someNamespace'"/>
        <xsl:namespace name="{$ns-prefix}" select="$ns-namespace"/>
    </xsl:template>
    <xsl:template match="node()|@* ">
        <xsl:copy>
            <xsl:apply-templates select="node() | @*"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

And this works for all prefixes that appear on elements, but it doesn't catch the prefixe开发者_StackOverflows of attributes. Here is a test document:

<RuleML xmlns="http://www.ruleml.org/0.91/xsd">
   <Assert textiri="xy&gt;z">
      <Importation xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="abc"
                   textiri="urn:common-logic:demo1"
                   xlink:href="http://common-logic.org/x&gt;cl/demos.xml"/>
      <a:anything xmlns:a="http://anything.org"
                  xmlns:xlink="http://www.w3.org/1999/xlink"/>
   </Assert>
</RuleML>

I want it to produce:

<RuleML xmlns="http://www.ruleml.org/0.91/xsd" xmlns:a="http://anything.org" xmlns:xlink="http://www.w3.org/1999/xlink" >
   <Assert textiri="xy&gt;z">
      <Importation xml:id="abc"
                   textiri="urn:common-logic:demo1"
                   xlink:href="http://common-logic.org/x&gt;cl/demos.xml"/>
      <a:anything/>
   </Assert>
</RuleML>

but instead I get

<RuleML xmlns="http://www.ruleml.org/0.91/xsd" xmlns:a="http://anything.org">
   <Assert textiri="xy&gt;z">
      <Importation xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="abc"
                   textiri="urn:common-logic:demo1"
                   xlink:href="http://common-logic.org/x&gt;cl/demos.xml"/>
      <a:anything xmlns:xlink="http://www.w3.org/1999/xlink"/>
   </Assert>
</RuleML>

Tara


Does the following do what you want?

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="xs">

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

  <xsl:template match="/*">
    <xsl:copy>
      <xsl:copy-of select="descendant::*/namespace::*"/>
      <xsl:apply-templates select="@* , node()"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

With Saxon 9.3 it seems to do the job on the sample you posted. I am however not sure what you want to do if there are several elements in different default namespaces or several elements in different namespaces but using the same prefix. For instance with

<root xmlns="http://example.com/ns1">
  <foo xmlns="http://example.com/ns2">
    <pf:bar xmlns:pf="http://example.com/ns3">
      <pf:foobar xmlns:pf="http://example.com/ns4"/>
    </pf:bar>
  </foo>
</root>

Saxon simply reports the error

Error at xsl:copy-of on line 15 of test2011061801Xsl2.xsl:
  XTDE0430: Cannot create two namespace nodes with the same prefix mapped to different URIs
  (prefix="", URI=http://example.com/ns2, URI=http://example.com/ns1)
  in built-in template rule

[edit] If you don't want an error to be reported you could try to implement a strategy to pull up namespace nodes as far up as possible but to avoid any collisions. That can be done with for-each-group, as in the following sample XSLT 2.0:

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="xs">

  <xsl:template match="@* | text() | processing-instruction() | comment()">
    <xsl:copy/>
  </xsl:template>

  <xsl:template match="*">
    <xsl:copy copy-namespaces="no">
      <xsl:for-each-group select="descendant-or-self::*/namespace::*" group-by="local-name()">
        <xsl:copy-of select="."/>
      </xsl:for-each-group>
      <xsl:apply-templates select="@* , node()"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

With the input being

<root xmlns="http://example.com/ns1">
  <foo xmlns="http://example.com/ns2">
    <pf:bar xmlns:pf="http://example.com/ns3">
      <pf:foobar xmlns:pf="http://example.com/ns4"/>
    </pf:bar>
  </foo>
</root>

Saxon 9.3 outputs

<?xml version="1.0" encoding="UTF-8"?><root xmlns="http://example.com/ns1" xmlns:pf="http://example.com/ns3">
  <foo xmlns="http://example.com/ns2">
    <pf:bar>
      <pf:foobar xmlns:pf="http://example.com/ns4"/>
    </pf:bar>
  </foo>
</root>


<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:strip-space elements="*"/>
    <xsl:output method="xml" indent="yes"/>
    <xsl:template match="*:RuleML">
        <xsl:copy>
        <xsl:for-each select="descendant::node()">
            <xsl:choose>
                <xsl:when test="self::text()"/>

                <xsl:otherwise>
                    <xsl:for-each select="namespace::node()">
                        <xsl:copy-of select="."/>
                    </xsl:for-each>        
                </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each>
            <xsl:apply-templates select="(node() | @*) except namespace::node()"/>
        </xsl:copy>
    </xsl:template>

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

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜