[SOLVED] XSLT – keep sequence between matching elements and attributes and text

Issue

How to keep selected elements, attributes and text/characters in the right sequence?

The XML contains superfluous elements (like <skip>), text/characters, superfluous attributes.

The XML is:

<?xml version="1.0" encoding="utf-8" ?>
<a>
  <b c="d">
    <skip>Skip this text</skip>
    one
    <e f="g" h="two">
      three    
    </e>
    <e f="g" h="four">
      five
    </e>
  </b>
</a>

The requested output should be:

<output>one <sub>two</sub> three <sub>four</sub> five</output>

The XSLT so far is:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="3.0">
  <xsl:output method="html" indent="yes" html-version="5"/>
  
  <xsl:template match="skip"/>

  <xsl:template match="/a">
    <xsl:apply-templates select="b"/>
  </xsl:template>
  
  <xsl:template match="b">
    <xsl:value-of select="e" />
    <xsl:value-of select="." />
  </xsl:template>

  <xsl:template match="e">
    <sup>
    <xsl:value-of select="@h" />
    </sup>
  </xsl:template>
</xsl:stylesheet>

I am trying to get this working via a XSLT Fiddle session.

About the XSLT version. Below is a simple test program I wrote in Java.

public class XsltFilterParentWithAllChildren {

  private static final String XML_FILENAME = "files/xslt-advanced-input.xml";
  private static final String XSLT_FILENAME = "files/xslt-advanced.xsl";
  private static final String XML_OUTPUT_FILENAME = "files/output.xml";

  public static void main(String[] args) {
    deleteFile( XML_OUTPUT_FILENAME);
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    try (InputStream is = new FileInputStream(XML_FILENAME)) {
      DocumentBuilder db = dbf.newDocumentBuilder();
      Document doc = db.parse(is);
      try (FileOutputStream output = new FileOutputStream(XML_OUTPUT_FILENAME)) {
        transform(doc, output);
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private static void transform(Document doc, OutputStream output) throws TransformerException {
    TransformerFactory transformerFactory = TransformerFactory.newInstance();
    Transformer transformer = transformerFactory.newTransformer(
            new StreamSource(new File(XSLT_FILENAME)));
    transformer.transform(new DOMSource(doc), new StreamResult(output));
  }

  private static void deleteFile( String pathname) {
    new File( pathname).delete();
  }
}

Solution

Using apply-templates ensures output in document input order, you just need to make sure your templates process the nodes you want and blocks (with empty templates) then ones you don’t want e.g.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="3.0" expand-text="yes">
  
  <xsl:mode on-no-match="text-only-copy"/>
  
  <xsl:template match="a">
    <output>
      <xsl:apply-templates/>
    </output>
  </xsl:template>
  
  <xsl:template match="e">
    <xsl:apply-templates select="@*, node()"/>
  </xsl:template>

  <xsl:template match="skip | @f"/>
  
  <xsl:template match="@h">
    <sup>{.}</sup>
  </xsl:template>
  
</xsl:stylesheet>

An additional <xsl:strip-space elements="*"/> might get you closer to a one line output, although it is not clear which whitespace you want to remove, an additional

  <xsl:template match="text()">
    <xsl:value-of select="normalize-space()"/>
  </xsl:template>

might help.

Answered By – Martin Honnen

Answer Checked By – Cary Denson (BugsFixing Admin)

Leave a Reply

Your email address will not be published.