Need to split the parent and child element into two seperate element - xslt

Hi I'm having the below input xml file:
<Description>Same Date <Text>True</Text></Description>
XSL I have tried for
<xsl:template match="Description">
<def>
<para>
<title>
<xsl:value-of select="Description"/>
</title>
<para>
<xsl:value-of select="Text"/>
</para>
</para>
</def>
</xsl:template>
Expected Output:
<def>
<para>
<title>Same Date</title>
<para>True</para>
</para>
</def>
I need to split the child element and change into seperate element.

You can try This:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" omit-xml-declaration="no"/>
<xsl:template match="Description">
<def>
<para>
<title>
<xsl:value-of select="normalize-space(node()[1])"/>
</title>
<xsl:if test="Text">
<para>
<xsl:value-of select="Text"/>
</para>
</xsl:if>
</para>
</def>
</xsl:template>
</xsl:stylesheet>

Change Following Code:-
<title><xsl:value-of select="Description"/></title>
to
<title><xsl:value-of select="normalize-space(substring-before(., Text))"/></title>

Related

Creating text from character code in XSLT

I am transforming documents from one XML standard to another. One of the requirements is that a numbered list uses alphabetical instead of roman numbering. I can create the unicode from the list position but it seems impossible to insert those into the XML output.
Maybe I am simply not seeing the obvious solution here. In JavaScript I would be using the fromCharCode( ) function but I have not yet found a similar option in XSLT.
I am using XSL 2.0 but could also use 3.0 if needed to make things easier.
I created a simple XML plus XSL to show the problem I need to solve. The actual files are much too big to be sharing here.
Sample XML input document:
<doc>
<p>Some text</p>
<p>Some more text</p>
</doc>
Required output:
<doc>
<list>
<list-item>
<label>A</label>
<p>Some text</p>
</list-item>
<list-item>
<label>B</label>
<p>Some more text</p>
</list-item>
</list>
</doc>
I have tried this:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" encoding="UTF-8"/>
<xsl:template match="/doc">
<xsl:copy>
<list>
<xsl:apply-templates select="p"/>
</list>
</xsl:copy>
</xsl:template>
<xsl:template match="p">
<xsl:variable name="number" as="xs:integer" select="position()"/>
<list-item>
<label>
<xsl:value-of select="concat('&#',$number + 65,';')" disable-output-escaping="yes"/>
</label>
<xsl:copy-of select="."/>
</list-item>
</xsl:template>
</xsl:stylesheet>
I have used disable-output-escaping on other generated items before (e.g. to put angular brackets into the XML output), but it seems that the standard character codes are not being handled in the same way. My output from the above XSL shows up like this:
doc>
<list>
<list-item>
<label>B</label>
<p>Some text</p>
</list-item>
<list-item>
<label>C</label>
<p>Some more text</p>
</list-item>
</list>
</doc>
Is there any way to do this elegantly (i.e. without me having to create a huge xsl:choose to call out every possible integer label value and then explicitly creating the alphabetical labels?
You can use codepoints-to-string() and have it convert the number into the letter.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/doc">
<xsl:copy>
<list>
<xsl:apply-templates select="p"/>
</list>
</xsl:copy>
</xsl:template>
<xsl:template match="p">
<list-item>
<label>
<xsl:value-of select="codepoints-to-string(position() + 64)"/>
</label>
<xsl:copy-of select="."/>
</list-item>
</xsl:template>
</xsl:stylesheet>
Or you could use xsl:number with value="position()" and format="A"
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/doc">
<xsl:copy>
<list>
<xsl:apply-templates select="p"/>
</list>
</xsl:copy>
</xsl:template>
<xsl:template match="p">
<list-item>
<label>
<xsl:number format="A" value="position()"/>
</label>
<xsl:copy-of select="."/>
</list-item>
</xsl:template>
</xsl:stylesheet>
A quick solution for values up to 26 (='Z') would be
<xsl:template match="p">
<xsl:variable name="alphabet" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<xsl:variable name="number" as="xs:integer" select="position()"/>
<list-item>
<label><xsl:value-of select="substring($alphabet,$number,1)"/></label>
<xsl:copy-of select="."/>
</list-item>
</xsl:template>
The output is as desired.
This could be extended to 26*26 by using a div and an xsl:if.

Finding following sibling of text node

I am trying to wrap all the text node in a <text> element, but facing challenge when encounter an inline elements (i, b, emphasis), that should be in same <text> node (in other words, it should be considered as text)... Please see input and desired output below:
(Note: I have to do this for specific inline elements only, hence kept it in param (it could be anything), for rest of the elements standard <text> rule should be applied. (Please see my xslt for details)
Input XML:
<?xml version="1.0" encoding="utf-8"?>
<root>
<para>XML Translation is a format that's used to <emphasis>exchange <i>localisation</i></emphasis>data</para>
<para>The process can now be reformulated with more detail as follows:<ul>
<li>Text extraction <note>Separation of translatable text from layout data</note></li>
<li>Pre-translation</li>
<li>Translation</li>
<li>Reverse conversion</li>
<li>Translation memory improvement</li>
</ul>above mentioned steps should <b>executed</b> sequentially</para>
</root>
OutPut should be:
<?xml version="1.0" encoding="utf-8"?>
<root>
<para>
<text xid="d0t3">XML Translation is a format that's used to <g ctype="emphasis">exchange <g ctype="i">localisation</g></g>data </text>
</para>
<para>
<text xid="d0t10">The process can now be reformulated with more detail as follows:</text>
<ul>
<li><text xid="d0t13">Text extraction <g ctype="note">Separation of translatable text from layout data</g></text></li>
<li><text xid="d0t17">Pre-translation</text></li>
<li><text xid="d0t19">Translation</text></li>
<li><text xid="d0t21">Reverse conversion</text></li>
<li><text xid="d0t23">Translation memory improvement</text></li>
</ul>
<text xid="d0t24">above mentioned steps should <g ctype="b">executed</g> sequentially</text>
</para>
</root>
I am trying something like this, but not able to achieve correct result:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:strip-space elements="*"/>
<xsl:param name="inlineElement" select="('emphasis', 'i', 'note', 'b')"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<text>
<xsl:attribute name="xid">
<xsl:value-of select="generate-id()"/>
</xsl:attribute>
<xsl:value-of select="."/>
<xsl:if test="following-sibling::node()[local-name()=$inlineElement]">
<g>
<xsl:apply-templates select="following-sibling::node()[local-name()=$inlineElement]/text()"/>
</g>
</xsl:if>
</text>
</xsl:template>
</xsl:stylesheet>
I would use for-each-group group-adjacent:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:param name="inlineElement" select="('emphasis', 'i', 'note', 'b')"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(local-name() = $inlineElement)]">
<xsl:copy>
<xsl:for-each-group select="node()" group-adjacent="boolean(self::text() | self::*[local-name() = $inlineElement])">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<text xid="{generate-id(current-group()[self::text()][1])}">
<xsl:apply-templates select="current-group()"/>
</text>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="*[local-name() = $inlineElement]">
<g ctype="{local-name()}">
<xsl:apply-templates/>
</g>
</xsl:template>
</xsl:stylesheet>
That way, with Saxon 9.5, I get
<?xml version="1.0" encoding="UTF-8"?>
<root>
<para>
<text xid="d1t3">XML Translation is a format that's used to <g ctype="emphasis">exchange <g ctype="i">localisation</g>
</g>data</text>
</para>
<para>
<text xid="d1t10">The process can now be reformulated with more detail as follows:</text>
<ul>
<li>
<text xid="d1t13">Text extraction <g ctype="note">Separation of translatable text from layout data</g>
</text>
</li>
<li>
<text xid="d1t17">Pre-translation</text>
</li>
<li>
<text xid="d1t19">Translation</text>
</li>
<li>
<text xid="d1t21">Reverse conversion</text>
</li>
<li>
<text xid="d1t23">Translation memory improvement</text>
</li>
</ul>
<text xid="d1t24">above mentioned steps should <g ctype="b">executed</g> sequentially</text>
</para>
</root>

Using XPATHs from file and adding relevant attribute

May be my XSL approach is wrong? please correct me the way to handle this situation
I want to grab XPATHs and Attrs from a mapping file, then use XPATH to match, and apply attributes to XML.
Here is my 3 inputs files:
mappings.xml
<?xml version="1.0" encoding="UTF-8"?>
<mappings>
<map xpath="//title" class="title" others="moreToCome" />
<map xpath="//subtitle" class="subtitle" others="moreToCome" />
<map xpath="//p" class="p" others="moreToCome" />
</mappings>
Source.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<title>title text</title>
<subtitle>subtitle text</subtitle>
<p>subtitle text</p>
</root>
StyleMapping.xsl
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="fMappings" select="document('mappings.xml')" />
<xsl:variable name="xpath"><xsl:text>justToMakeItGlobal</xsl:text></xsl:variable>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
<!-- loop thru map in mappings.xml -->
<xsl:for-each select="$fMappings//mappings/map">
<xsl:call-template name="dyn">
<xsl:with-param name="xpath" select="#xpath" />
<xsl:with-param name="class" select="#class" />
<xsl:with-param name="others" select="#others" />
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template match="$xpath" mode="dyn">
<xsl:param name="xpath"/>
<xsl:param name="class"/>
<xsl:param name="others"/>
<xsl:attribute name="class"><xsl:value-of select="$class" /></xsl:attribute>
<xsl:attribute name="others"><xsl:value-of select="$others" /></xsl:attribute>
</xsl:template>
</xsl:stylesheet>
This is what is planning to do, but i'm not getting the correct way of doing it in XSLT:
1. Read mappings.xml file
2. Loop thru each map tag
3. grab xpath and attr's
4. apply template match/select with above xpath
5. add attr's to above selected nodes
output.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<title class="title" others="moreToCome">title text</title>
<subtitle class="subtitle" others="moreToCome">subtitle text</subtitle>
<p class="p" others="moreToCome">subtitle text</p>
</root>
I don't think you can have a template with a calculated match pattern. Let me suggest a different approach:
mappings.xml
<?xml version="1.0" encoding="UTF-8"?>
<mappings>
<map elem="title" class="Title" others="moreToCome1" />
<map elem="subtitle" class="Subtitle" others="moreToCome2" />
<map elem="p" class="P" others="moreToCome3" />
</mappings>
stylesheet
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:variable name="mappings" select="document('mappings.xml')/mappings" />
<xsl:template match="*">
<xsl:variable name="elem" select="name()" />
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:if test="$elem = $mappings/map/#elem">
<xsl:attribute name="class">
<xsl:value-of select="$mappings/map[#elem=$elem]/#class"/>
</xsl:attribute>
<xsl:attribute name="others">
<xsl:value-of select="$mappings/map[#elem=$elem]/#others"/>
</xsl:attribute>
</xsl:if>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Testing with the following source XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<title original="yes">title text</title>
<subtitle>subtitle text</subtitle>
<p>para text</p>
<nomatch attr="test">another text</nomatch>
</root>
results in:
<?xml version="1.0" encoding="utf-8"?>
<root>
<title original="yes" class="Title" others="moreToCome1">title text</title>
<subtitle class="Subtitle" others="moreToCome2">subtitle text</subtitle>
<p class="P" others="moreToCome3">para text</p>
<nomatch attr="test">another text</nomatch>
</root>

Creating new XML nodes if sub node exists

I've been trying to achieve the following output using XSLT but have really been struggling. Thank you in advance for any assistance.
From
<par>
<run>Line one<break/>
Line two<break/>
</run>
<run>Another para of text<break/>
</run>
<run>3rd para but no break</run>
</par>
To
<document>
<para>Line one</para>
<para>Line two</para>
<para>Another para of text</para>
<para>3rd para but no break</para>
</document>
Thank you,
Dono
Here's a simple solution that is push-oriented and doesn't need <xsl:for-each>, <xsl:if>, or the self:: axis.
When this XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/*">
<document>
<xsl:apply-templates />
</document>
</xsl:template>
<xsl:template match="run/text()">
<para>
<xsl:value-of select="normalize-space()" />
</para>
</xsl:template>
</xsl:stylesheet>
...is applied to the provided XML:
<par>
<run>Line one<break/>
Line two<break/>
</run>
<run>Another para of text<break/>
</run>
<run>3rd para but no break</run>
</par>
...the wanted result is produced:
<document>
<para>Line one</para>
<para>Line two</para>
<para>Another para of text</para>
<para>3rd para but no break</para>
</document>
Assuming that your <run> elements are only ever going to contain text and <break/> elements, and that you want to normalise whitespace and exclude <para> elements that would only contain whitespace (suggested by your desired output), the following should work:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="par">
<document>
<xsl:apply-templates select="*"/>
</document>
</xsl:template>
<xsl:template match="run">
<xsl:for-each select="text()">
<xsl:if test="normalize-space(self::text()) != ''">
<para>
<xsl:value-of select="normalize-space(self::text())"/>
</para>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Output values in a certain way using XSLT/XPath 2.0

I have an XML like this:
<?xml version="1.0" encoding="UTF-8"?>
<Section>
<Chapter>
<Cell colname="1">
<Value>A</Value>
</Cell>
<Cell colname="2">
<MyValue>AAA</MyValue>
<MyValue>BBB</MyValue>
</Cell>
<Cell colname="3">
<MyCar>Honda</MyCar>
</Cell>
</Chapter>
<Chapter>
<Cell colname="1">
<Value>C</Value>
</Cell>
<Cell colname="2">
<MyValue>CCC</MyValue>
</Cell>
<Cell colname="3">
<MyCar>Toyota</MyCar>
</Cell>
</Chapter>
</Section>
I like the have a message (later on convert them tags) output like this:
A
AAA
Honda
A
BBB
Honda
C
CCC
Toyota
This is my XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" version="1.0" encoding="iso-8859-1" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="Section//Chapter"/>
</xsl:template>
<xsl:template match="Chapter">
<xsl:for-each select="Cell[#colname='2']//MyValue">
<xsl:message>
<xsl:value-of select="Cell[#colname='1']/Value"/>
<xsl:value-of select="."/>
<xsl:value-of select="Cell[#colname='3']/MyCar"/>
</xsl:message>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
Unfortunately it doesn't output what I'd like it to do :(.
I realize that for-each will change the context so the remaining value-ofs won't do anything.
What would be a solution for this ?.
TIA,
John
This XSLT 2.0 transformation (the equivalent XSLT 1.0 transformation can be mechanically written from this one).
Do note: This is a generic solution that works with any number of children and doesn't rely on hardcoded names.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Chapter">
<xsl:apply-templates select="Cell[1]"/>
</xsl:template>
<xsl:template match="Cell">
<xsl:param name="pText" as="xs:string*"/>
<xsl:apply-templates select="*[1]">
<xsl:with-param name="pText" select="$pText"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="Cell/*">
<xsl:param name="pText" as="xs:string*"/>
<xsl:variable name="vText" as="xs:string*"
select="$pText, string(.)"/>
<xsl:sequence select=
"$vText
[not(current()/../following-sibling::Cell)]"/>
<xsl:apply-templates select="../following-sibling::Cell[1]">
<xsl:with-param name="pText" select="$vText"/>
</xsl:apply-templates>
<xsl:apply-templates select="following-sibling::*">
<xsl:with-param name="pText" select="$pText"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<Section>
<Chapter>
<Cell colname="1">
<Value>A</Value>
</Cell>
<Cell colname="2">
<MyValue>AAA</MyValue>
<MyValue>BBB</MyValue>
</Cell>
<Cell colname="3">
<MyCar>Honda</MyCar>
</Cell>
</Chapter>
<Chapter>
<Cell colname="1">
<Value>C</Value>
</Cell>
<Cell colname="2">
<MyValue>CCC</MyValue>
</Cell>
<Cell colname="3">
<MyCar>Toyota</MyCar>
</Cell>
</Chapter>
</Section>
produces exactly the wanted, correct result:
A AAA Honda A BBB Honda C CCC Toyota
Explanation:
This is essentially a task for producing all combinations of values that belong to N groups (the children of a Cell make a group), taking one item from each group.
At any item of group K, we add this item to the current combination of items of the groups 1, 2, ..., K-1. Then we pass this newly formed combination to the group K+1. If we are an item in the last group (N), we print out (xsl:sequence) the whole accumulated combination.
When the control returns, all combinations of elements of the remaining groups (K+1, K+2, ..., N) have been appended to our current combination of the group items of groups 1, 2, ..., K. All these combinations have already been printed.
We pass the same group elements combination that was passed to us -- now we pass it to the following item in the current group (the following-sibling).
I have modified your approach a little, because I'm guessing that you really don't want to use <xsl:message> diagnostic operation. Besides that, I'm not generating XML on the output and I'm not using for-each:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text" version="1.0" encoding="iso-8859-1" indent="yes"/>
<xsl:template match="/Section">
<xsl:apply-templates select="Chapter"/>
</xsl:template>
<xsl:template match="Chapter">
<xsl:apply-templates select="Cell/MyValue" />
</xsl:template>
<xsl:template match="MyValue">
<xsl:value-of select="../../Cell[#colname='1']/Value/text()"/>
<xsl:text> </xsl:text>
<xsl:value-of select="."/>
<xsl:text> </xsl:text>
<xsl:value-of select="../../Cell[#colname='3']/MyCar"/>
<xsl:text> </xsl:text>
</xsl:template>
</xsl:stylesheet>