Copy value of node using reference into other node - xslt

I'm trying to copy the value of an XML node referenced by an identifier into another node of the graph.
The orignal file looks like this:
<Root>
<Object id="Id1">
<FileName>file.png</FileName>
</Object>
<Description>
<Content>
<Title>Nice Object</Title>
<ObjectReference>Id1</ObjectReference>
</Content>
</Description></Root>
In XSLT, I use a variable to identify the value of the reference node identifier.
<xsl:template match="Content">
<xsl:variable name="IdObject">
<xsl:value-of select="ObjectReference"/>
</xsl:variable>
<Out>
<Title>
<xsl:value-of select="Title"/>
</Title>
<FileName>
<xsl:value-of select="//Object[#id='$IdObject']/Filename"/>
</FileName>
</Out></xsl:template>
The value of 'FileName' is not copied. I select the wrong reference node, I think. I tried with 'Ancestor::' and 'Parent::'. That doesn't work either.
Do you have an idea?
Thanks
I would like to obtain the following result :
<Out>
<Name>Nice Object</Name>
<FileName>file.png</FileName>
</Out>

XSLT has a built-in key mechanism for resolving cross-references. 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:key name="obj" match="Object" use="#id" />
<xsl:template match="Content">
<Out>
<xsl:copy-of select="Title"/>
<xsl:copy-of select="key('obj', ObjectReference)/FileName"/>
</Out>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
applied to your input example, will return:
Result
<?xml version="1.0" encoding="UTF-8"?>
<Out>
<Title>Nice Object</Title>
<FileName>file.png</FileName>
</Out>
P.S. Your attempt did not work because:
You quoted the reference to the variable;
You used Filename instead of FileName.

Related

Why variable goes wrong as "document-node()" in sub xsl:template?

Looking directly at the example:
<?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="3.0">
<xsl:template match="/">
<xsl:variable name="v1" select="." as="document-node()"/>
<xsl:apply-templates select=".//name"/>
</xsl:template>
<xsl:template match="name">
<xsl:variable name="v2" select="." as="document-node()"/>
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
source xml:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item>
<name>John</name>
<age>21</age>
</item>
</root>
The question is that: variable v1 can be declarated well, but v2 has an error "Required item type of value of variable $v2 is document-node(); supplied expression (.) has item type element(Q{}name)".
I looked up the definition of document-node() in w3c xpath document, it says "document-node() matches any document node." 2.5.5.2 Matching an ItemType and an Item. I still can't understand why v2 is wrong with type document-node().
I have make a online test demo
Because it's not a document-node(), it's an element(). Use element() instead. <xsl:template match="/"> matches a document-node(), and <xsl:template match="name"> matches an element() node.

Is there a way to replace the for-each with apply-templates in an XSLT Transform?

Environment: XSLT 1.0
The transform will take each element in partOne section and lookup #field attribute in partTwo section using #find attribute and then output #value attribute.
I'm using a for-each loop and was wondering if apply-templates could work?
xml
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="file.xslt"?>
<xml>
<partOne>
<target field="hello"/>
<target field="world"/>
</partOne>
<partTwo>
<number input="2" find="hello" value="valone" />
<number input="2" find="world" value="valtwo" />
<number input="2" find="hello" value="valthree" />
<number input="2" find="world" value="valfour" />
</partTwo>
</xml>
xsl
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="/xml/partOne/target">
,<xsl:value-of select="#field"/>
<xsl:for-each select="/xml/partTwo/number[#find=current()/#field]">
,<xsl:value-of select="#value"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output:
,hello
,valone
,valthree
,world
,valtwo
,valfour
Well, it seems straight-forward to change
<xsl:for-each select="/xml/partTwo/number[#find=current()/#field]">
,<xsl:value-of select="#value"/>
</xsl:for-each>
to
<xsl:apply-templates select="/xml/partTwo/number[#find=current()/#field]"/>
with a template
<xsl:template match="partTwo/number">
,<xsl:value-of select="#value"/>
</xsl:template>
As your root template so far processes all elements you need to change it to
<xsl:template match="/">
<xsl:apply-templates select="xml/partOne"/>
</xsl:template>
to avoid processing the partTwo element(s) twice.
For the cross-reference you might want to use a key in both versions:
<xsl:key name="ref" match="partTwo/number" use="#find"/>
and then select="key('ref', #field)" instead of select="/xml/partTwo/number[#find=current()/#field]" for the apply-templates or for-each.

