I'm not able to figure out a way to attach an attribute node to output node in below scenario..
input xml:
<record>
<user>
<field name="LastName">user33</field>
<field name="FirstName">user33</field>
<field name="Title"/>
<field name="Email">user33#gmail.com</field>
<field name="WorkPhone"/>
<field name="Fax"/>
<field name="Description">new user</field>
<field name="Group Member"> group1</field>
</user>
</record>
Expected output:
<add class="user" id-val="user33 user33" >
<add-value attr="LastName">
<value type="string">user33</value>
</add-attr>
<add-value attr="FirstName">
<value type="string">user33</value>
</add-value>
<add-value attr="Email">
<value type="string">user33#gamil.com</value>
</add-value>
<add-value attr="Description">
<value type="string">new user</value>
</add-value>
</add>
this is the snippet of xslt that i have so far.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:variable name="lessThan" select="'<'"/>
<xsl:variable name="GreaterThan" select="'>'"/>
<xsl:template match="user">
<xsl:variable name="temp1" select="concat(field[#name=$srcdn-field-name1],' ')"/>
<xsl:variable name="temp2" select="concat($temp1,field[#name=$srcdn-field-name2])"/>
<xsl:variable name="src" select="translate($temp2,'+=,.\','-----')"/>
<xsl:value-of disable-output-escaping="yes" select="$lessThan"/>
<xsl:text>add</xsl:text>
<xsl:value-of disable-output-escaping="yes" select="$GreaterThan"/>
<!-- it is required to add attribute id-val to element <add> with value of $src-->
<xsl:for-each select="field[string()]">
<xsl:variable name="fieldValue" select="normalize-space(.)"/>
<xsl:choose>
<xsl:when test="#name !='Group Member'">
<add-value attr="{#name}">
<value type="string">
<xsl:value-of select="$fieldValue"/>
</value>
</add-value>
</xsl:when>
<xsl:otherwise>
<xsl:value-of disable-output-escaping="yes" select="$lessThan"/>
<xsl:text>/add</xsl:text>
<xsl:value-of disable-output-escaping="yes" select="$GreaterThan"/>
<!--perform some other operations-->
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Now my requirement is to have id-val and class as attributes of <add>.. In this context the <xsl:attribute> isn't working. What changes do i need to make to my xslt.?
You can't add an attribute to something that's not an element. That's one of the many reasons not to try to manually construct start and end tags. Try this:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="user">
<xsl:variable name="temp1" select="concat(field[#name=$srcdn-field-name1],' ')"/>
<xsl:variable name="temp2" select="concat($temp1,field[#name=$srcdn-field-name2])"/>
<xsl:variable name="src" select="translate($temp2,'+=,.\','-----')"/>
<add class="user" id-val="{$src}">
<!-- it is required to add attribute id-val to element <add> with value of $src-->
<xsl:for-each select="field[string()]
[not((. | preceding-sibling::field)
[#name = 'Group Member'])]">
<add-value attr="{#name}">
<value type="string">
<xsl:value-of select="normalize-space()"/>
</value>
</add-value>
</xsl:for-each>
</add>
</xsl:template>
</xsl:stylesheet>
Related
I have an XML which needs to be formatted to a CSV. I have chosen XSL to do achieve this. In addition to the XML file, I have a Properties file which needs to be looked through for getting the values for the variables defined in the XML.
Can any one help me in how to do the lookups via an XSL using an external csv file?
CSV file after transformation:
Article.AclFlag|%field.Article.AclFlag.name|false|true|true|||||||||||master-data|%category.MasterData|Integer||0|||Enum.Acls|%enum.Acls.name|%enum.Acls.entry.0;%enum.Acls.entry.1;%enum.Acls.entry.2;%enum.Acls.entry.3;%enum.Acls.entry.4;%enum.Acls.entry.5;%enum.Acls.entry.6;|0;1;2;3;4;5;6;|0;1;2;3;4;5;6;|Article|%entity.Article.name|Product2G Variant
XSL
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:output method="text" omit-xml-declaration="yes" indent="no"/>
<xsl:key name="kEntity" match="entity-type" use="#identifier" />
<xsl:key name="kCategory" match="category" use="#identifier" />
<xsl:key name="kFieldID" match="field-type" use="#identifier" />
<xsl:key name="kEnumID" match="enum" use="#identifier" />
<xsl:key name="k1" match="entry" use="#key"/>
<xsl:variable name="map-doc" select="document('../transform/Properties.properties')"/>
<xsl:template match="/">
<xsl:apply-templates select="repository/custom/entity/field"/>
</xsl:template>
<xsl:template match="field">
<xsl:copy>
<field>
<xsl:value-of select="#identifier"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="name"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="editable"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="visible"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="visible-from-top"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="max-length"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="exportPurpose"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="importPurpose"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="upper-bound"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="average-length"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="active"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="multiline"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="display-by-default"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="richtext"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="value"/>
<xsl:text>|</xsl:text>
</field>
<category-ref-name>
<xsl:value-of select="key('kCategory', #category-ref)/#identifier"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="key('kCategory', #category-ref)/name"/>
<xsl:text>|</xsl:text>
</category-ref-name>
<field-type-ref>
<xsl:value-of select="substring-after(key('kFieldID', #field-type-ref)/persistence-class-name, 'java.lang.')"/>
<xsl:text>|</xsl:text>
</field-type-ref>
<proxy-entity-ref>
<xsl:value-of select="key('kFieldID', #field-type-ref)/#proxy-ref"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="key('kFieldID', #field-type-ref)/lower-bound"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="key('kFieldID', #field-type-ref)/range-min"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="key('kFieldID', #field-type-ref)/range-max"/>
<xsl:text>|</xsl:text>
</proxy-entity-ref>
<enum-ref-name>
<xsl:value-of select="key('kEnumID', #enum-ref)/#identifier"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="key('kEnumID', #enum-ref)/name"/>
<xsl:text>|</xsl:text>
<xsl:for-each select="key('kEnumID', #enum-ref)/entry">
<xsl:value-of select="#label"/>
<xsl:text>;</xsl:text>
</xsl:for-each>
<xsl:text>|</xsl:text>
<xsl:for-each select="key('kEnumID', #enum-ref)/entry">
<xsl:value-of select="#external-code"/>
<xsl:text>;</xsl:text>
</xsl:for-each>
<xsl:text>|</xsl:text>
<xsl:for-each select="key('kEnumID', #enum-ref)/entry">
<xsl:value-of select="#key"/>
<xsl:text>;</xsl:text>
</xsl:for-each>
<xsl:text>|</xsl:text>
</enum-ref-name>
<entity>
<xsl:value-of select="../#identifier"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="../name"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="../#parentEntities-ref"/>
</entity>
</xsl:copy>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
Properties
field.Article.AclFlag.name= Object right type
category.MasterData= Header data
enum.Acls.name=Object right types
The csv file is also not formatted well.
Sample XML
<?xml version="1.0" encoding="UTF-8"?>
<repository>
<types>
<entity-type identifier="ArticleType" identifying-field-type-ref="ArticleType.Id">
<object-name>article</object-name>
<class-name>ArticleType</class-name>
<persistence-xpath>/Article</persistence-xpath>
<persistence-class-name>db.model.Article</persistence-class-name>
<lower-bound>1</lower-bound>
<upper-bound>1</upper-bound>
<field-type identifier="ArticleType.AclFlag">
<object-name>aclFlag</object-name>
<class-name>commons.AclFlags</class-name>
<persistence-xpath>/aclFlag</persistence-xpath>
<persistence-class-name>java.lang.Integer</persistence-class-name>
<fragment-column-access>Article.AclFlag</fragment-column-access>
<internal>true</internal>
<lower-bound>0</lower-bound>
<range-min></range-min>
<range-max></range-max>
<min-length>0</min-length>
</field-type>
</entity-type>
</types>
<custom>
<category identifier="master-data" order="1">
<name>%category.MasterData</name>
</category>
<enum identifier="Enum.Acls">
<name>%enum.Acls.name</name>
<description>%enum.Acls.description</description>
<class-name>com.heiler.ppm.repository.enumerations.StdEnumProvider</class-name>
<key-class-name>commons.AclFlags</key-class-name>
<entry label="%enum.Acls.entry.0" external-code="0" key="0"/>
<entry label="%enum.Acls.entry.1" external-code="1" key="1"/>
<entry label="%enum.Acls.entry.2" external-code="2" key="2"/>
<entry label="%enum.Acls.entry.3" external-code="3" key="3"/>
<entry label="%enum.Acls.entry.4" external-code="4" key="4"/>
<entry label="%enum.Acls.entry.5" external-code="5" key="5"/>
<entry label="%enum.Acls.entry.6" external-code="6" key="6"/>
</enum>
<entity entity-type-ref="ArticleType" identifier="Article" parentEntities-ref="Product2G Variant">
<name>%entity.Article.name</name>
<description>%entity.Article.description</description>
<label-pattern-short>{Article.SupplierAID}</label-pattern-short>
<label-pattern-long>{Article.SupplierAID} - {ArticleLang.DescriptionShort}</label-pattern-long>
<label-pattern-description>{ArticleLang.DescriptionLong}</label-pattern-description>
<field identifier="Article.AclFlag" category-ref="master-data" enum-ref="Enum.Acls" field-type-ref="ArticleType.AclFlag">
<name>%field.Article.AclFlag.name</name>
<description>%field.Article.AclFlag.description</description>
<editable>false</editable>
<visible>true</visible>
<visible-from-top>true</visible-from-top>
<help-context></help-context>
<mergeable>false</mergeable>
</field>
</custom>
</repository>
Sample Properties in XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>Default</comment>
<entry key="field.Article.AclFlag.name">Object right type</entry>
<entry key="category.MasterData">Header data</entry>
<entry key="enum.Acls.name">Object right types</entry>
</properties>
Any help is much appreciated.
Given that your properties are of this form
<entry key="field.Article.AclFlag.name">Object right type</entry>
You can define a key to look up entry elements by their key attribute, and I see such a key already exists in your XSLT
<xsl:key name="k1" match="entry" use="#key"/>
Next, ensure you have a variable reference to your external XML document
<xsl:variable name="map-doc" select="document('../transform/Properties.properties.xml')"/>
Then, to look up a value from the properties you can do this (obviously replacing the second argument with the actual value you want to look up):
<xsl:variable name="test" select="'enum.Acls.name'" />
<xsl:value-of select="key('k1', $test, $map-doc)" />
Note this form of the key function, with a third parameter, is only valid in XSLT 2.0. If you were to do <xsl:value-of select="key('k1', $test) />, it would look for the value in the input XML, not your properties XML.
In XSLT 1.0 you could do this to change the document context for the key
<xsl:variable name="test" select="'enum.Acls.name'" />
<xsl:for-each select="$map-doc">
<xsl:value-of select="key('k1', $test)" />
</xsl:for-each>
Alternatively, do it without a key
<xsl:variable name="test" select="'enum.Acls.name'" />
<xsl:value-of select="$map-doc//entry[#key=$test]" />
I would like to convert xml file format to another format;
using XSL version 1.0 or 2.0.
Input XML file:
<ROOT_XML>
<Struct id="_6" name="Result" context="_1" members="_9 _10 _11 _13 _14 "/>
<FundamentalType id="_7" name="int" size="32" align="32"/>
<FundamentalType id="_8" name="float" size="32" align="32"/>
<Field id="_9" name="angle" type="_8" offset="0" context="_6"/>
<Field id="_10" name="row" type="_7" offset="32" context="_6"/>
<Field id="_11" name="cloth" type="_18" offset="96" context="_6"/>
<Destructor id="_13" name="EmptyClass" artificial="1" throw="" context="_6">
</Destructor>
<Constructor id="_14" name="Result" context="_6">
<Argument type="_20" location="f0:2" file="f0" line="2"/>
</Constructor>
<Constructor id="_15" name="Result" context="_6"/>
<FundamentalType id="_17" name="unsigned int" size="32" align="32"/>
<ArrayType id="_18" min="0" max="29u" type="_21" size="240" align="8"/>
<ReferenceType id="_19" type="_6" size="32" align="32"/>
<ReferenceType id="_20" type="_6c" size="32" align="32"/>
<FundamentalType id="_21" name="char" size="8" align="8"/>
</ROOT_XML>
Output XML file:
<Struct Result>
<Fields>
<Field name="angle" type="float" size="32"/>
<Field name="row" type="int" size="32"/>
<Field name="cloth" type="char" size="240"/>
</Fields>
</Struct>
The is an example on how to parse the 'members' attribute list
http://www.w3.org/1999/XSL/Transform" version = "1.0">
<xsl:template match="/ROOT_XML/Struct">
<Struct><xsl:value-of select="name"/>
<xsl:choose>
<xsl:when test="boolean(./#members)">
<xsl:call-template name="tokenizeString">
<xsl:with-param name="list" select="./#members"/>
<xsl:with-param name="delimiter" select="' '"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</Struct>
</xsl:template>
<!--############################################################-->
<!--## Template to tokenize strings ##-->
<!--############################################################-->
<xsl:template name="tokenizeString">
<!--passed template parameter -->
<xsl:param name="list"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<member>
<!-- get everything in front of the first delimiter -->
<xsl:value-of select="substring-before($list,$delimiter)"/>
</member>
<xsl:call-template name="tokenizeString">
<!-- store anything left in another variable -->
<xsl:with-param name="list" select="substring-after($list,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="$list = ''">
<xsl:text/>
</xsl:when>
<xsl:otherwise>
<member>
<xsl:value-of select="$list"/>
</member>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
This code is a starting point to extract the relevant id's from the 'members' attribute of the 'Struct' node, and later be used to emit only the 'Field' nodes.
In addition, the output XML file might contains more than one 'Struct' node,
For Instance:
<Struct Result>
<Fields>
<Field name="angle" type="float" size="32"/>
<Field name="row" type="int" size="32"/>
<Field name="cloth" type="char" size="240"/>
</Fields>
</Struct>
<Struct Answer>
<Fields>
<Field name="direction" type="float" size="32"/>
<Field name="col" type="int" size="32"/>
<Field name="paper" type="char" size="232"/>
</Fields>
</Struct>
Thanks for the reply. still, I would like to emphasize the logic on how to get the xml output.
The xslt processor needs to parse the 'members' attribute of the Struct node.
The 'members' attribute is a list of Field's ids.
In the above example:
Only "members=_9 _10 _11" are Field nodes! and therefore they should be output as done by Michael previously.
The rest of the items in the list are omitted (i.e. members="_13 _14")
The combined code: (I need assistance to continue...)
http://www.w3.org/1999/XSL/Transform" version = "1.0">
<Struct><xsl:value-of select="name"/>
<xsl:choose>
<xsl:when test="boolean(./#members)">
<xsl:call-template name="tokenizeString">
<xsl:with-param name="list" select="./#members"/>
<xsl:with-param name="delimiter" select="' '"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</Struct>
</xsl:template>
<!--############################################################-->
<!--## Template to tokenize strings ##-->
<!--############################################################-->
<xsl:template name="tokenizeString">
<!--passed template parameter -->
<xsl:param name="list"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<member>
<!-- get everything in front of the first delimiter -->
<xsl:value-of select="substring-before($list,$delimiter)"/>
<!-- TODO: select holds the member's id...
Q: how should we continue from here?? -->
</member>
<xsl:call-template name="tokenizeString">
<!-- store anything left in another variable -->
<xsl:with-param name="list" select="substring-after($list,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="$list = ''">
<xsl:text/>
</xsl:when>
<xsl:otherwise>
<member>
<xsl:value-of select="$list"/>
<!-- TODO: select holds the member's id...
Q: how should we continue from here?? -->
</member>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="Field[key('f-type', #type)]">
<xsl:variable name="f-type" select="key('f-type', #type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$f-type/#size}"/>
</xsl:template>
<xsl:template match="Field[key('a-type', #type)]">
<xsl:variable name="a-type" select="key('a-type', #type)" />
<xsl:variable name="f-type" select="key('f-type', $a-type/#type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$a-type/#size}"/>
</xsl:template>
The following stylesheet:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="f-type" match="FundamentalType" use="#id" />
<xsl:key name="a-type" match="ArrayType" use="#id" />
<xsl:template match="ROOT_XML">
<Struct>
<xsl:apply-templates select="Field"/>
</Struct>
</xsl:template>
<xsl:template match="Field[key('f-type', #type)]">
<xsl:variable name="f-type" select="key('f-type', #type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$f-type/#size}"/>
</xsl:template>
<xsl:template match="Field[key('a-type', #type)]">
<xsl:variable name="a-type" select="key('a-type', #type)" />
<xsl:variable name="f-type" select="key('f-type', $a-type/#type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$a-type/#size}"/>
</xsl:template>
</xsl:stylesheet>
when applied to your example input, will return:
<?xml version="1.0" encoding="UTF-8"?>
<Struct>
<Field name="angle" type="float" size="32"/>
<Field name="row" type="int" size="32"/>
<Field name="cloth" type="char" size="240"/>
</Struct>
How this works:
If a Field's type matches an id of a FundamentalType, then
the type and the size values are looked up from the matching
FundamentalType;
If a Field's type matches an id of an ArrayType, then the size value is looked up from the matching ArrayType, while the type value is looked up from the FundamentalType whose id matches the type attribute of the ArrayType.
Edit:
If you want each Struct to include only Fields whose id is listed in its members attribute, you can do it this way:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="field" match="Field" use="#id" />
<xsl:key name="f-type" match="FundamentalType" use="#id" />
<xsl:key name="a-type" match="ArrayType" use="#id" />
<xsl:variable name="xml" select="/" />
<xsl:template match="/ROOT_XML">
<root>
<xsl:for-each select="Struct">
<Struct name="{#name}">
<xsl:apply-templates select="key('field', tokenize(#members, ' '))"/>
</Struct>
</xsl:for-each>
</root>
</xsl:template>
<xsl:template match="Field[key('f-type', #type)]">
<xsl:variable name="f-type" select="key('f-type', #type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$f-type/#size}"/>
</xsl:template>
<xsl:template match="Field[key('a-type', #type)]">
<xsl:variable name="a-type" select="key('a-type', #type)" />
<xsl:variable name="f-type" select="key('f-type', $a-type/#type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$a-type/#size}"/>
</xsl:template>
</xsl:stylesheet>
Edit 2
The same thing in XSLT 1.0:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="field" match="Field" use="#id" />
<xsl:key name="f-type" match="FundamentalType" use="#id" />
<xsl:key name="a-type" match="ArrayType" use="#id" />
<xsl:variable name="xml" select="/" />
<xsl:template match="/ROOT_XML">
<root>
<xsl:for-each select="Struct">
<xsl:variable name="members">
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="#members"/>
</xsl:call-template>
</xsl:variable>
<Struct name="{#name}">
<xsl:apply-templates select="key('field', exsl:node-set($members)/token)"/>
</Struct>
</xsl:for-each>
</root>
</xsl:template>
<xsl:template match="Field[key('f-type', #type)]">
<xsl:variable name="f-type" select="key('f-type', #type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$f-type/#size}"/>
</xsl:template>
<xsl:template match="Field[key('a-type', #type)]">
<xsl:variable name="a-type" select="key('a-type', #type)" />
<xsl:variable name="f-type" select="key('f-type', $a-type/#type)" />
<Field name="{#name}" type="{$f-type/#name}" size="{$a-type/#size}"/>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="text"/>
<xsl:param name="delimiter" select="' '"/>
<xsl:choose>
<xsl:when test="contains($text, $delimiter)">
<token>
<xsl:value-of select="substring-before($text, $delimiter)"/>
</token>
<!-- recursive call -->
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after($text, $delimiter)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<token>
<xsl:value-of select="$text"/>
</token>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I have this XML:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl">
<xsl:param name="navigation-xml">
<item id="home" title-en="Services" title-de="Leistungen" />
<item id="company" title-en="Company" title-de="Unternehmen" />
<item id="references" title-en="References" title-de="Referenzen" />
</xsl:param>
<xsl:param name="navigation" select="exsl:node-set($navigation-xml)/*" />
<xsl:param name="navigation-id" />
<xsl:template name="title">
<xsl:apply-templates select="$navigation" mode="title" />
</xsl:template>
<xsl:template match="item" mode="title">
<xsl:if test="$navigation-id = #id">
<xsl:choose>
<xsl:when test="$current-language = 'de'">
<xsl:value-of select="#title-de" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="#title-en" />
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
How can I refactor the last 12 lines, so that the attribute name (either #title-de or #title-en) gets determined dynamically rather than in the (silly) way I did it in?
Thanks for any help.
You could write
<xsl:template name="title">
<xsl:apply-templates select="$navigation" mode="title" />
</xsl:template>
<xsl:template match="item" mode="title">
<xsl:if test="$navigation-id = #id">
<xsl:choose>
<xsl:when test="$current-language = 'de'">
<xsl:value-of select="#title-de" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="#title-en" />
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
as
<xsl:template name="title">
<xsl:apply-templates select="$navigation[$navigation-id = #id]" mode="title" />
</xsl:template>
<xsl:template match="item" mode="title">
<xsl:value-of select="#*[local-name() = concat('title-', $current-language)]" />
</xsl:template>
IMHO, your problem starts much earlier. If you define your navigation-xml parameter as:
<xsl:param name="navigation-xml">
<item id="home">
<title lang="en">Services</title>
<title lang="de">Leistungen</title>
</item>
<item id="company">
<title lang="en">Company</title>
<title lang="de">Unternehmen</title>
</item>
<item id="references">
<title lang="en">References</title>
<title lang="de">Referenzen</title>
</item>
</xsl:param>
you will be able to address its individual nodes much more conveniently and elegantly.
I have a problem with field of solr. when multiple nodes does not fit, and it shows empty field.
<field name="specs"/>
the xml original:
<?xml version="1.0" encoding="UTF-8"?>
<tire xmlns="http://schema.grabber" xmlns:x="http://www.w3.org/1999/xhtml"
tire-type="2" product-type="tire" id="102694" trademark="dunlop"
season="3" width="130" height="70" wheels="12" load="62" speed="l"
host="norauto" model="d207" hostDetailId="102694" hostDbID="6">
<url>product/_102694.html</url>
<price>49.95</price>
<ecorate>1.15</ecorate>
<currency>€</currency>
<vat>true</vat>
<img>images_produits/650x650/dunlop-d207-runscoot.jpg</img>
<content>DUNLOP D207 RUNSCOOT</content>
<specs>
<spec name="b_xl">0</spec>
</specs>
</tire>
Transformation XSLT at XML solr, this is xslt of solr:
<xsl:template match="/">
<docs>
<xsl:apply-templates select="cb:tire|cb:products" />
</docs>
</xsl:template>
<xsl:template match="cb:tire">
<doc>
<xsl:apply-templates select="#*|*" />
</doc>
</xsl:template>
<xsl:template match="cb:products">
<xsl:apply-templates select="#*|*" />
</xsl:template>
<xsl:template match="*/*[#name and not(parent::cb:products)]">
<xsl:call-template name="field">
<xsl:with-param name="name" select="concat(name(),'_',#name)"/>
</xsl:call-template>
</xsl:template>
<xsl:template match="*/*[not(#name) and not(parent::cb:products)]">
<xsl:call-template name="field"/>
</xsl:teplate>
<xsl:template match="*[parent::cb:products]">
<xsl:choose>
<xsl:when test="not(text())">
<doc>
<xsl:apply-templates select="*|#*"/>
</doc>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="field"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="*|#*">
<xsl:call-template name="field">
<xsl:with-param name="value" select="."/>
</xsl:call-template>
</xsl:template>
<xsl:template name="field">
<xsl:param name="name" select="name()" />
<xsl:param name="value" select="text()" />
<field name="{translate(lower-case($name),' ','_')}">
<xsl:value-of select="$value" />
</field>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
The problem is the element <specs><spec name="b_xl">0</spec></specs>, the field is empty, the result not correct.
This is result XML:
<docs>
<doc>
<field name="tire-type">1</field>
<field name="product-type">tire</field>
<field name="season">1</field>
<field name="id">135-80-r13-70-t-kingstar-sk70</field>
<field name="trademark">kingstar</field>
<field name="model">sk70</field>
<field name="width">135</field>
<field name="height">80</field>
<field name="wheels">13</field>
<field name="load">70</field>
<field name="speed">t</field>
<field name="host">tires</field>
<field name="hostdetailid">135-80-r13-70-t-kingstar-sk70</field>
<field name="hostdbid">1000</field>
<field name="url">135-80-r13-70-t-kingstar-sk70.html</field>
<field name="price">29.73</field>
<field name="currency">€</field>
<field name="vat">true</field>
<field name="img">media/catalog/product/cache/1/image/9df78eab33525d08d6e5fb8d27136e95/0/1/0181050080001.png</field>
<field name="content">135/80 R13 70 T KINGSTAR SK70</field>
<field name="specs" />
</doc>
</docs>
I need to display the contents of the element if it contains specs.
To handle the children of specs as field you may add a template like this:
<xsl:template match="cb:specs" priority="1">
<xsl:apply-templates />
</xsl:template>
Which will generate following field:
<field name="spec_b_xl">0</field>
I do not know if this is as expected, because you didn't tell us who the output for specs should look like.
how show title attribute?
I would like to extract data from the #name.
<specs>
<spec name="b_homologation">1</spec>
<spec name="s_homologation_type">mo</spec>
<spec name="b_xl">0</spec>
<spec name="b_runflat">0</spec>
<spec name="s_consumption">e</spec>
<spec name="i_noise">72</spec>
<spec name="s_grip">c</spec>
</specs>
the result has to be:
<field name="b_homologation">1</field>
<field name="s_homologation_type">mo</field>
...
Thanks.
edit:
<xsl:template match="*|#*">
<xsl:call-template name="field">
<xsl:with-param name="value" select="."/>
</xsl:call-template>
</xsl:template>
<xsl:template name="field">
<xsl:param name="name" select="name()" />
<xsl:param name="value" select="text()" />
<field name="{$name}">
<xsl:value-of select="$value" />
</field>
</xsl:template>
And result is(not correct):
<field name="specs">1mo00e72c</field>
As I suggested in my comment, there's no need to mess around with named templates and parameters here, it's just a plain identity transform with tweaks:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!-- copy input to output verbatim ... -->
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()" /></xsl:copy>
</xsl:template>
<!-- ... except spec elements, whose name changes to field -->
<xsl:template match="spec">
<field><xsl:apply-templates select="#*|node()" /></field>
</xsl:template>
</xsl:stylesheet>
This will produce
<specs>
<field name="b_homologation">1</field>
<field name="s_homologation_type">mo</field>
<field name="b_xl">0</field>
<field name="b_runflat">0</field>
<field name="s_consumption">e</field>
<field name="i_noise">72</field>
<field name="s_grip">c</field>
</specs>
You can use the same trick if you want to rename the root specs element to something like fields, but you can't leave it out completely if you want your output to be well-formed XML.
something like this? Didn't test it, can include some mini-bugs :)
<xsl:template match="spec" >
<xsl:call-template name="outputToXml" >
<xsl:with-param name="name" select="#name" />
<xsl:with-param name="value" select="." />
</xsl:call-template>
</xsl:template>
<xsl:template name="outputToXml" >
<xsl:param name="name" />
<xsl:param name="value" />
<xsl:text><field name="</xsl:text>
<xsl:value-of select="$name" />
<xsl:text>"></xsl:text>
<xsl:value-of select="$value" />
<xsl:text></field></xsl:text>
</xsl:template>