How to merge in memory XML documents? - xslt

I have 2 batches of documents. In batch 1, I need to merge pit/score when its SPID equals to characteristic/score/SPID. The expected merge looks like batch 2 documents, characteristic/score/default is the merged content of pit/score. If the document already has the characteristic/score/default then keep it as it is.
Batch 1:
<creditRisk>
<characteristic>
<score>
<LID>C123</LID>
<SPID>106123</SPID>
<Sector>Real Estate, Rental and Leasing</Sector>
</score>
<score>
<LID>C470</LID>
<SPID>127999</SPID>
<Sector>Professional, Scientific and Technical Services</Sector>
</score>
</characteristic>
<pit>
<score>
<LID>C123</LID>
<SPID>106123</SPID>
<LTV>0.8654782868</LTV>
<LGD>0.099571924</LGD>
<Logarithm>-0.104884989</Logarithm>
</score>
<score>
<LID>C470</LID>
<SPID>127999</SPID>
<LTV>0.1950292239</LTV>
<LGD>0.4296130401</LGD>
<Logarithm>-0.561440272</Logarithm>
</score>
</pit>
</creditRisk>
Btach 2:
<creditRisk>
<characteristic>
<score>
<default>
<LID>C307</LID>
<SPID>363553</SPID>
<LTV>1.031735135</LTV>
<LGD>0.4421581764</LGD>
<Logarithm>-0.583679827</Logarithm>
</default>
<LID>C307</LID>
<SPID>363553</SPID>
<Sector>Manufacturing and Consumer Products</Sector>
</score>
<score>
<default>
<LID>C357</LID>
<SPID>329924</SPID>
<LTV>0.8326657196</LTV>
<LGD>0.1983093536</LGD>
<Logarithm>-0.221032473</Logarithm>
</default>
<LID>C357</LID>
<SPID>329924</SPID>
<Sector>Utilities, Construction and Other Infrastructure</Sector>
</score>
</characteristic>
</creditRisk>
Expected Output:
Batch 1:
<characteristic>
<score>
<default>
<LID>C123</LID>
<SPID>106123</SPID>
<LTV>0.8654782868</LTV>
<LGD>0.099571924</LGD>
<Logarithm>-0.104884989</Logarithm>
</default>
<LID>C123</LID>
<SPID>106123</SPID>
<Sector>Real Estate, Rental and Leasing</Sector>
</score>
<score>
<default>
<LID>C470</LID>
<SPID>127999</SPID>
<LTV>0.1950292239</LTV>
<Recovery>0.5703869599</Recovery>
<LGD>0.4296130401</LGD>
<Logarithm>-0.561440272</Logarithm>
</default>
<LID>C470</LID>
<SPID>127999</SPID>
<Sector>Professional, Scientific and Technical Services</Sector>
</score>
</characteristic>
My xsl1 doesn’t merge anything but wipe out all of the pit elements.
<xsl:template match="creditRisk">
<characteristic>
<xsl:for-each select="characteristic/score">
<score>
<default>
<xsl:for-each select="pit/score[SPID eq ./SPID]">
<xsl:copy-of select="./*"/>
</xsl:for-each>
</default>
<xsl:copy-of select="./*"/>
</score>
</xsl:for-each>
</characteristic>
</xsl:template>
My xsl2 failed with error:
A sequence of more than one item is not allowed as the second operand of 'eq' (, , , ...)
<xsl:template match="creditRisk">
<characteristic>
<xsl:variable name="character" select="characteristic/score"/>
<xsl:variable name="pit" select="pit/score"/>
<xsl:choose>
<xsl:when test="fn:exists($pit)">
<xsl:for-each select="$character">
<score>
<default>
<xsl:for-each select="$pit[SPID eq $character/SPID]">
<xsl:copy-of select="./*"/>
</xsl:for-each>
</default>
<xsl:copy-of select="$character/*"/>
</score>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
</xsl:choose>
</characteristic>
</xsl:template>
How can I get my xsl work? I can’t use <xsl:template match="/"> in the xsl due to the dependency. If it is BaseX, then it is freestanding.