A sequence of more than one item is not allowed as the second argument of concat()

The below xsl works fine if I do not bring in the "other_location_postal_code" field, which is commented here.
This is because there are multiple records if I bring in that field.
How can I have this xsl evaluate each record so it would write this record twice, once for the one "other location postal code" and the other?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:e="http://www.taleo.com/ws/tee800/2009/01" xmlns:fct="http://www.taleo.com/xsl_functions" exclude-result-prefixes="e fct">
<xsl:output method="xml" encoding="UTF-8" omit-xml-declaration="no"/>
<xsl:param name="OUTBOUND_FOLDER"/>
<xsl:template match="/">
<source>
<xsl:apply-templates select="//e:Requisition"/>
</source>
</xsl:template>
<xsl:template match="e:Requisition">
<xsl:variable name="job_id" select="e:ContestNumber"/>
<xsl:variable name="other_location_postal_code" select="e:JobInformation/e:JobInformation/e:OtherLocations/e:Location/e:NetworkLocation/e:NetworkLocation/e:ZipCode"/>
<job>
<job_id>
<xsl:value-of select="concat('<','![CDATA[',$job_id,']]','>')"/>
</job_id>
<other_location_postal_code>
<xsl:value-of select="concat('![CDATA[',$other_location_postal_code,']]')"/>
</other_location_postal_code>
</job>
</xsl:template>
</xsl:stylesheet>
I want it to come out like so:
<?xml version="1.0" encoding="UTF-8"?>
<source>
<job>
<job_id><![CDATA[15000005]]></job_id>
<other_location_postal_code><![CDATA[77382]]></other_location_postal_code>
</job>
<job>
<job_id><![CDATA[15000005]]></job_id>
<other_location_postal_code><![CDATA[37567]]></other_location_postal_code>
</job>
</source>
The initial XML looks like so:
<?xml version="1.0" encoding="UTF-8"?>
-<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
-<soapenv:Body>
-<ns1:getDocumentByKeyResponse xmlns:ns1="http://www.taleo.com/ws/integration/toolkit/2005/07" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
-<Document xmlns="http://www.taleo.com/ws/integration/toolkit/2005/07">
-<Attributes>
<Attribute name="count">1</Attribute>
<Attribute name="duration">0:00:00.088</Attribute>
<Attribute name="entity">Requisition</Attribute>
<Attribute name="mode">T-XML</Attribute>
<Attribute name="version">http://www.taleo.com/ws/tee800/2009/01</Attribute>
</Attributes>
-<Content>
-<ExportTXML xmlns="http://www.taleo.com/ws/integration/toolkit/2005/07" xmlns:e="http://www.taleo.com/ws/tee800/2009/01">
-<e:Requisition>
<e:ContestNumber>15000005</e:ContestNumber>
-<e:JobInformation>
-<e:JobInformation>
-<e:OtherLocations>
-<e:Location>
<e:ZipCode>77002</e:ZipCode>
</e:Location>
-<e:Location>
<e:ZipCode>77050</e:ZipCode>
</e:Location>
</e:OtherLocations>
</e:JobInformation>
</e:JobInformation>
</e:Requisition>
</ExportTXML>
</Content>
</Document>
</ns1:getDocumentByKeyResponse>
</soapenv:Body>
</soapenv:Envelope>
The error message simply says that an argument to the concat function is a sequence of more than one node. So some of your variable selects two or more nodes and concat expects as single node for each of its arguments. You will need to decide what you want to output, either only the first node with $var[1] or the concatenation with string-join($var, ' ').
Note that you don't need all those attempts to output CDATA section, you can tell the XSLT processor the cdata-section-elements on the xsl:output direction.
As you have now posted more details here is one suggestion to map each ZipCode element to job element:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:e="http://www.taleo.com/ws/tee800/2009/01"
exclude-result-prefixes="e">
<xsl:output indent="yes" cdata-section-elements="job_id other_location_postal_code"/>
<xsl:template match="/">
<source>
<xsl:apply-templates select="//e:OtherLocations/e:Location/e:ZipCode"/>
</source>
</xsl:template>
<xsl:template match="e:ZipCode">
<xsl:apply-templates select="ancestor::e:Requisition">
<xsl:with-param name="zc" select="current()"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="e:Requisition">
<xsl:param name="zc"/>
<job>
<job_id><xsl:value-of select="e:ContestNumber"/></job_id>
<other_location_postal_code><xsl:value-of select="$zc"/></other_location_postal_code>
</job>
</xsl:template>
</xsl:stylesheet>

