...any...

XSL transformation on all namespaces

I have XML that looks something like this:

<Root xmlns="http://widgetspecA.com/ns">
  ...any...
  <WidgetBox>
    <A/>
    <B/>
    <SmallWidget> <!-- minOccurs='0' -->
      ...any...
    </SmallWidget>
    <Widgets> <!-- minOccurs='0' -->
      ...any...
    </Widgets>
    ...any...
  </WidgetBox>
  ...any...
</Root>

      

and I want to convert it to this:

<Root xmlns="http://widgetspecB/ns">
  ...any...
  <WidgetBox>
    <A/>
    <B/>
    <Widgets>
      <Atom>
        ...any...
      </Atom>
      <Molecule>
        ...any...
      </Molecule>
    </Widgets>
    ...any...
  </WidgetBox>
  ...any...
</Root>

      

In other words:

<SmallWidget>

in specA means the same as <Atom>

in specB, so just rename the item.

<Widgets>

in specA means the same as <Molecule>

in specB, so just rename the item.

Wrap <Atom>

and <Molecule>

in a named element <Widgets>

which means something other than specA <Widgets>

.

Everything else is copied as is, but in a new namespace.

What is XSLT for?

SOLUTION?: I ended up going with this:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:old="http://widgetspecA.com/ns"
    xmlns="http://widgetspecB.com/ns"
    exclude-result-prefixes="old">

  <xsl:output method="xml"/>

  <xsl:template match="*">
    <xsl:element name="{name()}">
      <xsl:copy-of select="@*"/>
      <xsl:apply-templates/>
    </xsl:element>
  </xsl:template>

  <xsl:template match="old:SmallWidget" mode="single">
    <Atom>
      <xsl:apply-templates/>
    </Atom>
  </xsl:template>

  <xsl:template match="old:Widgets" mode="single">
      <Molecule>
        <xsl:apply-templates/>
      </Molecule>
  </xsl:template>

  <xsl:template match="old:SmallWidget[following-sibling::old:Widgets]">
      <Widgets>
       <xsl:apply-templates select="self::node()" mode="single"/>
       <xsl:apply-templates select="following-sibling::old:Widgets" mode="single"/>
      </Widgets>
  </xsl:template>

  <xsl:template match="old:Widgets[preceding-sibling::old:SmallWidget]"/>

  <xsl:template match="old:SmallWidget[not(following-sibling::old:Widgets)]">
      <Widgets>
       <xsl:apply-templates select="self::node()" mode="single"/>
      </Widgets>
  </xsl:template>

  <xsl:template match="old:Widgets[not(preceding-sibling::old:SmallWidget)]">
      <Widgets>
       <xsl:apply-templates select="self::node()" mode="single"/>
      </Widgets>
  </xsl:template>

</xsl:stylesheet>

      

+1


a source to share


1 answer


A good XSLT solution will map your human-readable rules into simple template rules. Here are the rules, in your words:

  • <SmallWidget>

    in specA means the same as <Atom>

    in specB, so just rename the item.
  • <Widgets>

    in specA means the same as <Molecule>

    in specB, so just rename the item.
  • Wrap <Atom>

    and <Molecule>

    in a named element <Widgets>

    which means something other than specA <Widgets>

    .
  • Everything else is copied as is, but in a new namespace.

