XSLT Filtering based on nodes and attributes - xslt

I'm new on XSLT and have a requirement to use XSLT to select values from an XML file of this form :
<?xml version="1.0" encoding="utf-8"?>
<deviceInstallation>
<order>
<orderID>296</orderID>
<orderPosID>1</orderPosID>
<action rvcd="2">unInstall</action>
</order>
<deviceInfo>
<actionInfo rvcd="1">Software Install</actionInfo>
<device>
<deviceID>1436</deviceID>
</device>
</deviceInfo>
<deviceInfo>
<actionInfo rvcd="2">Software Uninstall</actionInfo>
<device>
<deviceID>4112</deviceID>
</device>
</deviceInfo>
</deviceInstallation>
I need to filter the elements deviceinfo based on the attribute rvcd = 2 because this is what is defined on the same attribute of child element action of the order element.
I tried to write and xslt and used a var to get the value to filter but don't know how to use it :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text" />
<xsl:variable name="separator" select="';'" />
<xsl:variable name="newline" select="'
'" />
<xsl:variable name="actionFilter" select="/deviceInstallation/order/action[]/#rvcd" />
<xsl:template match="/">
<xsl:text>orderID;DeviceID</xsl:text>
<xsl:value-of select="$newline" />
<xsl:for-each select="/deviceInstallation">
<!--OrderID-->
<xsl:value-of select="/deviceInstallation/order/orderID"/>
<xsl:value-of select="$separator"/>
<!--DeviceID-->
<xsl:value-of select="/deviceInstallation/deviceInfo/device/deviceID"/> <!-- here want to filter on rvcd-->
<xsl:value-of select="$separator"/>
<xsl:value-of select="$newline" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Any help appreciated

IIUC, you want to do:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/deviceInstallation">
<xsl:variable name="orderID" select="order/orderID" />
<xsl:variable name="actionFilter" select="order/action/#rvcd" />
<xsl:text>orderID;DeviceID
</xsl:text>
<xsl:for-each select="deviceInfo[actionInfo/#rvcd=$actionFilter]">
<xsl:value-of select="$orderID" />
<xsl:text>;</xsl:text>
<xsl:value-of select="device/deviceID" />
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
or perhaps a bit more elegantly:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:key name="dev" match="deviceInfo" use="actionInfo/#rvcd" />
<xsl:template match="/deviceInstallation">
<xsl:variable name="orderID" select="order/orderID" />
<xsl:text>orderID;DeviceID
</xsl:text>
<xsl:for-each select="key('dev', order/action/#rvcd)">
<xsl:value-of select="$orderID" />
<xsl:text>;</xsl:text>
<xsl:value-of select="device/deviceID" />
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Related

Multiple Counters in XSLT

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>

XSLT: parametrize template

This is my xslt fragment:
<informacioPacient>
<talla>
<xsl:value-of select="//ns3:observation[ns3:code/#code='50373000' and ns3:code/#codeSystem='2.16.840.1.113883.6.96']//ns3:value/#value" />
</talla>
<pes>
<xsl:value-of select="//ns3:observation[ns3:code/#code='27113001' and ns3:code/#codeSystem='2.16.840.1.113883.6.96']//ns3:value/#value" />
</pes>
<edad>
<xsl:value-of select="//ns3:observation[ns3:code/#code='424144002' and ns3:code/#codeSystem='2.16.840.1.113883.6.96']//ns3:value/#value" />
</edad>
<etnia>
<xsl:value-of select="//ns3:observation[ns3:code/#code='372148003' and ns3:code/#codeSystem='2.16.840.1.113883.2.19.1.26']//ns3:value/#value" />
</etnia>
<fumador>
<xsl:value-of select="//ns3:observation[ns3:code/#code='230056004' and ns3:code/#codeSystem='2.16.840.1.113883.6.96']//ns3:value/#value" />
</fumador>
</informacioPacient>
I'd like to know if I could to improve readness. As you can see, select statement is always similar. The only elements change are literal values.
Could I do something more generic in order to get the same behavior?
xslt 1.0 with a key:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:ns3="urn:local"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="code-and-system" match="ns3:observation" use="concat(ns3:code/#code,'#', ns3:code/#codeSystem)"/>
<xsl:template match="/">
<informacioPacient>
<talla>
<xsl:value-of select="key('code-and-system', '50373000#2.16.840.1.113883.6.96')//ns3:value/#value"/>
</talla>
<pes>
<xsl:value-of select="key('code-and-system','27113001#2.16.840.1.113883.6.96')//ns3:value/#value" />
</pes>
<edad>
<xsl:value-of select="key('code-and-system','424144002#2.16.840.1.113883.6.96')//ns3:value/#value" />
</edad>
<etnia>
<xsl:value-of select="key('code-and-system','372148003#2.16.840.1.113883.2.19.1.26')//ns3:value/#value" />
</etnia>
<fumador>
<xsl:value-of select="key('code-and-system','230056004#2.16.840.1.113883.6.96')//ns3:value/#value" />
</fumador>
</informacioPacient>
</xsl:template>
</xsl:stylesheet>
xslt 2.0: As Martin Honnen commented, use a function
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:ns3="urn:local"
xmlns:f="local"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
>
<xsl:variable name="observations" as="element()*" select="//ns3:observation"/>
<xsl:template match="/">
<informacioPacient>
<talla>
<xsl:value-of select="f:getValue('50373000','2.16.840.1.113883.6.96')" />
</talla>
<pes>
<xsl:value-of select="f:getValue('27113001','2.16.840.1.113883.6.96')" />
</pes>
<edad>
<xsl:value-of select="f:getValue('424144002','2.16.840.1.113883.6.96')" />
</edad>
<etnia>
<xsl:value-of select="f:getValue('372148003','2.16.840.1.113883.2.19.1.26')" />
</etnia>
<fumador>
<xsl:value-of select="f:getValue('230056004','2.16.840.1.113883.6.96')" />
</fumador>
</informacioPacient>
</xsl:template>
<xsl:function name="f:getValue">
<xsl:param name="code" as="xs:string"/>
<xsl:param name="codeSystem" as="xs:string"/>
<xsl:value-of select="$observations[ns3:code[#code=$code and #codeSystem=$codeSystem]]//ns3:value/#value"/>
</xsl:function>
</xsl:stylesheet>

