I need an xsl that take the maximum of a "item_revision_id" from any "object_name".
From this input ........
<rows>
<row>
<row_element property_name="object_name">pluto</row_element>
<row_element property_name="description">description of version 00</row_element>
<row_element property_name="item_revision_id">00</row_element>
</row>
<row>
<row_element property_name="object_name">pluto</row_element>
<row_element property_name="description">description of version 01</row_element>
<row_element property_name="item_revision_id">01</row_element>
</row>
<row>
<row_element property_name="object_name">pippo</row_element>
<row_element property_name="description">description of version 02</row_element>
<row_element property_name="item_revision_id">02</row_element>
</row>
<row>
<row_element property_name="object_name">pippo</row_element>
<row_element property_name="description">description of version 00</row_element>
<row_element property_name="item_revision_id">00</row_element>
</row>
</rows>
the desired output is
<?xml version="1.0" encoding="Windows-1252"?>
<rows>
<row>
<row_element property_name="object_name">pluto</row_element>
<row_element property_name="description">description of version 01</row_element>
<row_element property_name="item_revision_id">01</row_element>
</row>
<row>
<row_element property_name="object_name">pippo</row_element>
<row_element property_name="description">description of version 02</row_element>
<row_element property_name="item_revision_id">02</row_element>
</row>
</rows>
I want to get the maximum row element not to create a new one with mixed values
<?xml version="1.0" encoding="Windows-1252"?>
<rows>
<row><!-- this is right -->
<row_element property_name="object_name">pluto</row_element>
<row_element property_name="description">description of version 01</row_element>
<row_element property_name="item_revision_id">01</row_element>
</row>
<row><!-- this is wrong -->
<row_element property_name="object_name">pippo</row_element>
<row_element property_name="description">description of version 00</row_element>
<row_element property_name="item_revision_id">02</row_element>
</row>
</rows>
any help ?
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:key name="kRowByObjName" match="row" use="*[#property_name='object_name']"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"row[not(generate-id()
=
generate-id(key('kRowByObjName', *[#property_name='object_name'])[1])
)]"/>
<xsl:template match="*[#property_name='item_revision_id']/text()">
<xsl:for-each select="key('kRowByObjName', ../../*[#property_name='object_name'])">
<xsl:sort select="*[#property_name='item_revision_id']"
data-type="number" order="descending"/>
<xsl:if test="position()=1">
<xsl:value-of select="*[#property_name='item_revision_id']"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<rows>
<row>
<row_element property_name="object_name">pluto</row_element>
<row_element property_name="item_revision_id">00</row_element>
</row>
<row>
<row_element property_name="object_name">pluto</row_element>
<row_element property_name="item_revision_id">01</row_element>
</row>
<row>
<row_element property_name="object_name">pippo</row_element>
<row_element property_name="item_revision_id">02</row_element>
</row>
<row>
<row_element property_name="object_name">pippo</row_element>
<row_element property_name="item_revision_id">00</row_element>
</row>
</rows>
produces the wanted, correct result:
<rows>
<row>
<row_element property_name="object_name">pluto</row_element>
<row_element property_name="item_revision_id">01</row_element>
</row>
<row>
<row_element property_name="object_name">pippo</row_element>
<row_element property_name="item_revision_id">02</row_element>
</row>
</rows>
II. XSLT 2.0 solution:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kRowByObjName" match="row" use="*[#property_name='object_name']"/>
<xsl:template match="/*">
<rows>
<xsl:for-each-group select="row" group-by="*[#property_name='object_name']">
<row>
<xsl:sequence select="*[#property_name='object_name']"/>
<xsl:sequence select=
"current-group()
/*[#property_name='item_revision_id'
and
. = max(current-group()/*[#property_name='item_revision_id']/number())
]"/>
</row>
</xsl:for-each-group>
</rows>
</xsl:template>
</xsl:stylesheet>
Related
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">
I've been trying to figure out how to get WSO2's ESB to make calls to two different APIs and combine their results into a single response, and running into nothing but trouble. At its most basic, I've got two backends I'm making requests to that respond something like this:
http://example.com/items:
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<response xmlns="http://example.com/response">
<query name="items" xmlns="http://example.com/query">
<row>
<id>1</id>
<name>Item 1</name>
</row>
<row>
<id>2</id>
<name>Item 2</name>
</row>
</query>
</response>
</soapenv:Body>
</soapenv:Envelope>
http://example.com/parts:
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<response xmlns="http://example.com/response">
<query name="parts" xmlns="http://example.com/query">
<row>
<id>1</id>
<part>Part 1.1</part>
</row>
<row>
<id>1</id>
<part>Part 1.2</part>
</row>
<row>
<id>1</id>
<part>Part 1.3</part>
</row>
<row>
<id>2</id>
<part>Part 2.1</part>
</row>
<row>
<id>2</id>
<part>Part 2.2</part>
</row>
</query>
</response>
</soapenv:Body>
</soapenv:Envelope>
I'd like to request both of those, then combine their results to look something like this:
<items>
<item>
<id>1</id>
<name>Item 1</name>
<parts>
<part>
<id>1</id>
<name>Part 1.1</name>
</part>
<part>
<id>1</id>
<name>Part 1.2</name>
</part>
<part>
<id>1</id>
<name>Part 1.3</name>
</part>
</parts>
</item>
<item>
<id>2</id>
<name>Item 2</name>
<parts>
<part>
<id>2</id>
<name>Part 2.1</name>
</part>
<part>
<id>2</id>
<name>Part 2.2</name>
</part>
</parts>
</item>
</items>
Basically, every response from both APIs has a list of rows, each of which contains an id element. The ids in the call to /items are unique within that response, and each row in the response from parts has an id that ties it to a row from /items.
I've got the following API definition in the ESB:
<?xml version="1.0" encoding="UTF-8"?>
<api context="/item_list" name="ItemList" xmlns="http://ws.apache.org/ns/synapse">
<resource methods="POST" uri-template="/">
<inSequence>
<header name="Content-Type" scope="transport" value="text/xml; charset=utf-8"/>
<clone>
<target>
<sequence>
<send>
<endpoint>
<address format="soap11" uri="http://example.com/items"/>
</endpoint>
</send>
</sequence>
</target>
<target>
<sequence>
<send>
<endpoint>
<address format="soap11" uri="http://example.com/parts"/>
</endpoint>
</send>
</sequence>
</target>
</clone>
</inSequence>
<outSequence>
<aggregate>
<correlateOn expression="//*[name()='response']/*[name()='query']/*[name()='row']/*[name()='id']" />
<completeCondition>
<messageCount max="2" min="2"/>
</completeCondition>
<onComplete expression="//*[name()='response']/*[name()='query']/*[name()='row']">
<send/>
</onComplete>
</aggregate>
</outSequence>
<faultSequence/>
</resource>
</api>
The inSequence here is heavily simplified, but it does send two valid queries and gets back the expected responses. The outSequence as it's written here never sends a response to the client or logs an error on the server. If I remove the correlateOn element from aggregate, I get back a single row, seemingly at random, from one of the two API calls. I think correlateOn is something I want to be using here, but I can't find any useful documentation on it from either WSO2 or Apache, so I'm sure I'm using it incorrectly. My XPath background is pretty weak, so I'm sure that expression could also use some work.
Am I at least on the right track here with the clone/aggregate pattern? How would I go about combining the results from these two queries into something similar to my example? If I can get something even sort of close, I should be able to do the rest with XSLT.
take a look at this demo:
Backend 1 with the items response:
<?xml version="1.0" encoding="UTF-8"?>
<proxy xmlns="http://ws.apache.org/ns/synapse"
name="items"
transports="https http"
startOnLoad="true">
<target>
<inSequence>
<payloadFactory media-type="xml">
<format>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<response xmlns="http://example.com/response">
<query xmlns="http://example.com/query" name="items">
<row>
<id>1</id>
<name>Item 1</name>
</row>
<row>
<id>2</id>
<name>Item 2</name>
</row>
</query>
</response>
</soapenv:Body>
</soapenv:Envelope>
</format>
<args/>
</payloadFactory>
<log level="full"/>
<loopback/>
</inSequence>
<outSequence>
<send/>
</outSequence>
<faultSequence/>
</target>
</proxy>
Backend 2 with the parts response:
<?xml version="1.0" encoding="UTF-8"?>
<proxy xmlns="http://ws.apache.org/ns/synapse"
name="parts"
transports="https http"
startOnLoad="true">
<target>
<inSequence>
<payloadFactory media-type="xml">
<format>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<response xmlns="http://example.com/response">
<query xmlns="http://example.com/query" name="parts">
<row>
<id>1</id>
<part>Part 1.1</part>
</row>
<row>
<id>1</id>
<part>Part 1.2</part>
</row>
<row>
<id>1</id>
<part>Part 1.3</part>
</row>
<row>
<id>2</id>
<part>Part 2.1</part>
</row>
<row>
<id>2</id>
<part>Part 2.2</part>
</row>
</query>
</response>
</soapenv:Body>
</soapenv:Envelope>
</format>
<args/>
</payloadFactory>
<log level="full"/>
<loopback/>
</inSequence>
<outSequence>
<send/>
</outSequence>
<faultSequence/>
</target>
</proxy>
My API calling backend 1 and backend 2 and transforming with xslt:
<?xml version="1.0" encoding="UTF-8"?>
<api xmlns="http://ws.apache.org/ns/synapse"
name="ItemList"
context="/item_list">
<resource methods="POST" uri-template="/">
<inSequence>
<header name="Action" scope="default" value="urn:mediate"/>
<call>
<endpoint>
<address uri="http://localhost:8283/services/items.itemsHttpSoap11Endpoint"
format="soap11"/>
</endpoint>
</call>
<enrich>
<source type="inline" clone="true">
<Payloads/>
</source>
<target type="property" property="Items"/>
</enrich>
<enrich>
<source clone="true" xpath="$body/*"/>
<target action="child" xpath="$ctx:Items"/>
</enrich>
<payloadFactory media-type="xml">
<format>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body/>
</soapenv:Envelope>
</format>
<args/>
</payloadFactory>
<call>
<endpoint>
<address uri="http://localhost:8283/services/parts.partsHttpSoap11Endpoint"
format="soap11"/>
</endpoint>
</call>
<enrich>
<source clone="true" xpath="$body/*[name()='response']/*[name()='query']"/>
<target type="property" property="Parts"/>
</enrich>
<enrich>
<source type="property" clone="true" property="Parts"/>
<target action="child" xpath="$ctx:Items"/>
</enrich>
<enrich>
<source type="property" property="Items"/>
<target type="body"/>
</enrich>
<xslt key="transformTwoSourcesToOneResult"/>
<loopback/>
</inSequence>
<outSequence>
<send/>
</outSequence>
<faultSequence/>
</resource>
</api>
And my xslt transformation:
<?xml version="1.0" encoding="UTF-8"?>
<localEntry key="transformTwoSourcesToOneResult" xmlns="http://ws.apache.org/ns/synapse">
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://example.com/query"
xmlns:ns1="http://example.com/response"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:exslt="http://exslt.org/common"
xmlns:saxon="http://saxon.sf.net/"
xmlns:syn="http://ws.apache.org/ns/synapse"
exclude-result-prefixes="ns0 ns1 xs">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="var1_instance_Payloads" select="."/>
<items>
<xsl:for-each select="$var1_instance_Payloads/syn:Payloads">
<xsl:variable name="var2_Payloads" select="."/>
<xsl:for-each select="$var2_Payloads/ns1:response/ns0:query/ns0:row">
<xsl:variable name="var2_row" select="."/>
<item>
<id>
<xsl:value-of select="number(string($var2_row/ns0:id))"/>
</id>
<name>
<xsl:value-of select="string($var2_row/ns0:name)"/>
</name>
<parts>
<xsl:for-each select="$var2_Payloads/ns0:query/ns0:row">
<xsl:variable name="var4_row" select="."/>
<xsl:if test="string((number(string($var2_row/ns0:id)) = number(string($var4_row/ns0:id)))) != 'false'">
<part>
<id>
<xsl:value-of select="number(string($var4_row/ns0:id))"/>
</id>
<name>
<xsl:value-of select="string($var4_row/ns0:part)"/>
</name>
</part>
</xsl:if>
</xsl:for-each>
</parts>
</item>
</xsl:for-each>
</xsl:for-each>
</items>
</xsl:template>
</xsl:stylesheet>
</localEntry>
The API response:
<items xmlns="http://ws.apache.org/ns/synapse" xmlns:syn="http://ws.apache.org/ns/synapse" xmlns:saxon="http://saxon.sf.net/" xmlns:exslt="http://exslt.org/common">
<item>
<id>1</id>
<name>Item 1</name>
<parts>
<part>
<id>1</id>
<name>Part 1.1</name>
</part>
<part>
<id>1</id>
<name>Part 1.2</name>
</part>
<part>
<id>1</id>
<name>Part 1.3</name>
</part>
</parts>
</item>
<item>
<id>2</id>
<name>Item 2</name>
<parts>
<part>
<id>2</id>
<name>Part 2.1</name>
</part>
<part>
<id>2</id>
<name>Part 2.2</name>
</part>
</parts>
</item>
</items>
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>
I have an xml source that approximates a table layout (output from a program) - a is the table holder, b hold the column headings d, c are the rows and e are the row cells:
<?xml version="1.0" encoding="utf-16"?>
<a>
<b>
<d/>
<d/>
<d/>
</b>
<c>
<e/>
<e/>
<e/>
</c>
</a>
</xml>
I have written the following xsl:
<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:template match="/">
<Lessons>
<xsl:for-each select="//c">
<xsl:call-template name="lesson" />
</xsl:for-each>
</Lessons>
</xsl:template>
<xsl:template name="lesson">
<Lesson>
<Teacher>
<xsl:value-of select="count(./preceding-sibling::*)+1" />
<xsl:value-of select="//b[count(./preceding-sibling::*)+1]" />
</Teacher>
</Lesson>
</xsl:template>
</xsl:stylesheet>
The first xsl:value-of of the Teacher element gives me the correct values. I would like the second xsl:value-of to return the value of the heading for that cell i.e. for the first e element that calls the template the first d element is returned and so on. However, it ouputs the value of all of the d elements.
Even if it is not entirely clear what you expect, I will try an answer.
As I understood you like to find the <d> element which has the same position than the <e> element.
This is either possible with getting the current position as a variable.
xsl:variable name ="pos" select="position()" />
<xsl:value-of select="//b/d[position() = $pos]/#id" />
or use count of preceding-sibling
<xsl:value-of select="//b/d[(count(current()/preceding-sibling::*)+1)]/#id" />
Update the part below is changed because of the comment from "George of all trades".
Be aware that the result of position()depends on the calling context.
For example following test xslt (version 1.0):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<Lessons>
<!-- Wrong result with position -->
<xsl:for-each select="//c/e">
<xsl:call-template name="lesson" />
</xsl:for-each>
</Lessons>
<Lessons2>
<!-- Wonted result even with position -->
<xsl:for-each select="//c">
<xsl:for-each select="e">
<xsl:call-template name="lesson" />
</xsl:for-each>
</xsl:for-each>
</Lessons2>
</xsl:template>
<xsl:template name="lesson">
<Lesson>
<Teacher>
<xsl:value-of select="#id" />
<xsl:text>,</xsl:text>
<xsl:value-of select="count(./preceding-sibling::*)+1" />
<xsl:text>,</xsl:text>
<xsl:variable name ="pos" select="position()" />
<xsl:value-of select="$pos" />
<xsl:text>,</xsl:text>
<xsl:value-of select="//b/d[position() = $pos]/#id" />
<xsl:text>,</xsl:text>
<xsl:value-of select="//b/d[count(current()/preceding-sibling::*)+1]/#id" />
</Teacher>
</Lesson>
</xsl:template>
</xsl:stylesheet>
With this input:
<?xml version="1.0" encoding="utf-16"?>
<xml>
<a>
<b>
<d id="d1"/>
<d id="d2"/>
<d id="d3"/>
</b>
<c>
<e id="e1"/>
<e id="e2"/>
<e id="e3"/>
</c>
<c>
<e id="e21"/>
<e id="e22"/>
<e id="e23"/>
</c>
<c>
<e id="e21"/>
<e id="e22"/>
<e id="e23"/>
</c>
</a>
</xml>
Generates this output:
<Lessons>
<Lesson>
<Teacher>e1,1,1,d1,d1</Teacher>
</Lesson>
<Lesson>
<Teacher>e2,2,2,d2,d2</Teacher>
</Lesson>
<Lesson>
<Teacher>e3,3,3,d3,d3</Teacher>
</Lesson>
<Lesson>
<Teacher>e21,1,4,,d1</Teacher>
</Lesson>
<Lesson>
<Teacher>e22,2,5,,d2</Teacher>
</Lesson>
<Lesson>
<Teacher>e23,3,6,,d3</Teacher>
</Lesson>
<Lesson>
<Teacher>e21,1,7,,d1</Teacher>
</Lesson>
<Lesson>
<Teacher>e22,2,8,,d2</Teacher>
</Lesson>
<Lesson>
<Teacher>e23,3,9,,d3</Teacher>
</Lesson>
</Lessons><Lessons2>
<Lesson>
<Teacher>e1,1,1,d1,d1</Teacher>
</Lesson>
<Lesson>
<Teacher>e2,2,2,d2,d2</Teacher>
</Lesson>
<Lesson>
<Teacher>e3,3,3,d3,d3</Teacher>
</Lesson>
<Lesson>
<Teacher>e21,1,1,d1,d1</Teacher>
</Lesson>
<Lesson>
<Teacher>e22,2,2,d2,d2</Teacher>
</Lesson>
<Lesson>
<Teacher>e23,3,3,d3,d3</Teacher>
</Lesson>
<Lesson>
<Teacher>e21,1,1,d1,d1</Teacher>
</Lesson>
<Lesson>
<Teacher>e22,2,2,d2,d2</Teacher>
</Lesson>
<Lesson>
<Teacher>e23,3,3,d3,d3</Teacher>
</Lesson>
</Lessons2>
This glossary derives the index from the first letter of each entry. I'm trying to work out how to show only the unique values. Have looked into preceding-sibling and position() but cannot seem to find the correct way to. I'm constrained to using XSLT 1.0 and attributes.
glossary.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="glossary.xsl"?>
<include>
<file name="data.xml"/>
</include>
data.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<glossary>
<entry term="cantaloupe" definition="A kind of melon"/>
<entry term="banana" definition="A tropical yellow fruit"/>
<entry term="apple" definition="A red fruit with seeds"/>
<entry term="orange" definition="An orange citrus fruit"/>
<entry term="Cherry" definition="A red fruit that grows in clusters "/>
<entry term="cranberry" definition="A sour berry enjoyed at Thanksgiving"/>
<entry term="avocado" definition="A mellow fruit enjoyed in guacamole"/>
</glossary>
glossary.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" doctype-system="about:legacy-compat" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<html>
<head></head>
<body>
<!-- Index: how to show unique values? -->
<xsl:for-each select="document('data.xml')/glossary/entry" >
<xsl:sort select="#term" data-type="text" order="ascending" case-order="upper-first"/>
<xsl:variable name="initial" select="substring(#term,1,1)" />
<xsl:value-of select="$initial" /> |
</xsl:for-each>
<!-- Glossary -->
<dl>
<xsl:for-each select="document('data.xml')/glossary/entry" >
<xsl:sort select="#term" data-type="text" order="ascending" case-order="upper-first"/>
<xsl:variable name="initial" select="substring(#term,1,1)" />
<!-- Alphabetical header: how to only the first instance of each letter? -->
<a name="{$initial}"><h1><xsl:value-of select="$initial" /></h1></a>
<dt><xsl:apply-templates select="#term"/></dt>
<dd><xsl:apply-templates select="#definition"/></dd>
</xsl:for-each>
</dl>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Output so far
a | a | b | c | C | c | o |
a
apple
A red fruit with seeds
a
avocado
A mellow fruit enjoyed in guacamole
b
banana
A tropical yellow fruit
c
cantaloupe
A kind of melon
C
Cherry
A red fruit that grows in clusters
c
cranberry
A sour berry enjoyed at Thanksgiving
o
orange
An orange citrus fruit
Desired output
a | b | c | o
a
apple
A red fruit with seeds
avocado
A mellow fruit enjoyed in guacamole
b
banana
A tropical yellow fruit
c
cantaloupe
A kind of melon
Cherry
A red fruit that grows in clusters
cranberry
A sour berry enjoyed at Thanksgiving
o
orange
An orange citrus fruit
This is an example of a grouping problem and in XSLT 1.0, the established way to do grouping is to use Muenchian Grouping. Unfortunately, your scenario requires finding the lower-case of characters on top of that, and that's a bit messy in XSLT 1.0.
Nonetheless, I've produced a solution and it goes as follows:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" doctype-system="about:legacy-compat"
encoding="UTF-8" indent="yes" />
<xsl:key name="kEntryInitial" match="entry/#term"
use="translate(substring(., 1, 1),
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'abcdefghijklmnopqrstuvwxyz')"/>
<xsl:template match="/">
<html>
<head></head>
<body>
<!-- Jump into the data.xml DOM so that keys work -->
<xsl:apply-templates select="document('data.xml')/glossary" />
</body>
</html>
</xsl:template>
<xsl:template match="/glossary">
<!-- Select terms with distinct initials (case invariant) -->
<xsl:variable name="termsByDistinctInitial"
select="entry/#term[generate-id() =
generate-id(key('kEntryInitial',
translate(substring(., 1, 1),
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'abcdefghijklmnopqrstuvwxyz'))[1])]" />
<!-- Header -->
<xsl:apply-templates select="$termsByDistinctInitial" mode="header">
<xsl:sort select="." data-type="text" order="ascending" />
</xsl:apply-templates>
<!-- Glossary -->
<dl>
<xsl:apply-templates select="$termsByDistinctInitial" mode="main">
<xsl:sort select="." data-type="text" order="ascending" />
</xsl:apply-templates>
</dl>
</xsl:template>
<xsl:template match="#term" mode="header">
<xsl:variable name="initial">
<xsl:call-template name="ToLower">
<xsl:with-param name="value" select="substring(., 1, 1)" />
</xsl:call-template>
</xsl:variable>
<a href="#{$initial}">
<xsl:value-of select="$initial" />
</a>
<xsl:if test="position() != last()">
<xsl:text> |</xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="#term" mode="main">
<xsl:variable name="initial">
<xsl:call-template name="ToLower">
<xsl:with-param name="value" select="substring(., 1, 1)" />
</xsl:call-template>
</xsl:variable>
<a name="{$initial}">
<h1>
<xsl:value-of select="$initial" />
</h1>
</a>
<xsl:apply-templates select="key('kEntryInitial', $initial)/.." />
</xsl:template>
<xsl:template match="entry">
<dt>
<xsl:apply-templates select="#term"/>
</dt>
<dd>
<xsl:apply-templates select="#definition"/>
</dd>
</xsl:template>
<xsl:template name="ToLower">
<xsl:param name="value" />
<xsl:value-of select="translate(substring($value, 1, 1),
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'abcdefghijklmnopqrstuvwxyz')"/>
</xsl:template>
</xsl:stylesheet>
When run on your input XML, this produces the following:
<!DOCTYPE html SYSTEM "about:legacy-compat">
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>a |b |c |o
<dl><a name="a"><h1>a</h1></a><dt>apple</dt>
<dd>A red fruit with seeds</dd>
<dt>avocado</dt>
<dd>A mellow fruit enjoyed in guacamole</dd><a name="b"><h1>b</h1></a><dt>banana</dt>
<dd>A tropical yellow fruit</dd><a name="c"><h1>c</h1></a><dt>cantaloupe</dt>
<dd>A kind of melon</dd>
<dt>Cherry</dt>
<dd>A red fruit that grows in clusters </dd>
<dt>cranberry</dt>
<dd>A sour berry enjoyed at Thanksgiving</dd><a name="o"><h1>o</h1></a><dt>orange</dt>
<dd>An orange citrus fruit</dd>
</dl>
</body>
</html>
One thing I'd suggest considering is using a simple XSLT to "prep" your glossary with initials:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="entry">
<xsl:copy>
<xsl:attribute name="initial">
<xsl:value-of select="translate(substring(#term, 1, 1),
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'abcdefghijklmnopqrstuvwxyz')"/>
</xsl:attribute>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This produces:
<glossary>
<entry initial="c" term="cantaloupe" definition="A kind of melon" />
<entry initial="b" term="banana" definition="A tropical yellow fruit" />
<entry initial="a" term="apple" definition="A red fruit with seeds" />
<entry initial="o" term="orange" definition="An orange citrus fruit" />
<entry initial="c" term="Cherry" definition="A red fruit that grows in clusters " />
<entry initial="c" term="cranberry" definition="A sour berry enjoyed at Thanksgiving" />
<entry initial="a" term="avocado" definition="A mellow fruit enjoyed in guacamole" />
</glossary>
then if you use this prepped version as the glossary, the main XSLT can be rid of all those ugly translate() functions and becomes a lot cleaner:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" doctype-system="about:legacy-compat"
encoding="UTF-8" indent="yes" />
<xsl:key name="kEntryInitial" match="entry/#initial" use="."/>
<xsl:template match="/">
<html>
<head></head>
<body>
<!-- Jump into the data.xml DOM so that keys work -->
<xsl:apply-templates select="document('data2.xml')/glossary" />
</body>
</html>
</xsl:template>
<xsl:template match="/glossary">
<!-- Select terms with distinct initials (case invariant) -->
<xsl:variable name="termsByDistinctInitial"
select="entry/#initial[generate-id() =
generate-id(key('kEntryInitial', .)[1])]" />
<!-- Header -->
<xsl:apply-templates select="$termsByDistinctInitial" mode="header">
<xsl:sort select="." data-type="text" order="ascending" />
</xsl:apply-templates>
<!-- Glossary -->
<dl>
<xsl:apply-templates select="$termsByDistinctInitial" mode="main">
<xsl:sort select="." data-type="text" order="ascending" />
</xsl:apply-templates>
</dl>
</xsl:template>
<xsl:template match="#initial" mode="header">
<a href="#{.}">
<xsl:value-of select="." />
</a>
<xsl:if test="position() != last()">
<xsl:text> |</xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="#initial" mode="main">
<a name="{.}">
<h1>
<xsl:value-of select="." />
</h1>
</a>
<xsl:apply-templates select="key('kEntryInitial', .)/.." />
</xsl:template>
<xsl:template match="entry">
<dt>
<xsl:apply-templates select="#term"/>
</dt>
<dd>
<xsl:apply-templates select="#definition"/>
</dd>
</xsl:template>
</xsl:stylesheet>
Of course, the final output is the same as the first example. If your XSLT processor supports the node-set() function, it's also possible to do both of these processing steps in a single XSLT.
The technique you need is called Muenchian grouping. First define a key that groups entry elements by the downcased first letter of their term
<xsl:key name="entryByInitial" match="entry" use="translate(substring(#term, 1, 1), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')" />
Then you use a trick with generate-id to extract just the first element that matches each key
<xsl:for-each select="document('data.xml')">
<!-- iterate over the "groups" to build the top links -->
<xsl:for-each select="glossary/entry[generate-id() = generate-id(key('entryByInitial', translate(substring(#term, 1, 1), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'))[1])]">
<xsl:sort select="translate(#term, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')" data-type="text" order="ascending"/>
<xsl:variable name="initial" select="translate(substring(#term, 1, 1), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')" />
<!-- insert a leading | before all but the first link -->
<xsl:if test="position() > 1"> | </xsl:if>
<xsl:value-of select="$initial" />
</xsl:for-each>
<!-- iterate over the groups again -->
<xsl:for-each select="glossary/entry[generate-id() = generate-id(key('entryByInitial', translate(substring(#term, 1, 1), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'))[1])]">
<xsl:sort select="translate(#term, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')" data-type="text" order="ascending"/>
<xsl:variable name="initial" select="translate(substring(#term, 1, 1), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')" />
<a name="{$initial}"><h1><xsl:value-of select="$initial" /></h1></a>
<dl>
<!-- apply templates for all entries with this key value -->
<xsl:apply-templates select="key('entryByInitial', $initial)">
<xsl:sort select="translate(#term, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')" data-type="text" order="ascending"/>
</xsl:apply-templates>
</dl>
</xsl:for-each>
</xsl:for-each>
and define a separate template
<xsl:template match="entry">
<dt><xsl:apply-templates select="#term"/></dt>
<dd><xsl:apply-templates select="#definition"/></dd>
</xsl:template>