XSLT finding matching node by comparing element text to substring result - string

I am processing this XML:
<Brand>
<Brand_Name>BLENDERM</Brand_Name>
<Brand_Code>1103</Brand_Code>
<Groups>
<Group>
<Group_Code>657</Group_Code>
<Parent_Code>0</Parent_Code>
<Group_Level>1</Group_Level>
<Group_Name>Brand Default</Group_Name>
<Product>
<Pip_code>0032359</Pip_code>
<Status>In Use</Status>
Using this XSLT:
<xsl:template match="Product" mode="phase-3">
<xsl:value-of select="document('rx_catmapping.xml')/descendant::mapping[source=substring(ancestor::Brand/Brand_Name,1,1)]/target"/>
</xsl:template>
Here is a sample of rx_catmapping.xml:
<Lookup xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<mapping>
<source>a</source>
<target>788</target>
</mapping>
<mapping>
<source>B</source>
<target>789</target>
</mapping>
</Lookup>
So, I am processing the Product element, which is a descendant of Brand. The first letter of Brand/Brand_Name in this case is B, and I am trying to output the value 789 by looking it up in rx_catmapping.xml. This should be really simple but I am completely stumped! I have tried changing the first part of the XPath to refer to document('rx_catmapping.xml')/Lookup/mapping, or document('rx_catmapping.xml')//mapping. I have also tried changing the first half of the comparison to string(source), or to source/text(), but neither of these works either. (The reason for trying this was that using source='B', for example, did seem to work, so I wondered if I was trying to compare two incompatible data types.)
Thanks in advance for your help.

Define a key
<xsl:key name="k1" match="mapping" use="source"/>
then use
<xsl:variable name="map-doc" select="document('rx_catmapping.xml')"/>
and
<xsl:variable name="letter" select="substring(ancestor::Brand/Brand_Name,1,1)"/>
<xsl:for-each select="$map-doc">
<xsl:value-of select="key('k1', $letter)/target"/>
</xsl:for-each>
With XSLT 2.0 you can simplify that to
<xsl:value-of select="key('k1', substring(ancestor::Brand/Brand_Name,1,1), $map-doc)"/>

The problem is, I believe, that at the point where you do ancestor::Brand/Brand_Name, the context is the mapping element in the external file. This worked for me
<xsl:template match="/">
<xsl:apply-templates select=".//Product"/>
</xsl:template>
<xsl:template match="Product">
<xsl:variable name="x" select="substring(ancestor::Brand/Brand_Name,1,1)"/>
<xsl:value-of select="document('file:///c:/temp/rx_catmapping.xml')//mapping[source=$x]/target"/>
</xsl:template>

Related

How to return the count of input that start with a string in XSL?

I'm trying to return the count of names which start with NB but im having trouble as it returns 0. I believe i got the syntax right so it's kind of frustrating to see it return 0. Any help would be appreciated!
XML Input:
<?xml version='1.1' encoding='UTF-8'?>
<breaches>
<breach>
<name>NB111</name>
<severity>MAJOR</severity>
</breach>
<breach>
<name>NB222</name>
<severity>MAJOR</severity>
</breach>
<breach>
<name>NB333</name>
<severity>MAJOR</severity>
</breach>
<breach>
<name>PO999</name>
<severity>MAJOR</severity>
</breach>
</breaches>
XSL
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:value-of select="count(/breaches/breach/name/*[contains(text(), 'NB')])" />
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>0
I'm trying to return the count of names which start with NB
If you want to count names that start with "NB" then use the starts-with() function, not the contains() function:
<xsl:value-of select="count(breaches/breach[starts-with(name, 'NB')])"/>
This actually counts breach elements that have a child name element that starts with "NB". IOW, it assumes each breach has only one name. If such assumption is incorrect, then use:
<xsl:value-of select="count(breaches/breach/name[starts-with(., 'NB')])"/>
to count the actual name elements.
You could do it like this :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:value-of select="count(breaches/breach/name[contains(., 'NB')])" />
</xsl:template>
</xsl:stylesheet>
See it working here : https://xsltfiddle.liberty-development.net/jxWZS86

xslt: remove space before and after specific elements

I'm using Saxon PE 9.7, XSLT version 3.0.
I try to remove space after <w> before <damage>. I have tried several solutions: normalize-space(), translate(., ' ', ''), even css, white-space: nowrap... I also looked to the solution proposed to How do I remove spaces in all attribute values using xslt?. Unfortunately, none worked.
TEI
<lg>
<!-- other <l> -->
<l>
<!-- other <w> -->
<w xml:id="ktu1-3_ii_l7_ym" type="noun" lemmaRef="uga/noun.xml#ym" rendition="#nowrap">y</w><damage agent="unknown"><supplied resp="KTU" rendition="#bracketBefore #bracketAfter"><w corresp="ktu1-3_ii_l7" type="part-of-noun">m</w></supplied></damage> <!-- type="part-of-noun" because I also have type="part-of-verb", and the display is different -->
</l>
</lg>
When I have damage/supplied/w before the second <w>, it works, but not after the first <w>
XSLT
<xsl:template match="lg/l[#n]">
<li>
<xsl:apply-templates/>
<sup style="font-size: 0.8em">
<xsl:value-of select="#n"/>
</sup>
</li>
</xsl:template>
<xsl:template match="lg/l[#n]/damage/supplied">
<xsl:choose>
<xsl:when test="#rendition">
<xsl:text>[</xsl:text>
<xsl:apply-templates select="./w[not(#rendition='notDisplay')]"/><xsl:text>]</xsl:text>
</xsl:when><xsl:otherwise><xsl:apply-templates select="./w[not(#rendition='notDisplay')]"/></xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="w">
<xsl:apply-templates select="normalize-space(.)"/>
</xsl:template>
<xsl:template match="lg/l[#n]/w">
<xsl:apply-templates select=".[#type= 'noun']" mode="css"/>
<xsl:apply-templates select="normalize-space(.)"/>
</xsl:template>
In advance, thank you for your kind advice.
I'm having trouble working out exactly what you want to achieve. What is the desired output? Are you talking about unwanted space in the visual rendition of the HTML, or about unwanted space in the XML/HTML transformation result?
Writing
<xsl:apply-templates select="normalize-space(.)"/>
is clearly wrong unless you have template rules that match atomic xs:string values, which seems unlikely. And neither of your <w> elements have any whitespace in their string-values, so normalize-space is a no-op anyway.

