gezgin
gezgin

Reputation: 207

Attributes are missing after sorting elements

I need to create an XSLT 2.0 stylesheet which processes the full periodic table, sorts the atoms (only their names) into the groups based on their state (i.e. STATE attribute of ATOM element) into a text file.

I did something but there are <ATOM> elements without STATE attributes. Those elements must appear in the result document as well.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>
    <xsl:template match="PERIODIC_TABLE">
      <xsl:for-each-group select="ATOM" group-by="@STATE" >
        <xsl:text> **    </xsl:text>
        <xsl:value-of select="current-grouping-key()"/>
        <xsl:text>
</xsl:text>
        <xsl:for-each select="current-group()">
          <xsl:sort select="NAME"/>
          <xsl:value-of select="NAME"/>
          <xsl:text>
</xsl:text>
        </xsl:for-each>
        <xsl:text>
</xsl:text>
      </xsl:for-each-group>
    </xsl:template>
</xsl:stylesheet>

Upvotes: 2

Views: 223

Answers (2)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243479

I have only slightly touched your code, making the following change:

Replace:

group-by="@STATE"

with:

group-by="(@STATE, 'UNKNOWN')[1]"

The transformation now is:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>
    <xsl:template match="PERIODIC_TABLE">
      <xsl:for-each-group select="ATOM"
        group-by="(@STATE, 'UNKNOWN')[1]" >
        <xsl:text> **    </xsl:text>
        <xsl:value-of select="current-grouping-key()"/>
        <xsl:text>
</xsl:text>
        <xsl:for-each select="current-group()">
          <xsl:sort select="NAME"/>
          <xsl:value-of select="NAME"/>
          <xsl:text>
</xsl:text>
        </xsl:for-each>
        <xsl:text>
</xsl:text>
      </xsl:for-each-group>
    </xsl:template>
</xsl:stylesheet>

And the wanted, correct result is produced:

 **    UNKNOWN
Actinium
Aluminum
Americium
Antimony
Arsenic
Astatine
Barium
Berkelium
Beryllium
Bohrium
Boron
Bromine
Cadmium
Calcium
Californium
Cerium
Cesium
Chlorine
Chromium
Cobalt
Copper
Curium
Dubnium
Dysprosium
Einsteinium
Erbium
Europium
Fermium
Fluorine
Francium
Gadolinium
Gallium
Germanium
Hafnium
Hassium
Holmium
Indium
Iodine
Iridium
Iron
Krypton
Lanthanum
Lawrencium
Lead
Lithium
Lutetium
Magnesium
Manganese
Meitnerium
Mendelevium
Mercury
Molybdenum
Neodymium
Neon
Neptunium
Nickel
Niobium
Nitrogen
Nobelium
Osmium
Oxygen
Palladium
Phosphorus
Platinum
Plutonium
Polonium
Potassium
Praseodymium
Promethium
Protactinium
Radium
Radon
Rhenium
Rhodium
Rubidium
Ruthenium
Rutherfordium
Samarium
Scandium
Seaborgium
Selenium
Silicon
Silver
Sodium
Strontium
Sulfur
Tantalum
Technetium
Tellurium
Terbium
Thallium
Thorium
Thulium
Tin
Titanium
Tungsten
Uranium
Vanadium
Ytterbium
Yttrium
Zinc
Zirconium
ununbium
ununnilium
unununium

 **    GAS
Argon
Helium
Hydrogen
Xenon

 **    SOLID
Bismuth
Carbon
Gold

Upvotes: 2

Daniel Haley
Daniel Haley

Reputation: 52858

You could just do an xsl:for-each for all of the ATOM's that don't have a STATE attribute. You could delimit this with somthing like "OTHER".

This modified version of your stylesheet:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text"/>
  <xsl:template match="PERIODIC_TABLE">
    <xsl:for-each-group select="ATOM" group-by="@STATE">
      <xsl:text> **    </xsl:text>
      <xsl:value-of select="current-grouping-key()"/>
      <xsl:text>&#xA;</xsl:text>
      <xsl:for-each select="current-group()">
        <xsl:sort select="NAME" case-order="#default"/>
        <xsl:value-of select="NAME"/>
        <xsl:text>&#xA;</xsl:text>
      </xsl:for-each>
      <xsl:text>&#xA;</xsl:text>
    </xsl:for-each-group>
    <xsl:if test="ATOM[not(@STATE)]">
      <xsl:text> **    OTHER&#xA;</xsl:text>
      <xsl:for-each select="ATOM[not(@STATE)]">
        <xsl:sort select="NAME" case-order="#default"/>
        <xsl:value-of select="NAME"/>
        <xsl:text>&#xA;</xsl:text>      
      </xsl:for-each>      
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

produces the following output (in all its glory):

 **    GAS
Argon
Helium
Hydrogen
Xenon

 **    SOLID
Bismuth
Carbon
Gold

 **    OTHER
Actinium
Aluminum
Americium
Antimony
Arsenic
Astatine
Barium
Berkelium
Beryllium
Bohrium
Boron
Bromine
Cadmium
Calcium
Californium
Cerium
Cesium
Chlorine
Chromium
Cobalt
Copper
Curium
Dubnium
Dysprosium
Einsteinium
Erbium
Europium
Fermium
Fluorine
Francium
Gadolinium
Gallium
Germanium
Hafnium
Hassium
Holmium
Indium
Iodine
Iridium
Iron
Krypton
Lanthanum
Lawrencium
Lead
Lithium
Lutetium
Magnesium
Manganese
Meitnerium
Mendelevium
Mercury
Molybdenum
Neodymium
Neon
Neptunium
Nickel
Niobium
Nitrogen
Nobelium
Osmium
Oxygen
Palladium
Phosphorus
Platinum
Plutonium
Polonium
Potassium
Praseodymium
Promethium
Protactinium
Radium
Radon
Rhenium
Rhodium
Rubidium
Ruthenium
Rutherfordium
Samarium
Scandium
Seaborgium
Selenium
Silicon
Silver
Sodium
Strontium
Sulfur
Tantalum
Technetium
Tellurium
Terbium
Thallium
Thorium
Thulium
Tin
Titanium
Tungsten
ununbium
ununnilium
unununium
Uranium
Vanadium
Ytterbium
Yttrium
Zinc
Zirconium

A couple of other things I did was to add case-order attributes to the the xsl:sort elements and I used &#xA; hex entity reference for newlines.

Another thing I usually do but didn't do here is use concat() instead of having a separate xsl:text for newlines/spaces.

Instead of this:

<xsl:value-of select="NAME"/>
<xsl:text>&#xA;</xsl:text>

you can do this:

<xsl:value-of select="concat(NAME,'&#xA;')"/>

Upvotes: 1

Related Questions