How to get max valued xref's 'rid' except from particular section - xslt

Please suggest for how get the maximum 'rid' value from all xrefs except from the 'Online' sections. By identify the max valued 'rid', then need to insert the attribute to those references which are higher to maximum value. Please see required result text.
XML:
<article>
<body>
<sec><title>Sections</title>
<p>The test <xref rid="b1">1</xref>, <xref rid="b2">2</xref>, <xref rid="b3 b4 b5">3-5</xref></p></sec>
<sec><title>Online</title><!--This section's xrefs no need to consider-->
<p>The test <xref rid="b6">6</xref></p>
<sec><title>Other</title>
<p><xref rid="b1">1</xref>, <xref rid="b7 b8">7-8</xref></p>
</sec>
</sec><!--This section's xrefs no need to consider-->
<sec>
<p>Final test test</p>
<sec><title>Third title</title><p>Last text</p></sec>
</sec>
</body>
<bm>
<ref id="b1">The ref01</ref>
<ref id="b2">The ref02</ref>
<ref id="b3">The ref03</ref>
<ref id="b4">The ref04</ref>
<ref id="b5">The ref05</ref>
<ref id="b6">The ref06</ref>
<ref id="b7">The ref07</ref>
<ref id="b8">The ref08</ref>
</bm>
</article>
XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:variable name="var1"><!--Variable to get the all 'rid's except sec/title contains 'Online' -->
<xsl:for-each select="//xref[not(. is ancestor::sec[title[contains(., 'Online')]]/descendant-or-self)]/#rid">
<!--xsl:for-each select="//xref/#rid[not(contains(ancestor::sec/title, 'Online'))]"--><!--for this xpath, error is : "XPTY0004: A sequence of more than one item is not allowed as the first argument" -->
<!--xsl:for-each select="//xref/#rid[not(contains(ancestor::sec[1]/title, 'Online')) and not(contains(ancestor::sec[2]/title, 'Online'))]"--><!--for this xpath we are getting the required result, but there may be several nesting of 'sec's -->
<xsl:choose>
<xsl:when test="contains(., ' ')">
<xsl:for-each select="tokenize(., ' ')">
<a><xsl:value-of select="."/></a>
</xsl:for-each>
</xsl:when>
<xsl:otherwise><a><xsl:value-of select="."/></a></xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="varMax1">
<xsl:for-each select="$var1/a">
<xsl:sort select="substring-after(., 'b')" order="descending" data-type="number"/>
<a><xsl:value-of select="."/></a>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="varMax"><!--Variable to get max valued RID -->
<xsl:value-of select="substring-after($varMax1/a[1], 'b')"/>
</xsl:variable>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
<xsl:template match="ref">
<xsl:variable name="varID"><xsl:value-of select="substring-after(#id, 'b')"/></xsl:variable>
<xsl:choose>
<xsl:when test="number($varMax) lt number($varID)">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="MoveRef">yes</xsl:attribute>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Required result:
<article>
<body>
<sec><title>Sections</title>
<p>The test <xref rid="b1">1</xref>, <xref rid="b2">2</xref>, <xref rid="b3 b4 b5">3-5</xref></p></sec>
<sec><title>Online</title><!--This section's xrefs no need to consider-->
<p>The test <xref rid="b6">6</xref></p>
<sec><title>Other</title>
<p><xref rid="b1">1</xref>, <xref rid="b7">7</xref>, <xref rid="b8">8</xref></p>
</sec>
</sec><!--This section's xrefs no need to consider-->
<sec>
<p>Final test test</p>
<sec><title>Third title</title><p>Last text</p></sec>
</sec>
</body>
<bm>
<ref id="b1">The ref01</ref>
<ref id="b2">The ref02</ref>
<ref id="b3">The ref03</ref>
<ref id="b4">The ref04</ref>
<ref id="b5">The ref05</ref>
<ref id="b6" MoveRef="yes">The ref06</ref>
<ref id="b7" MoveRef="yes">The ref07</ref>
<ref id="b8" MoveRef="yes">The ref08</ref>
</bm>
</article>
Here consider number 5 for 'b5' rid, 6 for 'b6'.... (Because alphanumeric)

