Ancestors' same-name siblings - xslt

<corpus>
<header id="1">
<file>
<info>
<title id="A" />
</info>
</file>
</header>
<TEI>
<header id="2">
<file>
<info>
<title id="B" />
</info>
</file>
</header>
<header id="3">
<file>
<info>
<record>
<title id="C" />
</record>
</info>
</file>
</header>
<header id="4">
<file>
</file>
</header>
</TEI>
</corpus>
$list is a set of <title> nodes.
The depth of <title> varies, but is always somewhere under a <header>. The depth of <header> varies, but its depth from root is always the same for all nodes in a given $list.
Given a $list, I need a for-each loop that loops through headers.
When the only node in $list is title A, I need to loop only through header 1.
When the nodes in $list are titles B and C, I need to loop through headers 2, 3 and 4.

Use:
$list/ancestor::header[1]/../header
XSLT - based verification:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vList1" select=
"/*/header[1]/*/*/title"/>
<xsl:variable name="vList2" select=
"/*/TEI//title"/>
<xsl:template match="/">
<xsl:copy-of select="$vList1"/>
====
<xsl:copy-of select="$vList2"/>
====
<xsl:copy-of select=
"$vList1/ancestor::header[1]/../header"/>
====
<xsl:copy-of select=
"$vList2/ancestor::header[1]/../header"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<corpus>
<header id="1">
<file>
<info>
<title id="A" />
</info>
</file>
</header>
<TEI>
<header id="2">
<file>
<info>
<title id="B" />
</info>
</file>
</header>
<header id="3">
<file>
<info>
<record>
<title id="C" />
</record>
</info>
</file>
</header>
<header id="4">
<file>
</file>
</header>
</TEI>
</corpus>
the XPath expressions are evaluated and the wanted in each case header elements are selected and output:
<title id="A"/>
====
<title id="B"/>
<title id="C"/>
====
<header id="1">
<file>
<info>
<title id="A"/>
</info>
</file>
</header>
====
<header id="2">
<file>
<info>
<title id="B"/>
</info>
</file>
</header>
<header id="3">
<file>
<info>
<record>
<title id="C"/>
</record>
</info>
</file>
</header>
<header id="4">
<file>
</file>
</header>

Related

I need to change an xml attribute value conditionaly from an external xml using Xsl