Select a parallel node in xslt

Consider following xml:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<base>
<a>
<b>
<c>Text 1</c>
</b>
</a>
</base>
<base>
<a>
<b>
<c>Text 2</c>
</b>
</a>
</base>
</root>
and this xsl
<?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="1.0">
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates select="root/base[1]" />
</xsl:template>
<xsl:template match="base//*">
<xsl:if test="text()">
<xsl:value-of select="text()" />
<!-- i need Text 2 here -->
</xsl:if>
<xsl:apply-templates />
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
The original xml is much more nested an i don't know the exact structure. But there is a parallel node with the same structure. If my template is at //root/base[1]/a/b/c I want to reference //root/base[2]/a/b/c
However I only know that I am in some node below //root/base[1] and that there is the same node in //root/base[2].
Is there a possibility to accomplish this goal?
This stylesheet requires no extensions, and does not make any assumption on unique names; the boring part is to pass the correct "twin node" every time you call xsl:apply-templates:
<?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="1.0">
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates select="root/base[1]">
<xsl:with-param name="twin" select="root/base[2]"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="*">
<xsl:param name="twin"/>
<xsl:if test="text()">
<xsl:value-of select="text()" />
<xsl:value-of select="$twin/text()"/>
</xsl:if>
<xsl:for-each select="*">
<xsl:variable name="pos" select="position()"/>
<xsl:apply-templates select=".">
<xsl:with-param name="twin" select="$twin/*[$pos]"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
Solution with using saxon:evaluate extension (http://saxon.sourceforge.net/saxon7.9/extensions.html#evaluate)
You can also use dyn:evaluate if you are using xslt 1.0 processors (http://exslt.org/dyn/index.html):
<?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"
xmlns:saxon="http://saxon.sf.net/"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates select="root/base[1]" />
</xsl:template>
<xsl:template match="base//*">
<xsl:if test="text()">
<xsl:variable name="path">
<xsl:call-template name="constructPath">
<xsl:with-param name="node" select="."/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="rewritedPath" select="concat('/root/base[2]', substring($path, 11))"/>
<xsl:value-of select="text()" />
<xsl:value-of select="saxon:evaluate($rewritedPath)" />
</xsl:if>
<xsl:apply-templates />
</xsl:template>
<xsl:template name="constructPath">
<xsl:param name="node"/>
<xsl:choose>
<xsl:when test="$node/parent::node()/name()">
<xsl:call-template name="constructPath">
<xsl:with-param name="node" select="$node/parent::node()"/>
</xsl:call-template>
<xsl:value-of select="concat('/', $node/name())"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat('/', $node/name())"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
Here we use recursive template "constructPath" to create string and process it in evaluate() function. This will select the exact node as in the parallel branch. Hope this will help.
Try the code below. This code will work only if each base element has children with unique element name.
<?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="1.0">
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates select="root/base[1]" />
</xsl:template>
<xsl:template match="base//*">
<xsl:if test="text()">
<xsl:variable name="name" select="name()"/>
<xsl:value-of select="text()" />
<xsl:value-of select="ancestor::base/following-sibling::base[1]//*[name() = $name]" />
</xsl:if>
<xsl:apply-templates />
</xsl:template>
<xsl:template match="text()" />
However you can use dyn:evaluate from exslt extension or saxon:eval

How to pattern match in XSLT and add values

As part of an XSLT, I need to add all the values of the "Duration" element and display the value. Now, the below XML is a part of the larger XML I'm working on. In the below XML, I need to match
a/TimesheetDuration/Day*/Duration, add the values and display them. I dont want to store all the values in variables and add them. Is there any other clean way of doing this?
<?xml version="1.0" ?>
<a>
<TimesheetDuration>
<Day1>
<BusinessDate>6/12/2013</BusinessDate>
<Duration>03:00</Duration>
</Day1>
<Day2>
<BusinessDate>6/13/2013</BusinessDate>
<Duration>04:00</Duration>
</Day2>
<Day3>
<BusinessDate>6/14/2013</BusinessDate>
<Duration>05:00</Duration>
</Day3>
</TimesheetDuration>
</a>
An XPath 2.0 solution, assuming the durations are in the form HH:MM, would be
sum(for $d in a//Duration
return xs:dayTimeDuration(replace($d, '(..):(..)', 'PT$1H$2M')))
In xslt 1.0 you could do it for example with following 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:template match="/">
<Durations>
<xsl:apply-templates select="a/TimesheetDuration/node()[starts-with(name(),'Day')]" />
<xsl:variable name="hours">
<xsl:call-template name="sumHours">
<xsl:with-param name="Day" select="a/TimesheetDuration/node()[starts-with(name(),'Day')][1]" />
</xsl:call-template>
</xsl:variable>
<SumOfHours>
<xsl:value-of select="$hours" />
</SumOfHours>
<!-- Sum of minutes would be calculated similarly -->
</Durations>
</xsl:template>
<xsl:template match="node()[starts-with(name(),'Day')]">
<xsl:copy-of select="Duration" />
</xsl:template>
<xsl:template name="sumHours">
<xsl:param name="tmpSum" select="0" />
<xsl:param name="Day" />
<xsl:variable name="newTmpSum" select="$tmpSum + substring-before($Day/Duration, ':')" />
<xsl:choose>
<xsl:when test="$Day/following-sibling::node()[starts-with(name(),'Day')]">
<xsl:call-template name="sumHours">
<xsl:with-param name="tmpSum" select="$newTmpSum" />
<xsl:with-param name="Day" select="$Day/following-sibling::node()[starts-with(name(),'Day')]" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$newTmpSum" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
It produces output
<?xml version="1.0" encoding="UTF-8"?>
<Durations>
<Duration>03:00</Duration>
<Duration>04:00</Duration>
<Duration>01:00</Duration>
<SumOfHours>8</SumOfHours>
</Durations>

How to include the node XML in my XSLT text output?

I'm trying to convert an XML file into a flat, pipe-delimited file with XSLT (for bulk-loading into Postgres). I would like the last column in my output to be the actual XML of the node (for additional post-processing and debugging). For example:
<Library>
<Book id="123">
<Title>Python Does Everythig</Title>
<Author>Smith</Author>
</Book>
<Book id="456">
<Title>Postgres is Neat</Title>
<Author>Wesson</Author>
</Book>
</Library>
Should generate
Python Does Everything|Smith|<Book id="123"><Title>Python Does Everythig</Title>Author>Smith</Author></Book>
Postgres is Neat|Wesson|<Book id="456"><Title>Postgres is Neat</Title><Author>Wesson</Author></Book>
My current XSL is
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*" />
<xsl:output method="text" omit-xml-declaration="yes" indent="no" />
<xsl:template match="//Book">
<xsl:value-of select="Title" />
<xsl:text>|</xsl:text>
<xsl:value-of select="Author" />
<!-- put in the newline -->
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
I am not sure if this is a recommended solution, but you could try setting the output method to xml, and then just using the xsl:copy-of function.
So, the following XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*" />
<xsl:output method="xml" omit-xml-declaration="yes" indent="no" />
<xsl:template match="//Book">
<xsl:value-of select="Title" />
<xsl:text>|</xsl:text>
<xsl:value-of select="Author" />
<xsl:text>|</xsl:text>
<xsl:copy-of select="." />
<!-- put in the newline -->
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, generates the following output
Python Does Everythig|Smith|<Book id="123"><Title>Python Does Everythig</Title><Author>Smith</Author></Book>
Postgres is Neat|Wesson|<Book id="456"><Title>Postgres is Neat</Title><Author>Wesson</Author></Book>
Try that :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates select="//Book"/>
</xsl:template>
<xsl:template match="Book">
<xsl:value-of select="Title" />
<xsl:text>|</xsl:text>
<xsl:value-of select="Author" />
<xsl:text>|</xsl:text>
<xsl:apply-templates select="." mode="outputTags"/>
</xsl:template>
<xsl:template match="*" mode="outputTags">
<xsl:text><</xsl:text>
<xsl:value-of select="local-name()"/>
<xsl:apply-templates select="#*"/>
<xsl:text>></xsl:text>
<xsl:apply-templates mode="outputTags"/>
<xsl:text></</xsl:text>
<xsl:value-of select="local-name()"/>
<xsl:text>></xsl:text>
<xsl:if test="self::Book">
<xsl:text>
</xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="#*">
<xsl:text> </xsl:text>
<xsl:value-of select="local-name()"/>
<xsl:text>="</xsl:text>
<xsl:value-of select="."/>
<xsl:text>"</xsl:text>
</xsl:template>
</xsl:stylesheet>
It produces the following result from your input file :
Python Does Everythig|Smith|<Book id="123"><Title>Python Does Everythig</Title><Author>Smith</Author></Book>
Postgres is Neat|Wesson|<Book id="456"><Title>Postgres is Neat</Title><Author>Wesson</Author></Book>