XPath relative path in expression

I am in 'group' node. From it, I want to find such 'item' node, that has 'id' attribute equals to current's 'group' node 'ref_item_id' attribute value. So in my case, by being in 'group' node B, I want 'item' node A as output. This works:
<xsl:value-of select="preceding-sibling::item[#id='1']/#description"/>
But this doesn't (gives nothing):
<xsl:value-of select="preceding-sibling::item[#id=#ref_item_id]/#description"/>
When I type:
<xsl:value-of select="#ref_item_id"/>
I have '1' as result. So this attribute is for sure accessible, but I can't find path to it from XPath expression above. I tried many '../' combinations, but couldn't get it work.
Code to test: http://www.xmlplayground.com/7l42fo
Full XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item description="A" id="1"/>
<item description="C" id="2"/>
<group description="B" ref_item_id="1"/>
</root>
Full XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="no"/>
<xsl:template match="root">
<xsl:for-each select="group">
<xsl:value-of select="preceding-sibling::item[#id=#ref_item_id]/#description"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This has to do with context. As soon as you enter a predicate, the context becomes the node currently being filtered by the predicate, and no longer the node matched by the template.
You have two options - use a variable to cache the outer scope data and reference that variable in your predicate
<xsl:variable name='ref_item_id' select='#ref_item_id' />
<xsl:value-of select="preceding-sibling::item[#id=$ref_item_id]/#description"/>
or make use of the current() function
<xsl:value-of select="preceding-sibling::item[#id=current()/#ref_item_id]/#description"/>
Your expression searches for an item whose id attribute matches its own ref_item_id. You need to capture the current ref_item_id in an xsl:variable and refer to that xsl:variable in the expression.
One more possible solution using xsl:key
<xsl:key name="kItemId" match="item" use="#id" />
<xsl:template match="root">
<xsl:for-each select="group">
<xsl:value-of select="key('kItemId', #ref_item_id)[1]/#description"/>
</xsl:for-each>
</xsl:template>
Looking at the XML, if I assume that you have <item> and <group> as siblings and in any order.
Then a sample input XML would look like the following.
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item description="A" id="1"/>
<item description="C" id="2"/>
<group description="B" ref_item_id="1"/>
<item description="D" id="1"/>
<group description="E" ref_item_id="2"/>
</root>
Now, if the goal is to extract the description of all the <item> nodes whose id is matching with corresponding <group> *nodes ref_item_id*. Then we can simply loop over only such <item> nodes and get their description.
<xsl:output method="text" indent="no"/>
<xsl:template match="root">
<xsl:for-each select="//item[(./#id=following-sibling::group/#ref_item_id) or (./#id=preceding-sibling::group/#ref_item_id)]">
<xsl:value-of select="./#description"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Since you say that nodes are having unique id and all nodes are placed before nodes.
I would recommend you to use the following XSL and loop over specific nodes instead of nodes.
<xsl:output method="text" indent="no"/>
<xsl:template match="root">
<xsl:for-each select="//item[./#id=following-sibling::group/#ref_item_id]">
<xsl:value-of select="./#description"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