XSLT 2
<xsl:template match="creditRisk[pit]">
<characteristic>
<xsl:call-template name="score">
<xsl:with-param name="characterScore" select="characteristic/score"/>
<xsl:with-param name="pitScore" select="pit/score"/>
</xsl:call-template>
</characteristic>
</xsl:template>
<xsl:template match="creditRisk[not(pit)]">
<xsl:copy-of select="./*"/>
</xsl:template>
<xsl:template name="score">
<xsl:param name="characterScore"/>
<xsl:param name="pitScore"/>
<xsl:for-each select="$characterScore">
<xsl:variable name="character" select="."/>
<score>
<default>
<xsl:for-each select="$pitScore[SPID eq $character/SPID]">
<xsl:copy-of select="./node()"/>
</xsl:for-each>
</default>
<xsl:copy-of select="$character/node()"/>
</score>
</xsl:for-each>
</xsl:template>
Given memory node, it is only fair to have a go at XSLT 3
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="creditRisk[pit]">
<characteristic>
<xsl:call-template name="score">
<xsl:with-param name="characterScore" select="characteristic/score"/>
<xsl:with-param name="pitScore" select="pit/score"/>
</xsl:call-template>
</characteristic>
</xsl:template>
<xsl:template match="creditRisk[not(pit)]">
<xsl:apply-templates/>
</xsl:template>
<xsl:template name="score">
<xsl:param name="characterScore"/>
<xsl:param name="pitScore"/>
<xsl:for-each select="$characterScore">
<xsl:variable name="character" select="."/>
<score>
<default>
<xsl:for-each select="$pitScore[SPID eq $character/SPID]">
<xsl:apply-templates/>
</xsl:for-each>
</default>
<xsl:apply-templates/>
</score>
</xsl:for-each>
</xsl:template>

Related

Split comma separated string into multiple values using xslt

I need to split two tags with comma separated string into a list of parent-child tags as shown below.
For example, the input will be :-
<UserID>162,163</UserID>
<UserName>Stacy,Stephen</UserName>
Expected output :-
Expected Output
Please help to achieve this result using xslt code
I tried the following format which I got from another query, but its generating a nested pattern instead of the list :-
<xsl:template name="tokenize">
<xsl:param name="textID" select="."/>
<xsl:param name="textName" select="."/>
<xsl:param name="separator" select="','"/>
<User>
<xsl:choose>
<xsl:when test="not(contains($textID, $separator))">
<ID>
<xsl:value-of select="normalize-space($textID)"/>
</ID>
</xsl:when>
<xsl:otherwise>
<ID>
<xsl:value-of select="normalize-space(substring-before($textID, $separator))"/>
</ID>
<xsl:call-template name="tokenize">
<xsl:with-param name="textID" select="substring-after($textID, $separator)"/>
<xsl:with-param name="textName" select="substring-after($textName, $separator)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
<xsl:choose>
<xsl:when test="not(contains($textName, $separator))">
<Name>
<xsl:value-of select="normalize-space($textName)"/>
</Name>
</xsl:when>
<xsl:otherwise>
<Name>
<xsl:value-of select="normalize-space(substring-before($textName, $separator))"/>
</Name>
<xsl:call-template name="tokenize">
<xsl:with-param name="textID" select="substring-after($textID, $separator)"/>
<xsl:with-param name="textName" select="substring-after($textName, $separator)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</User>
</xsl:template>
There are few assumptions before simplifying the tokenize template shared in the XSLT code.
The count of comma separated values in <UserID> and <UserName> is always equal.
There is a 1-1 correspondence on the indexes of the values i.e. 162 <-> Stacy and 163 <-> Stephen.
XSLT version is 1.0
A parent node <UserList> has been added as a root node for the shared input XML.
XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*" />
<xsl:template match="UserList">
<xsl:copy>
<xsl:call-template name="tokenize">
<xsl:with-param name="textID" select="normalize-space(UserID)" />
<xsl:with-param name="textName" select="normalize-space(UserName)" />
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="textID" />
<xsl:param name="textName" />
<xsl:param name="separator" select="','" />
<xsl:choose>
<xsl:when test="not(contains($textID, $separator) and contains($textName, $separator)) ">
<User>
<ID><xsl:value-of select="$textID" /></ID>
<Name><xsl:value-of select="$textName" /></Name>
</User>
</xsl:when>
<xsl:otherwise>
<User>
<ID><xsl:value-of select="substring-before($textID, $separator)" /></ID>
<Name><xsl:value-of select="substring-before($textName, $separator)" /></Name>
</User>
<xsl:call-template name="tokenize">
<xsl:with-param name="textID" select="normalize-space(substring-after($textID, $separator))" />
<xsl:with-param name="textName" select="normalize-space(substring-after($textName, $separator))" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Output
<UserList>
<User>
<ID>162</ID>
<Name>Stacy</Name>
</User>
<User>
<ID>163</ID>
<Name>Stephen</Name>
</User>
</UserList>

