Select value of element which is referenced by other - xslt

I have (this is an example) the following xml:
<?xml version="1.0" encoding="UTF-8"?>
<body>
<list>
<toot id="1">
<value>A</value>
</toot>
<toot id="2">
<value>B</value>
</toot>
<toot id="3">
<value>C</value>
</toot>
<toot id="4">
<value>D</value>
</toot>
</list>
<otherlist>
<foo>
<value ref="2" />
</foo>
<foo>
<value ref="3" />
</foo>
</otherlist>
</body>
And the following XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/body">
<xsl:apply-templates select="otherlist"/>
</xsl:template>
<xsl:template match="otherlist">
<xsl:for-each select="foo">
<result>
<value><xsl:value-of select="/body/list/toot[#id=value/#ref]/value" /></value><!-- This is the important -->
<ref><xsl:value-of select="value/#ref" /></ref>
</result>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
And this is the result when it make/transform the xml:
<?xml version="1.0" encoding="UTF-8"?>
<result>
<value/>
<ref>2</ref>
</result>
<result>
<value/>
<ref>3</ref>
</result>
And the problem is that is empty. What I wanna get is:
<?xml version="1.0" encoding="UTF-8"?>
<result>
<value>B</value>
<ref>2</ref>
</result>
<result>
<value>C</value>
<ref>3</ref>
</result>
I think the problem is the XPath /body/list/toot[#id=value/#ref]/value specifically the condition [#id=value/#ref]. Is not correct it? How to use a value of other element witch is reference by the current one?

Yes the problem is in XPath where the context is changing so you are actually looking for toot element which has #id attribute and value child with #ref attribute (which is actually child of foo) and these two are equal.
You can employ current() function to make it working
<xsl:value-of select="/body/list/toot[#id=current()/value/#ref]/value"/>
Or you can store value of #ref into a variable and use this variable in predicate
<xsl:variable name="tmpRef" select="value/#ref" />
<xsl:value-of select="/body/list/toot[#id=$tmpRef]/value"/>

Related

Importing OAI source into Filemaker

I have a problem with importing an OAI source into Filemaker. The mapping is ok but the result is empty.
This is the source:
<OAI-PMH xmlns="http://www.openarchives.org/OAI/2.0/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dc="http://dublincore.org/documents/dcmi-namespace/" xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd">
<responseDate>2015-01-15T12:05:11Z</responseDate>
<request verb="ListRecords" metadataPrefix="oai_dc">
http://api.memorix-maior.nl/collectiebeheer/oai-pmh/key/SORRY_THIS_KEY_I_CANNOT_SHOW/tenant/nfm
</request>
<ListRecords>
<record>
<header>
<identifier>
e:1d59bf74-a57c-11e1-af90-bf6f69fae6b6:000a80bf-e7d6-7670-b2bd-c269b2e58878
</identifier>
etc.
And this is the xslt I made:
<?xml version='1.0' encoding='UTF-8'?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<FMPXMLRESULT xmlns="http://www.filemaker.com/fmpxmlresult">
<ERRORCODE>0</ERRORCODE>
<METADATA>
<FIELD NAME="identifier" TYPE="TEXT"/>
</METADATA>
<RESULTSET>
<xsl:for-each select="OAI-PMH/ListRecords/record">
<ROW>
<COL>
<DATA><xsl:value-of select="header/identifier"/></DATA>
</COL>
</ROW>
</xsl:for-each>
</RESULTSET>
</FMPXMLRESULT>
</xsl:template>
</xsl:stylesheet>
To make it clear I only pointed the first field in the OAI source.
I hope you can help me to fix this.
Best regards,
Boudewijn Ridder
The reason why your attempt doesn't work is that the source XML nodes are in a namespace. You must declare this namespace in your stylesheet, assign it a prefix and use that prefix when addressing the nodes:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:oai="http://www.openarchives.org/OAI/2.0/">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/">
<FMPXMLRESULT xmlns="http://www.filemaker.com/fmpxmlresult">
<METADATA>
<FIELD NAME="identifier" TYPE="TEXT"/>
</METADATA>
<RESULTSET>
<xsl:for-each select="oai:OAI-PMH/oai:ListRecords/oai:record">
<ROW>
<COL>
<DATA><xsl:value-of select="oai:header/oai:identifier"/></DATA>
</COL>
</ROW>
</xsl:for-each>
</RESULTSET>
</FMPXMLRESULT>
</xsl:template>
</xsl:stylesheet>
Note:
If your input example is representative, you might want to use :
<xsl:value-of select="normalize-space(oai:header/oai:identifier)"/>
to trim the extraneous whitespace from the result.

Not able to group similar records in XSLT

I am trying to group all similar records based on language. But I am not able to group in XSLT.
I am using XSL KEY function group the record in XSLT. I am trying loop and add each group records to one group.
I have the following input xml.
<root>
<element name="David" language="German"></element>
<element name="Sarah" language="German"></element>
<element name="Isaac" language="English"></element>
<element name="Abraham" language="German"></element>
<element name="Jackson" language="English"></element>
<element name="Deweher" language="English"></element>
<element name="Jonathan" language="Hindi"></element>
<element name="Mike" language="Hindi"></element>
</root>
XSLT:
<?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:key name="lang" match="element" use="#language"></xsl:key>
<xsl:template match="/">
<root>
<xsl:for-each select="key('lang',//element/#language)">
<Group>
<xsl:attribute name="name" select=".//#language"></xsl:attribute>
<member><xsl:value-of select=".//#name"/></member>
</Group>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Expected Output :
<root>
<Group name="German">
<member>David</member>
<member>Sarah</member>
<member>Abraham</member>
</Group>
<Group name="English">
<member>Isaac</member>
<member>Jackson</member>
<member>Deweher</member>
</Group>
<Group name="Hindi">
<member>Jonathan</member>
<member>Mike</member>
</Group>
</root>
Actual Output :
<root>
<Group name="German">
<member>David</member>
</Group>
<Group name="German">
<member>Sarah</member>
</Group>
<Group name="English">
<member>Isaac</member>
</Group>
<Group name="German">
<member>Abraham</member>
</Group>
<Group name="English">
<member>Jackson</member>
</Group>
<Group name="English">
<member>Deweher</member>
</Group>
<Group name="Hindi">
<member>Jonathan</member>
</Group>
<Group name="Hindi">
<member>Mike</member>
</Group>
</root>
I am getting each records separately.
Can someone please let me know what went wrong in the XSL. Thanks :)
I made some changes in your stylesheet. This should achieve the result you expect:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes"/>
<xsl:key name="lang" match="element" use="#language"></xsl:key>
<xsl:template match="root">
<xsl:copy>
<xsl:for-each select="element[count(. | key('lang', #language)[1]) = 1]">
<Group name="{#language}">
<xsl:for-each select="key('lang', #language)">
<member><xsl:value-of select="#name"/></member>
</xsl:for-each>
</Group>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The first loop selects each unique language (a node-set of size 3), and creates a context for the inner loop. The inner loop iterates through each element and selects only the ones that have the same language.
Muenchian grouping may seem hard to grasp, but you can always apply the template shown in this tutorial and not have to think much. I simply applied that template to your example.
UPDATE: Here is a solution without using for-each loops:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes"/>
<xsl:key name="lang" match="element" use="#language"></xsl:key>
<xsl:template match="root">
<xsl:copy>
<xsl:apply-templates select="element[generate-id(.) = generate-id(key('lang', #language)[1])]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="element">
<Group name="{#language}">
<xsl:apply-templates select="key('lang', #language)" mode="member"/>
</Group>
</xsl:template>
<xsl:template match="element" mode="member">
<member><xsl:value-of select="#name"/></member>
</xsl:template>
</xsl:stylesheet>