Perhaps you can take a different approach rather than trying to find the maximum rid attribute that is not in an "online" section. Not least because it is not entirely clear what the maximum is when you are dealing with an alphanumeric string.
Instead, you could define a key to look up elements in the "online" section by their name
<xsl:key name="online" match="sec[title = 'Online']//*" use="name()" />
And then, another key, to look up the xref elements that occur in other sections
<xsl:key name="other" match="xref[not(ancestor::sec/title = 'Online')]" use="name()" />
Then, you can write a template to math the ref elements, and use an xsl:if to determine whether to add MoveRef attribute to it:
<xsl:variable name="id" select="#id" />
<xsl:if test="key('online', 'xref')[tokenize(#rid, ' ')[. = $id]] and not(key('other', 'xref')[tokenize(#rid, ' ')[. = $id]])">
Try this much shorter XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" />
<xsl:key name="online" match="sec[title = 'Online']//*" use="name()" />
<xsl:key name="other" match="xref[not(ancestor::sec/title = 'Online')]" use="name()" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ref">
<ref>
<xsl:variable name="id" select="#id" />
<xsl:if test="key('online', 'xref')[tokenize(#rid, ' ')[. = $id]] and not(key('other', 'xref')[tokenize(#rid, ' ')[. = $id]])">
<xsl:attribute name="MoveRef" select="'Yes'" />
</xsl:if>
<xsl:apply-templates select="#*|node()"/>
</ref>
</xsl:template>
</xsl:stylesheet>
You can actually amend the ref template to put the condition in the template match, if you wanted...
<xsl:template match="ref[key('online', 'xref')[tokenize(#rid, ' ')[. = current()/#id]] and not(key('other', 'xref')[tokenize(#rid, ' ')[. = current()/#id]])]">
<ref MoveRef="Yes">
<xsl:apply-templates select="#*|node()"/>
</ref>
</xsl:template>

Related

How to select range's between IDs

