0

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!

3 Answers3

1

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 ;
}
choroba
  • 231,213
  • 25
  • 204
  • 289
0

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>
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • This is very close. However, in the actual case, the root element always has a namespace, e.g, (Note also there may be a namespace prefix). Is it possible to make the transform namespace-sensitive? (e.g., instead of select="alert/info" use select="/*[local-name()='alert']/*[local-name()='info']"> and so on... – Eliot Christian Oct 22 '15 at 15:01
  • Does that namespace change? If not, I would simply use ``. – Martin Honnen Oct 22 '15 at 15:06
  • @EliotChristian, I have edited the answer to show how to deal with a single namespace or how to write the stylesheet to handle any namespace. – Martin Honnen Oct 22 '15 at 15:18
  • Excellent, Thank you!! Your solution seems to be doing exactly what is needed. – Eliot Christian Oct 22 '15 at 16:12
0

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>
Michael Kay
  • 156,231
  • 11
  • 92
  • 164
  • Thanks for this suggestion, but Martin's generalized solution is necessary because the actual CAP alerts are far more variable and complex (live data can be seen at https://alerts.internetalerts.org/feed ) – Eliot Christian Oct 22 '15 at 16:19