My question is traverse through an XML file and find the elements mentioned in variable 1 and replace them with the elements in variable 2 using simplest method.
Sample XML
Below is the sample XML file:
<?xml version="1.0" encoding="utf-8"?>
<root>
<figure id="f0005">
<label>Fig. 1</label>
<caption id="cn0005">
<simple-para id="sp0015">Schematic diagram of the experimental setup.</simple-para>
</caption>
</figure>
<figure id="f0010">
<label>Fig. 2</label>
<caption id="cn0010">
<simple-para id="sp0020">Schematic drawing of the orifice plate.</simple-para>
</caption>
</figure>
</root>
Using this method I am able to get the output. But not the one specified in the required output heading. I think I am doing something wrong in the for loop in the stylesheet. Please share your thoughts.
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"
version="3.0">
<xsl:output method="xml" encoding="utf-8" indent="yes" omit-xml-declaration="yes"/>
<xsl:variable name="els_element_name"
select="tokenize('label simple-para figure caption', '\s+')"/>
<xsl:variable name="html_element_name"
select="tokenize('div div div div div', '\s+')"/>
<xsl:template match="/">
<xsl:for-each select="//element()">
<xsl:variable name="ele_name" select="name()"/>
<xsl:if test="index-of($els_element_name, $ele_name)">
<xsl:variable name="array_val">
<xsl:value-of select="index-of($els_element_name, $ele_name)"/>
</xsl:variable>
<xsl:call-template name="optimized_code_start">
<xsl:with-param name="els_element" select="$ele_name"/>
<xsl:with-param name="position" select="$array_val"/>
</xsl:call-template>
<xsl:value-of select="."/>
<xsl:call-template name="optimized_code_end">
<xsl:with-param name="els_element" select="$ele_name"/>
<xsl:with-param name="position" select="$array_val"/>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="optimized_code_start">
<xsl:param name="els_element"/>
<xsl:param name="position" as="xs:integer"/>
<xsl:text disable-output-escaping="yes"><</xsl:text>
<xsl:value-of select="$html_element_name[$position]"/>
<xsl:text disable-output-escaping="yes"> class="</xsl:text>
<xsl:value-of select="$els_element"/>
<xsl:text disable-output-escaping="yes">"</xsl:text>
<xsl:for-each select="#*[(name()='id')]">
<xsl:text disable-output-escaping="yes"> </xsl:text>
<xsl:value-of select="name()"/>
<xsl:text disable-output-escaping="yes">="</xsl:text>
<xsl:value-of select="."/>
<xsl:text disable-output-escaping="yes">"</xsl:text>
</xsl:for-each>
<xsl:for-each select="#*[not(name()='id')]">
<xsl:choose>
<xsl:when test="name() = 'xml:lang'">
<xsl:text disable-output-escaping="yes"> </xsl:text>
<xsl:value-of select="name()"/>
<xsl:text disable-output-escaping="yes">="</xsl:text>
<xsl:value-of select="."/>
<xsl:text disable-output-escaping="yes">"</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text disable-output-escaping="yes"> data-</xsl:text>
<xsl:value-of select="name()"/>
<xsl:text disable-output-escaping="yes">="</xsl:text>
<xsl:value-of select="."/>
<xsl:text disable-output-escaping="yes">"</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<xsl:text disable-output-escaping="yes">></xsl:text>
</xsl:template>
<xsl:template name="optimized_code_end">
<xsl:param name="els_element"/>
<xsl:param name="position" as="xs:integer"/>
<xsl:text disable-output-escaping="yes"></</xsl:text>
<xsl:value-of select="$html_element_name[$position]"/>
<xsl:text disable-output-escaping="yes">></xsl:text>
</xsl:template>
</xsl:stylesheet>
Required output
I would like to get the output as mentioned below.
<html>
<div class="figure" id="f0005">
<div class="label">Fig. 1</div>
<div class="caption" id="cn0005">
<div class="simple-para" id="sp0015">Schematic diagram of the experimental setup.</div>
</div>
</div>
<div class="figure" id="f0010">
<div class="label">Fig. 2</div>
<div class="caption" id="cn0010">
<div class="simple-para" id="sp0020">Schematic drawing of the orifice plate.
</div>
</div>
</div>
</html>
You can simply use index-of and xsl:element to match as well as map the input names to output names:
<?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="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:variable name="els_element_name"
select="tokenize('label simple-para figure caption', '\s+')"/>
<xsl:variable name="html_element_name"
select="tokenize('div div div div div', '\s+')"/>
<xsl:template match="*[index-of($els_element_name, name()) > 0]">
<xsl:element name="{$html_element_name[index-of($els_element_name, name(current()))]}">
<xsl:apply-templates select="#*"/>
<xsl:attribute name="class" select="name()"/>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/bdxtra/2
Another possible approach might be to create a stylesheet implementing the mapping and run it in XSLT 3.0 directly with the transform function:
<?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"
xmlns:axsl="http://www.w3.org/1999/XSL/Transform-alias"
exclude-result-prefixes="#all"
version="3.0">
<xsl:namespace-alias stylesheet-prefix="axsl" result-prefix="xsl"/>
<xsl:variable name="stylesheet">
<axsl:stylesheet version="3.0">
<axsl:mode on-no-match="shallow-copy"/>
<xsl:for-each select="$els_element_name">
<axsl:template match="{.}">
<axsl:element name="{let $p := position() return $html_element_name[$p]}">
<axsl:apply-templates select="#*"/>
<axsl:attribute name="class" select="'{.}'"/>
<axsl:apply-templates/>
</axsl:element>
</axsl:template>
</xsl:for-each>
</axsl:stylesheet>
</xsl:variable>
<xsl:variable name="els_element_name"
select="tokenize('label simple-para figure caption', '\s+')"/>
<xsl:variable name="html_element_name"
select="tokenize('div div div div div', '\s+')"/>
<xsl:output indent="yes"/>
<xsl:template match="/">
<xsl:sequence select="$stylesheet"/>
<xsl:sequence
select="transform(
map {
'stylesheet-node' : $stylesheet,
'source-node' : .
}
)?output"/>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/bdxtra/4. For the real use case remove or comment out the <xsl:sequence select="$stylesheet"/> line but I have kept it in to show the created stylesheet.
Related
I need to maintain 2 counters in my xslt - EntryID and RowID. I have a xml which contains Name and Certifications the person holds. Now I need to maintain one counter for each person (EntryID) and one for each certification (RowID). And on top of the certifications, I need to add one more certification "New" which will have a RowID one number higher than the maximum number certifications the person holds.
Below is the XML:
<?xml version='1.0' encoding='UTF-8'?>
<wd:Report_Data xmlns:wd="urn:com.workday.report/bsvc">
<wd:Report_Entry>
<Name>Ram</Name>
<Certifications>
<Certificate>AWS</Certificate>
<Certificate>Workday</Certificate>
<Certificate>SAP</Certificate>
</Certifications>
</wd:Report_Entry>
<wd:Report_Entry>
<Name>Nitin</Name>
<Certifications>
<Certificate>Workday</Certificate>
</Certifications>
</wd:Report_Entry>
<wd:Report_Entry>
<Name>Joe</Name>
<Certifications>
<Certificate>SAP</Certificate>
<Certificate>AWS</Certificate>
</Certifications>
</wd:Report_Entry>
</wd:Report_Data>
The expected output is below. The name should appear only in the first row.
EntryID,Name,RowID,Certification
1,Ram,1,AWS
1,,2,Workday
1,,3,SAP
1,,4,NEW --> New certificate with row id 4 as Ram already has 3 certifications
2,Nitin,1,Workday --> Entry ID is 2 for Nitin and Row ID restarts from 1
2,,2,NEW
3,Joe,1,SAP
3,,2,AWS
3,,3,NEW
The XSLT I am able to build so far, but not giving desired output.
<?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"
xmlns:wd="urn:com.workday.report/bsvc"
xmlns:etv="urn:com.workday/etv"
exclude-result-prefixes="xs wd" version="3.0">
<xsl:output method="text" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="delimeter" select="','"/>
<xsl:variable name="lineFeed" select="'
'"/>
<root>
<xsl:for-each select="wd:Report_Data/wd:Report_Entry">
<EntryID><xsl:value-of select="position()"/></EntryID>
<xsl:value-of select="$delimeter"/>
<Name><xsl:value-of select="Name"/></Name>
<xsl:value-of select="$delimeter"/>
<xsl:for-each select="Certifications/Certificate">
<RowID><xsl:value-of select="position()"/></RowID>
<xsl:value-of select="$delimeter"/>
<xsl:value-of select="."/>
</xsl:for-each>
<xsl:value-of select="$lineFeed"/>
<EntryID><xsl:value-of select="position()"/></EntryID>
<xsl:value-of select="$delimeter"/>
<xsl:value-of select="$delimeter"/>
<text>NEW</text>
<xsl:value-of select="$lineFeed"/>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Please help me with correct XSLT.
To produce the wanted output with XSLT 3, I would use something like
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
xmlns:wd="urn:com.workday.report/bsvc"
expand-text="yes">
<xsl:param name="delimeter" select="','"/>
<xsl:param name="lineFeed" select="'
'"/>
<xsl:output method="text"/>
<xsl:template match="wd:Report_Data">
<xsl:value-of select="'EntryID','Name','RowID','Certification'" separator="{$delimeter}"/>
<xsl:value-of select="$lineFeed"/>
<xsl:apply-templates select="wd:Report_Entry"/>
</xsl:template>
<xsl:template match="wd:Report_Entry">
<xsl:apply-templates select="Certifications/Certificate"/>
</xsl:template>
<xsl:template match="Certificate">
<xsl:variable name="entry-id" as="xs:integer">
<xsl:number count="wd:Report_Entry"/>
</xsl:variable>
<xsl:variable name="row-id" as="xs:integer">
<xsl:number/>
</xsl:variable>
<xsl:value-of select="$entry-id, (../../Name[$row-id = 1], '')[1], $row-id, ." separator="{$delimeter}"/>
<xsl:value-of select="$lineFeed"/>
<xsl:if test="position() = last()">
<xsl:value-of select="$entry-id, '', $row-id + 1, 'NEW'" separator="{$delimeter}"/>
<xsl:value-of select="$lineFeed"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The XML result you show can be produced quite easily using:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wd="urn:com.workday.report/bsvc"
exclude-result-prefixes="wd">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/wd:Report_Data">
<root>
<xsl:for-each select="wd:Report_Entry">
<row>
<EntryID>
<xsl:value-of select="position()" />
</EntryID>
<Name>
<xsl:value-of select="Name" />
</Name>
<xsl:for-each select="Certifications/Certificate">
<Certificate>
<RowID>
<xsl:value-of select="position()" />
</RowID>
<cName>
<xsl:value-of select="." />
</cName>
</Certificate>
</xsl:for-each>
<Certificate>
<RowID>
<xsl:value-of select="count(Certifications/Certificate) + 1" />
</RowID>
<cName>NEW</cName>
</Certificate>
</row>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
To get a "flat" CSV output, you can do:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wd="urn:com.workday.report/bsvc">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/wd:Report_Data">
<!-- header -->
<xsl:text>EntryID,Name,RowID,Certification
</xsl:text>
<!-- data -->
<xsl:for-each select="wd:Report_Entry">
<xsl:variable name="entry-data">
<xsl:value-of select="position()" />
<xsl:text>,</xsl:text>
<xsl:value-of select="Name" />
<xsl:text>,</xsl:text>
</xsl:variable>
<!-- output -->
<xsl:for-each select="Certifications/Certificate">
<xsl:copy-of select="$entry-data"/>
<xsl:value-of select="position()" />
<xsl:text>,</xsl:text>
<xsl:value-of select="." />
<xsl:text>
</xsl:text>
</xsl:for-each>
<!-- new certificate -->
<xsl:copy-of select="$entry-data"/>
<xsl:value-of select="count(Certifications/Certificate) + 1" />
<xsl:text>,NEW
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
which in XSLT 3.0 can be reduced to:
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wd="urn:com.workday.report/bsvc"
expand-text="yes">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/wd:Report_Data">
<!-- header -->
<xsl:text>EntryID,Name,RowID,Certification
</xsl:text>
<!-- data -->
<xsl:for-each select="wd:Report_Entry">
<xsl:variable name="entry-data">{position()},{Name}</xsl:variable>
<!-- output -->
<xsl:for-each select="Certifications/Certificate, 'NEW'">{$entry-data},{position()},{.}
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I want to precalculate a subtree of nodes in an source XML, and the process them seperately (because I want the subset to be processed in different ways), and access some values from ancestors.
simple example
<numbers count="5">
<number value="1"/>
<number value="2"/>
<number value="3"/>
<number value="4"/>
<number value="5"/>
</numbers>
and lets say I have an xslt (MSXML) to extract the even nodes somehow
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<evens>
<xsl:for-each select="numbers/number">
<xsl:choose>
<xsl:when test="#value mod 2 = 0">
<even>
<xsl:attribute name="count">
<xsl:value-of select="../#count"/>
</xsl:attribute>
<xsl:attribute name="value">
<xsl:value-of select="#value"/>
</xsl:attribute>
</even>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</evens>
</xsl:template>
</xsl:stylesheet>
and we get..
<evens>
<even count="5" value="2" />
<even count="5" value="4" />
</evens>
nice...
but how can I seperate the filtering from the processing so something like...
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template name="calculateNodes">
<xsl:for-each select="numbers/number">
<xsl:choose>
<xsl:when test="#value mod 2 = 0">
<xsl:copy-of select="."/>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="nodes">
<xsl:call-template name="calculateNodes"/>
</xsl:variable>
<evens>
<xsl:for-each select="msxsl:node-set($nodes)/number">
<even>
<xsl:attribute name="count">
<xsl:value-of select="../#count"/>
</xsl:attribute>
<xsl:attribute name="value">
<xsl:value-of select="#value"/>
</xsl:attribute>
</even>
</xsl:for-each>
</evens>
</xsl:template>
</xsl:stylesheet>
this gives.
<evens>
<even count="" value="2" />
<even count="" value="4" />
</evens>
so...the ancestors arent copied.
Is there an idiomatic way to get out of this?
A copied node exists on its own, outside of the original tree. In your example, the parent of number is the $nodes variable, which does not have any attributes.
Why don't you do simply:
<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:template match="/numbers">
<xsl:variable name="nodes" select="number[#value mod 2 = 1]"/>
<evens>
<xsl:for-each select="$nodes">
<even count="{../#count}" value="{#value}"/>
</xsl:for-each>
</evens>
</xsl:template>
</xsl:stylesheet>
This way you have a variable containing a reference to the original nodes, instead of a copy. Then you also have access to the original parent. And the content of the variable is a node-set; you don't need to convert it.
this seems to work
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template name="calculateNodes">
<xsl:for-each select="numbers/number">
<xsl:choose>
<xsl:when test="#value mod 2 = 0">
<numberWrapper>
<xsl:attribute name="count">
<xsl:value-of select="../#count"/>
</xsl:attribute>
<xsl:copy-of select="."/>
</numberWrapper>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="nodes">
<xsl:call-template name="calculateNodes"/>
</xsl:variable>
<evens>
<xsl:for-each select="msxsl:node-set($nodes)/numberWrapper">
<even>
<xsl:attribute name="count">
<xsl:value-of select="#count"/>
</xsl:attribute>
<xsl:attribute name="value">
<xsl:value-of select="number/#value"/>
</xsl:attribute>
</even>
</xsl:for-each>
</evens>
</xsl:template>
</xsl:stylesheet>
Please check and suggest,
what should be the right code and idref value should be same as figure id. I am trying with analyze-string but unable to do.
Please check and suggest,
what should be the right code and idref value should be same as figure id. I am trying with analyze-string but unable to do.
input
<book>
<figure id="ch01fig01">
<label>Figure 01</label>
<figcaption>xxx</figcaption>
</figure>
<p>This is a Figure 01 and this is figure 02</p>
<p>This is a Figure 01 and this is figure 02</p>
<figure id="ch01fig02">
<label>Figure 02</label>
<figcaption>xxx</figcaption>
</figure>
</book>
output
<book>
<figure id="ch01fig01">
<label>Figure 01</label>
<figcaption>xxx</figcaption>
</figure>
<p>This is a <internal idref="ch01fig01">Figure 01</internal> and this is <internal idref="ch01fig02">Figure 02</internal></p>
<p>This is a <internal idref="ch01fig01">Figure 01</internal> and this is <internal idref="ch01fig02">Figure 02</internal></p>
<figure id="ch01fig02">
<label>Figure 02</label>
<figcaption>xxx</figcaption>
</figure>
</book>
xslt
<xsl:output indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//text()[not(parent::label)]">
<xsl:analyze-string select="." regex="figure\s+\d+" flags="i">
<xsl:matching-substring>
<internal>
<xsl:attribute name="idref">
<xsl:call-template name="mk">
<xsl:with-param name="mk11" select="."/>
</xsl:call-template>
</xsl:attribute>
<xsl:value-of select="."/>
</internal>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
<xsl:template name="mk">
<xsl:param name="mk11"/>
<xsl:for-each select="//figure">
<xsl:if test="child::label eq $mk11">
<xsl:value-of select="#id"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
First consider using a key to look up the figures...
<xsl:key name="figures" match="figure" use="lower-case(label)" />
(I am using lower-case here, because you have a "figure 02" in the text, but "Figure 02" in the label).
Your main problem is that within xsl:matching-substring you are no longer within the context of the original node you are matching, so you are probably getting an error along the lines of "the context item is not a node"
To get around this, define a variable to allow you to reference the original document...
<xsl:variable name="doc" select="/" />
Then to get the figure value using the key, you can do this...
<xsl:value-of select="key('figures', lower-case($mk11), $doc)/#id" />
So, this will look up the key in the context of the original document.
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes"/>
<xsl:key name="figures" match="figure" use="lower-case(label)" />
<xsl:variable name="doc" select="/" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//text()[not(parent::label)]">
<xsl:analyze-string select="." regex="figure\s+\d+" flags="i">
<xsl:matching-substring>
<internal>
<xsl:attribute name="idref">
<xsl:call-template name="mk">
<xsl:with-param name="mk11" select="."/>
</xsl:call-template>
</xsl:attribute>
<xsl:value-of select="."/>
</internal>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
<xsl:template name="mk">
<xsl:param name="mk11"/>
<xsl:value-of select="key('figures', lower-case($mk11), $doc)/#id" />
</xsl:template>
</xsl:stylesheet>
In fact, you can simplify this by doing away with the named template, and making use of Attribute Value Templates to create the idref attribute
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes"/>
<xsl:key name="figures" match="figure" use="lower-case(label)" />
<xsl:variable name="doc" select="/" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//text()[not(parent::label)]">
<xsl:analyze-string select="." regex="figure\s+\d+" flags="i">
<xsl:matching-substring>
<internal idref="{key('figures', lower-case(.), $doc)/#id}">
<xsl:value-of select="."/>
</internal>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
</xsl:stylesheet>
I used another way instead of the key as well it is also working good. Below is code done some changes.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="figs" select="//figure" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()[not(parent::label)]">
<xsl:choose>
<xsl:when test="matches(., 'Figure', 'i')">
<xsl:analyze-string select="." regex="(Figure ([0-9]+))" flags="i">
<xsl:matching-substring>
<xsl:variable name="ids" select="for $ss in $figs
return
if(matches($ss/label, regex-group(1), 'i'))
then $ss/#id
else ()"></xsl:variable>
<internal idref="{$ids[1]}">
<xsl:value-of select="."/>
</internal>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Below is the outcome after running XSLT.
<?xml version="1.0" encoding="UTF-8"?>
<book>
<figure id="ch01fig01">
<label>Figure 01</label>
<figcaption>xxx</figcaption>
</figure>
<p>This is a <internal idref="ch01fig01">Figure 01</internal> and this is <internal idref="ch01fig02">figure 02</internal>
</p>
<p>This is a <internal idref="ch01fig01">Figure 01</internal> and this is <internal idref="ch01fig02">figure 02</internal>
</p>
<figure id="ch01fig02">
<label>Figure 02</label>
<figcaption>xxx</figcaption>
</figure>
</book>
The transform I'm working on mergers two templates that has attributes that are space-separated.
An example would be:
<document template_id="1">
<header class="class1 class2" />
</document>
<document template_id="2">
<header class="class3 class4" />
</document>
And after the transform I want it to be like this:
<document>
<header class="class1 class2 class3 class4" />
</document>
How to achieve this?
I have tried (writing from memory):
<xsl:template match="/">
<header>
<xsl:attribute name="class">
<xsl:for-each select=".//header">
<xsl:value-of select="#class"/>
</xsl:for-each>
</xsl:attribute>
</header>
</xsl:template>
But that appends them all together, but I need them separated... and would be awesome if uniqued as well.
Thank you
Try this template, which simply adds a space before all the headers, apart from the first
<xsl:template match="/">
<header>
<xsl:attribute name="class">
<xsl:for-each select=".//header">
<xsl:if test="position() > 1">
<xsl:text> </xsl:text>
</xsl:if>
<xsl:value-of select="#class"/>
</xsl:for-each>
</xsl:attribute>
</header>
</xsl:template>
If you want your classes without repetitions, then use the following XSLT:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform 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="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<document>
<xsl:variable name="cls1">
<xsl:for-each select=".//header/#class">
<xsl:call-template name="tokenize">
<xsl:with-param name="txt" select="."/>
</xsl:call-template>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="cls" select="exsl:node-set($cls1)"/>
<header>
<xsl:attribute name="class">
<xsl:for-each select="$cls/*[not(preceding-sibling::* = .)]">
<xsl:value-of select="."/>
<xsl:if test="position() < last()">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:attribute>
</header>
</document>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="txt"/>
<xsl:if test="$txt">
<xsl:if test="contains($txt, ' ')">
<cls>
<xsl:value-of select="substring-before($txt, ' ')"/>
</cls>
<xsl:call-template name="tokenize">
<xsl:with-param name="txt" select="substring-after($txt, ' ')"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="not(contains($txt, ' '))">
<cls>
<xsl:value-of select="$txt"/>
</cls>
</xsl:if>
</xsl:if>
</xsl:template>
</xsl:transform>
In XSLT 1.0 it is much more difficult than in XSLT 2.0, but as you see,
it is possible.
Edit
A revised version using Muenchian grouping (inspired by comment from Michael):
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform 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="xml" encoding="UTF-8" indent="yes" />
<xsl:variable name="cls1">
<xsl:for-each select=".//header/#class">
<xsl:call-template name="tokenize">
<xsl:with-param name="txt" select="."/>
</xsl:call-template>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="cls2" select="exsl:node-set($cls1)"/>
<xsl:key name="classKey" match="cls" use="."/>
<xsl:template match="/">
<document>
<header>
<xsl:attribute name="class">
<xsl:for-each select="$cls2/*[generate-id() = generate-id(key('classKey', .)[1])]">
<xsl:value-of select="."/>
<xsl:if test="position() < last()">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:attribute>
</header>
</document>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="txt"/>
<xsl:if test="$txt">
<xsl:if test="contains($txt, ' ')">
<cls>
<xsl:value-of select="substring-before($txt, ' ')"/>
</cls>
<xsl:call-template name="tokenize">
<xsl:with-param name="txt" select="substring-after($txt, ' ')"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="not(contains($txt, ' '))">
<cls>
<xsl:value-of select="$txt"/>
</cls>
</xsl:if>
</xsl:if>
</xsl:template>
</xsl:transform>
Is there a way to convert these elsevier tags into mml:math tags?
<ce:chem>PEG<ce:inf>BOUND</ce:inf>
<ce:hsp sp="0.25"/>=<ce:hsp sp="0.25"/>PEG<ce:inf>TOT</ce:inf>
<ce:hsp sp="0.25"/>-<ce:hsp sp="0.25"/>PEG<ce:inf>FINAL</ce:inf>
</ce:chem>
Try this:
XML: (Ensure ce:chem content should not have line breaks and comment text. grouping functions can do better than my code, but I placed this jsut to meet the requirement)
<article xmlns:ce="http://www.elsevier.com/xml/common/dtd"
xmlns:sb="http://www.elsevier.com/xml/common/struct-bib/dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:mml="http://www.w3.org/1998/Math/MathML">
<p>
<ce:chem>PEG<ce:inf>BOUND</ce:inf><ce:hsp sp="0.25"/>=<ce:hsp sp="0.25"/>PEG<ce:inf>TOT</ce:inf><ce:hsp sp="0.25"/>-<ce:hsp sp="0.25"/>PEG<ce:inf>FINAL</ce:inf>A<ce:sup>2</ce:sup></ce:chem>
</p>
</article>
XSLT 2.0: (latest xslt)
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ce="http://www.elsevier.com/xml/common/dtd"
xmlns:sb="http://www.elsevier.com/xml/common/struct-bib/dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:mml="http://www.w3.org/1998/Math/MathML"
xmlns:ja="http://www.elsevier.com/xml/ja/dtd">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#*|node()"><xsl:copy><xsl:apply-templates/></xsl:copy></xsl:template>
<xsl:template match="ce:chem">
<xsl:copy>
<xsl:apply-templates select="node()[not(self::ce:inf)][not(self::ce:sup)] | #*" />
</xsl:copy>
</xsl:template>
<xsl:key name="ksub" match="ce:inf" use="generate-id(preceding-sibling::node()[1][self::text()])"/>
<xsl:key name="ksup" match="ce:sup" use="generate-id(preceding-sibling::node()[1][self::text()])"/>
<xsl:template match="ce:chem/text()">
<xsl:choose>
<xsl:when test="following-sibling::node()[1][name()='ce:inf']">
<xsl:element name="ce:msub">
<xsl:call-template name="tempNameElements"/>
<xsl:apply-templates select="key('ksub', generate-id())" />
</xsl:element>
</xsl:when>
<xsl:when test="following-sibling::node()[1][name()='ce:sup']">
<xsl:element name="ce:msup">
<xsl:call-template name="tempNameElements"/>
<xsl:apply-templates select="key('ksup', generate-id())" />
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="tempNameElements"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="ce:hsp">
<xsl:element name="mml:mspace">
<xsl:attribute name="width"><xsl:value-of select="#sp"/></xsl:attribute>
</xsl:element>
</xsl:template>
<xsl:template match="ce:inf">
<xsl:for-each select="node()">
<xsl:call-template name="tempNameElements"/>
</xsl:for-each>
</xsl:template>
<xsl:template match="ce:sup">
<xsl:for-each select="node()">
<xsl:call-template name="tempNameElements"/>
</xsl:for-each>
</xsl:template>
<xsl:template name="tempNameElements">
<xsl:choose>
<xsl:when test="starts-with(replace(self::text(), '([A-z]+)', 'A'), 'A')">
<xsl:element name="mml:mi"><xsl:value-of select="."/></xsl:element>
</xsl:when>
<xsl:when test="starts-with(replace(self::text(), '([0-9]+)', '9'), '9')">
<xsl:element name="mml:mn"><xsl:value-of select="."/></xsl:element>
</xsl:when>
<xsl:when test="matches(self::text(), '^(\(|\[|\{|=|\-)$')">
<xsl:element name="mml:mo"><xsl:value-of select="."/></xsl:element>
</xsl:when>
<xsl:otherwise><xsl:element name="mml:mtext"><xsl:value-of select="."/></xsl:element></xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Result:
<?xml version="1.0" encoding="UTF-8"?>
<article xmlns:ce="http://www.elsevier.com/xml/common/dtd"
xmlns:sb="http://www.elsevier.com/xml/common/struct-bib/dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:mml="http://www.w3.org/1998/Math/MathML">
<p>
<ce:chem>
<ce:msub>
<mml:mi>PEG</mml:mi>
<mml:mi>BOUND</mml:mi>
</ce:msub>
<mml:mspace width="0.25"/>
<mml:mo>=</mml:mo>
<mml:mspace width="0.25"/>
<ce:msub>
<mml:mi>PEG</mml:mi>
<mml:mi>TOT</mml:mi>
</ce:msub>
<mml:mspace width="0.25"/>
<mml:mo>-</mml:mo>
<mml:mspace width="0.25"/>
<ce:msub>
<mml:mi>PEG</mml:mi>
<mml:mi>FINAL</mml:mi>
</ce:msub>
<ce:msup>
<mml:mi>A</mml:mi>
<mml:mn>2</mml:mn>
</ce:msup>
</ce:chem>
</p>
</article>