XSLT 1.0 text nodes printing by default

I have looked at XSL xsl:template match="/" but the match pattern that triggered my question is not mentioned there.
I have a rather complex XML structure:
<?xml version="1.0" encoding="UTF-8"?>
<MATERIAL_DATA>
<LOG>
<USER>Peter</USER>
<DATE>2011-02-18</DATE>
<MATERIALS>
<item>
<MATNR>636207</MATNR>
<TEXTS>
<item>
<TEXT>granola bar 40gx24</TEXT>
</item>
</TEXTS>
<PRICES>
<item>
<MATNR>636207</MATNR>
<COST>125.78</COST>
</item>
</PRICES>
<SALESPRICES>
<item>
<B01>
<MATNR>636207</MATNR>
<CURR>CZK</CURR>
<DATBI>9999-12-31</DATBI>
<DATAB>2010-10-05</DATAB>
</B01>
<B02>
<item>
<PRICE>477.60</PRICE>
<KUNNR>234567</KUNNR>
</item>
</B02>
</item>
</SALESPRICES>
</item>
</MATERIALS>
</LOG>
</MATERIAL_DATA>
Now if I apply the following XSLT, my output looks correct:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
<xsl:template match="node() | #*">
<xsl:apply-templates select="* | #*" />
</xsl:template>
<xsl:template match="B02">
<xsl:element name="Mi">
<xsl:value-of select="item/KUNNR"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
I get the output:
<?xml version="1.0" encoding="UTF-8"?>
<Mi>234567</Mi>
But if I apply the XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
<xsl:template match="/*">
<xsl:element name="MenuItems">
<xsl:apply-templates select="LOG/MATERIALS/item/SALESPRICES/item"/>
</xsl:element>
</xsl:template>
<xsl:template match="B02">
<xsl:element name="Mi">
<xsl:value-of select="item/KUNNR"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
the output looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<MenuItems>
636207
CZK
9999-12-31
2010-10-05
<Mi>234567</Mi>
</MenuItems>
All values from the element <B01> are in the output! But why - I am not matching <B01>!?
How does
<xsl:template match="node() | #*">
<xsl:apply-templates select="* | #*" />
</xsl:template>
make the output come out correctly? All I am doing with this is match all nodes or attributes and apply-templates to everything or all attributes.
But in my opinion it should not make a difference to when I exactly match <B01>!
Why is this happening?
XSLT includes the following default templates (among others):
<!-- applies to both element nodes and the root node -->
<xsl:template match="*|/">
<xsl:apply-templates/>
</xsl:template>
<!-- copies values of text and attribute nodes through -->
<xsl:template match="text()|#*">
<xsl:value-of select="."/>
</xsl:template>
In your first stylesheet you're implicitly matching all text nodes with node(), thus overriding the default action. Then, in the B2 template, you output your target value and apply no further templates, which stops processing.
In the second stylesheet, you explicitly apply templates to all children of LOG/MATERIALS/item/SALESPRICES/item, causing the default templates to process the nodes you don't explicitly handle. Because you explicitly handle B2 without applying templates to its children, the default templates are never invoked for those nodes. But the default templates are applied to the children of B1.
Adding the following template to your second stylesheet would override the default action for text nodes:
<xsl:template match="text()|#*"></xsl:template>
With the following result:
<?xml version="1.0" encoding="UTF-8"?>
<MenuItems><Mi>234567</Mi></MenuItems>
More:
http://www.w3.org/TR/xslt#built-in-rule
Looks like you are running into the built in template rules.
Specifically the text rule - this will copy text nodes if not overridden.