Match a specific element in XML via XSL - xslt

I have the following XML message, which I am trying to go through via an XSL stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<array xmlns="http://www.w3.org/2005/xpath-functions">
<map>
<array key="results">
<map>
<string key="id">5b33c2e8-8ab2-4314-82bf-e41a007c076f</string>
<string key="profileId">0f53bfe5-4ef3-4424-9ad2-ad8181007e22</string>
</map>
</array>
<map key="_embedded">
<array key="individuals">
<map>
<string key="id">0f53bfe5-4ef3-4424-9ad2-ad8181007e22</string>
<string key="name">John</number>
</map>
</array>
</map>
<map key="paging">
<number key="pageNumber">1</number>
<number key="pageSize">10</number>
<number key="totalCount">4</number>
</map>
</map>
</array>
My code is like the following:
<xsl:template match="/" name="xsl:initial-template">
<xsl:variable name="input-as-xml" select="json-to-xml($json)"/>
<xsl:variable name="transformed-xml">
<array xmlns="http://www.w3.org/2005/xpath-functions">
<xsl:for-each select="$input-as-xml/fn:array/fn:map/fn:array[#key = 'results']/*">
<map xmlns="http://www.w3.org/2005/xpath-functions">
<xsl:variable name="profileId">
<xsl:value-of select="*[#key = 'profileId']"/>
</xsl:variable>
<xsl:if test="*[#key = 'id'] != ''">
<string key="id">
<xsl:value-of select="*[#key = 'id']"/>
</string>
</xsl:if>
<xsl:for-each select="../fn:map[#key = '_embeded']/fn:array[#key = 'individuals'][#id=$profileId]/*">
<array key="profiles">
<string key="id">
<xsl:value-of select="fn:map/*[#key = 'id']"/>
</string>
</array>
</xsl:for-each>
</map>
</xsl:for-each>
</array>
</xsl:variable>
<xsl:value-of select="xml-to-json($transformed-xml, map {'indent': true()})"/>
</xsl:template>
What I want to do is map the individuals/id with the results/profileId, but apparently the way I am matching the individuals is not correct.
I would need some help with matching correctly.

