Using <xsl:for-each> for incremental element names - xslt

I have an XML that is converted from a java map. So all the map keys are converted into node names. The XML structure is as below
<map>
<firstName>AAA</firstName>
<firstName1>BBB</firstName1>
<firstName2>CCC</firstName2>
<firstName3>DDD</firstName3>
</map>
I am trying to write a for-each loop to extract data from this XML to create an output XML. I have tried most of the options available such as name(), local-name(), contains(), etc but couldn't come up with something that worked. What are the options available since the incremental node name can go upto count 100 or more. Any inputs in coding the loop would be of great help. I am using XSLT 1.0.

There are many ways to select the children of the top element (map):
/*/*
This selects all elements that are children of the top element of the XML document.
/*/*[starts-with(name(), 'firstName')]
This selects all top element's children-elements, whose name starts with the string 'firstName'.
/*/*[starts-with(name(), 'firstName')
and floor(substring-after(name(), 'firstName')) = substring-after(name(), 'firstName')) ]
This selects all top element's children-elements, whose name starts with the string 'firstName' and the remaining substring after this is an integer.
/*/*[starts-with(name(), 'firstName')
and translate(name(), '0123456789', '') = 'firstName')) ]
This selects all top element's children-elements, whose name starts with the string 'firstName' and the remaining substring after this contains only digits.
Finally, in XPath 2.0 (XSLT 2.0) one can use regular expressions:
/*/*[matches(name(), '^firstName\d+$')]

This will select all the first level elements and their information, which you can then use as you wish:
<xsl:for-each select="/*/*">
<xsl:value-of select="local-name()"/>
<xsl:value-of select="."/>
</xsl:for-each>

Related

XSLT 2.0 using key with except returns unexpected result

NB: title changed to reflect the problem better.
My xml documents contain an element <tei:seg #type #xml:id #corresp> which wrap little 'stories'. The attribute #corresp allows me to connect these stories to a master story. For example, these seg are all connected by their #corresp:
doc1.xml//seg[#type='dep_event' #corresp='#JKL' #xml:id='doc1-05']
doc2.xml//seg[#type='dep_event' #corresp='#JKL' #xml:id='doc2-06']
doc6.xml//seg[#type='dep_event' #corresp='#JKL' #xml:id='doc6-03']
My objective is: when the XSLT template finds a #corresp, find other seg in other documents with the same #corresp and output their respective `#xml:id``
So, in the above example, if the current seg was #xml:id='doc1-05', the template outputs a list: Corresponds to doc2-06, doc6-03
Until I can solve the current problems with XSLT collection() in eXist-DB, I'm falling back on my previous solution: a 'TEI corpus' xml document which maintains a master list of all related tei-xml documents via xi:include. This way I provide a single document node whereby the processor can access and search all the xml documents.
So, I declare the corpus document:
<xsl:variable name="corpus" select="doc('ms609_corpus.xml')"/>
Then create a key for the #corresp:
<xsl:key name="correspkey" match="//tei:seg[#type='dep_event' and #corresp]" use="#corresp"/>
Then I use the key with the doc() to search:
<xsl:when test="tei:seg[#type='dep_event' and #corresp]">
<xsl:variable name="correspvar"
select="data(self::seg[#type='dep_event' and #corresp]/#corresp)"/>
<xsl:text>Corresponds to </xsl:text>
<xsl:value-of select="data($corpus/(key('correspkey',$correspvar) except $correspvar)/#xml:id)" separator=", "/>
</xsl:when>
It returns the results, but the except should exclude the current #corresp. Yet it is included in the results.
The except operator works on sequences of nodes based on node identity, see https://www.w3.org/TR/xpath20/#combining_seq defining
The except operator takes two node sequences as operands and returns a
sequence containing all the nodes that occur in the first operand but
not in the second operand ... All these operators eliminate duplicate
nodes from their result sequences based on node identity
Based on that I think you simply want
<xsl:value-of select="$corpus/(key('correspkey', current()/#corresp) except current())/#xml:id)" separator=", "/>
Using data on nodes which atomizes nodes to values and then trying to use except which works on nodes doesn't seem to make sense to me.

Comparing attribute values in XPath

