Eliot Christian
Eliot Christian

Reputation: 11

Transform multi-branched XML doc to multiple XML docs

We need to make multiple XML documents, each with one "info" element, from a single XML document that has an unknown number of "info" elements. For example, this document:

<alert>
  <identifier>2.49.0.1.124.76fea972.2015</identifier>  
  <info>
    <language>en</language>
  </info>
  <info>
    <language>fr</language>
  </info>
</alert>

should yield these two documents:

<alert>
  <identifier>2.49.0.1.124.76fea972.2015</identifier>  
  <info>
    <language>en</language>
  </info>
</alert>

<alert>
  <identifier>2.49.0.1.124.76fea972.2015</identifier>  
  <info>
    <language>fr</language>
  </info>
</alert>

While pruning siblings of an "info" element, we need to copy all nodes, attributes, namespaces, etc of all ancestors (to the root), as well as all nodes, attributes, namespaces, etc of the particular "info" element.

I am a newbie at XSLT and at a loss for how to do this. Any help would be greatly appreciated!

Upvotes: 0

Views: 61

Answers (3)

Martin Honnen
Martin Honnen

Reputation: 167716

Using an XSLT 2.0 processor like Saxon 9 or AltovaXML or XmlPrime you can use an approach like this:

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

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

<xsl:template match="/">
  <xsl:apply-templates select="alert/info" mode="split"/>
</xsl:template>

<xsl:template match="info" mode="split">
  <xsl:result-document href="language-{language}.xml">
    <xsl:apply-templates select="/*">
      <xsl:with-param name="info" select="current()" tunnel="yes"/>
    </xsl:apply-templates>
  </xsl:result-document>
</xsl:template>

<xsl:template match="info">
  <xsl:param name="info" tunnel="yes"/>
  <xsl:if test=". is $info">
    <xsl:next-match/>
  </xsl:if>
</xsl:template>

</xsl:stylesheet>

If the input is in a certain namespace, then use e.g. <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xpath-default-namespace="urn:oasis:names:tc:emergency:cap:1.2">.

If you want to run the XSLT independent of the namespace, then use

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

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

<xsl:template match="/">
  <xsl:apply-templates select="*:alert/*:info" mode="split"/>
</xsl:template>

<xsl:template match="*:info" mode="split">
  <xsl:result-document href="language-{language}.xml">
    <xsl:apply-templates select="/*">
      <xsl:with-param name="info" select="current()" tunnel="yes"/>
    </xsl:apply-templates>
  </xsl:result-document>
</xsl:template>

<xsl:template match="*:info">
  <xsl:param name="info" tunnel="yes"/>
  <xsl:if test=". is $info">
    <xsl:next-match/>
  </xsl:if>
</xsl:template>

</xsl:stylesheet>

Upvotes: 0

Michael Kay
Michael Kay

Reputation: 163549

Or perhaps this is a bit simpler than Martin's solution:

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

<xsl:template match="/">
 <xsl:for-each select="alert/info">
  <xsl:result-document href="language-{language}.xml">
    <alert>
      <xsl:copy-of select="../identifier"/>
      <xsl:copy-of select="."/>
    </alert>
  </xsl:result-document>
 </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

Upvotes: 0

choroba
choroba

Reputation: 242103

Easy in xsh:

my $orig := open file.xml ;
for my $info in /alert/info {
    my $clone := clone $orig ;
    my $i = count($info/preceding::info) ;
    delete $clone/alert/info[count(preceding::info) = $i] ;
    save :f concat('file', $i, '.xml') $clone ;
}

Upvotes: 1

Related Questions