XSL - Attributen verwerken
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
<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
- https://stackoverflow.com/questions/5332266/xslt-copy-attributes-from-child-element
- https://stackoverflow.com/questions/5941233/how-to-copy-specific-attribute-using-xslt
- https://stackoverflow.com/questions/45882396/in-xslt-how-to-copy-all-attributes-from-all-child-nodes-to-root-node-of-xml
- https://stackoverflow.com/questions/19016023/xslt-copy-attribute-into-a-new-element-in-the-child - Dit is precies mijn probleem
- http://dh.obdurodon.org/identity.xhtml