For each loop in xslt 1.0

How to write a for-each loop in xslt 1.0 which only consider Subbranch elements with ID=11 and 12, ignoring 13 and 14.
<root>
<branch ID='1'>
<subbranch ID='11'>
<Values DataType='String'>
<Value StringLength='3'>abc</Value>
</Values>
</subbranch>
<subbranch ID='12'>
<Values DataType='String'>
<Value StringLength='3'>def</Value>
</Values>
</subbranch>
<subbranch ID='13'>
<Values DataType='String'>
<Value StringLength='3'>uvw</Value>
</Values>
</subbranch>
<subbranch ID='14'>
<Values DataType='String'>
<Value StringLength='3'>xyz</Value>
</Values>
</subbranch>
</branch>
</root>
You can use xsl:for-each with an XPath expression that only matches the first two subbranches:
<xsl:template match="root">
<xsl:for-each select="branch/subbranch[#ID='11' or #ID='12']">
<!-- Emit something... -->
</xsl:for-each>
</xsl:template>
Or, alternately:
<xsl:template match="root">
<xsl:for-each select="branch/subbranch[position() < 3]">
<!-- Emit something... -->
</xsl:for-each>
</xsl:template>
It is recommended to avoid for-each constructions except when dealing with <xsl:key>.
You should use a match-template and apply-templates construct and just not consider the unwanted values.
This XSLT applied to your source:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes" method="xml"/>
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="subbranch[#ID='13']"/>
<xsl:template match="subbranch[#ID='14']"/>
</xsl:stylesheet>
gives this output:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<branch ID="1">
<subbranch ID="11">
<Values DataType="String">
<Value StringLength="3">abc</Value>
</Values>
</subbranch>
<subbranch ID="12">
<Values DataType="String">
<Value StringLength="3">def</Value>
</Values>
</subbranch>
</branch>
</root>
By matching the unwanted values in empty templates you avoid those parts of the XML to get processed, e.g. <xsl:template match="subbranch[#ID='13']"/>

