XSL - Attributen verwerken

Uit De Vliegende Brigade
Naar navigatie springen Naar zoeken springen
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

Het probleem

Ik wil XML-code importeren in een database. Daarbij komen alle eind-nodes overeen met kolomnamen. Daarvoor moeten tags uniek zijn. Dat heb ik hier geregeld. Nu het volgende probleem: Wat doe ik met attributen? → Die wil ik omfietsen naar aparte tags, om ze op dezelfde manier te kunnen importeren als de andere tags --

Maar niet altijd: Er zijn twee situaties:

Testdata

<?xml version="1.0"?>
<Items>
	<Item code="00000001" type="S">
		<Description>Dinges_01</Description>
		<FreeFields>
  			<FreeTexts>
				<FreeText number="1"/>
				<FreeText number="2">Veld_02</FreeText>
				<FreeText number="3">Veld_03</FreeText>
				<FreeText number="4"/>
			</FreeTexts>
		</FreeFields>
	</Item>
	<Item code="17900080" type="S">
		<Description>Widget_02</Description>
		<FreeFields>
  			<FreeTexts>
				<FreeText number="1"/>
				<FreeText number="2">Field_02</FreeText>
				<FreeText number="3">Field_03</FreeText>
				<FreeText number="4"/>
			</FreeTexts>
		</FreeFields>
	</Item>
</Items>

Situatie (1): Attribuutnaam wordt nieuw element

Concreter: Ik wil een tag-met-attributen zoals

<Item code="00000001" type="S">

transformeren naar

<item>
   <item_code>00000001</item_code>
   <item_type>S</item_type>

Situatie (2): Attribuutwaarde wordt elementnaam

Data zoals

<FreeText number="1"/>
<FreeText number="2">Veld_02</FreeText>
<FreeText number="3">Veld_03</FreeText>
<FreeText number="4"/>

moet worden:

<FreeText_number_01/>
<FreeText_number_02>Veld_02</FreeText_number_02>
<FreeText_number_03>Veld_03</FreeText_number_03>
<FreeText_number_04/>

De oplossing in de vorige paragraaf geeft hier wat onhandige resultaten:

<FreeText><FreeText_number>1</FreeText_number></FreeText>
<FreeText><FreeText_number>2</FreeText_number>Veld_02</FreeText>
<FreeText><FreeText_number>3</FreeText_number>Veld_03</FreeText>
<FreeText><FreeText_number>4</FreeText_number></FreeText>

Terminologie

Het is nogal lastig denken over een probleem, als je het niet eens kunt benoemen. Hierbij wat terminologie, onderhevig aan nieuwe inzichten:

In:

<Item code="00000001" type="S" searchcode="00000001">
   <Description>Widget</Description>
</item>

Hebben de onderdelen deze namen:

* <item>, <description> - Dit zijn tags
* <description>         - Eindnode
* code="00000001"       - Attribuut
* code                  - Attribuutnaam
* 00000001              - Attribuutwaarde.

Match op tagnaam

Je kunt wellicht op verschillende manieren te werk gaan:

  • Match op tag-naam
  • Match op attribuutnaam
  • Match op attribuutwaarde.

Poging (1) - Rest vd. node ontbreekt

Leuk beginnetje:

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

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

<xsl:template match="Item">

	<xsl:copy>
		<item_type>
			<xsl:value-of select="@type"/>
		</item_type>
	</xsl:copy>
</xsl:template>			


</xsl:stylesheet>

Probleem: Item-nodes worden nu compleet vervangen door alleen maar het veld item_type. De rest van de item-nodes worden niet opgenomen in de uitvoer

Poging (2) - Rest is er, maar rare volgorde

Nu wordt de rest van de node ook meegenomen, alleen staan de item_type-tags nu onderaan de nodes (valt op zich mee te leven):

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

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

<xsl:template match="Item">

	<xsl:copy>
		<xsl:apply-templates select="@*|node()"/>
		<item_type>
			<xsl:value-of select="@type"/>
		</item_type>
	</xsl:copy>
</xsl:template>			


</xsl:stylesheet>

De statements tav. attribuut en rest-van-de-node kun je helaas niet omdraaien. Dat krijg je de foutmelding:

Attribute nodes must be added before any child nodes to an element.

Poging (3) - Alle attributen + rare volgorde

Je kunt bij deze aanpak alle attributen van een tag in één XSL-script behandelen. Dat maakt het leven iets overzichtelijker:

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

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

