I have a node set and I'd like to use a predicate to select from it only those nodes which have a template's current node as their parent.
I'm using XSL 1.0 and msxsl.
In the following non-working code, the predicate [parent::current()] does not work as I want it to as current() is not a valid NodeTest:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:variable name="set" select="//node[#attr1 = 'bbb']"/>
<xsl:template name="TemplateA">
<xsl:for-each select="$set[parent::current()]">
<result-node>
<xsl:value-of select="#value"/>
<xsl:text>A</xsl:text>
</result-node>
</xsl:for-each>
</xsl:template>
<xsl:template name="TemplateB">
<xsl:for-each select="$set[parent::current()]">
<result-node>
<xsl:value-of select="#value"/>
<xsl:text>B</xsl:text>
</result-node>
</xsl:for-each>
</xsl:template>
<xsl:template match="/body">
<result>
<xsl:for-each select="parentNode">
<xsl:call-template name="TemplateA" />
<xsl:call-template name="TemplateB" />
</xsl:for-each>
</result>
</xsl:template>
</xsl:transform>
Here is a sample XML to feed the above:
<?xml version="1.0" encoding="UTF-8"?>
<body>
<parentNode>
<node attr1="aaa" value="1" />
<node attr1="bbb" value="2" />
</parentNode>
<parentNode>
<node attr1="aaa" value="3" />
<node attr1="bbb" value="4" />
</parentNode>
</body>
Here is the desired output:
<result>
<result-node>2A</result-node>
<result-node>2B</result-node>
<result-node>4A</result-node>
<result-node>4B</result-node>
</result>
The above example can also be found here: http://xsltransform.net/jyH9rMg
The way to compare nodes for identity in XSLT 1.0 is to use generate-id():
<xsl:for-each select="$set[generate-id(..) = generate-id(current())]">
.. is a shorthand for parent::node() and refers to the parent of the context node (the node being tested by this execution of the predicate).
Related
I have this XML document:
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<properties>
<property name="prop1" type="type1"/>
<property name="prop2" type="type2"/>
<property name="prop3" type="type3"/>
<property name="prop4" type="type1"/>
</properties>
<types>
<type name="type1" group="group1"/>
<type name="type2" group="group1"/>
<type name="type3" group="group2"/>
<type name="type4" group="group3"/>
</types>
<groups>
<group name="group1" owner="owner1"/>
<group name="group2" owner="owner2"/>
<group name="group3" owner="owner3"/>
</groups>
</metadata>
I am transforming it using this XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:variable name="docRoot" select="/" />
<xsl:for-each select="distinct-values($docRoot/metadata/properties/property/#type)">
<xsl:variable name="groupOwner" select="$docRoot/metadata/groups/group[#name=$docRoot/metadata/types/type[#name=current()]/#group]/#owner" />
<xsl:value-of select="$groupOwner"/><xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
What I want to do is print out the list of unique group owners for all of the properties in the document. I'm successfully filtering out duplicate types with distinct-values but can't see how to filter out duplicate owners.
The current output:
owner1
owner1
owner2
The required output:
owner1
owner2
If it helps no two groups have the same owner.
Keys can be your friend here...
<xsl:key name="types" match="type" use="#name" />
<xsl:key name="groups" match="group" use="#name" />
Then you can do this, without even any need for distinct-values because you won't get duplicate nodes returned this way:
<xsl:for-each select="key('groups', key('types', metadata/properties/property/#type)/#group)">
For example, try this XSLT
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:key name="types" match="type" use="#name" />
<xsl:key name="groups" match="group" use="#name" />
<xsl:template match="/">
<xsl:for-each select="key('groups', key('types', metadata/properties/property/#type)/#group)">
<xsl:value-of select="concat(#owner, '
')" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
In fact, you can simplify the xsl:for-each to this:
<xsl:value-of select="key('groups', key('types', metadata/properties/property/#type)/#group)/#owner" separator="
" />
Ah, just needed to try a bit harder:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:variable name="docRoot" select="/" />
<xsl:for-each select="distinct-values($docRoot/metadata/groups/group[#name=$docRoot/metadata/types/type[#name=$docRoot/metadata/properties/property/#type]/#group]/#owner)">
<xsl:value-of select="current()"/><xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Open to any suggestions for simplifying this though!
I am pretty much new to xsl.Currently facing a challenge in transforming the below sample xml to required output.
Sample XML:
<root>
<types>
<entity-type>
<field-type identifier="FieldType1" proxy-ref="TypeA">
<object-name>aclProxy</object-name>
<persistence-class-name>java.lang.Long</persistence-class-name>
</field-type>
<field-type identifier="FieldType2" proxy-ref="TypeB">
<object-name>aclName</object-name>
<persistence-class-name>java.lang.String</persistence-class-name>
</field-type>
</entity-type>
</types>
<custom>
<category identifier="parent" order="1">
<name>%category.parent</name>
</category>
<category identifier="texts" order="2">
<name>%category.Texts</name>
</category>
<field identifier="ArticleID" category-ref="parent" field-type-ref="FieldType1" proxy-entity-ref="ABC">
<name>%field.name</name>
<description>%field.Article.description</description>
</field>
<field identifier="ArticleName" category-ref="texts" field-type-ref="FieldType2" proxy-entity-ref="ABCD">
<name>%field.name.text</name>
<description>%field.Articletext.description</description>
</field>
</custom>
</root>
Output
<field identifier="ArticleID">
<name>%field.name</name>
<description>%field.Article.description</description>
<category-ref-name>%category.parent</category-ref-name>
<field-type-ref>Long</field-type-ref>
<proxy-entity-ref>TypeA</proxy-entity-ref>
</field>
<field identifier="ArticleName" >
<name>%field.name.text</name>
<description>%field.Articletext.description</description>
<category-ref-name>%category.Texts</category-ref-name>
<field-type-ref>String</field-type-ref>
<proxy-entity-ref>TypeB</proxy-entity-ref>
</field>
XSLT which I have been trying:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="category" match="category" use="#identifier" />
<xsl:key name="field" match="field" use="#category-ref" />
<xsl:apply-templates select="custom[not(key('field',category/#identifier))]"/>
<xsl:template match="/">
<xsl:text>something</xsl:text>
<xsl:value-of select="category/#identifier"/>
<xsl:apply-templates select="."/>
</xsl:template>
<xsl:template match="/">
<xsl:value-of select="field/#identifier"/>
<xsl:variable name="categoryparam" select="key('category', field/#category-ref)" />
<xsl:if test="$categoryparam">
<xsl:apply-templates select="$categoryparam"/>
<xsl:text>something</xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Basically the output should have only field(s) in it referring to the attributes and get the node values.
Any help is much appreciated.
Thanks in advance.
Let us start off with these 2 keys:
<xsl:key name="kCategory" match="category" use="#identifier" />
<xsl:key name="kFieldID" match="field-type" use="#identifier" />
you will need the values in category and field-type nodes later.
This template:
<xsl:template match="/">
<xsl:apply-templates select="root/custom/field"/>
</xsl:template>
will output the target field nodes
Having a template match with the field node to further manipuate the output:
<xsl:template match="field">
<xsl:copy>
<!-- copies the identifier attribute and all children -->
<xsl:copy-of select="#identifier|node()"/>
<category-ref-name>
<!-- gets the child name of the target category node
that matches the category-ref attribute -->
<xsl:value-of select="key('kCategory', #category-ref)/name"/>
</category-ref-name>
<field-type-ref>
<!-- gets the child persistence-class-name of the target
field-type node that matches the field-type-ref attribute,
with further substring manipulations -->
<xsl:value-of select="substring-after(key('kFieldID', #field-type-ref)/persistence-class-name, 'java.lang.')"/>
</field-type-ref>
<proxy-entity-ref>
<!-- gets the attribute proxy-ref of the target
field-type node that matches the field-type-ref
attribute -->
<xsl:value-of select="key('kFieldID', #field-type-ref)/#proxy-ref"/>
</proxy-entity-ref>
</xsl:copy>
</xsl:template>
The whole stylesheet is below:
<?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 indent="yes" omit-xml-declaration="yes"/>
<xsl:key name="kCategory" match="category" use="#identifier" />
<xsl:key name="kFieldID" match="field-type" use="#identifier" />
<xsl:template match="/">
<xsl:apply-templates select="root/custom/field"/>
</xsl:template>
<xsl:template match="field">
<xsl:copy>
<xsl:copy-of select="#identifier|node()"/>
<category-ref-name>
<xsl:value-of select="key('kCategory', #category-ref)/name"/>
</category-ref-name>
<field-type-ref>
<xsl:value-of select="substring-after(key('kFieldID', #field-type-ref)/persistence-class-name, 'java.lang.')"/>
</field-type-ref>
<proxy-entity-ref>
<xsl:value-of select="key('kFieldID', #field-type-ref)/#proxy-ref"/>
</proxy-entity-ref>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
See it in action (http://xsltfiddle.liberty-development.net/gWmuiJc).
May be my XSL approach is wrong? please correct me the way to handle this situation
I want to grab XPATHs and Attrs from a mapping file, then use XPATH to match, and apply attributes to XML.
Here is my 3 inputs files:
mappings.xml
<?xml version="1.0" encoding="UTF-8"?>
<mappings>
<map xpath="//title" class="title" others="moreToCome" />
<map xpath="//subtitle" class="subtitle" others="moreToCome" />
<map xpath="//p" class="p" others="moreToCome" />
</mappings>
Source.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<title>title text</title>
<subtitle>subtitle text</subtitle>
<p>subtitle text</p>
</root>
StyleMapping.xsl
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="fMappings" select="document('mappings.xml')" />
<xsl:variable name="xpath"><xsl:text>justToMakeItGlobal</xsl:text></xsl:variable>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
<!-- loop thru map in mappings.xml -->
<xsl:for-each select="$fMappings//mappings/map">
<xsl:call-template name="dyn">
<xsl:with-param name="xpath" select="#xpath" />
<xsl:with-param name="class" select="#class" />
<xsl:with-param name="others" select="#others" />
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template match="$xpath" mode="dyn">
<xsl:param name="xpath"/>
<xsl:param name="class"/>
<xsl:param name="others"/>
<xsl:attribute name="class"><xsl:value-of select="$class" /></xsl:attribute>
<xsl:attribute name="others"><xsl:value-of select="$others" /></xsl:attribute>
</xsl:template>
</xsl:stylesheet>
This is what is planning to do, but i'm not getting the correct way of doing it in XSLT:
1. Read mappings.xml file
2. Loop thru each map tag
3. grab xpath and attr's
4. apply template match/select with above xpath
5. add attr's to above selected nodes
output.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<title class="title" others="moreToCome">title text</title>
<subtitle class="subtitle" others="moreToCome">subtitle text</subtitle>
<p class="p" others="moreToCome">subtitle text</p>
</root>
I don't think you can have a template with a calculated match pattern. Let me suggest a different approach:
mappings.xml
<?xml version="1.0" encoding="UTF-8"?>
<mappings>
<map elem="title" class="Title" others="moreToCome1" />
<map elem="subtitle" class="Subtitle" others="moreToCome2" />
<map elem="p" class="P" others="moreToCome3" />
</mappings>
stylesheet
<?xml version="1.0" encoding="UTF-8"?>
<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:variable name="mappings" select="document('mappings.xml')/mappings" />
<xsl:template match="*">
<xsl:variable name="elem" select="name()" />
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:if test="$elem = $mappings/map/#elem">
<xsl:attribute name="class">
<xsl:value-of select="$mappings/map[#elem=$elem]/#class"/>
</xsl:attribute>
<xsl:attribute name="others">
<xsl:value-of select="$mappings/map[#elem=$elem]/#others"/>
</xsl:attribute>
</xsl:if>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Testing with the following source XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<title original="yes">title text</title>
<subtitle>subtitle text</subtitle>
<p>para text</p>
<nomatch attr="test">another text</nomatch>
</root>
results in:
<?xml version="1.0" encoding="utf-8"?>
<root>
<title original="yes" class="Title" others="moreToCome1">title text</title>
<subtitle class="Subtitle" others="moreToCome2">subtitle text</subtitle>
<p class="P" others="moreToCome3">para text</p>
<nomatch attr="test">another text</nomatch>
</root>
Below is my requirement. Can we do this using XSLT? I want to convert value of AttributeName as tag under policy and corresponding AttributeValue as value.
Input :
<Policy>
<Attributes>
<AttributeName>is_policy_loan</AttributeName>
<AttributeValue>Yes</AttributeValue>
</Attributes>
<Attributes>
<AttributeName>is_policy_owners</AttributeName>
<AttributeValue>Yes</AttributeValue>
</Attributes>
<Attributes>
<AttributeName>is_policy_twoyears</AttributeName>
<AttributeValue>Yes</AttributeValue>
</Attributes>
</Policy>
Output :
<Policy>
<is_policy_loan>Yes</is_policy_loan>
<is_policy_owners>Yes</is_policy_owners>
<is_policy_twoyears>Yes</is_policy_twoyears>
</Policy>
The following xsl file will do the job:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- create the <AttributeName>AttributeValue</..> nodes -->
<xsl:template match="//Attributes">
<xsl:variable name="name" select="AttributeName" />
<xsl:element name="{$name}">
<xsl:value-of select="AttributeValue" />
</xsl:element>
</xsl:template>
<!-- wrap nodes in a `Policy` node -->
<xsl:template match="/">
<Policy>
<xsl:apply-templates/>
</Policy>
</xsl:template>
</xsl:stylesheet>
The way i would do,
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" encoding="UTF-8" omit-xml-declaration="yes" />
<xsl:template match="Policy">
<xsl:element name="Policy">
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="Attributes">
<xsl:variable name="name" select="AttributeName" />
<xsl:element name="{$name}">
<xsl:value-of select="AttributeValue" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
output will be,
<Policy>
<is_policy_loan>Yes</is_policy_loan>
<is_policy_owners>Yes</is_policy_owners>
<is_policy_twoyears>Yes</is_policy_twoyears>
</Policy>
I need a little XSLT help. Couldn't figure out why the actual output is different from my expected output. Any help much appreciated!
XML
<?xml version="1.0"?>
<a>
<b c="d"/>
<b c="d"/>
<b c="d"/>
</a>
XSL
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="foo">
<xsl:param name="content"></xsl:param>
<xsl:value-of select="$content"></xsl:value-of>
</xsl:template>
<xsl:template match="/">
<xsl:call-template name="foo">
<xsl:with-param name="content">
<xsl:for-each select="a/b">
<e>
<xsl:value-of select="#c" />
</e>
</xsl:for-each>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
Actual Output
<?xml version="1.0"?>
ddd
Desired Output
<?xml version="1.0"?>
<e>d</e>
<e>d</e>
<e>d</e>
Note: Calling the template is mandatory. In my situation the template does more with extension functions.
Contrary to what ABach says, your xsl:param is fine. The only thing you need to change is your xsl:value-of. It should be a xsl:copy-of:
<xsl:template name="foo">
<xsl:param name="content"/>
<xsl:copy-of select="$content"/>
</xsl:template>
You're very close; you've just mixed up relative positioning and correct parameter usage within templates. Here's a slightly revised answer.
When this XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output omit-xml-declaration="no" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template name="foo">
<xsl:param name="pContent" />
<xsl:for-each select="$pContent">
<e>
<xsl:value-of select="#c" />
</e>
</xsl:for-each>
</xsl:template>
<xsl:template match="/*">
<xsl:call-template name="foo">
<xsl:with-param name="pContent" select="*" />
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
...is applied to the original XML:
<?xml version="1.0"?>
<a>
<b c="d" />
<b c="d" />
<b c="d" />
</a>
...the desired result is produced:
<?xml version="1.0"?>
<e>d</e>
<e>d</e>
<e>d</e>
In particular, notice the correct usage of <xsl:param> to include nodes based on their relative position. In your case, you are telling the XSLT parser to output the text values of the parameter that you're passing, rather than altering the nodes' contents in the way you want.