I want to get the value of a specific node, specified by its id attribute. But the behaviour of my XSL parser, Saxon, is not how I expected it to work.
This is my XSL code:
<xsl:template match="synonyme">
<xsl:element name="corr">
<xsl:value-of select="#connecteur" />
<xsl:value-of select="/liste/connecteur[#id=#connecteur]/forme[1]" />
</xsl:element>
</xsl:template>
I just matched a tag named synonyme which has a connecteur attribute. My code outputs the value of this attribute.
I also want to output the value of another node which has an id attribute matching the connecteur attribute of my currently matched synonyme tag. But no results are ever found for this query, the second value-of always has empty output.
If I write, e.g. [#id='c160'], where c160 is the exact same thing that is output by the first value-of, it works! But not when comparing to the #attribute of my matched node. How can I fix this?
The XML is basically
<liste><connecteur id="c160"><forme>foo</forme></connecteur>
<connecteur id="c161"><synonyme connecteur="c160" /></connecteur>
</liste>
and the expected output in place of the synonyme is <corr>c160 foo</corr>.
The predicate you use:
[#id=#connecteur]
is looking for an element with two attributes - id and connecteur- with equal values. To look for an element with an id attribute whose value matches the value of the current element's connecteur value, you need to use:
[#id=current()/#connecteur]
See: https://www.w3.org/TR/xslt/#function-current
A better solution would be to define a key as:
<xsl:key name="ref" match="connecteur" use="#id" />
then use:
<xsl:value-of select="key('ref', #connecteur)/forme" />
to resolve the cross-reference.
See: https://www.w3.org/TR/xslt/#key

Manage flags in xslt?

All,
i am searching in a list of fields those who has the type clob and i am writing it separed by a comma like this [field1, field2, ... fieldn]
my problem is how to identify the first matched field to write it without comma ( i can't use position() because the first field matched can be the first of the list or the last of the list)
I want to make this algorithm in xslt,
variable is_first = TRUE;
if(is_first) {
do smthng;
isfirst = False;
}
Actually it is not possible to make something like this in xslt since variable are immutable. There probably could be workarounds but you have to specify your need in more details.
edit:
If your input is string with values separated by commas...
<xsl:variable name="inputString" select="'field1,field2,field3a,field4,field3b'" />
... you could use tokenize() functions...
<xsl:variable name="tokenized" select="tokenize($inputString, ',')" />
... and then select items corresponding to your condition
<!-- Select item corresponding to condition (e.g. it contains 3). Take first one if there are several such items -->
<xsl:value-of select="$tokenized[contains(., '3')][1]" />
Edit2:
You can use separator attribute of xsl:value-of (xslt 2.0) for output of delimited values.
Assuming following variable
<xsl:variable name="list">
<item>first</item>
<item>second</item>
<item>third</item>
</xsl:variable>
this <xsl:value-of select="$list/item" separator="," /> makes desired output first,second,third
You need to write this using functional code rather than procedural code. It's not possible to do the conversion without seeing the context (it's much easier to work from the problem rather than from the solution in a lower-level language).
But the most common equivalent in XSLT would take the form
<xsl:for-each select=".....">
<xsl:if test="position() = 1"><!-- first time code --></xsl:if>
....
</xsl:for-each>

convert <xsl:variable> string value , to the value which I can select with <xsl:value-of select>

I have a variable from which I need to dynamically generate nodes
<xsl:template match="banner_discount_1 | banner_discount_2 | banner_discount_3">
<xsl:variable name="link">banner_discount_<xsl:value-of select="substring-after(name(.) ,'banner_discount_')" />_link</xsl:variable>
<xsl:value-of select="$link" />
</xsl:template>
<xsl:value-of> selects the string, but I want to be able to select the node which name matches the name of a variable.
In my case the node looks something like this:
<banner_discount_1_link />
<banner_discount_2_link />
...
Here is the xml I'm using
<banner_discount_1> 12 </banner_discount_1>
<banner_discount_2> 21 </banner_discount_2>
<banner_discount_3> 32 </banner_discount_3>
<banner_discount_1_link> link1 </banner_discount_1_link>
<banner_discount_2_link> link2 </banner_discount_2_link>
<banner_discount_3_link> link3 </banner_discount_3_link>
#MartinHonnen is on the right track, but you need to set the selection context as well.
Since you're in a template that's selecting the banner_discount_ nodes, that is your context. From your XML sample, it looks like the nodes you want to select are siblings, so this should work:
<xsl:value-of select="../*[local-name() = $link]"/>
It is preferable to target the nodes directly, but if they could be anywhere in the document, then you may resort to
<xsl:value-of select="//*[local-name() = $link]"/>
This is a last resort because it is potentially O(n) with respect to the number of nodes in the document.
Use <xsl:value-of select="*[local-name() = $link]"/>. If that does not help then consider to show a sample of the XML.

XSLT to call secondary XML based on first XML element/attribute

love the stuff - newbie Æthelred here
I have a XSLT 1.0 file pulling in a secondary XML (to a variable) to build a table
<xsl:variable name="table_values" select="document('./table_variants/external_table.xml')/xml/channel_1"/>
I then get the values i need from the variable, eg:
<xsl:value-of select="$table_values/monkey/tennis/#medals"/>
<xsl:value-of select="$table_values/monkey/tennis/#bananas"/>
What i want to do is have the first XML trigger/steer where to look for the table data.
I hoped i could, within the triggered XML, state the last part of the xpath - the 'channel_1' or 'channel_2',
<xsl:value-of select="xml/external_table_channel_to_use"/>
but apparently i cannot create a xpath on the fly like that
Please - What can i do?
What i want to do is have the first XML trigger/steer where to look
for the table data. I hoped i could, within the triggered XML, state
the last part of the xpath - the 'channel_1' or 'channel_2',
<xsl:value-of select="xml/external_table_channel_to_use"/> but
apparently i cannot create a xpath on the fly like that
Please - What can i do?
This can easily be done just extending the code that you already have.
Change this:
<xsl:variable name="table_values" select=
"document('./table_variants/external_table.xml')/xml/channel_1"/>
to this:
<xsl:variable name="table_values" select=
"document('./table_variants/external_table.xml')
/xml/*[name() = $channelName"/>
Needless to say, the variable (or global, external param) $channelName should have a value that is the (string) name of the element you want to use in the last location step of the XPath expression.