Map empty to a node with xslt

I am using "Altova MapForce" mapping tool and I'm mapping node to node. The resulted xml (Using the altova xslt processor) is without nodes that are not empty. The resulted xml (Using the biztalk xslt processor, with the generated xsl from the Altova Mapforce) is with nodes that are not empty. That nodes are empty on the source.
My Goal is to connect the source node (Using Altova MapForce mapping tool) to a custom written XSLT, and then to connect it to the target.
Here is my code: in.xml - Instance input for executing the map (Please notice the empty id tag: )
<ns0:Root xmlns:ns0="http://BizTalk_Server_Project1.Schema1">
<id root="root_0" extension="extension_1" />
</ns0:Root>
Schema1.xsd - source and target schema
<?xml version="1.0" encoding="utf-16"?>
<xs:schema xmlns="http://BizTalk_Server_Project1.Schema1" xmlns:b="http://schemas.microsoft.com/BizTalk/2003" targetNamespace="http://BizTalk_Server_Project1.Schema1" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Root">
<xs:complexType>
<xs:sequence>
<xs:element name="id">
<xs:complexType>
<xs:attribute name="root" type="xs:string" />
<xs:attribute name="extension" type="xs:string" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
New.mfd - Altova mapping file
<?xml version="1.0" encoding="UTF-8"?>
<mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="19">
<component name="defaultmap1" blackbox="0" uid="1" editable="1">
<properties SelectedLanguage="xslt"/>
<structure>
<children>
<component name="document" library="xml" uid="4" kind="14">
<properties/>
<view rbx="150" rby="200"/>
<data>
<root>
<header>
<namespaces>
<namespace/>
<namespace uid="http://BizTalk_Server_Project1.Schema1"/>
<namespace uid="http://www.altova.com/mapforce"/>
</namespaces>
</header>
<entry name="FileInstance" ns="2" expanded="1">
<entry name="document" ns="2" expanded="1" casttotargettypemode="cast-in-subtree">
<entry name="Root" ns="1" expanded="1">
<entry name="id" expanded="1">
<entry name="extension" type="attribute" outkey="4"/>
</entry>
</entry>
</entry>
</entry>
</root>
<document schema="Schema1.xsd" outputinstance="Schema1.xml" instanceroot="{http://BizTalk_Server_Project1.Schema1}Root"/>
<wsdl/>
</data>
</component>
<component name="document" library="xml" uid="5" kind="14">
<properties XSLTDefaultOutput="1"/>
<view ltx="593" rbx="743" rby="200"/>
<data>
<root>
<header>
<namespaces>
<namespace/>
<namespace uid="http://BizTalk_Server_Project1.Schema1"/>
<namespace uid="http://www.altova.com/mapforce"/>
</namespaces>
</header>
<entry name="FileInstance" ns="2" expanded="1">
<entry name="document" ns="2" expanded="1" casttotargettypemode="cast-in-subtree">
<entry name="Root" ns="1" expanded="1">
<entry name="id" expanded="1">
<entry name="extension" type="attribute" inpkey="5"/>
</entry>
</entry>
</entry>
</entry>
</root>
<document schema="Schema1.xsd" outputinstance="Schema1.xml" instanceroot="{http://BizTalk_Server_Project1.Schema1}Root"/>
<wsdl/>
</data>
</component>
</children>
<graph directed="1">
<edges/>
<vertices>
<vertex vertexkey="4">
<edges>
<edge vertexkey="5" edgekey="6"/>
</edges>
</vertex>
</vertices>
</graph>
</structure>
</component>
</mapping>
Xsl.xsl - Xsl generated by the Altova Mapper
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns0="http://BizTalk_Server_Project1.Schema1" xmlns:agt="http://www.altova.com/Mapforce/agt" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="ns0 agt xs">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template name="agt:var2_MapToSchema1_function">
<xsl:param name="par0"/>
<xsl:attribute name="extension">
<xsl:value-of select="string($par0/#extension)"/>
</xsl:attribute>
</xsl:template>
<xsl:template match="/">
<Root xmlns="http://BizTalk_Server_Project1.Schema1">
<xsl:attribute name="xsi:schemaLocation" namespace="http://www.w3.org/2001/XMLSchema-instance">http://BizTalk_Server_Project1.Schema1 C:/Users/OhadAv/Desktop/ForAltova/Schema1.xsd</xsl:attribute>
<id xmlns="">
<xsl:for-each select="ns0:Root/id">
<xsl:variable name="var1_extension">
<xsl:if test="#extension">
<xsl:value-of select="'1'"/>
</xsl:if>
</xsl:variable>
<xsl:if test="string(boolean(string($var1_extension))) != 'false'">
<xsl:call-template name="agt:var2_MapToSchema1_function">
<xsl:with-param name="par0" select="."/>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</id>
</Root>
</xsl:template>
</xsl:stylesheet>
GeneratedByBizTalkMapperAfterXSLTmap.xml - Output instance of the BizTalk mapper after using the "Xsl.xsl" for the map (Please notice the non-empty id tag that has been generated when editing the xml file:
<?xml version="1.0" encoding="utf-8"?>
<Root xsi:schemaLocation="http://BizTalk_Server_Project1.Schema1 C:/Users/OhadAv/Desktop/ForAltova/Schema1.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://BizTalk_Server_Project1.Schema1">
<id extension="extension_1" xmlns="">
</id>
</Root>
The xslt generated by the Altova mapper looks pretty messy.
e.g. use of a one time only variable, a call template just used once, and repeated conversion like string(boolean(string(
I'm not 100% clear on what mapping you need to do in your map.
If you want to remove the root attribute entirely, and provide an id element even if it is missing, then the below xslt will work:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://BizTalk_Server_Project1.Schema1"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="ns0 xs">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/ns0:Root">
<Root xmlns="http://BizTalk_Server_Project1.Schema1">
<xsl:element name="id">
<xsl:for-each select="ns0:Root/id">
<xsl:if test="#extension">
<xsl:attribute name="extension">
<xsl:value-of select="string(#extension)"/>
</xsl:attribute>
</xsl:if>
</xsl:for-each>
</xsl:element>
</Root>
</xsl:template>
</xsl:stylesheet>
This produces XML without the xmlns, extraneous schema locations, etc like so:
<?xml version="1.0" encoding="utf-8"?>
<Root xmlns="http://BizTalk_Server_Project1.Schema1">
<id extension="extension_1" />
</Root>
If however you mean that you want to omit the id element entirely if it isn't present on the input node, then change the template as follows.
<xsl:template match="/ns0:Root">
<Root xmlns="http://BizTalk_Server_Project1.Schema1">
<xsl:if test="id">
<xsl:element name="id">
<xsl:attribute name="extension">
<xsl:value-of select="string(id/#extension)"/>
</xsl:attribute>
</xsl:element>
</xsl:if>
</Root>
</xsl:template>
Which Maps
<ns0:Root xmlns:ns0="http://BizTalk_Server_Project1.Schema1">
</ns0:Root>
To:
<?xml version="1.0" encoding="utf-8"?>
<Root xmlns="http://BizTalk_Server_Project1.Schema1" />

Using xslt, transform one xml document based upon the contents of a second xml document

I have one set of documents that implicitly define the allowed fields for a second set of objects that have to be transformed into a third set of documents (
which "rules" document to use depends upon the content of the file being transformed) e.g.
<!-- One example rules document -->
<document object="obj1_rules">
<field name="A"/>
<field name="B"/>
<field name="C"/>
</document>
<!-- Document to be tranformed based upon obj1_rules-->
<document object="obj1">
<field name="A"/>
<field name="B"/>
<field name="C"/>
<field name="D"/>
<field name="E"/>
</document>
<!-- Desired result-->
<document object="obj1">
<newfield name="A"/>
<newfield name="B"/>
<newfield name="C"/>
</document>
Is it possible to do this transformation using xslt?
I see that "There is no way in XSLT of constructing XPath expressions (e.g. variable references) at run-time." So I am out of luck, or I am just looking at this problem incorrectly? Thanks!
Here is a simple solution:
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- -->
<xsl:variable name="vrtfRules">
<document object="obj1_rules">
<field name="A"/>
<field name="B"/>
<field name="C"/>
</document>
</xsl:variable>
<!-- -->
<xsl:variable name="vRules" select=
"document('')/*/xsl:variable
[#name = 'vrtfRules']
/*
"/>
<!-- -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- -->
<xsl:template match="field">
<xsl:if test="#name = $vRules/*/#name">
<newfield>
<xsl:apply-templates select="node()|#*"/>
</newfield>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on the originaly-provided source XML document:
<document object="obj1">
<field name="A"/>
<field name="B"/>
<field name="C"/>
<field name="D"/>
<field name="E"/>
</document>
produces the desired result:
<document object="obj1">
<newfield name="A"/>
<newfield name="B"/>
<newfield name="C"/>
</document>
Note that the "rules document" is within the stylesheet just for compactness. When it is a separate document, just the document() function used will need to be adjusted with the actual href.
Maybe I'm oversimplifying, but is there a reason why your "rules document" cannot simply be an XSLT?
Well, I can see why you'd like to have rules in a simple xml file rather than in a full-fledged xsl stylesheet but you're simply skipping a step.
You need to make a xsl stylesheet that will transform your xml rule document into a xsl stylesheet that you will then apply onto your source xml.
The trick is with namespaces and not getting confused by the mix of xsl rules applied and xsl rules generated.
<?xml version="1.0" ?>
<xsl:stylesheet
xmlns="YOUR_NAMESPACE_HERE"
xmlns:output="http://www.w3.org/1999/XSL/Transform"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output
method="xml"
indent="yes"
media-type="text/xsl" />
<xsl:template match="/">
<output:stylesheet version="2.0">
<xsl:apply-templates />
</output:stylesheet>
</xsl:template>
<xsl:template match="document[#object]">
<output:template match="document[#object='{#object}']">
<output:copy>
<xsl:apply-templates />
</output:copy>
</output:template>
</xsl:template>
<xsl:template match="field[#name]">
<output:if test="field[#name='{#name}']">
<output:copy-of select="field[#name='{#name}']" />
</output:if>
</xsl:template>
</xsl:stylesheet>
I presumed you'd use the same document object attribute in the rules and in the documents themselves (this is much simpler imo).
So, you run your rules document through the stylesheet above. The result is a new xsl stylesheet that does exactly what you describe in your xml rule document. You then apply this resulting stylesheet onto your source document and you should get the result you expect.