Using a variable in group-starting-with

I have a stylesheet designed to add some structure which currently looks like this:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://docbook.org/ns/docbook" xpath-default-namespace="http://docbook.org/ns/docbook">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="book">
<xsl:copy>
<xsl:for-each select="#*">
<xsl:copy/>
</xsl:for-each>
<xsl:copy-of select="info"/>
<xsl:for-each-group select="*" group-starting-with="chapnumber">
<xsl:choose>
<xsl:when test="current-group()[1]/name() = 'chapnumber'">
<xsl:variable name="chapter_number" select="replace(., '.*([0-9]).*', '$1')"/>
<chapter label="{$chapter_number}">
<xsl:call-template name="nest_head1">
<xsl:with-param name="working_group" select="current-group() except ."/>
</xsl:call-template>
</chapter>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="nest_head1">
<xsl:with-param name="working_group" select="current-group() except ."/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template name="nest_head1">
<xsl:param name="working_group"/>
<xsl:for-each-group select="$working_group" group-starting-with="head1">
<xsl:choose>
<xsl:when test="current-group()[1]/name() = 'head1'">
<section>
<xsl:call-template name="nest_head2">
<xsl:with-param name="working_group" select="current-group()"/>
</xsl:call-template>
</section>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="nest_head2">
<xsl:with-param name="working_group" select="current-group()"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>
<xsl:template name="nest_head2">
<xsl:param name="working_group"/>
<xsl:for-each-group select="$working_group" group-starting-with="head2">
<xsl:choose>
<xsl:when test="current-group()[1]/name() = 'head2'">
<section>
<xsl:apply-templates select="current-group()"/>
</section>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="head1|head2|head3|head4|head5">
<info>
<title>
<xsl:apply-templates/>
</title>
</info>
</xsl:template>
<xsl:template match="chaptitle">
<info>
<title>
<xsl:apply-templates/>
</title>
</info>
</xsl:template>
<xsl:template match="italic">
<emphasis role="italic">
<xsl:apply-templates/>
</emphasis>
</xsl:template>
<xsl:template match="bold">
<emphasis role="bold">
<xsl:apply-templates/>
</emphasis>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:for-each select="#*">
<xsl:copy/>
</xsl:for-each>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I dislike how each heading level has it's own template (especially as there may in theory be many more levels of headings) and thought it could be condensed to a single, more usable template with a variable for the heading level, like so:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://docbook.org/ns/docbook" xpath-default-namespace="http://docbook.org/ns/docbook">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="book">
<xsl:copy>
<xsl:for-each select="#*">
<xsl:copy/>
</xsl:for-each>
<xsl:copy-of select="info"/>
<xsl:for-each-group select="*" group-starting-with="chapnumber">
<xsl:choose>
<xsl:when test="current-group()[1]/name() = 'chapnumber'">
<xsl:variable name="chapter_number" select="replace(., '.*([0-9]).*', '$1')"/>
<chapter label="{$chapter_number}">
<xsl:call-template name="nest_headings">
<xsl:with-param name="working_group" select="current-group() except ."/>
<xsl:with-param name="heading_level" select="1"/>
</xsl:call-template>
</chapter>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="nest_headings">
<xsl:with-param name="working_group" select="current-group() except ."/>
<xsl:with-param name="heading_level" select="1"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template name="nest_headings">
<xsl:param name="working_group"/>
<xsl:param name="heading_level"/>
<xsl:variable name="heading_name">
<xsl:value-of select="concat('head', string($heading_level))"/>
</xsl:variable>
<xsl:if test="$heading_level < 6">
<xsl:for-each-group select="$working_group" group-starting-with="$heading_name">
<xsl:choose>
<xsl:when test="current-group()[1]/name() = $heading_name">
<section>
<xsl:call-template name="nest_headings">
<xsl:with-param name="working_group" select="current-group()"/>
<xsl:with-param name="heading_level" select="$heading_level + 1"/>
</xsl:call-template>
</section>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="nest_headings">
<xsl:with-param name="working_group" select="current-group()"/>
<xsl:with-param name="heading_level" select="$heading_level + 1"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:if>
</xsl:template>
<xsl:template match="head1|head2|head3|head4|head5">
<info>
<title>
<xsl:apply-templates/>
</title>
</info>
</xsl:template>
<xsl:template match="chaptitle">
<info>
<title>
<xsl:apply-templates/>
</title>
</info>
</xsl:template>
<xsl:template match="italic">
<emphasis role="italic">
<xsl:apply-templates/>
</emphasis>
</xsl:template>
<xsl:template match="bold">
<emphasis role="bold">
<xsl:apply-templates/>
</emphasis>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:for-each select="#*">
<xsl:copy/>
</xsl:for-each>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
But this stylesheet fails to compile with A variable reference is not allowed in an XSLT pattern (except in a predicate)
A mailing list I found suggests group-starting-with="*[name()=$heading_name]. Using this will create the section nodes, but they will be empty.
Is there a way to achieve what I'm looking for?
Sample input:
<?xml version="1.0" encoding="UTF-8"?>
<book xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" role="fullText" xml:lang="en">
<chapnumber>Chapter 1</chapnumber>
<chaptitle>Chapter Title</chaptitle>
<head1>Heading 1</head1>
<para>1st paragraph</para>
<head2>Heading 2</head2>
<para>2nd paragraph</para>
<head2>Heading 2 2</head2>
<para>Final paragraph</para>
</book>
Expected output:
<?xml version="1.0" encoding="UTF-8"?>
<book xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0" role="fullText" xml:lang="en">
<chapter label="1">
<info>
<title>Chapter Title</title>
</info>
<section>
<info>
<title>Heading 1</title>
</info>
<para>1st paragraph</para>
<section>
<info>
<title>Heading 2</title>
</info>
<para>2nd paragraph</para>
</section>
<section>
<info>
<title>Heading 2 2</title>
</info>
<para>Final paragraph</para>
</section>
</section>
</chapter>
</book>
You need to make sure you process the items once the grouping is done so use <xsl:apply-templates select="current-group()"/> in the xsl:otherwise:
<xsl:template name="nest_headings">
<xsl:param name="working_group"/>
<xsl:param name="heading_level"/>
<xsl:variable name="heading_name" select="concat('head', $heading_level)"/>
<xsl:if test="$heading_level < 6">
<xsl:for-each-group select="$working_group" group-starting-with="*[local-name() eq $heading_name]">
<xsl:choose>
<xsl:when test="local-name() eq $heading_name">
<section>
<xsl:call-template name="nest_headings">
<xsl:with-param name="working_group" select="current-group()"/>
<xsl:with-param name="heading_level" select="$heading_level + 1"/>
</xsl:call-template>
</section>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:if>
</xsl:template>
The test="current-group()[1]/name() = $heading_name" could be shortened to test="local-name() eq $heading_name".
See http://xsltransform.net/pPqsHTm/1 for a full sample.