<xsl:template match="Item">

	<xsl:copy>
		<xsl:apply-templates select="@*|node()"/>
		<item_type>
			<xsl:value-of select="@type"/>
		</item_type>

		<item_code>
			<xsl:value-of select="@code"/>
		</item_code>

	</xsl:copy>
</xsl:template>			


</xsl:stylesheet>

Match op attribuutnaam

Je verwijst naar de naam van een attribuut middels @. Zie voorbeelden hierboven.

Misschien is dit leuker bedacht dan gedaan, want dit werkt in ieder geval niet:

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

<!-- ======================= -->
<!-- Identity transformation -->
<!-- ======================= -->

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


<!-- ====================== -->
<!-- Match op attribuutnaam -->
<!-- ====================== -->

<xsl:template match="@code">

	<xsl:copy>
		<xsl:apply-templates select="@*|node()"/>
		<item_code>
			<xsl:value-of select="@code"/>
		</item_code>

	</xsl:copy>
</xsl:template>			

</xsl:stylesheet>

Match op attribuutwaarde

?

Alle attributen transformeren naar subelementen

Indrukwekkend:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:template match="node()">
      <xsl:copy>
         <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
   </xsl:template>

   <xsl:template match="@*">
      <xsl:element name="{name(..)}_{name()}">
         <xsl:value-of select="." />
      </xsl:element>
   </xsl:template>
</xsl:stylesheet>

Casus: Attributen & subnotes (lente 2018)

De data aan het begin van dit hoofdstuk wordt nu iets ingewikkelder: Het gaat om de items ItemCategory:

<?xml version="1.0"?>
<Items>
   <Item code="00000001" type="S">
      <Description>Dinges_01</Description>
      <FreeFields>
           <FreeTexts>
            <FreeText number="1"/>
            <FreeText number="2">Veld_02</FreeText>
            <FreeText number="3">Veld_03</FreeText>
            <FreeText number="4"/>
         </FreeTexts>
      </FreeFields>

      <ItemCategory number="9" code="">
           <Description/>
      </ItemCategory>
      <ItemCategory number="10" code="2">
           <Description>Bezig</Description>
      </ItemCategory>

   </Item>

   <Item code="00000002" type="S">
      <Description>Widget_01</Description>
      <FreeFields>
           <FreeTexts>
            <FreeText number="1"/>
            <FreeText number="2">Field_02</FreeText>
            <FreeText number="3">Field_03</FreeText>
            <FreeText number="4"/>
         </FreeTexts>
      </FreeFields>

      <ItemCategory number="9" code="">
           <Description/>
      </ItemCategory>
      <ItemCategory number="10" code="2">
           <Description>Active</Description>
      </ItemCategory>

   </Item>   
</Items>

Wat hier bijzonder aan is:

  • ItemCategory-tags hebben twee atributen, die beiden geïncorporeerd moeten worden in de tagnaam
  • ItemCategory-tags hebben geen eigen waarde (itt. de eerdere situatie met de FreeText-velden)
  • ItemCategory-tags hebben subnodes Description die uniek gemaakt moeten worden
  • De code-attributen zijn leeg. Gaat dat wel goed?

Aanpak: Zo snel mogelijk omfietsen naar een patroon dat ik al ken en waar ik al een oplossing voor hem.

Eerste stap: Attributen → subnodes

De code hierboven om van alle attributen subnodes te maken, werkt hier prima:

<ItemCategory>
   <ItemCategory_number>9</ItemCategory_number>
   <ItemCategory_code/>
   <Description/>
</ItemCategory>

<ItemCategory>
   <ItemCategory_number>10</ItemCategory_number>
   <ItemCategory_code>2</ItemCategory_code>
   <Description>Active</Description>
</ItemCategory>

Tweede stap: Attributen opnemen in veldnamen

In de eerste node in de code hierboven:

  • '9' onderdeel uitmaken van de elementnaam
  • 'code'-veld: Subnode
  • 'description'-veld: Subnode.

Bv.:

<ItemCategory>
   <ItemCategory_number_9>
      <ItemCategory_number_9_code/>
      <ItemCategory_number_9_Description/>
   </ItemCategory_number>
</ItemCategory>

Resultaat:

<item_category_9>
   <ItemCategory_code/>
   <Description/>
</item_category_9>

<item_category_10>
   <ItemCategory_code>2</ItemCategory_code>
   <Description>Active</Description>
</item_category_10>

Derde stap: Veldnamen uniek maken

Bronnen