Populating nodes based on condition in Receiver file

I need to populate one segment based on input values.the requirement like below
in the input payload we are getting below segment like
<charac>
<charactername>
<charactervalue>
</charac>
so the above segment may come multiple times but based on the few values only need to populate the segment for example,
<charac>
<charactername>print</charactername>
<charactervalue>123</charactervalue>
</charac>
<charac>
<charactername>comp</charactername>
<charactervalue>1234</charactervalue>
</charac>
<charac>
<charactername>pal</charactername>
<charactervalue>1235</charactervalue>
</charac>
so here only I need populate segment when charactername is equal to only print or comp
the receiver structure the segment is
<e1edl1>
<at>
<rt>
</e1edl1>
so the output should be like
<e1edl1>
<at>print</at>
<rt>123</rt>
</e1edl1>
<e1edl1>
<at>comp</at>
<rt>1234</rt>
</e1edl1>
I tried with below code
<ns0:if test="count(./charac)!=0">
<ns0:for-each select="./charac">
<e1edl1 SEGMENT="1">
<at>
<ns0:value-of select="charactername" />
</at>
<rt>
<ns0:value-of select="charactervalue" />
</rt>
</e1edl1>
</ns0:for-each>
</ns0:if>
could you please help on this.
Regards,
Janardhan
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="//charac[charactername='print' or charactername='comp']" />
</xsl:template>
<xsl:template match="charac">
<e1edl1>
<at><xsl:value-of select="charactername" /></at>
<rt><xsl:value-of select="charactervalue" /></rt>
</e1edl1>
</xsl:template>
</xsl:stylesheet>

Remove the first elements of a path in XSLT

I'm trying to remove the first 3 'tokens' of a path-looking string in XSLT 2.0.
For instance, go from D:/FolderA/folderB/folderC/file.ext to folderC/file.ext.
I cant find a quick idea to do it other than using a recursive function I'm struggling to write.
<xsl:variable name="tokenizedPath" select="(tokenize($url,'/'))" />
<xsl:value-of select="yy:restofpath($tokenizedPath,2)" />
where yy:restofpath could be something like:
<xsl:function name="yy:restofpath" as="xs:string">
<xsl:param name="pathtokens"/>
<xsl:param name="startIndex"/>
<xsl:variable name="length" select="count($pathtokens)"/>
<xsl:for-each select="$pathtokens">
<xsl:value-of select="string-join(.,yy:restofpath($pathtokens,),'')"/>
</xsl:for-each>
</xsl:function>
This is a dumb function that I cant write, I'm confused how to process my tokenized string.
Maybe there is a simpler built-in way to do that?
Given:
<xsl:variable name="tokenizedPath" select="tokenize($url,'/')" />
then you can use:
<xsl:value-of select="string-join($tokenizedPath[position() gt 3], '/')"/>

Schematron: Element within mixed-type element only with other content

My XML Data should look like this:
<mixed_type_parent>
...text...
<other_element1/>
<other_element2/>
<element_in_question>...some content...</element_in_question>
<other_element2>...text...</other_element2>
<other_element1>...text...</other_element1>
...text...
</mixed_type_parent>
What I want to make sure using Schematron is that the "element_in_question" may only appear within "mixed_type_parent" if there is some text outside of the "element_in_question".
That means
<mixed_type_parent>
<element_in_question>...some content...</element_in_question>
</mixed_type_parent>
is not allowed and should cause an error.
I tried to get the string-length of all text immediately within "mixed_type_parent"
string-length(replace(ancestor::mixed_type_parent[1]/text(),' ', ''))
But, again, there is one of the most annoying errors in XPath: "A sequence of more than one item is not allowed as the first argument of replace()"
In XSLT I have solved this problem by the simplest function you can think about:
<xsl:function name="locfun:make_string">
<xsl:param name="input_sequence"/>
<xsl:value-of select="$input_sequence"/>
</xsl:function>
(It is really a shame that there seams to be no such built-in function in XPath.)
But how can I use this function in Schematron? I didn't find a solution for this.
And other than that: How do I get all text form all other childs of "mixed_type_parent" except "mixed_type_parent"?
Try this:
string-join(ancestor::mixed_type_parent[1]/text(),'')=''
For your second question: How do I get all text form all other childs of "mixed_type_parent" except "mixed_type_parent"?
/mixed_type_parent/*[not(self::element_in_question)]/text()
considering this input XML,
<mixed_type_parent>
<element_in_question>...some content...</element_in_question>
</mixed_type_parent>
when this XSLT is applied:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:template match="element_in_question">
<xsl:choose>
<xsl:when test="preceding-sibling::text() or following-sibling::text()">
true
</xsl:when>
<xsl:otherwise>
false
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
produces
false

Resources