XSLT: Splitting element contents on a text delimiter, keeping elements

I'm trying to parse element contents and split them on a delimiter, while keeping all elements in the parent. I don't need -- don't want -- to find the delimiter inside the child elements.
<data>
<parse-field>Some text <an-element /> more text; cheap win? ;
<another-element>with delimiter;!</another-element>; final text</parse-field>
</data>
Should become
<data>
<parsed-field>
<field>Some text <an-element /> more text</field>
<field>cheap win?</field>
<field><another-element>with limiter;!</another-element></field>
<field>final text</field>
</parsed-field>
</data>
I've got a hacked-together solution that examines all "parse-field/text()" and replaces the delimiter with <token />, then a second pass to pick out the pieces around the<token>s, but it's... hacked. And unpleasant. I'm wondering if there's a better way.
I'm using XSLT-2.0, open to XSLT-1.0 solutions. SAXON processor.
This is not (yet?) a complete answer, just an outline of a possible approach. If you would make your first pass something like:
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="parse-field/text()">
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="."/>
</xsl:call-template>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="text"/>
<xsl:param name="delimiter" select="';'"/>
<xsl:choose>
<xsl:when test="contains($text, $delimiter)">
<field>
<xsl:value-of select="substring-before($text, $delimiter)"/>
</field>
<!-- recursive call -->
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after($text, $delimiter)"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="position()=last()">
<field><xsl:value-of select="$text"/></field>
</xsl:when>
<xsl:when test="$text">
<text><xsl:value-of select="$text"/></text>
</xsl:when>
</xsl:choose>
</xsl:template>
you would obtain:
<?xml version="1.0" encoding="UTF-8"?>
<data>
<parse-field>
<text>Some text </text>
<an-element/>
<field> more text</field>
<field> cheap win? </field>
<another-element>with delimiter;!</another-element>
<field/>
<field> final text</field>
</parse-field>
</data>
This is now a grouping problem, where elements of <parse-field> need to be grouped, with each group ending with <field>.
Best approach I've had so far, in simple form:
<xsl:variable name="delimiter" select="';'" />
<xsl:template match="foo">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:call-template name="tokenize" />
</xsl:copy>
</xsl:template>
<xsl:template name="tokenize">
<xsl:variable name="rough">
<xsl:apply-templates mode="tokenize" />
</xsl:variable>
<xsl:copy>
<xsl:group-by select="$rough/*" group-ending-with="delimiter">
<field><xsl:apply-templates select="current-group()[not(self::delimiter)]" /></field>
</xsl:group>
</xsl:copy>
</xsl:template>
<xsl:template match="*" mode="tokenize">
<xsl:copy>
<xsl:apply-templates select="#*|*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="text()" mode="tokenize">
<xsl:analyze-string select="." regex="([^{$delimiter}]*){$delimiter}">
<xsl:matching-substring>
<xsl:value-of select="regex-group(1)" /><delimiter/>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="." />
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>

