How to compare multiple nodes with XPath - xpath

Given the XML below, I am trying to write a XPath which returns only this match:
<TOKEN BEGIN="17" END="19" SENTENCE_BEGIN="0" SENTENCE_END="156" PROP="C" DOUBLE="YES" />
This is the input file:
<?xml version="1.0" encoding="utf-8"?>
<DOCUMENT>
<SECTION>
<PARAGRAPH TRACK="4">
<SENTENCE NAME="PRIMARY" COUNT="4">
<TOKEN BEGIN="9" END="11" SENTENCE_BEGIN="0" SENTENCE_END="156" />
<TOKEN BEGIN="32" END="37" SENTENCE_BEGIN="0" SENTENCE_END="156" />
<TOKEN BEGIN="167" END="169" SENTENCE_BEGIN="158" SENTENCE_END="316" />
<TOKEN BEGIN="210" END="215" SENTENCE_BEGIN="158" SENTENCE_END="316" />
</SENTENCE>
<SENTENCE NAME="SECONDARY" COUNT="2">
<TOKEN BEGIN="139" END="141" SENTENCE_BEGIN="0" SENTENCE_END="156" PROP="A" DOUBLE="YES" />
<TOKEN BEGIN="143" END="145" SENTENCE_BEGIN="0" SENTENCE_END="156" PROP="B" />
</SENTENCE>
<SENTENCE NAME="SECONDARY" COUNT="1">
<TOKEN BEGIN="17" END="19" SENTENCE_BEGIN="0" SENTENCE_END="156" PROP="C" DOUBLE="YES" />
</SENTENCE>
</PARAGRAPH>
</SECTION>
</DOCUMENT>
This is my xpath string:
//TOKEN [#DOUBLE] [#BEGIN <= ../../SENTENCE[#NAME='PRIMARY']/TOKEN/#END] [ (#SENTENCE_BEGIN = ../../SENTENCE[#NAME='PRIMARY']/TOKEN/#SENTENCE_BEGIN) and (#SENTENCE_END = ../../SENTENCE[#NAME='PRIMARY']/TOKEN/#SENTENCE_END) ]
I receive as output two nodes:
<TOKEN BEGIN="17" END="19" SENTENCE_BEGIN="0" SENTENCE_END="156" PROP="C" DOUBLE="YES" />
and
<TOKEN BEGIN="139" END="141" SENTENCE_BEGIN="0" SENTENCE_END="156" PROP="A" DOUBLE="YES" />
The result above is not correct because I want to check that:
the SENTENCE_BEGIN SENTENCE_END values of TOKEN[#DOULBE=YES] and
SENTENCE[#NAME="PRIMARY"]/TOKEN are the same, and
the value of the attribute BEGIN of TOKEN[#DOULBE=YES] is less than the BEGIN of SENTENCE[#NAME="PRIMARY"]/TOKEN
The result
<TOKEN BEGIN="139" END="141" SENTENCE_BEGIN="0" SENTENCE_END="156" PROP="A" DOUBLE="YES" />
is not correct, as the BEGIN value (139) is greater than the corresponding BEGIN values of the tokens with the same SENTENCE_BEGIN SENTENCE_END values:
<TOKEN BEGIN="9" END="11" SENTENCE_BEGIN="0" SENTENCE_END="156" />
<TOKEN BEGIN="32" END="37" SENTENCE_BEGIN="0" SENTENCE_END="156" />
How can I modify the code line to obtain the desired result?

Since you've tagged your question XSLT 1.0, I'd use XSLT instead of pure XPath. That way you can use xsl:key and also current().
Example...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="primary_tokens"
match="SENTENCE[#NAME='PRIMARY']/TOKEN"
use="concat(#SENTENCE_BEGIN,'|',#SENTENCE_END)"/>
<xsl:template match="/*">
<xsl:for-each select=".//TOKEN[#DOUBLE='YES'][key('primary_tokens',concat(#SENTENCE_BEGIN,'|',#SENTENCE_END))]">
<xsl:if test="key('primary_tokens',concat(#SENTENCE_BEGIN,'|',#SENTENCE_END))[#BEGIN > current()/#BEGIN]">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Fiddle: http://xsltfiddle.liberty-development.net/gWmuiJ8

Related

XPath preceding depth-first tree element

I have a tree structure:
<pages>
<page id="1">
<page id="1A" />
<page id="1B" />
</page>
<page id="2" />
<page id="3">
<page id="3A" />
<page id="3B" />
<page id="3C" />
</page>
</pages>
I want to emit "previous" links with XSLT. I have succeeded in creating "next" links.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/pages">
<pages>
<xsl:for-each select=".//page">
<page id="{#id}" prev="???" next="{(child::page|following::page)/#id}" />
</xsl:for-each>
</pages>
</xsl:template>
</xsl:stylesheet>
Expected output:
$ xsltproc test.xsl test.xml
<?xml version="1.0"?>
<pages>
<page id="1" prev="" next="1A"/>
<page id="1A" prev="1" next="1B"/>
<page id="1B" prev="1A" next="2"/>
<page id="2" prev="1B" next="3"/>
<page id="3" prev="2" next="3A"/>
<page id="3A" prev="3" next="3B"/>
<page id="3B" prev="3A" next="3C"/>
<page id="3C" prev="3B" next=""/>
</pages>
I think
<page id="{#id}" prev="{(ancestor::page[1]/#id|preceding::page[1]/#id)[last()]}" next="{(child::page|following::page)/#id}" />
is what you are looking for.
Another way you could look at it:
XSLT 1.0 (with a node-set() extension function)
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/pages">
<xsl:variable name="all-pages">
<xsl:copy-of select=".//page"/>
</xsl:variable>
<pages>
<xsl:for-each select="exsl:node-set($all-pages)/page">
<page id="{#id}" prev="{preceding-sibling::page[1]/#id}" next="{following-sibling::page[1]/#id}" />
</xsl:for-each>
</pages>
</xsl:template>
</xsl:stylesheet>

Using XSLT, is there a way to make the sort order of a nodeset match the order of a second nodeset?

I am acting on a set of documents that have a <DataTypes> area, which defines the structure of groups of primative datatypes and other structures, and a <Tags> area, which defines the values of instances of these datatypes.
Original XML
<?xml version="1.0" encoding="utf-8" ?>
<Program>
<DataTypes>
<DataType Name="String20">
<Member Name="LEN" DataType="INTEGER" Dimension="0" />
<Member Name="DATA" DataType="BYTE" Dimension="20" />
</DataType>
<DataType Name="UDT_Params">
<Member Name="InAlarm" DataType="BIT" Dimension="0" />
<Member Name="SetPoint" DataType="FLOAT" Dimension="0" />
<Member Name="DwellTime" DataType="INTEGER" Dimension="0" />
<Member Name="UserName" DataType="String20" Dimension="0" />
</DataType>
</DataTypes>
<Tags>
<Tag Name="MyParameters" DataType="UDT_Params">
<Data Name="InAlarm" DataType="BIT" Value="0" />
<Data Name="SetPoint" DataType="FLOAT" Value="4.5" />
<Data Name="DwellTime" DataType="INTEGER" Value="10" />
<Data Name="UserName" DataType="String20">
<Data Name="LEN" DataType="INTEGER" Value="3" />
<Data Name="DATA" DataType="String20" > <!--The system I'm working in shows strings as arrays of BYTES in DataType, -->
Bob <!--but calls them out as Strings when they are used as tags. I cannot change it.-->
</Data>
</Data>
</Tag>
</Tags>
</Program>
Stylesheet
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!--Packing algorithm. Works fine on datatypes, but not on Tags.-->
<xsl:template name="pack-nodes">
<xsl:param name="nodes" />
<!--Omitted for brevity-->
</xsl:template>
<!--Pack DataTypes-->
<xsl:variable name="datatypes-packed">
<xsl:call-template name="pack-nodes">
<xsl:with-param name="nodes" select="/Program/DataTypes/DataType" />
</xsl:call-template>
</xsl:variable>
<!--Write DataTypes to output.-->
<xsl:template match="/Program/DataTypes">
<xsl:copy>
<xsl:for-each select="msxsl:node-set($datatypes-packed)">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<!--Pack tags-->
<xsl:variable name="tags-packed">
<xsl:call-template name="pack-nodes">
<xsl:with-param name="nodes" select="/Program/Tags/Tag" />
</xsl:call-template>
</xsl:variable>
<!--Write Tags to output.-->
<xsl:template match="/Program/Tags">
<xsl:copy>
<xsl:for-each select="msxsl:node-set($tags-packed)">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="utf-8"?>
<Program>
<DataTypes>
<DataType Name="String20">
<Member Name="LEN" DataType="INTEGER" Dimension="0"/>
<Member Name="DATA" DataType="BYTE" Dimension="20"/>
</DataType>
<DataType Name="Parameters" DataType="UDT_Params">
<Member Name="UserName" DataType="String20" Dimension="0"/>
<Member Name="SetPoint" DataType="FLOAT" Dimension="0"/>
<Member Name="DwellTime" DataType="INTEGER" Dimension="0"/>
<Member Name="InAlarm" DataType="BIT" Dimension="0"/>
</DataType>
</DataTypes>
<Tags>
<Tag Name="MyParameters" DataType="UDT_Params">
<Data Name="UserName" DataType="String20">
<Data Name="DATA" DataType="String20"> <!--Note that DATA comes before LEN -->
Bob
</Data>
<Data Name="LEN" DataType="INTEGER" Value="3"/>
</Data>
<Data Name="SetPoint" DataType="FLOAT" Value="4.5"/>
<Data Name="DwellTime" DataType="INTEGER" Value="10"/>
<Data Name="InAlarm" DataType="BIT" Value="0"/>
</Tag>
</Tags>
</Program>
My operations on the DataTypes section adds nodes and changes the node order. For the section to work correctly, the tag elements must match the contents and order of their respective datatypes, exactly.
If I keep a variable in memory of the final state of the DataSet nodes, is there a simple way to have the tag nodes look up their dataset (via the Structure and StructureMember #DataSet attributes, and sort their members accordingly?
I'm having trouble figuring out where to start.
NOTE: Transformation must be in XSLT 1.0. I'm using .Net, and don't want to introduce a lot of dependencies on external libraries.
It's a bit tricky in XSLT 1.0 (isn't everything?) but a technique that sometimes works is to construct a variable $tokens containing the list of tokens in the required order, for example "|Description|Name|ProcessEntityIndex|Severity|...", and then sort on select="string-length(substring-before($tokens, concat('|',#Name)))".
Using Michael Kay's suggesting for sorting, I ended up with the following:
Stylesheet
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<!--Skipped to new part -->
<xsl:template name="sort-by-datatype">
<xsl:param name="tags" />
<xsl:param name="datatypes" />
<xsl:for-each select="msxsl:node-set($tags)">
<!--First, do an edge-check.-->
<xsl:variable name="edge">
<xsl:call-template name="edge-check">
<xsl:with-param name="n1" select="." />
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<!--No children, nothing to sort. Just do a deep copy.-->
<xsl:when test="$edge = 'true'">
<xsl:copy-of select="."/>
</xsl:when>
<xsl:otherwise>
<!--Search for datatype in the DataTypes nodeset, and use it to create a list of members in order.-->
<xsl:variable name="tag-datatype" select="./#DataType" />
<xsl:variable name="tokens-untrimmed">
<xsl:for-each select="msxsl:node-set($datatypes)/DataType[#Name = $tag-datatype]/Member">
<xsl:value-of select="concat(' | ', #Name)"/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="tokens" select="substring-after($tokens-untrimmed, '|')" />
<xsl:choose>
<!--If tokens string is empty (maybe because we couldn't find the datatype?), just copy the tag as it is, then recurse.-->
<xsl:when test="string-length($tokens) = 0">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:call-template name="sort-by-datatype">
<xsl:with-param name="tags" select="." />
<xsl:with-param name="datatypes" select="$datatypes" />
</xsl:call-template>
</xsl:copy>
</xsl:when>
<!--Otherwise, sort members in the same order as datatype-->
<xsl:otherwise>
<!--Build variable with sorted members.-->
<xsl:variable name="tag-members-sorted">
<xsl:for-each select="*">
<xsl:sort data-type="number" order="ascending" select="string-length(substring-before($tokens, concat(' | ', #Name)))" /> <!--Magic Sort Algorithm-->-->
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<!--Copy the parent node node.-->
<xsl:copy>
<xsl:copy-of select="#*"/>
<!--Now sort and copy the children.-->
<xsl:for-each select="msxsl:node-set($tag-members-sorted)/*">
<!--Recurse. This copies the child node.-->
<xsl:call-template name="sort-by-datatype">
<xsl:with-param name="tags" select="." />
<xsl:with-param name="datatypes" select="$datatypes" />
</xsl:call-template>
</xsl:for-each>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
<!--Pack tags-->
<xsl:variable name="tags-packed">
<xsl:call-template name="sort-by-datatype">
<xsl:with-param name="tags" select="/Program/Tags/Tag" />
<xsl:with-param name="datatypes" select="$datatypes-packed" />
</xsl:call-template>
</xsl:variable>
<!--Skipped for brevity-->
</xsl:stylesheet>

XSLT scheme for create a table whose header and rows are separate and hierarchical

I have a problem in order to convert a file that looks like this into a "flat" table
These files are uploaded during the exchange. There are many of them, but they have one template. First a description of the headers, then only the values.
<?xml version="1.0" encoding="UTF-8"?>
<extdata user="test">
<scheme name="CRMOrder" request="get" success="true">
<data>
<s>
<d name="CRMOrder">
<f name="ActionDate" type="Date" />
<f name="CRMClientId" type="String" />
<f name="CreateId" type="Date" />
<f name="StatusId" type="String" />
<f name="Summa" type="Decimal" />
<f name="WareHouseId" type="String" />
<d name="CRMOrderLine">
<f name="Price" type="Decimal" />
<f name="LineNumber" type="Integer" />
<f name="Quantity" type="Decimal" />
<f name="Discount" type="Integer" />
</d>
<d name="CRMOrderOption">
<f name="OptionTypeId" type="String" />
<f name="Value" type="String" />
<f name="OptionTypeName" type="String" />
</d>
</d>
</s>
<o>
<d name="CRMOrder">
<r>
<f>2022-01-11T00:00:00</f>
<f>69244</f>
<f>2142256774</f>
<f>Accepted</f>
<f>2318.0600</f>
<f>62</f>
<d name="CRMOrderLine">
<r>
<f>64.7800</f>
<f>1</f>
<f>18.0000</f>
<f>62</f>
</r>
<d name="CRMOrderOption">
<r>
<f>2022-01-10T00:00:00</f>
<f>Comment</f>
<f>1</f>
</r>
</d>
</d>
</r>
</d>
</o>
</data>
</scheme>
</extdata>
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" />
<xsl:template match="/extdata/scheme/data">
<ValueTable xmlns="http://v8.1c.ru/8.1/data/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<-- get the total number of columns -->
<xsl:variable name="recordColumn" select="count(s/d/f/#name) + count(s/d/d/f/#name)" />
<-- I didn’t understand how to create headers in one cycle, so there are two cycles with the same content -->
<xsl:for-each select="s/d/f">
<column>
<Name xsi:type="xs:string">
<xsl:value-of select="#name" />
</Name>
<ValueType>
<xsl:if test="#type= 'String'">
<Type>xs:string</Type>
<StringQualifiers>
<Length>150</Length>
<AllowedLength>Variable</AllowedLength>
</StringQualifiers>
</xsl:if>
<xsl:if test="#type= 'Date'">
<Type>xs:dateTime</Type>
<DateQualifiers>
<DateFractions>DateTime</DateFractions>
</DateQualifiers>
</xsl:if>
<xsl:if test="#type= 'Decimal' or #type= 'Currency'">
<Type>xs:decimal</Type>
<NumberQualifiers>
<Digits>20</Digits>
<FractionDigits>4</FractionDigits>
<AllowedSign>Any</AllowedSign>
</NumberQualifiers>
</xsl:if>
<xsl:if test="#type= 'Integer'">
<Type>xs:decimal</Type>
<NumberQualifiers>
<Digits>20</Digits>
<FractionDigits>0</FractionDigits>
<AllowedSign>Any</AllowedSign>
</NumberQualifiers>
</xsl:if>
</ValueType>
</column>
</xsl:for-each>
<xsl:for-each select="s/d/d/f">
<column>
<Name xsi:type="xs:string">
<xsl:value-of select="#name" />
</Name>
<ValueType>
<xsl:if test="#type= 'String'">
<Type>xs:string</Type>
<StringQualifiers>
<Length>150</Length>
<AllowedLength>Variable</AllowedLength>
</StringQualifiers>
</xsl:if>
<xsl:if test="#type= 'Date'">
<Type>xs:dateTime</Type>
<DateQualifiers>
<DateFractions>DateTime</DateFractions>
</DateQualifiers>
</xsl:if>
<xsl:if test="#type= 'Decimal' or #type= 'Currency'">
<Type>xs:decimal</Type>
<NumberQualifiers>
<Digits>20</Digits>
<FractionDigits>4</FractionDigits>
<AllowedSign>Any</AllowedSign>
</NumberQualifiers>
</xsl:if>
<xsl:if test="#type= 'Integer'">
<Type>xs:decimal</Type>
<NumberQualifiers>
<Digits>20</Digits>
<FractionDigits>0</FractionDigits>
<AllowedSign>Any</AllowedSign>
</NumberQualifiers>
</xsl:if>
</ValueType>
</column>
</xsl:for-each>
<xsl:for-each select="o/d/r/f">
<-- Problem is here -->
<xsl:variable name="counter" select="position()" />
<xsl:if test="$counter = 1">
<xsl:text disable-output-escaping="yes"><row></xsl:text>
</xsl:if>
<xsl:if test="$counter mod $recordColumn = 0">
<xsl:text disable-output-escaping="yes"></row></xsl:text>
<xsl:if test="$counter != last() ">
<xsl:text disable-output-escaping="yes"><row></xsl:text>
</xsl:if>
</xsl:if>
<xsl:if test="$counter mod $recordColumn != 0">
<Value>
<xsl:value-of select="." />
</Value>
</xsl:if>
</xsl:for-each>
</ValueTable>
</xsl:template>
</xsl:stylesheet>
How do i get the table from this file. The beginning and end of the row about the string and value probably did not look very beautiful, but it works. There are now 12 columns, as a result, my table row contains only 6 first values.
Expected result
<ValueTable xmlns="http://v8.1c.ru/8.1/data/core"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<column>
<Name xsi:type="xs:string">ActionDate</Name>
<ValueType>
<Type>xs:dateTime</Type>
<DateQualifiers>
<DateFractions>DateTime</DateFractions>
</DateQualifiers>
</ValueType>
</column>
<column>
<Name xsi:type="xs:string">CRMClientId</Name>
<ValueType>
<Type>xs:string</Type>
<StringQualifiers>
<Length>150</Length>
<AllowedLength>Variable</AllowedLength>
</StringQualifiers>
</ValueType>
</column>
<column>
<Name xsi:type="xs:string">CreateId</Name>
<ValueType>
<Type>xs:dateTime</Type>
<DateQualifiers>
<DateFractions>DateTime</DateFractions>
</DateQualifiers>
</ValueType>
</column>
<column>
<Name xsi:type="xs:string">StatusId</Name>
<ValueType>
<Type>xs:string</Type>
<StringQualifiers>
<Length>150</Length>
<AllowedLength>Variable</AllowedLength>
</StringQualifiers>
</ValueType>
</column>
<column>
<Name xsi:type="xs:string">Summa</Name>
<ValueType>
<Type>xs:decimal</Type>
<NumberQualifiers>
<Digits>20</Digits>
<FractionDigits>4</FractionDigits>
<AllowedSign>Any</AllowedSign>
</NumberQualifiers>
</ValueType>
</column>
<column>
<Name xsi:type="xs:string">WareHouseId</Name>
<ValueType>
<Type>xs:string</Type>
<StringQualifiers>
<Length>150</Length>
<AllowedLength>Variable</AllowedLength>
</StringQualifiers>
</ValueType>
</column>
<column>
<Name xsi:type="xs:string">Price</Name>
<ValueType>
<Type>xs:decimal</Type>
<NumberQualifiers>
<Digits>20</Digits>
<FractionDigits>4</FractionDigits>
<AllowedSign>Any</AllowedSign>
</NumberQualifiers>
</ValueType>
</column>
<column>
<Name xsi:type="xs:string">LineNumber</Name>
<ValueType>
<Type>xs:decimal</Type>
<NumberQualifiers>
<Digits>20</Digits>
<FractionDigits>0</FractionDigits>
<AllowedSign>Any</AllowedSign>
</NumberQualifiers>
</ValueType>
</column>
<column>
<Name xsi:type="xs:string">Quantity</Name>
<ValueType>
<Type>xs:decimal</Type>
<NumberQualifiers>
<Digits>20</Digits>
<FractionDigits>4</FractionDigits>
<AllowedSign>Any</AllowedSign>
</NumberQualifiers>
</ValueType>
</column>
<column>
<Name xsi:type="xs:string">Discount</Name>
<ValueType>
<Type>xs:decimal</Type>
<NumberQualifiers>
<Digits>20</Digits>
<FractionDigits>0</FractionDigits>
<AllowedSign>Any</AllowedSign>
</NumberQualifiers>
</ValueType>
</column>
<Name xsi:type="xs:string">OptionTypeId</Name>
<ValueType>
<Type>xs:string</Type>
<StringQualifiers>
<Length>150</Length>
<AllowedLength>Variable</AllowedLength>
</StringQualifiers>
</ValueType>
</column>
<column>
<Name xsi:type="xs:string">Value</Name>
<ValueType>
<Type>xs:string</Type>
<StringQualifiers>
<Length>150</Length>
<AllowedLength>Variable</AllowedLength>
</StringQualifiers>
</ValueType>
</column>
<column>
<Name xsi:type="xs:string">OptionTypeName</Name>
<ValueType>
<Type>xs:string</Type>
<StringQualifiers>
<Length>150</Length>
<AllowedLength>Variable</AllowedLength>
</StringQualifiers>
</ValueType>
</column>
<row>
<Value>2022-01-11T00:00:00</Value>
<Value>69244</Value>
<Value>2142256774</Value>
<Value>Accepted</Value>
<Value>2318.0600</Value>
<Value>62</Value>
<Value>64.7800</Value>
<Value>1</Value>
<Value>18.0000</Value>
<Value>62</Value>
<Value>2022-01-10T00:00:00</Value>
<Value>Comment</Value>
<Value>1</Value>
</row>
</ValueTable>
The given example is somewhat ambiguous. Assuming that s is the header row, and that o is a data row containing exactly one value for each column in the header row, and in the same order (IOW, that the input is already a "flat" table), the result shown could be produced using:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://v8.1c.ru/8.1/data/core"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/extdata">
<ValueTable>
<!-- header -->
<xsl:for-each select="scheme/data/s//f">
<column>
<Name xsi:type="xs:string">
<xsl:value-of select="#name"/>
</Name>
<ValueType>
<xsl:choose>
<xsl:when test="#type= 'String'">
<Type>xs:string</Type>
<StringQualifiers>
<Length>150</Length>
<AllowedLength>Variable</AllowedLength>
</StringQualifiers>
</xsl:when>
<xsl:when test="#type='Date'">
<Type>xs:dateTime</Type>
<DateQualifiers>
<DateFractions>DateTime</DateFractions>
</DateQualifiers>
</xsl:when>
<xsl:when test="#type='Decimal' or #type='Currency'">
<Type>xs:decimal</Type>
<NumberQualifiers>
<Digits>20</Digits>
<FractionDigits>4</FractionDigits>
<AllowedSign>Any</AllowedSign>
</NumberQualifiers>
</xsl:when>
<xsl:when test="#type='Integer'">
<Type>xs:decimal</Type>
<NumberQualifiers>
<Digits>20</Digits>
<FractionDigits>0</FractionDigits>
<AllowedSign>Any</AllowedSign>
</NumberQualifiers>
</xsl:when>
</xsl:choose>
</ValueType>
</column>
</xsl:for-each>
<!-- data -->
<xsl:for-each select="scheme/data/o">
<row>
<xsl:for-each select=".//f">
<Value>
<xsl:value-of select="."/>
</Value>
</xsl:for-each>
</row>
</xsl:for-each>
</ValueTable>
</xsl:template>
</xsl:stylesheet>
Unfortunately your provided XML is invalid. And you don't provide an example how the output should look like.
But however, just a quick, hopefully helpful answer to your main problem. How to avoid repeating the whole header definition or other stylesheet parts.
You can OR multiple paths in XPath
<xsl:for-each select="s/d/f | s/d/d/f">

XSLT 2.0 Sort by variable from another style sheet

I need to create XML that is sorted by numeric values I pull from another XSLT, which I use as a cross reference. The below source XML (source.xml) has four alpha characters at Partner/Header/#whse.
<?xml version="1.0" encoding="UTF-8"?>
<Partner partnerId="TradingPartner1">
<Header whse="NCCH" >
<Contract claimNumber="00000000" />
</Header>
<Header whse="TXAU" >
<Contract claimNumber="00000000" />
</Header>
<Header whse="LANO" >
<Contract claimNumber="00000000" />
</Header>
<Header whse="MIGR">
<Contract claimNumber="00000000" />
</Header>
<Header whse="TXHO">
<Contract claimNumber="00000000" />
</Header>
</Partner>
I need to cross reference the alpha characters to get the DUNS+4.
I use this XSLT (Duns_config.xslt) to get the DUNS.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="SHIPTODUNS">
<xsl:param name="Whse" />
<xsl:choose>
<xsl:when test="$Whse = 'LANO'"><xsl:value-of select="'0044893600101'" /></xsl:when>
<xsl:when test="$Whse = 'TXHO'"><xsl:value-of select="'0044893600103'" /></xsl:when>
<xsl:when test="$Whse = 'TXAU'"><xsl:value-of select="'0044893600105'" /></xsl:when>
<xsl:when test="$Whse = 'NCCH'"><xsl:value-of select="'0044893600214'" /></xsl:when>
<xsl:when test="$Whse = 'MIGR'"><xsl:value-of select="'8949713340601'" /></xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
In the main XSLT (Transaction.xslt), I include the Duns_config.xslt and call SHIPTODUNS putting the data in the variable $headerDuns. I then get the last three digits of the DUNS+4 and put them into the variable $varWhse and try to sort by this variable:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:include href="Duns_config.xslt"/>
<xsl:template match="Partner">
<Partner partnerId="{./#partnerId}">
<xsl:apply-templates select="./Header" />
</Partner>
</xsl:template>
<xsl:template match="Header">
<xsl:variable name="headerDuns">
<xsl:call-template name = "SHIPTODUNS">
<xsl:with-param name="Whse" select="./#whse" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="varWhse">
<xsl:value-of select="substring($headerDuns, 11, 3)" />
</xsl:variable>
<xsl:for-each select="current()">
<xsl:sort select="$varWhse" />
<transaction varwhse="{$varWhse}">
<duns number="{$headerDuns}" />
</transaction>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The output is not sorted by the $varWhse:
<?xml version="1.0" encoding="UTF-8"?>
<Partner partnerId="TradingPartner1">
<transaction varwhse="214">
<duns number="0044893600214"/>
</transaction>
<transaction varwhse="105">
<duns number="0044893600105"/>
</transaction>
<transaction varwhse="101">
<duns number="0044893600101"/>
</transaction>
<transaction varwhse="601">
<duns number="8949713340601"/>
</transaction>
<transaction varwhse="103">
<duns number="0044893600103"/>
</transaction>
</Partner>
I am wanting the data to come out like this:
<?xml version="1.0" encoding="UTF-8"?>
<Partner partnerId="TradingPartner1">
<transaction varwhse="101">
<duns number="0044893600101"/>
</transaction>
<transaction varwhse="103">
<duns number="0044893600103"/>
</transaction>
<transaction varwhse="105">
<duns number="0044893600105"/>
</transaction>
<transaction varwhse="214">
<duns number="0044893600214"/>
</transaction>
<transaction varwhse="601">
<duns number="8949713340601"/>
</transaction>
</Partner>
Anyone see what I am doing wrong or have another way? This is my first post on this site. It's a lot of information and I hope it makes sense.
One of the problems with your code is that the xsl:sort in your Header template is not the first instruction; xsl:sort has to be the first instruction. Another problem is that the sorting appears in the Header template, but it would have to be applied on an upper level. To correct these mistakes, some restructuring of your templates has to be performed.
For the following solution I chose to put all of the logic into one template - with two steps:
Fill a variable named mapping with a sub-data-structure with item elements.
Iterate over that variable with a sorted xsl:for-each and output the transaction elements.
The result looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:include href="Duns_config.xslt"/>
<xsl:template match="Partner">
<Partner partnerId="{./#partnerId}">
<xsl:variable name="mapping">
<xsl:for-each select="Header">
<xsl:variable name="headerDuns">
<xsl:call-template name = "SHIPTODUNS">
<xsl:with-param name="Whse" select="#whse" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="varWhse">
<xsl:value-of select="substring($headerDuns, 11, 3)" />
</xsl:variable>
<item>
<whse><xsl:value-of select="$varWhse" /></whse>
<duns><xsl:value-of select="$headerDuns" /></duns>
</item>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="$mapping/item">
<xsl:sort select="whse" /> <!-- See how xsl:sort is the first instruction -->
<transaction varwhse="{whse}">
<duns number="{duns}"/>
</transaction>
</xsl:for-each>
</Partner>
</xsl:template>
</xsl:stylesheet>
And its output is:
<?xml version="1.0" encoding="UTF-8"?>
<Partner partnerId="TradingPartner1">
<transaction varwhse="101">
<duns number="0044893600101"/>
</transaction>
<transaction varwhse="103">
<duns number="0044893600103"/>
</transaction>
<transaction varwhse="105">
<duns number="0044893600105"/>
</transaction>
<transaction varwhse="214">
<duns number="0044893600214"/>
</transaction>
<transaction varwhse="601">
<duns number="8949713340601"/>
</transaction>
</Partner>
Which is as desired.
Apply sorting after getting the result of choose. What happen is you are still generating the xml output, but you are sorting it on the Header item level.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:include href="Duns_config.xslt"/>
<xsl:template match="Partner">
<xsl:variable name="partial">
<PartnerTemp partnerId="{./#partnerId}">
<xsl:apply-templates select="./Header" />
</PartnerTemp>
</xsl:variable>
<xsl:apply-templates select="$partial"></xsl:apply-templates>
</xsl:template>
<xsl:template match="PartnerTemp">
<Partner partnerId="{./#partnerId}">
<xsl:perform-sort select="transaction">
<xsl:sort select="#varwhse"/>
</xsl:perform-sort>
</Partner>
</xsl:template>
<xsl:template match="Header">
<xsl:variable name="headerDuns">
<xsl:call-template name = "SHIPTODUNS">
<xsl:with-param name="Whse" select="./#whse" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="varWhse">
<xsl:value-of select="substring($headerDuns, 11, 3)" />
</xsl:variable>
<xsl:for-each select="current()">
<transaction varwhse="{$varWhse}">
<duns number="{$headerDuns}" />
</transaction>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

How to access variable as parameter from another template XSLT

Hello I have one problem, I have 2 variables(min_value and max_value) they are in different templates MIN and MAX, in MAX I want to calculate the difference between max_value and min_value but the result is NAN since min_value values are not transferred from one template to another.
<xsl:template match="MAX">
<xsl:param name= "min_value"/>
<xsl:variable name="max_value" select= "SHARE_RATE"/>
<span class="max_rate">
MAX: <xsl:apply-templates select="SHARE_RATE"/>
</span>
<br/>
<span class= "diff">
Diff: (<xsl:value-of select="$max_value - $min_value"/>)
</span>
</xsl:template>
<xsl:template match="MIN">
<xsl:variable name="min_value" select="SHARE_RATE"/>
<span class="SHARE_RATE">
MIN: <xsl:apply-templates select="SHARE_RATE"/>
</span>
<xsl:apply-templates select="MAX">
<xsl:with-param name="min_value" select= "$min_value"/>
</xsl:apply-templates>
</xsl:template>
UPDATE
<page shareid="%" min_rate="%" max_rate="%" skip="0">
<TRANSACTIONS>
<MIN num="1">
<SHAREID>0</SHAREID>
<SHARE_RATE>1200</SHARE_RATE>
</MIN>
<MIN num="2">
<SHAREID>1</SHAREID>
<SHARE_RATE>4200</SHARE_RATE>
</MIN>
<MIN num="3">
<SHAREID>2</SHAREID>
<SHARE_RATE>1600</SHARE_RATE>
</MIN>
<MIN num="4">
<SHAREID>3</SHAREID>
<SHARE_RATE>6100</SHARE_RATE>
</MIN>
<MIN num="5">
<SHAREID>4</SHAREID>
<SHARE_RATE>550</SHARE_RATE>
</MIN>
<MIN num="6">
<SHAREID>5</SHAREID>
<SHARE_RATE>420</SHARE_RATE>
</MIN>
<MIN num="7">
<SHAREID>6</SHAREID>
<SHARE_RATE>2000</SHARE_RATE>
</MIN>
</TRANSACTIONS>
<TRANSACTIONS>
<MAX num="1">
<SHAREID>0</SHAREID>
<SHARE_RATE>2100</SHARE_RATE>
</MAX>
<MAX num="2">
<SHAREID>1</SHAREID>
<SHARE_RATE>5200</SHARE_RATE>
</MAX>
<MAX num="3">
<SHAREID>2</SHAREID>
<SHARE_RATE>2000</SHARE_RATE>
</MAX>
<MAX num="4">
<SHAREID>3</SHAREID>
<SHARE_RATE>7000</SHARE_RATE>
</MAX>
<MAX num="5">
<SHAREID>4</SHAREID>
<SHARE_RATE>1000</SHARE_RATE>
</MAX>
<MAX num="6">
<SHAREID>5</SHAREID>
<SHARE_RATE>2520</SHARE_RATE>
</MAX>
<MAX num="7">
<SHAREID>5</SHAREID>
<SHARE_RATE>2520</SHARE_RATE>
</MAX>
</TRANSACTIONS>
</page>
UPDATE 2
<?xml version="1.0" encoding="ISO-8859-2"?>
<?xml-stylesheet type="text/xsl" href="share.xsl"?>
<page connection="labor"
xmlns:xsql="urn:oracle-xsql"
shareid="%"
min_rate= "%"
max_rate= "%"
skip="0">
<xsql:query rowset-element="TRANSACTIONS"
row-element="MIN"
skip-rows="{#skip}"
max-rows="{#max-rows}"
bind-params="shareid min_rate ">
SELECT
f77inq.shares.shareid, A.share_rate
FROM
f77inq.trans A
INNER JOIN f77inq.shares ON A.shareid = f77inq.shares.shareid
WHERE A.shareid LIKE ?
AND A.share_rate=
(
SELECT
MIN(share_rate)
FROM
f77inq.trans B
WHERE
B.shareid = A.shareid
)
AND A.share_rate LIKE?
</xsql:query>
<xsql:query rowset-element="TRANSACTIONS"
row-element="MAX"
skip-rows="{#skip}"
max-rows="{#max-rows}"
bind-params="shareid">
SELECT
f77inq.shares.shareid, A.share_rate
FROM
f77inq.trans A
INNER JOIN f77inq.shares ON A.shareid = f77inq.shares.shareid
WHERE A.shareid LIKE ?
AND A.share_rate=
(
SELECT
MAX(share_rate)
FROM
f77inq.trans B
WHERE
B.shareid = A.shareid
)
</xsql:query>
</page>
I would suggest you use a key to link the MIN and MAX values, based on a common SHAREID. Here's an example that uses XML output for better clarity:
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:strip-space elements="*"/>
<xsl:key name="max" match="MAX" use="SHAREID" />
<xsl:template match="/page">
<root>
<xsl:apply-templates select="TRANSACTIONS/MIN"/>
</root>
</xsl:template>
<xsl:template match="MIN">
<xsl:variable name="id" select="SHAREID" />
<xsl:variable name="min" select="SHARE_RATE" />
<xsl:variable name="max" select="key('max', $id)/SHARE_RATE" />
<share id="{$id}">
<min><xsl:value-of select="$min"/></min>
<max><xsl:value-of select="$max"/></max>
<diff><xsl:value-of select="$max - $min"/></diff>
</share>
</xsl:template>
</xsl:stylesheet>
Applied to your example input, the result is:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<share id="0">
<min>1200</min>
<max>2100</max>
<diff>900</diff>
</share>
<share id="1">
<min>4200</min>
<max>5200</max>
<diff>1000</diff>
</share>
<share id="2">
<min>1600</min>
<max>2000</max>
<diff>400</diff>
</share>
<share id="3">
<min>6100</min>
<max>7000</max>
<diff>900</diff>
</share>
<share id="4">
<min>550</min>
<max>1000</max>
<diff>450</diff>
</share>
<share id="5">
<min>420</min>
<max>2520</max>
<diff>2100</diff>
</share>
<share id="6">
<min>2000</min>
<max>2520</max>
<diff>520</diff>
</share>
</root>
ok, what I understand is, that you want to match MIN/MAX values with the same SHAREID. In this case the following xslt might do what you need:
<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:template match="MAX">
<xsl:variable name="id" select="SHAREID"/>
<!-- or '0' handles the case where no coresponding MIN/SHARE_RATE is found -->
<xsl:variable name="min_value" select="//MIN[SHAREID=$id]/SHARE_RATE or '0'"/>
<xsl:variable name="max_value" select= "SHARE_RATE"/>
<span class="max_rate">
MAX: <xsl:value-of select="$max_value"/>
</span>
<br/>
<span class= "diff">
Diff: (<xsl:value-of select="$max_value - $min_value"/>)
</span>
</xsl:template>
<!-- MAX does it all, so ignore output of MIN -->
<xsl:template match="MIN"/>
<xsl:template match="*">
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>

Resources