Let it go:

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:in="http://widgetspecA.com/ns"
  xmlns="http://widgetspecB.com/ns"
  exclude-result-prefixes="in">

  <!-- 1. Rename <SmallWidget> -->
  <xsl:template mode="rename" match="in:SmallWidget">Atom</xsl:template>

  <!-- 2. Rename <Widgets> -->
  <xsl:template mode="rename" match="in:Widgets">Molecule</xsl:template>

  <!-- 3. Wrap <Atom> & <Molecule> with <Widgets> -->
  <xsl:template match="in:SmallWidget">
    <!-- ASSUMPTION: in:Widgets immediately follows in:SmallWidget -->
    <Widgets>
      <xsl:apply-templates mode="convert" select="."/>
      <xsl:apply-templates mode="convert" select="following-sibling::in:Widgets"/>
    </Widgets>
  </xsl:template>

          <!-- Skip by this in regular processing;
               it gets explicitly converted inside <Widgets> (see above) -->
          <xsl:template match="in:Widgets"/>

          <!-- Also, don't copy whitespace appearing
               immediately before in:Widgets -->
          <xsl:template match="text()
                               [following-sibling::node()[1][self::in:Widgets]]"/>


  <!-- 4: Everything copied as is, but in the new namespace -->

    <!-- Copy non-element nodes as is -->
    <xsl:template match="@* | text() | comment() | processing-instruction()">
      <xsl:copy/>
    </xsl:template>

    <!-- By default, just convert elements to new namespace
         (exceptions under #3 above) -->
    <xsl:template match="*">
      <xsl:apply-templates mode="convert" select="."/>
    </xsl:template>

            <xsl:template mode="convert" match="*">
              <!-- Optionally rename the element -->
              <xsl:variable name="name">
                <xsl:apply-templates mode="rename" select="."/>
              </xsl:variable>
              <xsl:element name="{$name}">
                <xsl:apply-templates select="@* | node()"/>
              </xsl:element>
            </xsl:template>

                    <!-- By default, just use the same local
                         name as in the input document -->
                    <xsl:template mode="rename" match="*">
                      <xsl:value-of select="local-name()"/>
                    </xsl:template>

</xsl:stylesheet>

      

Note that it is important to use a function local-name()

, not a function name()

. If you do name()

, your stylesheet will break if your source document starts using a namespace prefix that is not explicitly declared in your stylesheet (unless you add an attribute namespace

to <xsl:element>

to force the namespace even if the prefix appears). However, if we use local-name()

, we are safe; it will never include a prefix, so the result element will accept our default namespace for the stylesheet.

Executing the specified stylesheet according to your original sample document gives exactly what you requested:



<Root xmlns="http://widgetspecB.com/ns">...any...<WidgetBox>...any...
  <Widgets><Atom>
    ...any...
  </Atom><Molecule>
    ...any...
  </Molecule></Widgets>...any...
</WidgetBox>...any...</Root>

      

Let me know if you have any questions. XSLT is not powerful!

PS If I wanted to be precise in reproducing whitespace, as in your example, I could use stepwise "chaining" processing, where I apply templates to only one node at a time, and each template rule is responsible for continuing to process its next sibling. But it seemed overkill for this situation.

UPDATE: The new solution you posted is very smart. However, this can be simplified. I accepted your new solution and made some recommended changes below, as well as comments indicating what I changed and why I made those changes.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:old="http://widgetspecA.com/ns"
    xmlns="http://widgetspecB.com/ns"
    exclude-result-prefixes="old">

  <!-- "xml" is the default; no real need for this
  <xsl:output method="xml"/>
  -->

  <!-- This works fine if you only want to copy elements, attributes,
       and text. Just be aware that comments and PIs will get
       effectively stripped out, because the default template rule
       for those is to do nothing.
  -->
  <xsl:template match="*">
    <xsl:element name="{name()}">
      <xsl:copy-of select="@*"/>
      <xsl:apply-templates/>
    </xsl:element>
  </xsl:template>

  <xsl:template match="old:SmallWidget" mode="single">
    <Atom>
      <xsl:apply-templates/>
    </Atom>
  </xsl:template>

  <xsl:template match="old:Widgets" mode="single">
      <Molecule>
        <xsl:apply-templates/>
      </Molecule>
  </xsl:template>

  <!-- You actually only need one rule for <old:SmallWidget>.
       Why? Because the behavior of this rule will always
       be exactly the same as the behavior of the other rule
       you supplied below.
  -->
  <xsl:template match="old:SmallWidget"> <!--[following-sibling::old:Widgets]">-->
      <Widgets>
                      <!-- "." means exactly the same thing as "self::node()" -->
       <xsl:apply-templates select="." mode="single"/>

       <!-- If the node-set is empty, then this will be a no-op anyway,
            so it safe to have it here even for the case when
            <old:Widgets> is not present in the source tree. -->
                                    <!-- This XPath expression ensures
                                         that you only process the next
                                         sibling element - and then only
                                         if it name is <old:Widgets>.
                                         Your schema might not allow it,
                                         but this is a clearer communication
                                         of your intention, and it will also
                                         work correctly if another
                                         old:SmallWidget/old:Widget pair
                                         appeared later in the document.
                                    -->
       <xsl:apply-templates select="following-sibling::*[1][self::old:Widgets]"
                            mode="single"/>
      </Widgets>
  </xsl:template>

                                  <!-- updated this predicate for the
                                       same reason as above. Answers the
                                       question: Is the element right before
                                       this one a SmallWidget? (as opposed to:
                                       Are there any SmallWidget elements
                                       before this one?) -->
  <xsl:template match="old:Widgets[preceding-sibling::*[1][self::old:SmallWidget]]"/>

  <!-- Removed, because this rule effectively has the same behavior as the other one above
  <xsl:template match="old:SmallWidget[not(following-sibling::old:Widgets)]">
      <Widgets>
       <xsl:apply-templates select="self::node()" mode="single"/>
      </Widgets>
  </xsl:template>
  -->

  <!-- no need for the predicate. The format of this pattern (just a name)
       causes this template rule priority to be 0. Your other rule
       for <old:Widgets> above has priority of .5, which means that it
       will override this one automatically. You don't need to repeat
       the constraint. Alternatively, you could keep this predicate
       and remove the other one. Either way it will work. (It probably
       a good idea to place these rules next to each other though,
       so you can read it like an if/else statement) -->
  <xsl:template match="old:Widgets">  <!--[not(preceding-sibling::*[1][self::old:SmallWidget])]">-->
      <Widgets>
       <xsl:apply-templates select="." mode="single"/>
      </Widgets>
  </xsl:template>

</xsl:stylesheet>

      

+1


a source







All Articles