This is a followup to this question.
I have several <span> tags in a document with several semicolon separated style attributes. Right now I have 3 specific style attributes I'm looking for to translate into tags. All works well in the example above as long as the style attribute only contains one of the three style attributes. If a span has more, I get an ambiguous rule match.
The three style attributes I'm looking for are font-style:italic, font-weight:600, and text-decoration:underline which should be removed from the style attribute and transformed into <em>, <strong>, and <u>, respectively.
Here's my current XSLT:
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="span[
contains(translate(#style, ' ', ''), 'font-style:italic')
]">
<xsl:copy>
<xsl:attribute name="style">
<xsl:value-of select="substring-before(#style, ' font-style')"/>
<xsl:value-of select="substring-after(#style, 'italic;')"/>
</xsl:attribute>
<em>
<xsl:apply-templates select="node()"/>
</em>
</xsl:copy>
</xsl:template>
<xsl:template match="span[
contains(translate(#style, ' ', ''), 'font-weight:600')
]">
<xsl:copy>
<xsl:attribute name="style">
<xsl:value-of select="substring-before(#style, ' font-weight')"/>
<xsl:value-of select="substring-after(#style, '600;')"/>
</xsl:attribute>
<strong>
<xsl:apply-templates select="node()"/>
</strong>
</xsl:copy>
</xsl:template>
<xsl:template match="span[
contains(translate(#style, ' ', ''), 'text-decoration:underline')
]">
<xsl:copy>
<xsl:attribute name="style">
<xsl:value-of select="substring-before(#style, ' text-decoration')"/>
<xsl:value-of select="substring-after(#style, 'underline;')"/>
</xsl:attribute>
<u>
<xsl:apply-templates select="node()"/>
</u>
</xsl:copy>
</xsl:template>
Which will generate the ambiguous rule warning doesn't work correctly on some elements that contain more than one of the listed attributes.
An example of the input:
<span style=" text-decoration: underline; font-weight:600; color:#555555">some text</span>
gets transformed to:
<span style=" font-weight:600; color:#555555"><u>some text</u></span>
when the desired result is:
<span style="color:#555555"><b><u>some text</u></b></span>
How can I fix the ambiguous rule match for this?
Thanks in advance
Update:
If i set priorty on each of the templates to descending values and run the XSLT again on the output of the first XSLT run, everything works as expected. There has to be an easier way than running it through the transformation twice. Any ideas?
As Alejandro and Tomalak suggested, replacing the style attributes with a class attribute for CSS classes is an option, too.
EDIT: Just in case real problem gets hide, I have simplified the stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:s="styles"
exclude-result-prefixes="s msxsl">
<s:s prop="font-style:italic" name="em"/>
<s:s prop="font-weight:600" name="strong"/>
<s:s prop="text-decoration:underline" name="u"/>
<xsl:variable name="vStyles" select="document('')/*/s:s"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="span[#style]">
<xsl:variable name="vrtfProp">
<xsl:call-template name="parser"/>
</xsl:variable>
<xsl:variable name="vProp"
select="msxsl:node-set($vrtfProp)/*"/>
<xsl:copy>
<xsl:apply-templates select="#*[name()!='style']"/>
<xsl:attribute name="style">
<xsl:for-each select="$vProp[not(.=$vStyles/#prop)]">
<xsl:value-of select="concat(.,';')"/>
</xsl:for-each>
</xsl:attribute>
<xsl:call-template name="generate">
<xsl:with-param
name="pElements"
select="$vStyles[#prop=$vProp]/#name"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="generate">
<xsl:param name="pElements" select="/.."/>
<xsl:choose>
<xsl:when test="$pElements">
<xsl:element name="{$pElements[1]}">
<xsl:call-template name="generate">
<xsl:with-param
name="pElements"
select="$pElements[position()>1]"/>
</xsl:call-template>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="parser">
<xsl:param name="pString" select="concat(#style,';')"/>
<xsl:if test="contains($pString,';')">
<xsl:variable
name="vProp"
select="substring-before($pString,';')"/>
<prop>
<xsl:value-of
select="concat(
normalize-space(
substring-before($vProp,':')
),
':',
normalize-space(
substring-after($vProp,':')
)
)"/>
</prop>
<xsl:call-template name="parser">
<xsl:with-param
name="pString"
select="substring-after($pString,';')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Output:
<span style="color:#555555;"><strong><u>some text</u></strong></span>
Note: Simpler parsing with space normalization to match properties in an existencial comparison. Generating content without optimization (selecting no match, selecting match). "Stateful" or "stackful" named template for output nested elements. Either way there are two rules (identity and span with #style overwriting it) and two names templates (parser/tokenizer and generator of nested content)
Original stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:s="styles"
xmlns:t="tokenizer"
exclude-result-prefixes="s t msxsl">
<s:s r="font-style" v="italic" e="em"/>
<s:s r="font-weight" v="600" e="strong"/>
<s:s r="text-decoration" v="underline" e="u"/>
<t:t s=";" n="p"/>
<t:t s=":" n="t"/>
<xsl:variable name="vStyles" select="document('')/*/s:s"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="span[#style]">
<xsl:variable name="vrtfStyles">
<xsl:call-template name="tokenizer"/>
</xsl:variable>
<xsl:copy>
<xsl:apply-templates select="#*[name()!='style']"/>
<xsl:call-template name="generate">
<xsl:with-param name="pStyles"
select="msxsl:node-set($vrtfStyles)/*"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="generate">
<xsl:param name="pStyles" select="/.."/>
<xsl:param name="pAttributes" select="/.."/>
<xsl:param name="pElements" select="/.."/>
<xsl:choose>
<xsl:when test="$pStyles">
<xsl:variable name="vMatch"
select="$vStyles[#r=$pStyles[1]/t[1]]
[#v=$pStyles[1]/t[2]]"/>
<xsl:call-template name="generate">
<xsl:with-param name="pStyles"
select="$pStyles[position()>1]"/>
<xsl:with-param name="pAttributes"
select="$pAttributes|
$pStyles[1][not($vMatch)]"/>
<xsl:with-param name="pElements"
select="$pElements|$vMatch"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="$pAttributes">
<xsl:attribute name="style">
<xsl:for-each select="$pAttributes">
<xsl:value-of select="concat(t[1],':',t[2],';')"/>
</xsl:for-each>
</xsl:attribute>
<xsl:call-template name="generate">
<xsl:with-param name="pElements" select="$pElements"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="$pElements">
<xsl:element name="{$pElements[1]/#e}">
<xsl:call-template name="generate">
<xsl:with-param name="pElements"
select="$pElements[position()>1]"/>
</xsl:call-template>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="tokenizer">
<xsl:param name="pTokenizer" select="document('')/*/t:t"/>
<xsl:param name="pString" select="#style"/>
<xsl:choose>
<xsl:when test="not($pTokenizer)">
<xsl:value-of select="normalize-space($pString)"/>
</xsl:when>
<xsl:when test="contains($pString,$pTokenizer[1]/#s)">
<xsl:call-template name="tokenizer">
<xsl:with-param name="pTokenizer" select="$pTokenizer"/>
<xsl:with-param name="pString"
select="substring-before(
$pString,
$pTokenizer[1]/#s
)"/>
</xsl:call-template>
<xsl:call-template name="tokenizer">
<xsl:with-param name="pTokenizer" select="$pTokenizer"/>
<xsl:with-param name="pString"
select="substring-after(
$pString,
$pTokenizer[1]/#s
)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{$pTokenizer[1]/#n}">
<xsl:call-template name="tokenizer">
<xsl:with-param name="pTokenizer"
select="$pTokenizer[position()>1]"/>
<xsl:with-param name="pString" select="$pString"/>
</xsl:call-template>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Note: Recursion paradise. Nested tokenizer for parsing style properties. "Stateful" template for nested content (and performance matching properties, by the way)
Here is an XSLT 1.0 solution using the str-split-to-words template/function of FXSL:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
exclude-result-prefixes="ext"
>
<xsl:import href="strSplit-to-Words.xsl"/>
<xsl:output indent="yes" omit-xml-declaration="yes"/>
<xsl:param name="pStyleReps">
<r s="font-style:italic"><em/></r>
<r s="font-weight:600"><strong/></r>
<r s="text-decoration:underline"><u/></r>
</xsl:param>
<xsl:variable name="vReps" select=
"document('')/*/xsl:param[#name='pStyleReps']/*"/>
<xsl:template match="span">
<xsl:variable name="vrtfStyles">
<xsl:call-template name="str-split-to-words">
<xsl:with-param name="pStr" select="#style"/>
<xsl:with-param name="pDelimiters" select="';'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vStyles" select=
"ext:node-set($vrtfStyles)/*"/>
<xsl:choose>
<xsl:when test=
"not($vReps/#s[contains(current()/#style, .)])">
<xsl:copy-of select="."/>
</xsl:when>
<xsl:otherwise>
<span>
<xsl:copy-of select="#*"/>
<xsl:attribute name="style">
<xsl:for-each select=
"$vStyles[not(translate(.,' ','')=$vReps/#s)]">
<xsl:value-of select="."/>
<xsl:if test="not(position()=last())">;</xsl:if>
</xsl:for-each>
</xsl:attribute>
<xsl:call-template name="styles2markup">
<xsl:with-param name="pStyles" select=
"$vReps/#s
[contains
(translate(current()/#style, ' ', ''),
.
)
]"/>
</xsl:call-template>
</span>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="styles2markup">
<xsl:param name="pResult" select="text()"/>
<xsl:param name="pStyles"/>
<xsl:choose>
<xsl:when test="not($pStyles)">
<xsl:copy-of select="$pResult"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vrtfnewResult">
<xsl:element name="{name($pStyles[1]/../*)}">
<xsl:copy-of select="$pResult"/>
</xsl:element>
</xsl:variable>
<xsl:call-template name="styles2markup">
<xsl:with-param name="pStyles" select=
"$pStyles[position()>1]"/>
<xsl:with-param name="pResult" select=
"ext:node-set($vrtfnewResult)/*"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<span style=" text-decoration: underline; font-weight:600; color:#555555">some text</span>
the wanted, correct result is produced:
<span style=" color:#555555">
<u>
<strong>some text</strong>
</u>
</span>
Related
My requirement is as below
1) Replace Space with %20
2) Replace / with %2F
3) empty elements with value as dummy
Sample Input:
<?xml version="1.0" encoding="UTF-8" ?>
<process xmlns="http://xmlns.oracle.com/CCS/Project5/BPELProcess1">
<Sender>Sender 1</Sender>
<TransactionId>TransactionId/2</TransactionId>
<TransactionType>TransactionType5</TransactionType>
<Status>Status6</Status>
<Limit>70.73</Limit>
<Remarks>Remarks8</Remarks>
<Result>GlobalResult9</Result>
<Type>DecisionType10</Type>
<DecidedBy>DecidedBy11</DecidedBy>
<AddRequest1/>
<AddRequest2>RAMA</AddRequest2>
</process>
Required Output:
<process xmlns="http://xmlns.oracle.com/CCS/Project5/BPELProcess1">
<Sender>Sender%201</Sender>
<TransactionId>TransactionId%2F2</TransactionId>
<TransactionType>TransactionType5</TransactionType>
<Status>Status6</Status>
<Limit>70.73</Limit>
<Remarks>Remarks8</Remarks>
<Result>GlobalResult9</Result>
<Type>DecisionType10</Type>
<DecidedBy>DecidedBy11</DecidedBy>
<AddRequest1>DUMMY</AddRequest1>
<AddRequest2>RAMA</AddRequest2>
</process>
I have tried below XSLT but could not find a wat replace empty node to Dummy
eg
<AddRequest1></AddRequest1> is not coverted to <AddRequest1>DUMMY</AddRequest1>
XSLT tried is as below
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.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:template match="//text()">
<xsl:call-template name="rep_SPLChar">
<xsl:with-param name="text" select="."/>
</xsl:call-template>
</xsl:template>
<xsl:template name="rep_SPLChar">
<xsl:param name="text"/>
<xsl:variable name="temp_space" select="'%20'"/>
<xsl:variable name="temp_backslash" select="'%2F'"/>
<xsl:if test="normalize-space($text) != ''">
<xsl:choose>
<xsl:when test="contains($text,' ')">
<xsl:call-template name="rep_SPLChar">
<xsl:with-param name="text"
select="concat((concat(substring-before($text,' '),$temp_space)),substring-after($text,' '))"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="contains($text,'/')">
<xsl:call-template name="rep_SPLChar">
<xsl:with-param name="text"
select="concat((concat(substring- before($text,'/'),$temp_backslash)),substring-after($text,'/'))"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Try adding this template to your XSLT to match "empty" elements
<xsl:template match="*[not(*)][not(normalize-space())]">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:text>DUMMY</xsl:text>
</xsl:copy>
</xsl:template>
Given the following XML:
<application>
<documentation>Before text.
[This|http://google.com] is a link.
Click [here|http://google.com] for another link.
After text.
</documentation>
</application>
I want to replace new lines with <br/> tags and create links. The outcome should produce the following HTML:
Before text.<br/>
This is a link.<br/>
Click here for another link.<br/>
After text.
I have the XSLT below with the appropriate templates. My problem is that the <br/> elements added by the first function are stripped out by the second function. I've tried using an identity transform, but I can't wrap my head around it.
Any help would be appreciated.
My current XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" version="1.0"/>
<xsl:template match="application">
<p>
<xsl:variable name="with_new_lines">
<xsl:call-template name="replace-newlines">
<xsl:with-param name="text" select="documentation"/>
<xsl:with-param name="replace" select="'
'"/>
<xsl:with-param name="by" select="'new_line'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="with_links">
<xsl:call-template name="create-links">
<xsl:with-param name="text">
<xsl:copy-of select="$with_new_lines"/>
</xsl:with-param>
</xsl:call-template>
</xsl:variable>
<xsl:copy-of select="$with_links"/>
</p>
</xsl:template>
<xsl:template name="replace-newlines">
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($text, '
')">
<xsl:copy-of select="substring-before($text,'
')"/>
<br/>
<xsl:call-template name="replace-newlines">
<xsl:with-param name="text" select="substring-after($text,'
')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Changing [here|http://a.com] to hereand
[http://a.com] to http://a.com-->
<xsl:template match="*" name="create-links">
<xsl:param name="text" select="."/>
<xsl:choose>
<xsl:when test="contains($text, 'http')">
<xsl:copy-of select="substring-before($text,'[')"/>
<xsl:variable name="the_link_and_after">
<xsl:copy-of select="substring-after($text,'[')"/>
</xsl:variable>
<xsl:variable name="the_link">
<xsl:copy-of select="substring-before($the_link_and_after,']')"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="contains($the_link,'|')">
<xsl:variable name="the_url">
<xsl:copy-of select="substring-after($the_link,'|')"/>
</xsl:variable>
<a href="{$the_url}">
<xsl:copy-of select="substring-before($the_link,'|')"/>
</a>
</xsl:when>
<xsl:otherwise>
<a href="{$the_link}">
<xsl:copy-of select="$the_link"/>
</a>
</xsl:otherwise>
</xsl:choose>
<!--<xsl:copy-of select="substring-after($text,']')" />-->
<xsl:call-template name="create-links">
<xsl:with-param name="text" select="substring-after($text,']')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
The problem with your approach is that each of your named templates creates a result-tree-fragment, containing a mixture of text nodes and elements (either <br> or <a>). When you send the result to be processed as a string by the other template, only the text nodes survive the treatment.
I would suggest you start by tokenizing the input into lines as elements. Then send each line in turn to be processed by the other template:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="html" encoding="utf-8"/>
<xsl:template match="application">
<xsl:variable name="lines">
<xsl:call-template name="tokenize-lines">
<xsl:with-param name="text" select="documentation"/>
</xsl:call-template>
</xsl:variable>
<p>
<xsl:for-each select="exsl:node-set($lines)/line">
<xsl:call-template name="create-links">
<xsl:with-param name="text" select="."/>
</xsl:call-template>
<br/>
</xsl:for-each>
</p>
</xsl:template>
<xsl:template name="tokenize-lines">
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($text, '
')">
<line><xsl:copy-of select="substring-before($text,'
')"/></line>
<xsl:call-template name="tokenize-lines">
<xsl:with-param name="text" select="substring-after($text,'
')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<line><xsl:copy-of select="$text"/></line>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="create-links">
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($text, 'http')">
<xsl:copy-of select="substring-before($text,'[')"/>
<xsl:variable name="the_link_and_after">
<xsl:copy-of select="substring-after($text,'[')"/>
</xsl:variable>
<xsl:variable name="the_link">
<xsl:copy-of select="substring-before($the_link_and_after,']')"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="contains($the_link,'|')">
<xsl:variable name="the_url">
<xsl:copy-of select="substring-after($the_link,'|')"/>
</xsl:variable>
<a href="{$the_url}">
<xsl:copy-of select="substring-before($the_link,'|')"/>
</a>
</xsl:when>
<xsl:otherwise>
<a href="{$the_link}">
<xsl:copy-of select="$the_link"/>
</a>
</xsl:otherwise>
</xsl:choose>
<!--<xsl:copy-of select="substring-after($text,']')" />-->
<xsl:call-template name="create-links">
<xsl:with-param name="text" select="substring-after($text,']')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Alternatively, you could call the 2nd template from within the 1st one, for each line separately before returning it:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="utf-8"/>
<xsl:template match="application">
<p>
<xsl:call-template name="create-lines">
<xsl:with-param name="text" select="documentation"/>
</xsl:call-template>
</p>
</xsl:template>
<xsl:template name="create-lines">
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($text, '
')">
<xsl:call-template name="create-links">
<xsl:with-param name="text" select="substring-before($text,'
')"/>
</xsl:call-template>
<br/>
<xsl:call-template name="create-lines">
<xsl:with-param name="text" select="substring-after($text,'
')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="create-links">
<xsl:with-param name="text" select="$text"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="create-links">
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($text, 'http')">
<xsl:copy-of select="substring-before($text,'[')"/>
<xsl:variable name="the_link_and_after">
<xsl:copy-of select="substring-after($text,'[')"/>
</xsl:variable>
<xsl:variable name="the_link">
<xsl:copy-of select="substring-before($the_link_and_after,']')"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="contains($the_link,'|')">
<xsl:variable name="the_url">
<xsl:copy-of select="substring-after($the_link,'|')"/>
</xsl:variable>
<a href="{$the_url}">
<xsl:copy-of select="substring-before($the_link,'|')"/>
</a>
</xsl:when>
<xsl:otherwise>
<a href="{$the_link}">
<xsl:copy-of select="$the_link"/>
</a>
</xsl:otherwise>
</xsl:choose>
<!--<xsl:copy-of select="substring-after($text,']')" />-->
<xsl:call-template name="create-links">
<xsl:with-param name="text" select="substring-after($text,']')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
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
I want to get transformation from <mo>(</mo> till <mo>)</mo> to <mfenced>..</mfenced>.
Sample Input is given below:
Sample XML:
<?xml version="1.0" encoding="UTF-8"?>
<chapter xmlns="http://www.w3.org/1998/Math/MathML">
<p>
<math>
<mi>sin</mi>
<mo>(</mo>
<mi>x</mi>
<mi>y</mi>
<mo>)</mo>
<mo>=</mo>
<mi>sin</mi>
<mi>x</mi>
<mi>sin</mi>
<mi>y</mi>
</math>
</p>
</chapter>
XSLT 2.0 tried:
<?xml version='1.0'?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:m="http://www.w3.org/1998/Math/MathML"
xmlns="http://www.w3.org/1998/Math/MathML"
exclude-result-prefixes="m">
<xsl:output method="xml"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="m:mo">
<xsl:if test="(.)='('">
<mfenced><xsl:apply-templates select="following-sibling::*[(.)=')']" mode="copy"/>
</mfenced>
</xsl:if>
<xsl:if test="(.)=')'"></xsl:if>
</xsl:template>
<xsl:template match="m:mo" mode="copy"/>
</xsl:stylesheet>
Output required:
<?xml version="1.0" encoding="UTF-8"?>
<chapter xmlns="http://www.w3.org/1998/Math/MathML">
<p>
<math>
<mi>sin</mi>
<mfenced>
<mi>x</mi>
<mi>y</mi>
</mfenced>
<mo>=</mo>
<mi>sin</mi>
<mi>x</mi>
<mi>sin</mi>
<mi>y</mi>
</math>
</p>
</chapter>
This is the xslt I use for converting brackets in mo to mfenced.
It uses the algorithm of finding matching brackets.
<?xml version='1.0'?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:m="http://www.w3.org/1998/Math/MathML"
xmlns="http://www.w3.org/1998/Math/MathML"
exclude-result-prefixes="m">
<xsl:output method="xml"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="m:mo[(.)='(']">
<xsl:variable name="brcount">
<xsl:value-of select="count(following-sibling::m:mo[(.)='(' or (.)=')'])"/>
</xsl:variable>
<xsl:call-template name="for.oploop">
<xsl:with-param name="i"><xsl:value-of select="'0'"/></xsl:with-param>
<xsl:with-param name="j"><xsl:value-of select="'1'"/></xsl:with-param>
<xsl:with-param name="count"><xsl:value-of select="$brcount"/></xsl:with-param>
<xsl:with-param name="stringop"><xsl:value-of select="'('"/></xsl:with-param>
<xsl:with-param name="stringcl"><xsl:value-of select="')'"/></xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template match="m:mo[(.)=')']">
<xsl:variable name="brcount">
<xsl:value-of select="count(preceding-sibling::m:mo[(.)='(' or (.)=')'])"/>
</xsl:variable>
<xsl:call-template name="for.clloop">
<xsl:with-param name="i"><xsl:value-of select="'0'"/></xsl:with-param>
<xsl:with-param name="j"><xsl:value-of select="'1'"/></xsl:with-param>
<xsl:with-param name="count"><xsl:value-of select="$brcount"/></xsl:with-param>
<xsl:with-param name="stringop"><xsl:value-of select="'('"/></xsl:with-param>
<xsl:with-param name="stringcl"><xsl:value-of select="')'"/></xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template match="m:mo[(.)='[']">
<xsl:variable name="brcount">
<xsl:value-of select="count(following-sibling::m:mo[(.)='[' or (.)=']'])"/>
</xsl:variable>
<xsl:call-template name="for.oploop">
<xsl:with-param name="i"><xsl:value-of select="'0'"/></xsl:with-param>
<xsl:with-param name="j"><xsl:value-of select="'1'"/></xsl:with-param>
<xsl:with-param name="count"><xsl:value-of select="$brcount"/></xsl:with-param>
<xsl:with-param name="stringop"><xsl:value-of select="'['"/></xsl:with-param>
<xsl:with-param name="stringcl"><xsl:value-of select="']'"/></xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template match="m:mo[(.)=']']">
<xsl:variable name="brcount">
<xsl:value-of select="count(preceding-sibling::m:mo[(.)='[' or (.)=']'])"/>
</xsl:variable>
<xsl:call-template name="for.clloop">
<xsl:with-param name="i"><xsl:value-of select="'0'"/></xsl:with-param>
<xsl:with-param name="j"><xsl:value-of select="'1'"/></xsl:with-param>
<xsl:with-param name="count"><xsl:value-of select="$brcount"/></xsl:with-param>
<xsl:with-param name="stringop"><xsl:value-of select="'['"/></xsl:with-param>
<xsl:with-param name="stringcl"><xsl:value-of select="']'"/></xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template name="for.oploop">
<xsl:param name="i" />
<xsl:param name="j" />
<xsl:param name="count" />
<xsl:param name="stringop" />
<xsl:param name="stringcl" />
<xsl:if test="$i = $count">
<xsl:if test="$j > '0'">
<xsl:text disable-output-escaping="yes"><mfenced separators="" open="</xsl:text>
<xsl:value-of select="$stringop"/>
<xsl:text disable-output-escaping="yes">" close=""></mfenced></xsl:text>
</xsl:if>
<xsl:if test="$j = '0'">
<xsl:text disable-output-escaping="yes"><mfenced separators="" open="</xsl:text>
<xsl:value-of select="$stringop"/>
<xsl:text disable-output-escaping="yes">" close="</xsl:text>
<xsl:value-of select="$stringcl"/>
<xsl:text disable-output-escaping="yes">"></xsl:text>
</xsl:if>
</xsl:if>
<xsl:if test="$i < $count">
<xsl:choose>
<xsl:when test="$j = '0'">
<xsl:text disable-output-escaping="yes"><mfenced separators="" open="</xsl:text>
<xsl:value-of select="$stringop"/>
<xsl:text disable-output-escaping="yes">" close="</xsl:text>
<xsl:value-of select="$stringcl"/>
<xsl:text disable-output-escaping="yes">"></xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="following-sibling::m:mo[(.)=$stringop or (.)=$stringcl][$i + 1] = $stringop">
<xsl:call-template name="for.oploop">
<xsl:with-param name="i">
<xsl:value-of select="$i + 1"/>
</xsl:with-param>
<xsl:with-param name="j">
<xsl:value-of select="$j + 1"/>
</xsl:with-param>
<xsl:with-param name="count">
<xsl:value-of select="$count"/>
</xsl:with-param>
<xsl:with-param name="stringop"><xsl:value-of select="$stringop"/></xsl:with-param>
<xsl:with-param name="stringcl"><xsl:value-of select="$stringcl"/></xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:when test="following-sibling::m:mo[(.)=$stringop or (.)=$stringcl][$i + 1] = $stringcl">
<xsl:call-template name="for.oploop">
<xsl:with-param name="i">
<xsl:value-of select="$i + 1"/>
</xsl:with-param>
<xsl:with-param name="j">
<xsl:value-of select="$j - 1"/>
</xsl:with-param>
<xsl:with-param name="count">
<xsl:value-of select="$count"/>
</xsl:with-param>
<xsl:with-param name="stringop"><xsl:value-of select="$stringop"/></xsl:with-param>
<xsl:with-param name="stringcl"><xsl:value-of select="$stringcl"/></xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
<xsl:template name="for.clloop">
<xsl:param name="i" />
<xsl:param name="j" />
<xsl:param name="count" />
<xsl:param name="stringop" />
<xsl:param name="stringcl" />
<xsl:if test="$i = $count">
<xsl:if test="$j > '0'">
<xsl:text disable-output-escaping="yes"><mfenced separators="" open="" close="</xsl:text>
<xsl:value-of select="$stringcl"/>
<xsl:text disable-output-escaping="yes">"></mfenced></xsl:text>
</xsl:if>
<xsl:if test="$j = '0'">
<xsl:text disable-output-escaping="yes"></mfenced></xsl:text>
</xsl:if>
</xsl:if>
<xsl:if test="$i < $count">
<xsl:choose>
<xsl:when test="$j = '0'">
<xsl:text disable-output-escaping="yes"></mfenced></xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="preceding-sibling::m:mo[(.)=$stringop or (.)=$stringcl][$i + 1] = $stringop">
<xsl:call-template name="for.clloop">
<xsl:with-param name="i">
<xsl:value-of select="$i + 1"/>
</xsl:with-param>
<xsl:with-param name="j">
<xsl:value-of select="$j - 1"/>
</xsl:with-param>
<xsl:with-param name="count">
<xsl:value-of select="$count"/>
</xsl:with-param>
<xsl:with-param name="stringop"><xsl:value-of select="$stringop"/></xsl:with-param>
<xsl:with-param name="stringcl"><xsl:value-of select="$stringcl"/></xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:when test="preceding-sibling::m:mo[(.)=$stringop or (.)=$stringcl][$i + 1] = $stringcl">
<xsl:call-template name="for.clloop">
<xsl:with-param name="i">
<xsl:value-of select="$i + 1"/>
</xsl:with-param>
<xsl:with-param name="j">
<xsl:value-of select="$j + 1"/>
</xsl:with-param>
<xsl:with-param name="count">
<xsl:value-of select="$count"/>
</xsl:with-param>
<xsl:with-param name="stringop"><xsl:value-of select="$stringop"/></xsl:with-param>
<xsl:with-param name="stringcl"><xsl:value-of select="$stringcl"/></xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
These Stack Overflow questions may help
XSLT combine multiple nodes into single node
Transform two nodes into one using XSLT
Recursively combine identical sibling elements in XSLT
You can use this template to transform the parent-with-child into the combined element:
<xsl:template match="parent">
<combined>
<xsl:copy-of select="#* | child/#*" />
</combined>
</xsl:template>
What this does is copy all the attributes from the input <parent> element and its <child>, into the output <combined> element.
You'll also want the identity template, in order to pass the <data> element and other nodes through:
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*" />
</xsl:copy>
</xsl:template>
Here a quick and a little dirty solution.
This should also work with nested braces , but will generated not well formed xml if the braces are not matching in input.
Try this:
<
?xml version='1.0'?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:m="http://www.w3.org/1998/Math/MathML"
xmlns="http://www.w3.org/1998/Math/MathML"
exclude-result-prefixes="m">
<xsl:output method="xml"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="m:mo[.=')']" >
<xsl:text disable-output-escaping="yes"><![CDATA[</mfenced>]]></xsl:text>
</xsl:template>
<xsl:template match="m:mo[.='(']" >
<xsl:text disable-output-escaping="yes"><![CDATA[<mfenced>]]></xsl:text>
</xsl:template>
</xsl:stylesheet>
There is XML document:
<data>how;many;i;can;tell;you</data>
Need to get XML using XSLT version 1:
<manydata>
<onedata>how</onedata>
<onedata>many</onedata>
<onedata>i</onedata>
<onedata>can</onedata>
<onedata>tell</onedata>
<onedata>you</onedata>
</manydata>
How I can do it?
UPDATE:
Output format must be XML.
This recursive solution is probably one of the shortest possible:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="data">
<manydata><xsl:apply-templates/></manydata>
</xsl:template>
<xsl:template match="text()" name="tokenize">
<xsl:param name="pText" select="."/>
<xsl:if test="string-length($pText)">
<onedata>
<xsl:value-of select=
"substring-before(concat($pText,';'),';')"/>
</onedata>
<xsl:call-template name="tokenize">
<xsl:with-param name="pText" select=
"substring-after($pText,';')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document;
<data>how;many;i;can;tell;you</data>
the wanted, correct result is produced:
<manydata>
<onedata>how</onedata>
<onedata>many</onedata>
<onedata>i</onedata>
<onedata>can</onedata>
<onedata>tell</onedata>
<onedata>you</onedata>
</manydata>
<xsl:template match="data">
<manydata>
<!--
empty <manydata/> will be generated,
if <data/> without child text()
-->
<xsl:apply-templates select="text()"/>
</manydata>
</xsl:template>
<xsl:template match="data/text()">
<!-- start point for recursion -->
<xsl:call-template name="tokenize-string">
<xsl:with-param name="separator" select="';'"/>
<xsl:with-param name="string" select="text()"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="tokenize-string">
<xsl:param name="separator"/>
<xsl:param name="string"/>
<xsl:variable name="string-before-separator"
select="substring-before( $string, $separator )"/>
<onedata>
<xsl:choose>
<!-- if $separator presents in $string take first piece -->
<xsl:when test="$string-before-separator">
<xsl:value-of select="$string-before-separator"/>
</xsl:when>
<!-- take whole $string, if no $separator in the $string -->
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</onedata>
<!-- recursive call, if separator was found -->
<xsl:if test="$string-before-separator">
<xsl:call-template name="tokenize-string">
<xsl:with-param name="separator" select="$separator"/>
<xsl:with-param name="string"
select="substring-after( $string, $separator )"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
Try this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="data">
<xsl:element name="manydata">
<xsl:call-template name="splitsemicolons">
<xsl:with-param name="text" select="text()" />
</xsl:call-template>
</xsl:element>
</xsl:template>
<xsl:template name="splitsemicolons">
<xsl:param name="text" />
<xsl:choose>
<xsl:when test="contains($text,';')">
<xsl:element name="onedata">
<xsl:value-of select="substring-before($text,';')" />
</xsl:element>
<xsl:call-template name="splitsemicolons">
<xsl:with-param name="text" select="substring-after($text,';')" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:element name="onedata">
<xsl:value-of select="$text" />
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
This uses a named template that is called recursively, each time outputting what's before the first ;, and calling itself with everything after the first ;. If there isn't a ;, it just outputs whatever's left as-is.
You can use the XSLT extension library to get the tokenize function. Here is how the final code will look like:
<xsl:template match="/">
<manydata>
<xsl:for-each select="str:tokenize(data, ';')">
<xsl:value-of select="." />
</xsl:for-each>
</manydata>
</xsl:template>
</xsl:stylesheet>
Please note you will have to import the extension library into you XSLT using:
<xsl:import href="path/str.xsl" />
before you use the library functions.