I have the following 2 xml files,
Mapping.xml
<xml>
<map ordinal="0" reverse="xxx" forward="ThisIsXxx" />
<map ordinal="0" reverse="yyy" forward="ThisIsYyy" />
<map ordinal="0" reverse="zzz" forward="thisIsZzz" />
<map ordinal="0" reverse="xx1" forward="ThisIsXx1Info" />
<map ordinal="0" reverse="yy1" forward="ThisIsYy1Info" />
<map ordinal="0" reverse="zz1" forward="ThisIsZz1Info" />
</xml>
and an input xml file with a series of elements with some of them having a 'name' attribute
second.xml
<?xml version="1.0" encoding="UTF-8"?>
<xml>
<Children>
<child name="xxx">
<info name="xx1">
</info>
</child>
<child name="yyy">
<info name="yy1">
</info>
</child>
<child name="zzz">
<info name="zz1">
</info>
</child>
</Children> <!-- Added by edit -->
</xml>
What I need is if direction is 'forward' then the second.xml files #name to be set to the value from #forward from the Mapping.xml such as the following,
output.xml
<?xml version="1.0" encoding="UTF-8"?>
<xml>
<Children>
<child name="ThisIsXxx">
<info name="ThisIsXx1Info">
</info>
</child>
<child name="ThisIsZzz">
<info name="ThisIsYy1Info">
</info>
</child>
<child name="ThisIsYyy">
<info name="ThisIsZz1Info">
</info>
</child>
</xml>
Xslt file I have is setting all #name="" but not getting any values from the external file..
<?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"/>
<!-- get the mapping db -->
<xsl:variable name="mapDb" select="document('Mapping.xml')" />
<xsl:variable name="mapFwd" select="forward" />
<!-- for all #name find the #mapdb/*/#reverse == #name and set #name=#mapDb/*/forward -->
<xsl:template match="*/#name">
<xsl:choose>
<xsl:when test="$mapFwd='forward'">
<xsl:attribute name="name">
<xsl:value-of select="$mapDb/xml/map[#reverse='{#name}']/#forward"/>
</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="name">
<xsl:value-of select="$mapDb/xml/map[#forward='{#name}']/#reverse"/>
</xsl:attribute>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--Copy Everything unchanged-->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I would appreciate some help.
I think you want to use current() e.g. $mapDb/xml/map[#reverse=current()] in your comparisons in the template matching the #name attribute and of course the variable should be initialized with <xsl:variable name="mapFwd" select="'forward'"/>.

How can I get the attribute of the grandparent node from child node via template xslt?

XML:
<Grandparent>
<Parent>
<Children id ="1">
<Info>
<Name>
<label name ="chname" />
</Name>
</Info>
</Children>
<Children name ="2">
<Info>
<Name>
<label name="chname" />
</Name>
</Info>
</Children>
<Children id ="3">
<Info>
<Name>
<label name="chname" />
</Name>
</Info>
</Children>
</Parent>
</Grandparent>
XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="label">
<label id="../../../preceding-sibling::Children/#id">
<xsl:apply-templates select="#*|node()"/>
</label>
</xsl:template>
</xsl:stylesheet>
Expected Output:
<Grandparent>
<Parent>
<Children id ="1">
<Info>
<Name>
<label id="1" name ="chname" />
</Name>
</Info>
</Children>
<Children name ="2">
<Info>
<Name>
<label id="2" name="chname" />
</Name>
</Info>
</Children>
<Children id ="3">
<Info>
<Name>
<label id="3" name="chname" />
</Name>
</Info>
</Children>
</Parent>
</Grandparent>
Im adding A attribute id to "label" tag via template. How can I get the attribute "id" from Children node? this is my code
<label id="../../../preceding-sibling::Children/#id">
it doesnt work. Am I missing something here?
thanks in advance :)
If you want to have the results of an Xpath expression as an attribute, you need to use Attribute Value Templates, so you should be writing it as this
<label id="{../../../preceding-sibling::Children/#id}">
The curly braces indicate it is an expression to be evaluated, rather than a string to be output literally.
However, I think the expression is wrong though in this case. You should be actually doing this:
<label id="{../../../#id}">
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="label">
<label id="{../../../#id}">
<xsl:apply-templates select="#*|node()"/>
</label>
</xsl:template>
</xsl:stylesheet>
When applied to your XML, the following is output
<Grandparent>
<Parent>
<Children id="1">
<Info>
<Name>
<label id="1" name="chname"/>
</Name>
</Info>
</Children>
<Children name="2">
<Info>
<Name>
<label id="" name="chname"/>
</Name>
</Info>
</Children>
<Children id="3">
<Info>
<Name>
<label id="3" name="chname"/>
</Name>
</Info>
</Children>
</Parent>
</Grandparent>
You can use an AVT:
<label id="{../../../#id}">

Grouping with xslt and child nodes