Based on your comment
the individuals array has a <string key="id"> , basically I want to
select values only if the id within the array has the value equal to
profileId
you might want to select fn:array[#key = 'individuals'][.//fn:string[#id = $profileId]], that is, an array having a descendant string with the matching id.
It is hard to give a more complete example as the input for instance has <string key="name">John</number> which is not well-formed.
As in previous questions, I think moving to template based matching instead of nested for-eachs in a single template would make the code more managable and easier to structure, read and maintain.

Related

Remove elements that have attribute with a value that matches to some other element's attribute value

Suppose I have the following input file:
<root>
<container_items>
<item Id="a">
<Content Name="red_dark" />
</item>
<item Id="b">
<Content Name="yellow" />
</item>
<item Id="c">
<Content Name="blue_dark" />
</item>
<item Id="d">
<Content Name="green" />
</item>
</container_items>
<container_refs>
<item_ref Id="a" />
<item_ref Id="b" />
<item_ref Id="c" />
<item_ref Id="d" />
</container_refs>
</root>
The real file is a little more complicated, but I will make it look simpler with closer criteria here to remove those 'item' elements that have 'Content' child element with a Name attribute that ends with "_dark". I managed to remove the 'item' elements that I don't need, however, the corresponding 'item_ref' elements left. Let's say I removed the 'item' elements that match my criteria. My goal is the 'item_ref' elements with Id="a" or ="c" also to be removed (those are the Id's of the matched and removed 'item' elements). So the expected end result is.
<root>
<container_items>
<item Id="b">
<Content Name="yellow" />
</item>
<item Id="d">
<Content Name="green" />
</item>
</container_items>
<container_refs>
<item_ref Id="b" />
<item_ref Id="d" />
</container_refs>
</root>
Apparently, I need to remove all 'item_ref' elements that have Id attribute with a value in the list of values collected from certain 'item' elements' Id attributes (that match my existing criteria for 'item' elements).
My XSL file is the following (focusing only on the criteria):
<xsl:template match="//item[./Content[substring(#Name, string-length(#Name)- string-length('_dark') + 1) = '_dark']]" />
Based on my criteria the 'item' elements matching the criteria are removed, but then the associated 'item_ref' elements remain in the input file, causing the following result:
<root>
<container_items>
<item Id="b">
<Content Name="yellow" />
</item>
<item Id="d">
<Content Name="green" />
</item>
</container_items>
<container_refs>
<item_ref Id="a" />
<item_ref Id="b" />
<item_ref Id="c" />
<item_ref Id="d" />
</container_refs>
</root>
Thanks in advance for your support.
You can achieve this with a xsl:key and two empty templates:
<xsl:key name="items" match="container_items/item" use="#Id" />
and the two empty templates are
<xsl:template match="container_items/item[substring(Content/#Name, string-length(Content/#Name)-string-length('_dark') + 1) = '_dark']" />
<xsl:template match="container_refs/item_ref[substring(key('items',#Id)/Content/#Name,string-length(key('items',#Id)/Content/#Name)-string-length('_dark') + 1) = '_dark']" />
The first one removes the items from container_items and the second one removes the item_refs from the container_refs.
Define a key as:
<xsl:key name="item" match="item" use="#Id" />
then use:
<xsl:template match="item_ref[substring(key('item', #Id)/#Name, string-length(key('item', #Id)/#Name) - string-length('_dark') + 1) = '_dark']"/>
to remove the item_ref nodes.
There's probably a more efficient way to do this by storing the relevant Ids in a variable, but that's the general idea.
Here's a way to do it.
You could optimize the //item[...] by using a key call.
<?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"
version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="item[contains(#Name,'_dark')]"/>
<xsl:template match="item_ref">
<xsl:variable name="ir" select="."/>
<xsl:if test="//item[#Id=$ir/#Id and not(contains(#Name,'_dark'))]">
<xsl:copy>
<xsl:attribute name="Id"><xsl:value-of select="#Id"/></xsl:attribute>
</xsl:copy>
</xsl:if>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
See it working here : https://xsltfiddle.liberty-development.net/93dFepP

Match node in JSON input

I have the following code, which I am using to transform a JSON file to another JSON.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" xmlns:fcn="http://www.re.com/2018/local-functions" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs fn fcn exsl" version="3.0">
<xsl:param as="xs:string" name="json">[{
"results": [
{
"id": "5b33c2e8-8ab2-4314-82bf-e41a007c076f",
"profileId": "0f53bfe5-4ef3-4424-9ad2-ad8181007e22"
}
],
"_embedded": {
},
"paging": {
"pageNumber": 1,
"pageSize": 10,
"totalCount": 4
}
}]</xsl:param>
<xsl:output indent="yes" method="text" />
<xsl:template match="/" name="xsl:initial-template">
<xsl:variable name="input-as-xml" select="json-to-xml($json)" />
<xsl:variable name="transformed-xml">
<array xmlns="http://www.w3.org/2005/xpath-functions">
<xsl:for-each select="fn:array[#key = 'results']/*">
<map>
<xsl:if test="*[#key = 'id'] != ''">
<string key="id">
<xsl:value-of select="fn:string[#key = 'id']" />
</string>
</xsl:if>
</map>
</xsl:for-each>
</array>
</xsl:variable>
<xsl:value-of select="xml-to-json($transformed-xml, map {'indent': true()})" />
</xsl:template>
</xsl:stylesheet>
I am trying to match the results object, but when I apply the stylesheet, I am getting an empty JSON output.
Some help with matching the results object correctly would be appreciated.
The input-as-xml:
<?xml version="1.0" encoding="UTF-8"?>
<array xmlns="http://www.w3.org/2005/xpath-functions">
<map>
<array key="results">
<map>
<string key="id">5b33c2e8-8ab2-4314-82bf-e41a007c076f</string>
<string key="profileId">0f53bfe5-4ef3-4424-9ad2-ad8181007e22</string>
</map>
</array>
<map key="_embedded"/>
<map key="paging">
<number key="pageNumber">1</number>
<number key="pageSize">10</number>
<number key="totalCount">4</number>
</map>
</map>
</array>
For starters, I think the xsl:for-each should be something like <xsl:for-each select="$input-as-xml/fn:array[#key = 'results']/*">, so you're selecting within the right context.
But I don't think that's quite enough: The XML that comes from the conversion will contain an <fn:array> whose first child will be an <fn:map> which will have an entry of the form <fn:array key="results">, so you need to select deeper into the structure.
It's probably a good idea to print out the value of $input-as-xml (in indented form) for ease of diagnostics.

Copy single value in XML to two different places in other XML

I have an XML file with this structure:
<DetailTxt>
<Text>
<span>Some Text</span>
</Text>
<TextComplement Kind="Owner" MarkLbl="1">
<ComplCaption>
Caption 1
</ComplCaption>
<ComplBody>
Body 1
</ComplBody>
</TextComplement>
<Text>
<span>More Text</span>
</Text>
</DetailTxt>
Here is the part of the XSLT that is relevant here:
<xsl:template match="*[local-name() = 'DetailTxt']">
<xsl:apply-templates select="*[local-name() = 'Text']"/>
</xsl:template>
<xsl:template match="*[local-name() = 'Text']">
<item name="{local-name()}">
<richtext>
<par>
<run>
<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>
<xsl:apply-templates/>
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</run>
</par>
</richtext>
</item>
<item name="{local-name()}">
<richtext>
<par>
<run>
<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>
<xsl:value-of select="concat('[', ../TextComplement/#Kind, ../TextComplement/#MarkLbl,']')" />
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</run>
</par>
</richtext>
</item>
</xsl:template>
I expect the output to look like this:
<item name="Text">
<richtext>
<par>
<run><![CDATA[
<span>Some Text</span>
</p>]]></run>
</par>
</richtext>
</item>
<item name="Text">
<richtext>
<par>
<run><![CDATA[[Owner1]]]></run>
</par>
</richtext>
</item>
But the line using the TextComplement XPath looks like this:
<run><![CDATA[[]]]></run>
All values from TextComplement are missing. Whats wrong with the XPath here?
EDIT: I completely reworked my question and put in a CONCRETE question resulting from the first answer. That kind of invalidates the first answer but IMHO improves the question.
Not sure how the XSLT looks like but you can try adding the following template with the concat() function for getting the output.
<xsl:template match="Text">
<document version="9.0" form="Form1">
<item name="{local-name()}">
<xsl:copy-of select="span" />
</item>
<item name="{local-name()}">
<span>
<xsl:value-of select="concat('[', ../TextComplement/#Kind, ../TextComplement/#MarkLbl, ']')" />
</span>
</item>
</document>
</xsl:template>
This template is applied to the <Text> node and the ../ is used to go up one level and then access the attributes of <TextComplement> using the XPath.
The output of the template when applied to your XML will look like.
<document form="Form1" version="9.0">
<item name="Text">
<span>Some Text</span>
</item>
<item name="Text">
<span>[Owner1]</span>
</item>
</document>
The same template will also get applied to the <Text> node having More Text content and produce similar output.
I found a solution myself for the concrete question. I quess this is IBM Notes / LotusScript specific issue.
When using the selector
../TextComplement/#Kind
the parser returned an empty string. I changed to
../*[local-name() = 'TextComplement']/#Kind
and later (more concrete) to:
./following-sibling::*[local-name() = 'TextComplement']/#Kind
And that worked. I personally see no difference in these notations, but it seams that internally they are handled differently.

how to fetch the value of one node based on the value from another node

I want to fetch the "index" value based on what is present in the string
<sch name="main">
<norm string="back-slash"/>
<norm string="open-braces" />
<norm string="close-braces" />
</sch>
<strings name="consts">
<string name="back-slash" val="\\" index="0"/>
<string name="close-braces" val="]" index="2"/>
<string name="remove-null" val="null" index="3" />
</strings>
i tried this but it doesnt' work. Can yuou please help?
<xsl:template match="norm" >
<xsl:variable name="$nme" select="#string"/>
<xsl:value-of select="/strings/#name=$nme/#index"/>,
</xsl:template>
/strings/#name=$nme/#index
is not valid XPath. You need an attribute selector if you wish to target a node by one of its attributes.
/strings/*[#name=$nme]/#index
First of all, the name of your variable $nme is not a valid Qname.
Instead of
<xsl:variable name="$nme" select="#string"/>
you should use
<xsl:variable name="nme" select="#string"/>
try this template:
<xsl:template match="norm" >
<xsl:variable name="nme" select="#string"/>
<xsl:value-of select="../following-sibling::strings/string[#name=$nme]/#index"/>,
</xsl:template>

How to assign XSLT variables with XML attributes

Here is my XML file:
<Veranstaltungen xmlns="urn:schemas-etourist:Veranstaltung">
<Veranstaltung attribute1="xyz" attribute2="xyz">
<OBJECT>
<string xmlns="urn:eTourist:i18n" xml:lang="de-DE">GERMAN TEXT</string>
<string xmlns="urn:eTourist:i18n" xml:lang="en-GB">ENGLISH TEXT</string>
<string xmlns="urn:eTourist:i18n" xml:lang="cs-CZ">CZECH TEXT</string>
</OBJECT>
...
And here my XSLT variables:
...
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:td="urn:schemas-etourist:Veranstaltung"
xmlns:td2="urn:schemas-etourist:SchemaExtension"
xmlns:td3="urn:eTourist:i18n"
xmlns:php="http://php.net/xsl"
extension-element-prefixes="php">
...
<xsl:variable name="german">
<xsl:value-of select="td:OBJECT/td3:string[#xml:lang='de-DE']"></xsl:value-of>
</xsl:variable>
<xsl:variable name="english">
<xsl:value-of select="td:OBJECT/td3:string[#xml:lang='en-GB']"></xsl:value-of>
</xsl:variable>
...
Variable 'german' is filled correctly, HOWEVER variable 'english' is filled with the GERMAN TEXT value. How do we fill variable 'english' with the ENGLISH TEXT value?
Many thanks for any help!
Try using the lang() function, which is designed to match languages like this, taking account of scope (remember an xml:lang attribute applies to all descendant elements as well as to the element on which the attribute is specified):
<xsl:variable name="german">
<xsl:value-of select="td:OBJECT/td3:string[lang('de')]"></xsl:value-of>
</xsl:variable>
<xsl:variable name="english">
<xsl:value-of select="td:OBJECT/td3:string[lang('en')]"></xsl:value-of>
</xsl:variable>