XSL - Attributen verwerken

Uit De Vliegende Brigade
(wijz) ← Oudere versie | Huidige versie (wijz) | Nieuwere versie → (wijz)
Naar navigatie springen Naar zoeken springen

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