Please suggest for how to select the range's between IDs. Example if range is 5-8, then 6,7 are required ids. If figs <link href="fig3">-<link href="fig7">, then fig4 fig5 fig6 are required IDs.
XML:
<root>
<p id="p1">This <link href="#fig-0001 #fig-0002"/>, <link href="#fig-0003"/>-<link href="#fig-0006"/></p>
<figure xml_id="fig-0001"><label>Fig. 1</label><caption><p>One</p></caption></figure>
<figure xml_id="fig-0002"><label>Fig. 2</label><caption><p>Two</p></caption></figure>
<figure xml_id="fig-0003"><label>Fig. 3</label><caption><p>Three</p></caption></figure>
<figure xml_id="fig-0004"><label>Fig. 4</label><caption><p>Four</p></caption></figure>
<figure xml_id="fig-0005"><label>Fig. 5</label><caption><p>Five</p></caption></figure>
<figure xml_id="fig-0006"><label>Fig. 6</label><caption><p>Six</p></caption></figure>
</root>
XSLT2:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|#*">
<xsl:copy><xsl:apply-templates select="node()|#*"/></xsl:copy>
</xsl:template>
<xsl:key name="kFloat" match="*" use="#xml_id"/>
<xsl:template match="link[ancestor::p/following-sibling::*[1][matches(name(), '^(figure)$')]][matches(key('kFloat', if(contains(#href, ' ')) then substring-after(substring-before(#href, ' '), '#') else substring-after(#href, '#'))/name(), '^(figure)$')]">
<xsl:copy><xsl:apply-templates select="node()|#*"/></xsl:copy><!-- link element retaining-->
<!--Range between IDs selection -->
<xsl:if test="matches(preceding-sibling::node()[1][self::text()], '^(&#x2013;|&#x02013;|–|-)$')">
<xsl:variable name="varRangeFirst" select="substring-after(preceding-sibling::node()[2][name()='link']/#href, '#')"/>
<xsl:variable name="varRangeLast" select="substring-after(#href, '#')"/>
<xsl:variable name="varRangeBetweenIDs1">
<!--xsl:value-of select="for $i in key('kFloat', $varRangeLast)/preceding-sibling::figure return $i/#xml_id"/--><!-- this will select all preceding figures, but it should between 3 and 6 -->
<xsl:value-of select="for $i in key('kFloat', $varRangeLast)/preceding-sibling::figure[for $k in preceding-sibling::figure return contains($k/#xml_id, $varRangeFirst)] return $i/#xml_id"/><!-- here getting error--><!-- please suggest to select range's between IDs from this -->
<!--xsl:if test="matches(key('kFloat', $varRangeLast)/name(), '^(figure)$')">
<xsl:for-each select="key('kFloat', $varRangeLast)/preceding-sibling::figure">
<a><xsl:value-of select="#xml_id"/></a>
</xsl:for-each>
</xsl:if-->
</xsl:variable>
<xsl:for-each select="$varRangeBetweenIDs1/a">
<xsl:variable name="var2"><xsl:value-of select="preceding-sibling::a"/></xsl:variable>
<xsl:if test="contains($var2, $varRangeFirst)">
<xsl:element name="float"><xsl:attribute name="id" select="."/></xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:if>
<xsl:for-each select="tokenize(#href, ' ')"><!--for each link's individual hrefs will have respective float element -->
<float id="{substring-after(., '#')}"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Required Result:
<root>
<p id="p1">This <link href="#fig-0001 #fig-0002"/><float id="fig-0001"/><float id="fig-0002"/>, <link href="#fig-0003"/><float id="fig-0003"/>-<link href="#fig-0006"/><float id="fig-0004"/><float id="fig-0005"/><float id="fig-0006"/></p>
<figure xml_id="fig-0001"><label>Fig. 1</label><caption><p>One</p></caption></figure>
<figure xml_id="fig-0002"><label>Fig. 2</label><caption><p>Two</p></caption></figure>
<figure xml_id="fig-0003"><label>Fig. 3</label><caption><p>Three</p></caption></figure>
<figure xml_id="fig-0004"><label>Fig. 4</label><caption><p>Four</p></caption></figure>
<figure xml_id="fig-0005"><label>Fig. 5</label><caption><p>Five</p></caption></figure>
<figure xml_id="fig-0006"><label>Fig. 6</label><caption><p>Six</p></caption></figure>
</root>
I couldn't quite work out your logic from your current XSLT, so I would consider a different approach, using templates to match the various types of link element you required. Specifically, have separate ones for link elements that precede or follow a text node with a hyphen in.
Try this XSLT. This makes use of the intersect function to get the range of elements you require in the case of 003-006.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="kFloat" match="figure" use="#xml_id"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy><xsl:apply-templates select="node()|#*"/></xsl:copy>
</xsl:template>
<xsl:template match="p/link[following-sibling::node()[1][self::text()][. = '-']][following-sibling::node()[2][self::link]]" priority="3">
<xsl:call-template name="identity" />
<xsl:apply-templates select="key('kFloat', substring-after(#href, '#'))" mode="float" />
</xsl:template>
<xsl:template match="p/link[preceding-sibling::node()[1][self::text()][. = '-']][preceding-sibling::node()[2][self::link]]" priority="2">
<xsl:call-template name="identity" />
<xsl:variable name="firstLink" select="preceding-sibling::node()[2]" />
<xsl:apply-templates select="key('kFloat', substring-after($firstLink/#href, '#'))/following-sibling::figure intersect key('kFloat', substring-after(#href, '#'))/preceding-sibling::figure" mode="float" />
<xsl:apply-templates select="key('kFloat', substring-after(#href, '#'))" mode="float" />
</xsl:template>
<xsl:template match="p/link[#href]">
<xsl:next-match />
<xsl:variable name="doc" select="/" />
<xsl:for-each select="for $ref in tokenize(#href, ' ') return substring-after($ref, '#')">
<xsl:apply-templates select="key('kFloat', ., $doc)" mode="float" />
</xsl:for-each>
</xsl:template>
<xsl:template match="figure" mode="float">
<float id="{#xml_id}"/>
</xsl:template>
</xsl:stylesheet>
See it in action at http://xsltfiddle.liberty-development.net/ncdD7nu
With the help from Tim Sir's suggestion, I modified my answer as below.
<xsl:value-of select="for $i in key('kFloat', $varRangeLast)/preceding-sibling::figure
[some $k in preceding-sibling::figure satisfies
contains($k/#xml_id, $varRangeFirst)] return $i/#xml_id"/>
XSLT2:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|#*">
<xsl:copy><xsl:apply-templates select="node()|#*"/></xsl:copy>
</xsl:template>
<xsl:key name="kFloat" match="*" use="#xml_id"/>
<xsl:template match="link[ancestor::p/following-sibling::*[1][matches(name(), '^(figure)$')]][matches(key('kFloat', if(contains(#href, ' ')) then substring-after(substring-before(#href, ' '), '#') else substring-after(#href, '#'))/name(), '^(figure)$')]">
<xsl:copy><xsl:apply-templates select="node()|#*"/></xsl:copy><!-- link element retaining-->
<!--Range between IDs selection -->
<xsl:if test="matches(preceding-sibling::node()[1][self::text()], '^(&#x2013;|&#x02013;|–|-)$')">
<xsl:variable name="varRangeFirst" select="substring-after(preceding-sibling::node()[2][name()='link']/#href, '#')"/>
<xsl:variable name="varRangeLast" select="substring-after(#href, '#')"/>
<xsl:variable name="varRangeBetweenIDs1">
<xsl:value-of select="for $i in key('kFloat', $varRangeLast)/preceding-sibling::figure[some $k in preceding-sibling::figure satisfies contains($k/#xml_id, $varRangeFirst)] return $i/#xml_id"/>
</xsl:variable>
<xsl:for-each select="tokenize($varRangeBetweenIDs1, ' ')">
<xsl:element name="float"><xsl:attribute name="id" select="."/></xsl:element>
</xsl:for-each>
</xsl:if>
<xsl:for-each select="tokenize(#href, ' ')"><!--for each link's individual hrefs will have respective float element -->
<float id="{substring-after(., '#')}"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Check if xsl:attribute name is valid for XSL-FO

In my data it's possible that there are one or more processing-instructions which are used to give that specific block of content new attributes or overwrite the values of existing ones.
The way I do this requires it that the name of the PIs are valid xsl attribute names.
The Question: Is it possible to check within the xsl-stylesheet if the name of the PI is an actual valid (=allowed as <xsl:attribute name="*thisname*"> in XSL-FO) attribute name?
<xsl:if test="./processing-instruction()"> <!-- add condition to test for valid name? -->
<xsl:for-each select="./processing-instruction()">
<xsl:variable name="pi_name"><xsl:value-of select="local-name()" /></xsl:variable>
<xsl:attribute name="{$pi_name}"><xsl:value-of select="." /></xsl:attribute>
</xsl:for-each>
</xsl:if>
EDIT:
Regarding this problem: Check if xsl:attribute name is valid for XSL-FO
That's the code I use derived from Tony's solution:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="handle_block_attribute">
<xsl:variable name="attributes" select="document('PATH\attributelist.xml')//attributelist/attribute"/>
<xsl:if test=".//processing-instruction()">
<xsl:for-each select=".//processing-instruction()">
<xsl:variable name="pi_name"><xsl:value-of select="local-name()" /></xsl:variable>
<xsl:choose>
<xsl:when test="$pi_name = $attributes">
<xsl:attribute name="{$pi_name}">
<xsl:value-of select="." />
</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<fo:inline color="red">Invalid attribute-name in PI: <xsl:value-of select="$pi_name" /></fo:inline><fo:block />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The template is called in this way:
<xsl:template match="para">
<fo:block xsl:use-attribute-sets="para.standard">
<xsl:call-template name="handle_block_attribute" />
<xsl:apply-templates/>
</fo:block>
</xsl:template>
And that's the data:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<book>
<sect class="hierarchic" type="chapter">
<para class="heading" style="Chapter">
<inline style="HeadingText">HD</inline>
</para>
<para style="Standard">Lorem ipsum dolor sit amet consectetuer eleifend consequat pede Aenean est. <?font-size 13pt?><?fotn-family Arial?><?color green?><?fline-height 3pt?></para>
<para style="Standard">Consequat semper tortor id convallis leo Phasellus eget non sagittis neque.</para>
</sect>
</book>
EDIT2:
Well, maybe there is a more elegant way, but it works: I'm going with two for-each-loops, one for the correct PIs and after that another one for the flawed ones including error-output.
<xsl:if test=".//processing-instruction()">
<xsl:for-each select=".//processing-instruction()">
<xsl:variable name="pi_name"><xsl:value-of select="local-name()" /></xsl:variable>
<xsl:if test="$pi_name = $attributes">
<xsl:attribute name="{$pi_name}">
<xsl:value-of select="." />
</xsl:attribute>
</xsl:if>
</xsl:for-each>
<xsl:for-each select=".//processing-instruction()">
<xsl:variable name="pi_name"><xsl:value-of select="local-name()" /></xsl:variable>
<xsl:if test="not($pi_name = $attributes)">
<fo:inline color="red">Invalid attribute name in PI: <xsl:value-of select="$pi_name" /></fo:inline><fo:block />
</xsl:if>
</xsl:for-each>
</xsl:if>
You've made it harder than necessary for yourself by using XSLT 1.0 instead of either XSLT 2.0 or XSLT 3.0 simply because it's harder to construct a list of property names to compare against. With the later versions, you could just make a sequence of strings. With XSLT 1.0, the simplest way (that I can remember) is to put each property name as the value of a separate element and then compare the prospective property name against the selected set of elements. The magic of XPath's = is that it is true if any value of one side matches any value on the other side.
You can extract the definitive list of XSL-FO property names by processing the XML source for the XSL specification: http://www.w3.org/TR/2006/REC-xsl11-20061205/xslspec.xml
With this stylesheet:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:local="uuid:503f6e96-fda1-4464-98b6-a60dbecc5946"
exclude-result-prefixes="local">
<properties xmlns="uuid:503f6e96-fda1-4464-98b6-a60dbecc5946">
<property>font-size</property>
<property>font-weight</property>
</properties>
<xsl:variable name="properties" select="document('')/*/local:properties/local:property"/>
<xsl:template match="fo:*">
<xsl:copy>
<xsl:copy-of select="#*" />
<xsl:for-each select="processing-instruction()">
<xsl:variable name="pi_name" select="local-name()" />
<xsl:choose>
<xsl:when test="$pi_name = $properties">
<xsl:attribute name="{$pi_name}">
<xsl:value-of select="." />
</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:message>Unrecognised property: <xsl:value-of select="$pi_name"
/></xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
this document:
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:block><?font-size 16pt?><?font-weight bold?></fo:block>
<fo:block><?fotn-size 16pt?><?bogus bold?></fo:block>
</fo:root>
generates this:
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:block font-size="16pt" font-weight="bold"/>
<fo:block/>
</fo:root>
plus messages about 'fotn-size' and 'bogus'.
I put the property names in the stylesheet to make it easier to test. You could put the XML in an external document and not have to fuss with the local namespace. The namespace was necessary because the only non-XSLT elements allowed at the top level of an XSLT stylesheet have to be in a non-XSLT namespace.
As Martin Honnen has commented, PI target and attributes names must conform the Name production of XML specification. Thus, the only case when this might conflict is when a PI target could be interpreted as a QName and the prefix does not match an in scope namespace binding.
Because of that, : is not allowed in PI target.
Here is the errors given by different XSLT processor when parsing the input source:
<?a:b 7pt?>
<root/>
Saxon:
org.xml.sax.SAXParseException; systemId: urn:from-string; lineNumber:
1; columnNumber: 6; A colon is not allowed in the name 'a:b' when
namespaces are enabled.
MSXML:
Input parsing error: Entity names, PI targets, notation names and
attribute values declared to be of types ID, IDREF(S), ENTITY(IES) or
NOTATION cannot contain any colons. , 1:7, <?a:b 7pt?>
Edit: about <?fotn-size 7pt?>, this stylesheet following your example
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<root>
<xsl:if test="./processing-instruction()">
<xsl:for-each select="./processing-instruction()">
<xsl:variable name="pi_name">
<xsl:value-of select="local-name()" />
</xsl:variable>
<xsl:attribute name="{$pi_name}">
<xsl:value-of select="." />
</xsl:attribute>
</xsl:for-each>
</xsl:if>
</root>
</xsl:template>
</xsl:stylesheet>
With this input:
<?fotn-size 7pt?>
<root/>
Output:
<root fotn-size="7pt"/>

updating an xml from another one using ids

I have to pick text from translation.xml and update skeleton.xml's respective (where #xid =#id) nodes . in other words IF #id of translation.xml matches with #xid of skeleton.xml, the text content should be placed under skeleton.xml (+ if there is child element present the text content (inline element i, b, etc) also should go in respective parent element: Please see example below for more details:
Note: the inline element could be anything in skeleton.xml (its not restrict to b or i, so it should be generic)
skeleton.xml
<root>
<para a="b" b="c">
<text xid="1">This is first para <b xid="2" a="c" b="d">This is bold <i xid="3" b="d" c="e">This is italics</i> rest of bold</b> rest of para</text>
</para>
<para><text xid="4">This is second para</text></para>
<para><text xid="5">This is unchanged para</text></para>
<para><text xid="6">This is unchanged para</text></para>
</root>
translation.xml
<root>
<TU id="1">
<source>This is first para <g id="2" tagName="b">This is bold <g id="3" tagName="i">This is italics</g> rest of bold</g> rest of para</source>
<target>suum primum para <g id="2" tagName="b">Et hoc confidens, <g id="3" tagName="i">Hoc est, Te Deum</g> Reliqua autem audet,</g> reliqua autem verba haec</target>
</TU>
<TU id="4">
<source>This is second para</source>
<target>Hoc est secundum verba haec</target>
</TU>
</root>
UpdatedSkeleton.xml
<root>
<para a="b" b="c">
<text xid="1">suum primum para <b xid="2" a="c" b="d">Et hoc confidens, <i xid="3" b="d" c="e">Hoc est, Te Deum</i> Reliqua autem audet,</b> reliqua autem verba haec</text>
</para>
<para><text xid="4">Hoc est secundum verba haec</text></para>
<para><text xid="5">This is unchanged para</text></para>
<para><text xid="6">This is unchanged para</text></para>
</root>
I am trying with this code, but facing challenge to place text of inline content at the right place:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:param name="translation" select="'file:/C:/Skeleton.xml'"></xsl:param>
<xsl:variable name="doc">
<xsl:copy-of select="doc($translation)"/>
</xsl:variable>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text">
<xsl:variable name="skelID" select="#xid"/>
<xsl:choose>
<xsl:when test="$doc//*[$skelID=#id]">
<xsl:apply-templates select="$doc//*[$skelID=#id]/target"/>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
You can copy the <text> node adding <xsl:copy> to your test block, and copy the xid attribute using <xsl:attribute>. For the unmodified nodes, you can use <xsl:copy-of> which copies the full tree, including attributes:
<xsl:when test="$doc//*[$skelID=#id]">
<xsl:copy>
<xsl:attribute name="xid">
<xsl:value-of select="$skelID"/>
</xsl:attribute>
<xsl:apply-templates select="$doc//*[$skelID=#id]/target"/>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
That will also copy the <target> element. You can remove it adding a template:
<xsl:template match="target">
<xsl:apply-templates/>
</xsl:template>
You also aren't replacing the <g> tag. I assume the #tagName attribute says what it's supposed to be transformed into. This should do it:
<xsl:template match="g">
<xsl:element name="{#tagName}">
<xsl:attribute name="xid">
<xsl:value-of select="#id"/>
</xsl:attribute>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
I might have missed something, but that will probably solve most of your problem.

Extract Xpaths of all nodes and then their attributes

I am struggling with xslt from the past 2 days, owing to my starter status.My requirement is that given any input XML file ,I want the output to be a list of all the XPaths of all the tags in order in which they appear in the original XML document(parent, then parent,parents Attributes list/child, parent/child/childOFchild and so forth). THe XSLT should not be specific to any single XMl schema. It should work for any XML file, which is a valid one.
Ex:
If the Input XML Is :
<v1:Root>
<v1:UserID>test</v1:UserID>
<v1:Destination>test</v1:Destination>
<v1:entity name="entiTyName">
<v11:attribute name="entiTyName"/>
<v11:attribute name="entiTyName"/>
<v11:attribute name="entiTyName"/>
<v11:filter type="entiTyName">
<v11:condition attribute="entiTyName" operator="eq" value="{FB8D669E-D090-E011-8F43-0050568E222C}"/>
<v11:condition attribute="entiTyName" operator="eq" value="1"/>
</v11:filter>
<v11:filter type="or">
<v11:filter type="or">
<v11:filter type="and">
<v11:filter type="and">
<v11:condition attribute="cir_customerissuecode" operator="not-like" value="03%"/>
</v11:filter>
</v11:filter>
</v11:filter>
</v11:filter>
</v1:entity>
</v1:Root>
I want my output to be :
/v1:Root/v1:UserID
/v1:Root/v1:Destination
/v1:Root/v1:entity/#name
/v1:Root/v1:entity/v11:attribute
/v1:Root/v1:entity/v11:attribute/#name
/v1:Root/v1:entity/v11:attribute[2]
/v1:Root/v1:entity/v11:attribute[2]/#name
/v1:Root/v1:entity/v11:attribute[3]
/v1:Root/v1:entity/v11:attribute[3]/#name
/v1:Root/v1:entity/v11:filter/#type
/v1:Root/v1:entity/v11:filter/v11:condition
/v1:Root/v1:entity/v11:filter/v11:condition/#attribute
/v1:Root/v1:entity/v11:filter/v11:condition/#operator
/v1:Root/v1:entity/v11:filter/v11:condition/#value
/v1:Root/v1:entity/v11:filter/v11:condition[2]
/v1:Root/v1:entity/v11:filter/v11:condition[2]/#attribute
/v1:Root/v1:entity/v11:filter/v11:condition[2]/#operator
/v1:Root/v1:entity/v11:filter/v11:condition[2]/#value
/v1:Root/v1:entity/v11:filter[2]/v11:filter/#type
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/#type
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter/#type
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter/v11:condition
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter/v11:condition/#attribute
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter/v11:condition/#operator
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter/v11:condition/#value
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter[2]/#type
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter[2]/v11:condition
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter[2]/v11:condition/#attribute
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter[2]/v11:condition/#operator
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter[2]/v11:condition/#value
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter[2]/v11:condition[2]
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter[2]/v11:condition[2]/#attribute
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter[2]/v11:condition[2]/#operator
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter[2]/v11:condition[2]/#value
So, it is basically all the XPath of each element ,then the Xpath of the elements Attributes.
I have an XSLT with me, which is like this:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="no" />
<xsl:template match="*[not(child::*)]">
<xsl:for-each select="ancestor-or-self::*">
<xsl:value-of select="concat('/', name())" />
<xsl:if test="count(preceding-sibling::*[name() = name(current())]) != 0">
<xsl:value-of
select="concat('[', count(preceding-sibling::*[name() = name(current())]) + 1, ']')" />
</xsl:if>
</xsl:for-each>
<xsl:apply-templates select="*" />
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="*" />
</xsl:template>
</xsl:stylesheet>
THe output which gets Produced does not cater to complex tags and also the tag's attributes in the resulting Xpath list :(.
Kindly help me in fixing this xslt to produce the output as mentioned above.
THe present output from the above XSLT is like this :
/v1:Root/v1:UserID
/v1:Root/v1:Destination
/v1:Root/v1:entity/v11:attribute
/v1:Root/v1:entity/v11:attribute[2]
/v1:Root/v1:entity/v11:attribute[3]
/v1:Root/v1:entity/v11:filter/v11:condition
/v1:Root/v1:entity/v11:filter/v11:condition[2]
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter/v11:condition
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter[2]/v11:condition
/v1:Root/v1:entity/v11:filter[2]/v11:filter/v11:filter/v11:filter[2]/v11:condition[2]
/v1:Root/v1:entity/v11:filter[2]/v11:filter[2]/v11:filter/v11:condition
/v1:Root/v1:entity/v11:filter[2]/v11:filter[2]/v11:filter[2]/v11:condition
/v1:Root/v1:entity/v11:filter[2]/v11:filter[2]/v11:filter[2]/v11:condition[2]
/v1:Root/v1:entity/v11:filter[2]/v11:filter[2]/v11:filter[2]/v11:condition[3]
I think there's a discrepancy between your sample input and output, in that the output describes a filter element with two conditions that's not in the source XML. At any rate, I believe this works:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="no" />
<!-- Handle attributes -->
<xsl:template match="#*">
<xsl:apply-templates select="ancestor-or-self::*" mode="buildPath" />
<xsl:value-of select="concat('/#', name())"/>
<xsl:text>
</xsl:text>
</xsl:template>
<!-- Handle non-leaf elements (just pass processing downwards) -->
<xsl:template match="*[#* and *]">
<xsl:apply-templates select="#* | *" />
</xsl:template>
<!-- Handle leaf elements -->
<xsl:template match="*[not(*)]">
<xsl:apply-templates select="ancestor-or-self::*" mode="buildPath" />
<xsl:text>
</xsl:text>
<xsl:apply-templates select="#*" />
</xsl:template>
<!-- Outputs a path segment for the matched element: '/' + name() + [ordinalPredicate > 1] -->
<xsl:template match="*" mode="buildPath">
<xsl:value-of select="concat('/', name())" />
<xsl:variable name="sameNameSiblings" select="preceding-sibling::*[name() = name(current())]" />
<xsl:if test="$sameNameSiblings">
<xsl:value-of select="concat('[', count($sameNameSiblings) + 1, ']')" />
</xsl:if>
</xsl:template>
<!-- Ignore text -->
<xsl:template match="text()" />
</xsl:stylesheet>

how to merge element using xslt?

I have an reference type of paragraph with element.
Example
Input file:
<reference>
<emph type="bold">Antony</emph><emph type="bold">,</emph> <emph type="bold">R.</emph>
<emph type="bold">and</emph> <emph type="bold">Micheal</emph><emph type="bold">,</emph> <emph type="bold">V.</emph>
<emph type="italic">reference title</emph></reference>
Output received now:
<p class="reference"><strong>Antony</strong><strong>,</strong> <strong>R.</strong>
<strong>and</strong> <strong>Micheal</strong><strong>,</emph>
<emph type="bold">V.</strong> <em>reference title></em></p>
Required output file:
<p class="reference"><strong>Antony, R. and Micheal, V.</strong> <em>reference title</em></p>
My xslt scripts:
<xsl:template match="reference">
<p class="reference"><xsl:apply-templates/></p>
</xsl:template>
<xsl:template match="emph">
<xsl:if test="#type='bold'">
<strong><xsl:apply-templates/></strong>
</xsl:if>
<xsl:if test="#type='italic'">
<em><xsl:apply-templates/></em>
</xsl:if>
</xsl:template>
What needs to be corrected in xslt to get the <strong> element single time like the required output file?
Please advice anyone..
By,
Antny.
This is an XSLT 1.0 solution:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="xml" encoding="utf-8" />
<!-- the identity template copies everything verbatim -->
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*" />
</xsl:copy>
</xsl:template>
<!-- this matches the first <emph> nodes of their kind in a row -->
<xsl:template match="emph[not(#type = preceding-sibling::emph[1]/#type)]">
<xsl:variable name="elementname">
<xsl:choose>
<xsl:when test="#type='bold'">strong</xsl:when>
<xsl:when test="#type='italic'">em</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:if test="$elementname != ''">
<!-- the first preceding node with a different type is the group separator -->
<xsl:variable
name="boundary"
select="generate-id(preceding-sibling::emph[#type != current()/#type][1])
" />
<xsl:element name="{$elementname}">
<!-- select all <emph> nodes of the row with the same type... -->
<xsl:variable
name="merge"
select=". | following-sibling::emph[
#type = current()/#type
and
generate-id(preceding-sibling::emph[#type != current()/#type][1]) = $boundary
]"
/>
<xsl:apply-templates select="$merge" mode="text" />
</xsl:element>
</xsl:if>
</xsl:template>
<!-- default: keep <emph> nodes out of the identity template mechanism -->
<xsl:template match="emph" />
<!-- <emph> nodes get their special treatment here -->
<xsl:template match="emph" mode="text">
<!-- effectively, this copies the text node via the identity template -->
<xsl:apply-templates />
<!-- copy the first following node - if it is a text node
(this is to get interspersed spaces into the output) -->
<xsl:if test="
generate-id(following-sibling::node()[1])
=
generate-id(following-sibling::text()[1])
">
<xsl:apply-templates select="following-sibling::text()[1]" />
</xsl:if>
</xsl:template>
</xsl:stylesheet>
It results in:
<reference>
<strong>Antony, R. and Micheal, V.</strong>
<em>reference title</em>
</reference>
I'm not overly happy with
<xsl:variable
name="merge"
select=". | following-sibling::emph[
#type = current()/#type
and
generate-id(preceding-sibling::emph[#type != current()/#type][1]) = $boundary
]"
/>
if someone has a better idea, please tell me.
Here is my method, which uses recursive calls of a template to match elements with the same type.
It first matchs the first 'emph' element, and them recursively calls a template matching 'emph' elements of the same type. Next, it repeats the process matching the next 'emph' element of a type different to the one currently matched.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="utf-8"/>
<!-- Match root element -->
<xsl:template match="reference">
<p class="reference">
<!-- Match first emph element -->
<xsl:apply-templates select="emph[1]"/>
</p>
</xsl:template>
<!-- Used to match first occurence of an emph element for any type -->
<xsl:template match="emph">
<xsl:variable name="elementname">
<xsl:if test="#type='bold'">strong</xsl:if>
<xsl:if test="#type='italic'">em</xsl:if>
</xsl:variable>
<xsl:element name="{$elementname}">
<xsl:apply-templates select="." mode="match">
<xsl:with-param name="type" select="#type"/>
</xsl:apply-templates>
</xsl:element>
<!-- Find next emph element with a different type -->
<xsl:apply-templates select="following-sibling::emph[#type!=current()/#type][1]"/>
</xsl:template>
<!-- Used to match emph elements of a specific type -->
<xsl:template match="*" mode="match">
<xsl:param name="type"/>
<xsl:if test="#type = $type">
<xsl:value-of select="."/>
<xsl:apply-templates select="following-sibling::*[1]" mode="match">
<xsl:with-param name="type" select="$type"/>
</xsl:apply-templates>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Where this currently fails though, is that it doesn't match the whitespace in between the 'emph' elements.