Given the following XML:
<interface name="Serial1/0"/>
<interface name="Serial2/0.0"/>
<interface name="Serial2/0.1"/>
<interface name="Serial3/0:0"/>
<interface name="Serial3/0:1"/>
I am trying to produce the following output:
<interface name="Serial1/0">
<unit name="Serial1/0"/>
</interface>
<interface name="Serial2/0">
<unit name="Serial2/0.0"/>
<unit name="Serial2/0.1"/>
</interface>
<interface name="Serial3/0">
<unit name="Serial3/0:0"/>
<unit name="Serial3/0:1"/>
</interface>
I have created the following function for splitting the string:
<xsl:template name="getPhysicalInterfaceName">
<xsl:param name="interfaceName"/>
<xsl:choose>
<xsl:when test="contains($interfaceName, ':')">
<xsl:value-of select="substring-before($interfaceName, ':')"/>
</xsl:when>
<xsl:when test="contains($interfaceName, '.')">
<xsl:value-of select="substring-before($interfaceName, '.')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$interfaceName"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
I found references to using the xsl:key element, but I didn't see an obvious way to use it in the context. Any idea? (I am using xsltproc (XSLT1.0) to do the transformation.)
XSLT 1.0:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:key
name = "kInterfaceByName"
match = "interface"
use = "
substring-before(concat(
substring-before(concat(#name, ':'), ':'),
'.'), '.')
"
/>
<xsl:template match="i">
<xsl:copy>
<xsl:apply-templates mode="group" select="interface" />
</xsl:copy>
</xsl:template>
<xsl:template match="interface" mode="group">
<xsl:variable name="name" select="
substring-before(concat(
substring-before(concat(#name, ':'), ':'),
'.'), '.')
" />
<xsl:variable name="interfaces" select="key('kInterfaceByName', $name)" />
<!-- Muenchian step -->
<xsl:if test="generate-id()=generate-id($interfaces[1])">
<interface name="{$name}">
<xsl:apply-templates mode="unit" select="$interfaces" />
</interface>
</xsl:if>
</xsl:template>
<xsl:template match="interface" mode="unit">
<unit name="{#name}" />
</xsl:template>
</xsl:stylesheet>
when applied to
<i>
<interface name="Serial1/0"/>
<interface name="Serial2/0.0"/>
<interface name="Serial2/0.1"/>
<interface name="Serial3/0:0"/>
<interface name="Serial3/0:1"/>
</i>
results in
<i>
<interface name="Serial1/0">
<unit name="Serial1/0" />
</interface>
<interface name="Serial2/0">
<unit name="Serial2/0.0" />
<unit name="Serial2/0.1" />
</interface>
<interface name="Serial3/0">
<unit name="Serial3/0:0" />
<unit name="Serial3/0:1" />
</interface>
</i>
The XPath expression replaces your getPhysicalInterfaceName template.
It does, on the examples of 'Serial2/0.0' and 'Serial3/0:1':
append a ':' (=> 'Serial2/0.0:'; 'Serial3/0:1:')
take everything before the first ':' (=> 'Serial2/0.0'; 'Serial3/0')
append a '.' (=> 'Serial2/0.0.'; 'Serial3/0.')
take everything before the first '.' (=> 'Serial2/0'; 'Serial3/0')
EDIT: Simplified XPath expression. My first try worked but was more complex:
concat(
substring-before(#name, '/'),
'/',
substring-before(
concat(
translate(substring-after(#name, '/'), '.', ':'), ':'
),
':'
)
)
On the plus side, the above expression correctly handles colons and dots in the first part of the name, e.g. 'Some.Serial3/0:1'. The shorter one does not. If you expect dots in the name, use the longer expression. An explanation of it is in the revision history of this post.
You should to take a look into Muenchian method to group XML items.
Related
Please suggest for how get the maximum 'rid' value from all xrefs except from the 'Online' sections. By identify the max valued 'rid', then need to insert the attribute to those references which are higher to maximum value. Please see required result text.
XML:
<article>
<body>
<sec><title>Sections</title>
<p>The test <xref rid="b1">1</xref>, <xref rid="b2">2</xref>, <xref rid="b3 b4 b5">3-5</xref></p></sec>
<sec><title>Online</title><!--This section's xrefs no need to consider-->
<p>The test <xref rid="b6">6</xref></p>
<sec><title>Other</title>
<p><xref rid="b1">1</xref>, <xref rid="b7 b8">7-8</xref></p>
</sec>
</sec><!--This section's xrefs no need to consider-->
<sec>
<p>Final test test</p>
<sec><title>Third title</title><p>Last text</p></sec>
</sec>
</body>
<bm>
<ref id="b1">The ref01</ref>
<ref id="b2">The ref02</ref>
<ref id="b3">The ref03</ref>
<ref id="b4">The ref04</ref>
<ref id="b5">The ref05</ref>
<ref id="b6">The ref06</ref>
<ref id="b7">The ref07</ref>
<ref id="b8">The ref08</ref>
</bm>
</article>
XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:variable name="var1"><!--Variable to get the all 'rid's except sec/title contains 'Online' -->
<xsl:for-each select="//xref[not(. is ancestor::sec[title[contains(., 'Online')]]/descendant-or-self)]/#rid">
<!--xsl:for-each select="//xref/#rid[not(contains(ancestor::sec/title, 'Online'))]"--><!--for this xpath, error is : "XPTY0004: A sequence of more than one item is not allowed as the first argument" -->
<!--xsl:for-each select="//xref/#rid[not(contains(ancestor::sec[1]/title, 'Online')) and not(contains(ancestor::sec[2]/title, 'Online'))]"--><!--for this xpath we are getting the required result, but there may be several nesting of 'sec's -->
<xsl:choose>
<xsl:when test="contains(., ' ')">
<xsl:for-each select="tokenize(., ' ')">
<a><xsl:value-of select="."/></a>
</xsl:for-each>
</xsl:when>
<xsl:otherwise><a><xsl:value-of select="."/></a></xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="varMax1">
<xsl:for-each select="$var1/a">
<xsl:sort select="substring-after(., 'b')" order="descending" data-type="number"/>
<a><xsl:value-of select="."/></a>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="varMax"><!--Variable to get max valued RID -->
<xsl:value-of select="substring-after($varMax1/a[1], 'b')"/>
</xsl:variable>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
<xsl:template match="ref">
<xsl:variable name="varID"><xsl:value-of select="substring-after(#id, 'b')"/></xsl:variable>
<xsl:choose>
<xsl:when test="number($varMax) lt number($varID)">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="MoveRef">yes</xsl:attribute>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Required result:
<article>
<body>
<sec><title>Sections</title>
<p>The test <xref rid="b1">1</xref>, <xref rid="b2">2</xref>, <xref rid="b3 b4 b5">3-5</xref></p></sec>
<sec><title>Online</title><!--This section's xrefs no need to consider-->
<p>The test <xref rid="b6">6</xref></p>
<sec><title>Other</title>
<p><xref rid="b1">1</xref>, <xref rid="b7">7</xref>, <xref rid="b8">8</xref></p>
</sec>
</sec><!--This section's xrefs no need to consider-->
<sec>
<p>Final test test</p>
<sec><title>Third title</title><p>Last text</p></sec>
</sec>
</body>
<bm>
<ref id="b1">The ref01</ref>
<ref id="b2">The ref02</ref>
<ref id="b3">The ref03</ref>
<ref id="b4">The ref04</ref>
<ref id="b5">The ref05</ref>
<ref id="b6" MoveRef="yes">The ref06</ref>
<ref id="b7" MoveRef="yes">The ref07</ref>
<ref id="b8" MoveRef="yes">The ref08</ref>
</bm>
</article>
Here consider number 5 for 'b5' rid, 6 for 'b6'.... (Because alphanumeric)
Perhaps you can take a different approach rather than trying to find the maximum rid attribute that is not in an "online" section. Not least because it is not entirely clear what the maximum is when you are dealing with an alphanumeric string.
Instead, you could define a key to look up elements in the "online" section by their name
<xsl:key name="online" match="sec[title = 'Online']//*" use="name()" />
And then, another key, to look up the xref elements that occur in other sections
<xsl:key name="other" match="xref[not(ancestor::sec/title = 'Online')]" use="name()" />
Then, you can write a template to math the ref elements, and use an xsl:if to determine whether to add MoveRef attribute to it:
<xsl:variable name="id" select="#id" />
<xsl:if test="key('online', 'xref')[tokenize(#rid, ' ')[. = $id]] and not(key('other', 'xref')[tokenize(#rid, ' ')[. = $id]])">
Try this much shorter XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" />
<xsl:key name="online" match="sec[title = 'Online']//*" use="name()" />
<xsl:key name="other" match="xref[not(ancestor::sec/title = 'Online')]" use="name()" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ref">
<ref>
<xsl:variable name="id" select="#id" />
<xsl:if test="key('online', 'xref')[tokenize(#rid, ' ')[. = $id]] and not(key('other', 'xref')[tokenize(#rid, ' ')[. = $id]])">
<xsl:attribute name="MoveRef" select="'Yes'" />
</xsl:if>
<xsl:apply-templates select="#*|node()"/>
</ref>
</xsl:template>
</xsl:stylesheet>
You can actually amend the ref template to put the condition in the template match, if you wanted...
<xsl:template match="ref[key('online', 'xref')[tokenize(#rid, ' ')[. = current()/#id]] and not(key('other', 'xref')[tokenize(#rid, ' ')[. = current()/#id]])]">
<ref MoveRef="Yes">
<xsl:apply-templates select="#*|node()"/>
</ref>
</xsl:template>
All-
I am trying to understand the root cause for 2 nodes getting merged.
The input XML
<?xml version='1.0' encoding='UTF-8'?>
<Employees>
<Employee>
<Employee_ID>E00001</Employee_ID>
<Legal_Name Descriptor="John Doe" />
<lastName>Doe</lastName>
<firstName>John</firstName>
<P_From_Date>2015-04-01-08:00</P_From_Date>
<P_End_Date>2015-12-31-08:00</P_End_Date>
<Transaction>
<Plan Descriptor="Plan A" />
<effective_date>2015-03-22-08:00</effective_date>
<end_date>2015-10-22</end_date>
<Annual_Cost>6000</Annual_Cost>
</Transaction>
<Transaction>
<Plan Descriptor="Plan A" />
<effective_date>2015-02-03-08:00</effective_date>
<Annual_Cost>4000</Annual_Cost>
</Transaction>
<Transaction>
<Plan Descriptor="Plan A" />
<effective_date>2013-02-03-08:00</effective_date>
<Annual_Cost>3000</Annual_Cost>
</Transaction>
<Transaction>
<Plan Descriptor="Plan B" />
<effective_date>2014-12-03-08:00</effective_date>
<Annual_Cost>12000</Annual_Cost>
</Transaction>
<Transaction>
<Plan Descriptor="Plan B" />
<effective_date>2014-10-03-08:00</effective_date>
<Annual_Cost>1000</Annual_Cost>
</Transaction>
</Employee>
<Employee>
<Employee_ID>E00002</Employee_ID>
<Legal_Name Descriptor="John Doe" />
<lastName>Test</lastName>
<firstName>Jane</firstName>
<P_From_Date>2015-01-01-08:00</P_From_Date>
<Transaction>
<Plan Descriptor="Plan D" />
<effective_date>2015-05-22-08:00</effective_date>
<Annual_Cost>12000</Annual_Cost>
</Transaction>
<Transaction>
<Plan Descriptor="Plan D" />
<effective_date>2014-03-01-08:00</effective_date>
<Annual_Cost>9000</Annual_Cost>
</Transaction>
<Transaction>
<Plan Descriptor="Plan C" />
<effective_date>2014-12-03-08:00</effective_date>
<Annual_Cost>3000</Annual_Cost>
</Transaction>
<Transaction>
<Plan Descriptor="Plan C" />
<effective_date>2013-01-03-08:00</effective_date>
<Annual_Cost>3000</Annual_Cost>
</Transaction>
</Employee>
</Employees>
And the XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<!-- TODO customize transformation rules
syntax recommendation http://www.w3.org/TR/xslt
-->
<xsl:variable name="newline" select="'
'"/>
<xsl:variable name="delimiter">,</xsl:variable>
<xsl:variable name="qualifier">
<xsl:text>"</xsl:text>
</xsl:variable>
<xsl:template match="/Employees">
<EES>
<xsl:apply-templates/>
</EES>
</xsl:template>
<xsl:template match="Employee">
<EE>
<EID>
<xsl:value-of select="Employee_ID" />
</EID>
<FULNAME>
<xsl:value-of select="Legal_Name/#Descriptor"/>
</FULNAME>
<LNAME>
<xsl:value-of select="lastName"/>
</LNAME>
<FNAME>
<xsl:value-of select="firstName"/>
</FNAME>
<xsl:variable name="bework">
<xsl:for-each-group select="Transaction" group-by="Plan/#Descriptor" >
<berow>
<CurrentGroup>
<xsl:value-of select="current-grouping-key()"/>
</CurrentGroup>
<parm_from_date><xsl:value-of select="../P_From_Date" /></parm_from_date>
<xsl:for-each select="current-group()">
<begin-date><xsl:copy-of select="effective_date" /></begin-date>
<include-flag >
<xsl:choose>
<xsl:when test="xs:date( substring(effective_date,1,10)) >= xs:date(substring(../P_From_Date,1,10))">
<xsl:text>Y</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>N</xsl:text>
</xsl:otherwise>
</xsl:choose>
</include-flag>
<adj-begin-date>
<xsl:choose>
<xsl:when test="xs:date(effective_date) >= xs:date(../P_From_Date_1) ">
<xsl:value-of select="effective_date"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="../P_From_Date_1"/>
</xsl:otherwise>
</xsl:choose>
</adj-begin-date>
<end_date>
<xsl:value-of select="current-group()/end_date"/>
</end_date>
<Cost>
<xsl:value-of select="Annual_Cost"/>
</Cost>
</xsl:for-each>
</berow>
</xsl:for-each-group>
</xsl:variable>
<xsl:for-each select="$bework/berow">
<CGR>
<xsl:value-of select="CurrentGroup" />
</CGR>
<PFRMDT>
<xsl:value-of select="parm_from_date" />
</PFRMDT>
<BDT>
<xsl:value-of select="begin-date" />
</BDT>
<FLAG>
<xsl:value-of select="include-flag" />
</FLAG>
<ADJBGNDT>
<xsl:value-of select="$qualifier"/>
</ADJBGNDT>
<EDATE>
<xsl:value-of select="$qualifier"/>
</EDATE>
<CO>
<xsl:value-of select="Cost" />
</CO>
</xsl:for-each>
</EE>
</xsl:template>
</xsl:stylesheet>
The result
<?xml version="1.0" encoding="UTF-8"?>
<EES xmlns:xs="http://www.w3.org/2001/XMLSchema">
<EE>
<EID>E00001</EID>
<FULNAME>John Doe</FULNAME>
<LNAME>Doe</LNAME>
<FNAME>John</FNAME>
<CGR>Plan A</CGR>
<PFRMDT>2015-04-01-08:00</PFRMDT>
<BDT>2015-03-22-08:00 2015-02-03-08:00 2013-02-03-08:00</BDT>
<FLAG>N N N</FLAG>
<ADJBGNDT>"</ADJBGNDT>
<EDATE>"</EDATE>
<CO>6000 4000 3000</CO>
<CGR>Plan B</CGR>
<PFRMDT>2015-04-01-08:00</PFRMDT>
<BDT>2014-12-03-08:00 2014-10-03-08:00</BDT>
<FLAG>N N</FLAG>
<ADJBGNDT>"</ADJBGNDT>
<EDATE>"</EDATE>
<CO>12000 1000</CO>
</EE>
<EE>
<EID>E00002</EID>
<FULNAME>John Doe</FULNAME>
<LNAME>Test</LNAME>
<FNAME>Jane</FNAME>
<CGR>Plan D</CGR>
<PFRMDT>2015-01-01-08:00</PFRMDT>
<BDT>2015-05-22-08:00 2014-03-01-08:00</BDT>
<FLAG>Y N</FLAG>
<ADJBGNDT>"</ADJBGNDT>
<EDATE>"</EDATE>
<CO>12000 9000</CO>
<CGR>Plan C</CGR>
<PFRMDT>2015-01-01-08:00</PFRMDT>
<BDT>2014-12-03-08:00 2013-01-03-08:00</BDT>
<FLAG>N N</FLAG>
<ADJBGNDT>"</ADJBGNDT>
<EDATE>"</EDATE>
<CO>3000 3000</CO>
</EE>
</EES>
If you look at John Doe's BDT node, there are 2 dates. These are 2 nodes for the sample plan are getting added to the same node despite looping through the current group.
WHat is causing this? And what should be done to remedy this? I will have to use variables as there is more manulations I will have to do. But that is for another day.
Thanks for providng me some insight.
Inside of your bework variable you create a berow element for each Transaction group but then you use <xsl:for-each select="current-group()"> to output a begin-date for each Transaction in that group, without structuring or wrapping them further. With your input that means that berow element can contain two or three begin-date elements.
Then you have <xsl:for-each select="$bework/berow"> and inside
<BDT>
<xsl:value-of select="begin-date" />
</BDT>
which will select and output the string value of the two or three begin-date elements.
I am not sure which value you want to output for BDT, you could use e.g. <xsl:value-of select="begin-date[1]"/> or <xsl:value-of select="begin-date[last()]"/> to output only the string value of the first or last element created earlier.
Perhaps you just need to change:
<BDT>
<xsl:value-of select="begin-date" />
</BDT>
to:
<BDT>
<xsl:copy-of select="begin-date" />
</BDT>
In XSLT 2.0, xsl:value-of will generate a single text node, concatenating the values of all matching nodes, separated by a space (or another separator, if specified).
Additional explanation:
WHat is causing this?
The reason why your nodes are getting merged into a single text node is that you are using xsl:value-of when you try to fetch them from the $bework variable in order to write them into the output tree.
Consider the following simplified example:
XML
<root>
<item>
<category>A</category>
<amount>1000</amount>
</item>
<item>
<category>A</category>
<amount>500</amount>
</item>
<item>
<category>A</category>
<amount>250</amount>
</item>
<item>
<category>B</category>
<amount>600</amount>
</item>
<item>
<category>B</category>
<amount>300</amount>
</item>
</root>
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/root">
<xsl:variable name="groups">
<xsl:for-each-group select="item" group-by="category">
<group category="{current-grouping-key()}">
<xsl:for-each select="current-group()">
<amount>
<xsl:value-of select="amount"/>
</amount>
</xsl:for-each>
</group>
</xsl:for-each-group>
</xsl:variable>
<output>
<xsl:for-each select="$groups/group">
<group category="{#category}">
<copy-of-amount>
<xsl:copy-of select="amount"/>
</copy-of-amount>
<for-each-amount>
<xsl:for-each select="amount">
<new-node value="{.}"/>
</xsl:for-each>
</for-each-amount>
<sum-of-amount>
<xsl:value-of select="sum(amount)"/>
</sum-of-amount>
<value-of-amount>
<xsl:value-of select="amount"/>
</value-of-amount>
</group>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<output>
<group category="A">
<copy-of-amount>
<amount>1000</amount>
<amount>500</amount>
<amount>250</amount>
</copy-of-amount>
<for-each-amount>
<new-node value="1000"/>
<new-node value="500"/>
<new-node value="250"/>
</for-each-amount>
<sum-of-amount>1750</sum-of-amount>
<value-of-amount>1000 500 250</value-of-amount>
</group>
<group category="B">
<copy-of-amount>
<amount>600</amount>
<amount>300</amount>
</copy-of-amount>
<for-each-amount>
<new-node value="600"/>
<new-node value="300"/>
</for-each-amount>
<sum-of-amount>900</sum-of-amount>
<value-of-amount>600 300</value-of-amount>
</group>
</output>
As you can see, inside the $myVar variable, each group contains 2-3 distinct amount nodes. You can copy them, sum them or create something for each one of them. However, when you do:
<xsl:value-of select="amount"/>
you are addressing all the amount nodes in the current group, and you will get a result that incorporates them all, same as:
<xsl:value-of select="sum(amount)"/>
returns a result based on all the amount nodes being addressed.
And what should be done to remedy this?
We won't know that until you tell us what is the actual result you want to get. In the comments below you said that:
the final result is really the total cost per employee.
If so, the example above shows how to get it.
I’m new in the xslt topic and have a problem that can't solve on my own.
Here e excample of my xml file:
<node>
<failure><![CDATA[
some useless information.
CRS urn:ogc:def:crs:EPSG::25830 not defined.
CRS urn:ogc:def:crs:EPSG::25833 not possible.
CRS urn:ogc:def:crs:EPSG::25830 not defined.
some useless information.]]>
</failure>
</node>
The main problem is that the information stand in a CDATA block and many different informations are mixed up. I have found a way to get them out, but only as a string value not able to differentiate between the sort.
I need a way to extract elements that fit the pattern: "CRS [-unknown-] [id] not [result]"
What i want is something like this:
<failure>
<CRS>
<id> urn:ogc:def:crs:EPSG::25830 </id>
<result> not defined </result>
</CRS>
<CRS>
<id> urn:ogc:def:crs:EPSG::25833 </id>
<result> not posible </result>
</CRS>
<CRS>
<id> urn:ogc:def:crs:EPSG::25830 </id>
<result> not defined </result>
</CRS>
</failure>
Can somebody help me or made experience with simular problems?
XSLT 2.0 has xsl:analyze-string which is designed for precisely this task, so if at all possible I suggest you upgrade to a 2.0 processor such as Saxon:
<xsl:template match="node">
<failure>
<xsl:analyze-string select="failure"
regex="^\s*CRS\s*(\S+)\s*(not\s*.*)$" flags="m">
<xsl:matching-substring>
<CRS>
<id><xsl:value-of select="regex-group(1)" /></id>
<result><xsl:value-of select="normalize-space(regex-group(2))" /></result>
</CRS>
</xsl:matching-substring>
</xsl:analyze-string>
</failure>
</xsl:template>
String manipulation facilities in XSLT 1.0 are extremely limited in comparison, and since XSLT is a functional language without updateable variables you'd have to write some sort of hideously complex set of recursive call-template logic to split up the text into separate lines and then extract the relevant bits out of each line in turn using substring-before and substring-after.
<xsl:template name="each-line">
<xsl:param name="val" />
<!-- pull out everything before the first newline and normalize (trim leading
and trailing whitespace and squash internal whitespace to a single space
character -->
<xsl:variable name="firstLine"
select="normalize-space(substring-before($val, '
'))" />
<!-- pull out everything after the first newline -->
<xsl:variable name="rest" select="substring-after($val, '
')" />
<xsl:if test="$firstLine">
<xsl:call-template name="process-line">
<xsl:with-param name="line" select="$firstLine" />
</xsl:call-template>
</xsl:if>
<!-- if there are still some non-empty lines left then process them recursively -->
<xsl:if test="normalize-space($rest)">
<xsl:call-template name="each-line">
<xsl:with-param name="val" select="$rest" />
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="process-line">
<xsl:param name="line" />
<xsl:if test="starts-with($line, 'CRS ') and contains($line, ' not ')">
<!-- here $line will be something like
"CRS urn:ogc:def:crs:EPSG::25830 not defined." -->
<CRS>
<!-- ID is everything between the first and second spaces -->
<id><xsl:value-of select="substring-before(substring-after($line, ' '), ' ')" /></id>
<!-- result is everything after the second space -->
<result><xsl:value-of select="substring-after(substring-after($line, ' '), ' ')" /></result>
</CRS>
</xsl:if>
</xsl:template>
You would call this logic using a construct like
<xsl:template match="node">
<failure>
<xsl:call-template name="each-line">
<xsl:with-param name="val" select="failure" />
</xsl:call-template>
</failure>
</xsl:template>
I'm trying to generate very simple documentation from the annotations in a Relax NG XML Schema. For example, given the following Relax NG:
<?xml version="1.0" encoding="UTF-8"?>
<grammar xmlns="http://relaxng.org/ns/structure/1.0" xmlns:a="http://relaxng.org/ns/compatibility/annotations/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<start>
<element name="topNode">
<ref name="topNode-ref"/>
</element>
</start>
<define name="topNode-ref">
<a:documentation>This is the top of the doc.</a:documentation>
<oneOrMore>
<element name="level1">
<ref name="level1-ref"/>
</element>
</oneOrMore>
</define>
<define name="level1-ref">
<a:documentation>Here's some notes about level1.</a:documentation>
<attribute name="att1">
<a:documentation>Details about att1.</a:documentation>
</attribute>
<element name="subLevel2">
<ref name="subLevel2-ref"/>
</element>
</define>
<define name="subLevel2-ref">
<a:documentation>Notes on subLevel2.</a:documentation>
<attribute name="subAtt"/>
<zeroOrMore>
<element name="subLevel3">
<ref name="subLevel3-ref"/>
</element>
</zeroOrMore>
</define>
<define name="subLevel3-ref">
<a:documentation>And here is subLevel3.</a:documentation>
<attribute name="subSubAtt"/>
</define>
</grammar>
Which would be used to valid an XML file like:
<?xml version="1.0" encoding="UTF-8"?>
<topNode>
<level1 att1="some test">
<subLevel2 subAtt="more text"></subLevel2>
</level1>
<level1 att1="quick">
<subLevel2 subAtt="brown">
<subLevel3 subSubAtt="fox"></subLevel3>
</subLevel2>
</level1>
</topNode>
I'd like to be able to produce documentation that lists the basic XPath to each element/attribute and then display any corresponding documentation annotations. For example:
/topNode
This is the top of the doc.
/topNode/level1
Here's some notes about level1
/topNode/level1/#att1
Details about att1.
etc...
Eventually, I'll add in more documentation about "zeroOrMore", possible data types, etc... but I need to get this first step solved first.
I've found the Techquila RELAX-NG Documentation Tools. I've played around with the rng to docbook stylesheet, but it don't do what I'm looking for. It just lists elements individually with no details about the XPath as far as I can tell. I don't see how I can use it as a starting point to get the output I'm after.
Is it possible (and if so, how?) to produce this type of documentation output with XSLT given the RelaxNG example provided?
While XSLT would be ideal, it's not a requirement. I'm open for anything that gets the job done.
This will work for a very simple grammar like your example.
<?xml version='1.0'?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:r="http://relaxng.org/ns/structure/1.0"
xmlns:a="http://relaxng.org/ns/compatibility/annotations/1.0"
>
<xsl:output method="text" />
<xsl:template match="/">
<xsl:apply-templates select="//r:define[a:documentation] | //r:attribute[a:documentation]" />
</xsl:template>
<xsl:template match="r:define">
<xsl:variable name="doc" select="a:documentation" />
<xsl:call-template name="print-path">
<xsl:with-param name="elm" select="//r:element[r:ref/#name=current()/#name]" />
</xsl:call-template>
<xsl:value-of select="$doc" /><xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="r:attribute">
<xsl:variable name="doc" select="a:documentation" />
<xsl:call-template name="print-path">
<xsl:with-param name="elm" select="//r:element[r:ref/#name=current()/ancestor::r:define/#name]" />
<xsl:with-param name="path" select="concat('/#',#name)" />
</xsl:call-template>
<xsl:value-of select="$doc" /><xsl:text>
</xsl:text>
</xsl:template>
<xsl:template name="print-path">
<xsl:param name="elm" />
<xsl:param name="path" />
<xsl:variable name="parent" select="//r:ref[#name=$elm/ancestor::r:define/#name]/ancestor::r:element" />
<xsl:message><xsl:value-of select="$elm/#name" /></xsl:message>
<xsl:choose>
<xsl:when test="$parent">
<xsl:call-template name="print-path">
<xsl:with-param name="elm" select="$parent" />
<xsl:with-param name="path" select="concat('/',$elm/#name,$path)" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat('/',$elm/#name,$path)" /><xsl:text>
</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Is it possible to access child node attributes in xsl:if
I have to convert that Xml to text file
<Report>
<Total>
<RecordValues>
<Record>
<FieldValue fieldName="index" fieldValue="1" />
<FieldValue fieldName="dtrk_sysid" fieldValue="0"/>
<FieldValue fieldName="version" fieldValue="100" />
<FieldValue fieldName="user" fieldValue="tester" />
<FieldValue fieldName="date_modified" fieldValue="2010-10-18 12:18:12" />
<FieldValue fieldName="object_name" fieldValue="Menu" />
<FieldValue fieldName="permission" fieldValue="Permission X" />
</Record>
<Record>
<FieldValue fieldName="index" fieldValue="2" />
<FieldValue fieldName="dtrk_sysid" fieldValue="55555"/>
<FieldValue fieldName="version" fieldValue="100" />
<FieldValue fieldName="user" fieldValue="user1" />
<FieldValue fieldName="date_modified" fieldValue="2010-12-15 12:18:12" />
<FieldValue fieldName="object_name" fieldValue="Control" />
<FieldValue fieldName="permission" fieldValue="Permission E" />
</Record>
<Record>
<FieldValue fieldName="index" fieldValue="3" />
<FieldValue fieldName="dtrk_sysid" fieldValue="55555"/>
<FieldValue fieldName="version" fieldValue="15" />
<FieldValue fieldName="user" fieldValue="user2" />
<FieldValue fieldName="date_modified" fieldValue="2010-10-02 12:18:12" />
<FieldValue fieldName="object_name" fieldValue="Run" />
<FieldValue fieldName="permission" fieldValue="Permission R" />
</Record>
</RecordValues>
</Total>
I already know how to do that, but the file must have header record which is should appear only once as the first record of the file. It must contains some default values and some values from FieldValue node.
Here is the example of header record:
HDRTT55555EE000KK20101018UU
1 100 101810
tester Menu Permission X
2 100 121510
user1 Control Permission E
3 15 100210
user2 Run Permission R
Here is what I've done so far:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="yes" encoding="UTF-8" omit-xml- declaration="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="/Report/Total/RecordValues"/>
<xsl:apply-templates select="/Report/Total/RecordValues/Record/FieldValue"/>
</xsl:template>
<xsl:template match="RecordValues">
<xsl:text>HDR</xsl:text>
<xsl:text>TT</xsl:text>
<xsl:variable name="fvsys" select="Record/FieldValue[#fieldName = 'dtrk_sysid']"/>
<xsl:choose>
<xsl:when test="$fvsys/#fieldValue != '0'">
<xsl:value-of select="$fvsys/#fieldValue"/>
</xsl:when>
<xsl:otherwise>
<xsl:text>12343</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:text>EE</xsl:text>
<xsl:text>000</xsl:text>
<xsl:text>KK</xsl:text>
<xsl:if test="Record/FieldValue[#fieldName='date_modified']">
<xsl:call-template name="ppad">
<xsl:with-param name="str" select="concat(substring(translate(Record/FieldValue[#fieldName = 'date_modified']/#fieldValue,'-',''),5,4), substring(Record/FieldValue[#fieldName = 'date_modified']/#fieldValue,3,2))"/>
<xsl:with-param name="len" select="6"/>
</xsl:call-template>
</xsl:if>
<xsl:text>UU</xsl:text>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="FieldValue">
</xsl:template>
<xsl:template name="ppad">
<xsl:param name="str"/>
<xsl:param name="chr" select="' '"/>
<xsl:param name="len" select="0"/>
<xsl:choose>
<xsl:when test="string-length($str) < $len">
<xsl:call-template name="ppad">
<xsl:with-param name="str" select="concat($str, $chr)"/>
<xsl:with-param name="len" select="$len"/>
<xsl:with-param name="chr" select="$chr"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$str"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Thanks for your help.
The main problem with the following if statement:
<xsl:if test="Record/FieldValue/#fieldName='dtrk_sysid' and Record/FieldValue/#fieldValue!='0'">
<xsl:value-of select="Record/FieldValue/#fieldValue"/>
</xsl:if>
is that the #fieldValue you're trying to compare after the and is supposed to be the #fieldValue of the <FieldValue> whose #fieldName is 'dtrk_sysid', but there is nothing that limits the second comparison to that particular <FieldValue>. The <xsl:if test=...> does not set a context. <xsl:value-of> will then select the first #fieldValue attribute of all Record/FieldValue nodes under the context node, which by luck happens to be the right one, in your sample input XML (but maybe not always?).
To fix that, you can capture the context (the <FieldValue> whose #fieldName is 'dtrk_sysid') in a variable:
<xsl:variable name="fvsys" select="(Record/FieldValue[#fieldName = 'dtrk_sysid'])[1]"/>
Then you can replace your first two if statements with:
<xsl:choose>
<xsl:when test="$fvsys/#fieldValue != '0'">
<xsl:value-of select="$fvsys/#fieldValue"/>
</xsl:when>
<xsl:otherwise>
<xsl:text>12343</xsl:text>
</xsl:otherwise>
</xsl:choose>
(The above has been modified to fit the behavior described in comments.)
You can replace your third if statement with:
<xsl:value-of select="(Record/FieldValue[#fieldName = 'date_modified'])[1]/#fieldValue"/>
This will not output anything if no such FieldValue exists.
If after this you're still not getting the desired output, tell us what output you are getting, and how it differs from the desired output.