Rolling sum of template result in XSL1.0

I've searched far and wide for an example of this, and have so far had no luck in applying a sum template to make my XSTL work.
This is the XML (number of lines varies on each planfeature)
<PlanFeatures>
<PlanFeature name="Line0001">
<CoordGeom>
<Line>
<Start pntRef="7540">5605 8950 1020</Start>
<End pntRef="7541">5605 8951 1019</End>
</Line>
<Line>
<Start pntRef="7541">5605 8951 1019</Start>
<End pntRef="7542">5605 8947 1019</End>
</Line>
<Line>
<Start pntRef="7542">5605 8947 1019</Start>
<End pntRef="7543">5605 8940 1011</End>
</Line>
<Line>
<Start pntRef="7543">5605 8940 1011</Start>
<End pntRef="7544">5605 8931 1020</End>
</Line>
</CoordGeom>
</PlanFeature>
</PlanFeatures>
This is where I'm at with the XSL, which uses a recursive call template to calculate the distance of each line segment.
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:landxml="http://www.landxml.org/schema/LandXML-1.2" xmlns:hexagon="http://xml.hexagon.com/schema/HeXML-1.5" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" version="1.0" encoding="UTF-16" indent="no" omit-xml-declaration="yes"/>
<xsl:variable name="XML" select="/"/>
<xsl:variable name="fileExt" select="'txt'"/>
<xsl:variable name="fileDesc" select="'line distance report'"/>
<xsl:template match="/">
<xsl:for-each select="$XML">
<xsl:for-each select="landxml:LandXML/landxml:PlanFeatures/landxml:PlanFeature">
<xsl:value-of select="#name"/><xsl:text>::</xsl:text>
<xsl:for-each select="landxml:CoordGeom/landxml:Line">
<xsl:value-of select="landxml:Start/#pntRef"/><xsl:text>-</xsl:text>
<xsl:variable name="lista" select="landxml:Start"/>
<xsl:variable name="x1" select="substring-before($lista,' ')"/>
<xsl:variable name="yt1" select="substring-after($lista,' ')"/>
<xsl:variable name="y1" select="substring-before($yt1,' ')"/>
<xsl:variable name="z1" select="substring-after($yt1,' ')"/>
<xsl:variable name="listb" select="landxml:End"/>
<xsl:value-of select="landxml:End/#pntRef"/><xsl:text>: </xsl:text>
<xsl:variable name="x2" select="substring-before($listb,' ')"/>
<xsl:variable name="yt2" select="substring-after($listb,' ')"/>
<xsl:variable name="y2" select="substring-before($yt2,' ')"/>
<xsl:variable name="z2" select="substring-after($yt2,' ')"/>
<xsl:variable name="seg" select= "((($x2 - $x1)*($x2 - $x1))+(($y2 - $y1)*($y2 - $y1))+(($z2 - $z1)*($z2 - $z1)))"/>
<xsl:call-template name="root">
<xsl:with-param name="X" select="$seg"/>
</xsl:call-template>
<xsl:text>, </xsl:text>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
<xsl:template name="root">
<xsl:param name="X"/>
<xsl:param name="xn" select="0"/>
<xsl:param name="xn_1" select="($X+1) div 2"/>
<xsl:choose>
<xsl:when test="string(number($X)) = 'NaN'">
<xsl:value-of select=" ' ' "/>
</xsl:when>
<xsl:when test="($xn_1 - $xn) * ($xn_1 - $xn) < 0.00000001">
<xsl:value-of select='format-number($xn_1, "#.000")'/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="root">
<xsl:with-param name="X" select="$X"/>
<xsl:with-param name="xn" select="$xn_1"/>
<xsl:with-param name="xn_1" select="($xn_1 + ($X div $xn_1)) div 2"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I need to sum the value of X (distance) from the root call template, to create a value which represents the sum of each line segment. I think I need to use a match template, but so far it hand even got close to working.
Currently exporting LINEID::StartPt-EndPt: dist, StartPt-EndPt: dist, etc. I need the sum of the 'dist' to be shown at the end of each line as well. As below
Line0001::7540-7541: 1.414, 7541-7542: 2.000, 7542-7543: 12.042, 7543-7544: 12.720
but I would like
Line0001::7540-7541: 1.414, 7541-7542: 2.000, 7542-7543: 12.042, 7543-7544: 12.728 -- 28.184
Any help would be appreciated... the examples on this site have helped me so much already, but I just can't seem to get through this roadblock.
Cheers,
Chris
You can accomplish this with some relatively simple recursion and parameter passing. Try replacing your first template with these four templates:
<xsl:template match="/">
<xsl:for-each select="$XML">
<xsl:apply-templates
select="landxml:LandXML/landxml:PlanFeatures/landxml:PlanFeature" />
</xsl:for-each>
</xsl:template>
<xsl:template match ="landxml:PlanFeature">
<xsl:value-of select="concat(#name, '::')" />
<xsl:apply-templates select="landxml:CoordGeom/landxml:Line[1]" />
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="landxml:Line">
<xsl:param name="total" select="0" />
<xsl:value-of
select="concat(landxml:Start/#pntRef, '-', landxml:End/#pntRef, ': ')"/>
<xsl:variable name="len">
<xsl:call-template name="root">
<xsl:with-param name="X">
<xsl:call-template name="seg" />
</xsl:with-param>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$len"/>
<xsl:variable name="next" select="following-sibling::landxml:Line[1]" />
<xsl:variable name="newTot" select="$total + $len" />
<xsl:choose>
<xsl:when test="$next">
<xsl:text>, </xsl:text>
<xsl:apply-templates select="$next">
<xsl:with-param name="total" select="$newTot" />
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(' -- ', format-number($newTot, '#.000'))" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="seg">
<xsl:variable name="lista" select="landxml:Start"/>
<xsl:variable name="x1" select="substring-before($lista,' ')"/>
<xsl:variable name="yt1" select="substring-after($lista,' ')"/>
<xsl:variable name="y1" select="substring-before($yt1,' ')"/>
<xsl:variable name="z1" select="substring-after($yt1,' ')"/>
<xsl:variable name="listb" select="landxml:End"/>
<xsl:variable name="x2" select="substring-before($listb,' ')"/>
<xsl:variable name="yt2" select="substring-after($listb,' ')"/>
<xsl:variable name="y2" select="substring-before($yt2,' ')"/>
<xsl:variable name="z2" select="substring-after($yt2,' ')"/>
<xsl:value-of select= "($x2 - $x1)*($x2 - $x1)+
($y2 - $y1)*($y2 - $y1)+
($z2 - $z1)*($z2 - $z1)"/>
</xsl:template>
When run on your sample input (after adjusting it to match the paths in your XSLT), the result is:
Line0001::7540-7541: 1.414, 7541-7542: 4.000, 7542-7543: 10.630, 7543-7544: 12.728 -- 28.772