I have tried some of the examples here, but I still can't get the correct output. I haven't worked much with XSLT before. I want to group on the "Detail" element and get all the "Data" elements matching that group as children to the "Detail" element.
Example:
input
<?xml version="1.0" encoding="utf-8"?>
<File>
<Detail type="A" group="1" >
<Data>
<Nr>1</Nr>
</Data>
<Data>
<Nr>2</Nr>
</Data>
</Detail>
<Detail type="B" group="1">
<Data>
<Nr>3</Nr>
</Data>
<Data>
<Nr>4</Nr>
</Data>
</Detail>
<Detail type="B" group="2">
<Data>
<Nr>5</Nr>
</Data>
</Detail>
<Detail type="A" group="1">
<Data>
<Nr>6</Nr>
</Data>
</Detail>
</File>
Wanted output ("Detail type="A" group="1"> elements grouped together and all the elements matching that group as children)
<?xml version="1.0" encoding="utf-8"?>
<File>
<Detail type="A" group="1" >
<Data>
<Nr>1</Nr>
</Data>
<Data>
<Nr>2</Nr>
</Data>
<Data>
<Nr>6</Nr>
</Data>
</Detail>
<Detail type="B" group="1">
<Data>
<Nr>3</Nr>
</Data>
<Data>
<Nr>4</Nr>
</Data>
</Detail>
<Detail type="B" group="2">
<Data>
<Nr>5</Nr>
</Data>
</Detail>
</File>
Thanks for help :)
I. XSLT 1.0
Here's a solution that makes use of push-oriented templates, rather than <xsl:for-each> (which is normally a more reusable approach).
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="no" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="kDetailAttr" match="Detail" use="concat(#type, '+', #group)" />
<xsl:template match="/*">
<File>
<xsl:apply-templates select="Detail[generate-id() = generate-id(key('kDetailAttr', concat(#type, '+', #group))[1])]" />
</File>
</xsl:template>
<xsl:template match="Detail">
<Detail type="{#type}" group="{#group}">
<xsl:copy-of select="key('kDetailAttr', concat(#type, '+', #group))/Data" />
</Detail>
</xsl:template>
</xsl:stylesheet>
...is applied to the original XML:
<?xml version="1.0" encoding="UTF-8"?>
<File>
<Detail type="A" group="1">
<Data>
<Nr>1</Nr>
</Data>
<Data>
<Nr>2</Nr>
</Data>
</Detail>
<Detail type="B" group="1">
<Data>
<Nr>3</Nr>
</Data>
<Data>
<Nr>4</Nr>
</Data>
</Detail>
<Detail type="B" group="2">
<Data>
<Nr>5</Nr>
</Data>
</Detail>
<Detail type="A" group="1">
<Data>
<Nr>6</Nr>
</Data>
</Detail>
</File>
...the wanted result is produced:
<?xml version="1.0" encoding="utf-8"?>
<File>
<Detail type="A" group="1">
<Data>
<Nr>1</Nr>
</Data>
<Data>
<Nr>2</Nr>
</Data>
<Data>
<Nr>6</Nr>
</Data>
</Detail>
<Detail type="B" group="1">
<Data>
<Nr>3</Nr>
</Data>
<Data>
<Nr>4</Nr>
</Data>
</Detail>
<Detail type="B" group="2">
<Data>
<Nr>5</Nr>
</Data>
</Detail>
</File>
Explanation:
By properly applying the Muenchian Grouping Method (which is necessary in XSLT 1.0, given that it does not have any "inline" grouping mechanism), we can find unique nodes and group their descendants.
II. XSLT 2.0
When this XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output omit-xml-declaration="no" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/*">
<File>
<xsl:for-each-group select="Detail"
group-by="concat(#type, '+', #group)">
<Detail type="{#type}" group="{#group}">
<xsl:copy-of select="current-group()/Data" />
</Detail>
</xsl:for-each-group>
</File>
</xsl:template>
</xsl:stylesheet>
...is applied to the original XML, the same correct result is produced.
Explanation:
By properly applying XSLT 2.0's for-each-group element, we can arrive at the same result.
NOTE: This question languished in the bin for 6 hours before I took it up. My answer languished in the bin for two hours before someone else disguised some non-essential comments as an answer.
Study up on Muenchian grouping. It's handy for these grouping problems.
The heavy lifters are <xsl:key>, which creates a key based on a concat of #type and #group, and this line here, <xsl:for-each select="File/Detail[count(. | key('details', concat(#type,'_',#group))[1]) = 1]">, which isolates the first occurrence of the Detail node having a particular key and by extension a particular pairing of #group and #type.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:key name="details" match="Detail"
use="concat(#type,'_',#group)"/>
<xsl:template match='/'>
<File>
<xsl:for-each select="File/Detail[count(. | key('details', concat(#type,'_',#group))[1]) = 1]">
<xsl:sort select="concat(#type,'_',#group)" />
<Detail type="{#type}" group="{#group}">
<xsl:for-each select="key('details', concat(#type,'_',#group))">
<xsl:copy-of select="Data"/>
</xsl:for-each>
</Detail>
</xsl:for-each>
</File>
</xsl:template>
</xsl:stylesheet>

XSL for-each-group using two group-by parameters: first group by date then by name

Hello I'm trying to create grouping in the pdf file
and I have to use grouping inside another grouping. f.e. group by date, then group by name, then by city...
xml:
<record>
<name>Palace1</name>
<info>
<date>2012-01-01</date>
<city>Random1</city>
</info>
<info>
<date>2012-01-01</date>
<city>SuperRandom</city>
</info>
<info>
<date>2012-01-02</date>
<city>Random22</city>
</info>
...
</record>
<record>
<name>Palace2</name>
<info>
<date>2012-01-01</date>
<city>Random99</city>
</info>
<info>
<date>2012-01-02</date>
<city>Random1</city>
</info>
...
</record>
...
So lets say we need to group our records by date from 2012-01-01 to 2012-01-01 and the group them by name
Date 2012-01-01
Place1
Random1
SuperRandom
Palace2
Random99
Date 2012-01-02
Palace1
Random22
Palace2
Random1
SO I was using
<xsl:for-each-group select="dt:record" group-by="dt:info/dt:date">
<xsl:sort select="dt:date" order="ascending"/>
<fo:block font-weight="bold"> Date: <xsl:value-of select="format-dateTime(dt:date,'[Y0001].[M01].[D01]','en',(),'lt')"/></fo:block>
<xsl:for-each select="current-group()"> //here im guessing we should put another for- each-group
<xsl:for-each-group select="parent::dt:info/dt:record" group-by="dt:name">
<fo:block>Place1 <xsl:value-of select="dt:name"><fo:block>
<xsl:for-each select="current-group()">
<fo:block> <xsl:value-of select="dt:info/dt:city"></fo:block>
</xsl:for-each>
</xsl:for-each-group>
</xsl:for-each>
</xsl:for-each-group>
but this doesn't work... for some reason I get way more record names then I should
Here is how to do such groupings:
<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:template match="/*">
<xsl:for-each-group select="*/info" group-by="date">
<xsl:sort select="date"/>
Date: <xsl:value-of select="date"/>
<xsl:for-each-group select="current-group()" group-by="../name">
<xsl:value-of select="concat('
',../name)"/>
<xsl:for-each-group select="current-group()" group-by="city">
<xsl:value-of select="concat('
', city)"/>
</xsl:for-each-group>
</xsl:for-each-group>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the well-formed XML document derived from the non-wellformed provided XML:
<t>
<record>
<name>Palace1</name>
<info>
<date>2012-01-01</date>
<city>Random1</city>
</info>
<info>
<date>2012-01-01</date>
<city>SuperRandom</city>
</info>
<info>
<date>2012-01-02</date>
<city>Random22</city>
</info> ...
</record>
<record>
<name>Palace2</name>
<info>
<date>2012-01-01</date>
<city>Random99</city>
</info>
<info>
<date>2012-01-02</date>
<city>Random1</city>
</info> ...
</record>
</t>
the wanted, correctly-grouped result is produced:
Date: 2012-01-01
Palace1
Random1
SuperRandom
Palace2
Random99
Date: 2012-01-02
Palace1
Random22
Palace2
Random1
Here is an example, it outputs HTML, no XSL-FO but the grouping is of course the same:
<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="html" indent="yes"/>
<xsl:template match="root">
<xsl:for-each-group select="record/info" group-by="xs:date(date)">
<xsl:sort select="current-grouping-key()"/>
<div>
<h2><xsl:value-of select="current-grouping-key()"/></h2>
<xsl:for-each-group select="current-group()" group-by="parent::record/name">
<div>
<h3><xsl:value-of select="current-grouping-key()"/></h3>
<ul>
<xsl:for-each select="current-group()">
<li>
<xsl:value-of select="city"/>
</li>
</xsl:for-each>
</ul>
</div>
</xsl:for-each-group>
</div>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
When applied on
<root>
<record>
<name>Palace1</name>
<info>
<date>2012-01-01</date>
<city>Random1</city>
</info>
<info>
<date>2012-01-01</date>
<city>SuperRandom</city>
</info>
<info>
<date>2012-01-02</date>
<city>Random22</city>
</info>
...
</record>
<record>
<name>Palace2</name>
<info>
<date>2012-01-01</date>
<city>Random99</city>
</info>
<info>
<date>2012-01-02</date>
<city>Random1</city>
</info>
...
</record>
</root>
I get
<div>
<h2>2012-01-01</h2>
<div>
<h3>Palace1</h3>
<ul>
<li>Random1</li>
<li>SuperRandom</li>
</ul>
</div>
<div>
<h3>Palace2</h3>
<ul>
<li>Random99</li>
</ul>
</div>
</div>
<div>
<h2>2012-01-02</h2>
<div>
<h3>Palace1</h3>
<ul>
<li>Random22</li>
</ul>
</div>
<div>
<h3>Palace2</h3>
<ul>
<li>Random1</li>
</ul>
</div>
</div>

XSLT to remove elements from xml

I have a following xml.
<?xml version="1.0" encoding="windows-1252"?>
<Person>
<Header>
<Header>1</Header>
</Header>
<Details Id="2">
<First>GERRARD</First>
<Last>STEVE1 </Last>
</Details>
<Details Id="3">
<First>GERRARD</First>
<Last>STEVE2 </Last>
</Details>
<Details Id="3">
<First>GERRARD</First>
<Last>STEVE3 </Last>
</Details>
<Footer>
<Footer>liverpool</Footer>
</Footer>
</Person>
I need to delete the Details element and generate another xml which looks as follows
<?xml version="1.0" encoding="windows-1252"?>
<Person>
<Header>
<Header>1</Header>
</Header>
<Footer>
<Footer>liverpool</Footer>
</Footer>
</Person>
Thanks in advance.
<?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="Details"/>
</xsl:stylesheet>