XSL or XSLT or XSLFO: which can expand text?

Suppose I have an XML file:
<document>
<title>How the West was Won</title>
<chapter>
Lorem Ipsum Blah .....
</chapter>
</document>
Is it possible to use XSLT or XSLFO or XSL to render the title like this:
How the West was Won
--------------------
Lorem Ipsum Blah .....
How would I generate the correct number of dashes on the line following the Title?
I know it's very atypical, but you can also use template modes:
<xsl:template match="title">
<xsl:variable name="dashes">
<xsl:apply-templates select="." mode="dashes"/>
</xsl:variable>
<xsl:value-of select="concat(.,'
',$dashes,'
')"/>
</xsl:template>
<xsl:template match="text()" mode="dashes">
<xsl:param name="length" select="string-length() - 1"/>
<xsl:text>-</xsl:text>
<xsl:apply-templates select="self::text()[boolean($length)]"
mode="dashes">
<xsl:with-param name="length" select="$length - 1"/>
</xsl:apply-templates>
</xsl:template>
Try this:
<xsl:template match="title">
<xsl:value-of select="text()"/>
<xsl:text>
</xsl:text>
<xsl:call-template name="Underline">
<xsl:with-param name="length" select="string-length(text())"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="Underline">
<xsl:param name="length"/>
<xsl:choose>
<xsl:when test="$length > 0">-<xsl:call-template name="Underline"><xsl:with-param name="length" select="$length - 1"/></xsl:call-template></xsl:when>
</xsl:choose>
</xsl:template>
Underline is a recursive template that outputs a given number of dashes.
This is another case where it's much simpler in XSLT 2.0.
<xsl:for-each select="1 to string-length(title)">-</xsl:for-each>