Combine values of two XML files by IDs using XSLT - html

I have two XML files which I want to transform into HTML using a single XSL file. In the elements.xml I've got a part which combines values from these XML files by ids. Now in my HTML file, I want to present every <element> as a separate <div> in which I want to list names of effects that are linked in <linkedId>. I assume there would be some extensive use of variables but I can't get my head around it.
For example, output for the first element should look like this:
<div>
<div><p>NAME2</p></div>
<div><p>NAME1</p></div>
</div>
elements.xml
<elements>
<listOfElements>
<element>
<id>ID-element-1</id>
*some data*
</element>
<element>
<id>ID-element-2</id>
*some data*
</element>
(...)
</listOfElements>
<linkedIds>
<linkedId>
<idOfElement>ID-element-1</idOfElement>
<idOfEffect>ID-effect-2</idOfEffect>
<idOfEffect>ID-effect-1</idOfEffect>
<linkedId>
<linkedId>
<idOfElement>ID-element-2</idOfElement>
<idOfEffect>ID-effect-2</idOfEffect>
<idOfEffect>ID-effect-4</idOfEffect>
<idOfEffect>ID-effect-7</idOfEffect>
<linkedId>
(...)
</linkedIds>
</elements>
effects.xml
<effects>
<effect>
<idEffect>ID-effect-1</idEffect>
<name>NAME1</name>
</effect>
<effect>
<idEffect>ID-effect-2</idEffect>
<name>NAME2</name>
</effect>
<effect>
<idEffect>ID-effect-4</idEffect>
<name>NAME4</name>
</effect>
<effect>
<idEffect>ID-effect-7</idEffect>
<name>NAME7</name>
</effect>
</effect>
transform.xsl
<xsl:template match="elements">
<div>
<xsl:for-each select="elements/element">
<xsl:variable name="ElementID" select='linkedIds/linkedId/idOfElement'/>
<xsl:apply-templates select="document('effects.xml')/effects"/>
???
</xsl:for-each>
</div>
</xsl:template>
<xsl:template match="effects">
<xsl:for-each select="effects/effect">
<div>
<p><xsl:value-of select="name"/></p>
</div>
</xsl:for-each>
</xsl:template>

Define two keys
<xsl:key name="k1" match="linkedIds/linkedId" use="idOfElement"/>
<xsl:key name="k2" match="effect" use="idEffect"/>
then in the template matching element use them
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:param name="effects-url" select="'test2016051804.xml'"/>
<xsl:variable name="effects-doc" select="document($effects-url)"/>
<xsl:output method="html" indent="yes"/>
<xsl:key name="k1" match="linkedIds/linkedId" use="idOfElement"/>
<xsl:key name="k2" match="effect" use="idEffect"/>
<xsl:template match="/">
<html lang="en">
<body>
<xsl:apply-templates select="//element"/>
</body>
</html>
</xsl:template>
<xsl:template match="elements/listOfElements/element">
<div>
<xsl:variable name="linkedIds" select="key('k1', id)"/>
<xsl:for-each select="$effects-doc">
<xsl:apply-templates select="key('k2', $linkedIds/idOfEffect)/name"/>
</xsl:for-each>
<!-- with XSLT 2.0 you can simply use
<xsl:apply-templates select="key('k2', key('k1', id), $effects-doc)"/>
for the above 4 lines
-->
</div>
</xsl:template>
<xsl:template match="effect/name">
<div>
<p>
<xsl:value-of select="."/>
</p>
</div>
</xsl:template>
</xsl:stylesheet>
That way the inputs
<elements>
<listOfElements>
<element>
<id>ID-element-1</id> *some data* </element>
<element>
<id>ID-element-2</id> *some data* </element>
</listOfElements>
<linkedIds>
<linkedId>
<idOfElement>ID-element-1</idOfElement>
<idOfEffect>ID-effect-2</idOfEffect>
<idOfEffect>ID-effect-1</idOfEffect>
</linkedId>
<linkedId>
<idOfElement>ID-element-2</idOfElement>
<idOfEffect>ID-effect-2</idOfEffect>
<idOfEffect>ID-effect-4</idOfEffect>
<idOfEffect>ID-effect-7</idOfEffect>
</linkedId>
</linkedIds>
</elements>
and (you can set the parameter effects-url in the stylesheet as needed to your file name)
<effects>
<effect>
<idEffect>ID-effect-1</idEffect>
<name>NAME1</name>
</effect>
<effect>
<idEffect>ID-effect-2</idEffect>
<name>NAME2</name>
</effect>
<effect>
<idEffect>ID-effect-4</idEffect>
<name>NAME4</name>
</effect>
<effect>
<idEffect>ID-effect-7</idEffect>
<name>NAME7</name>
</effect>
</effects>
are transformed into
<html lang="en">
<body>
<div>
<div>
<p>NAME1</p>
</div>
<div>
<p>NAME2</p>
</div>
</div>
<div>
<div>
<p>NAME2</p>
</div>
<div>
<p>NAME4</p>
</div>
<div>
<p>NAME7</p>
</div>
</div>
</body>
</html>

Related

XLT cross referencing between xml tags to decide what a class property should be added

I have an xml file, that describes a conversation between Agent and Client. Both parties have a userId, that is assigned in the newParty tag.
Subsequent messages and notices, refer to this userId. I would like for when a message or a notice is processed, that a lookup is done by using the userId, to add the string "Agent" or "Client" to the class property of the generated HTML.
<?xml version="1.0"?>
<chatTranscript startAt="2016-10-06T09:16:40Z" sessionId="0001GaBYC53D000K">
<newParty userId="007957F616780001" timeShift="1" visibility="ALL" eventId="1">
<userInfo personId="" userNick="John Doe" userType="CLIENT" protocolType="FLEX" timeZoneOffset="120"/>
<userData>
<item key="GMSServiceId">5954d184-f89d-4f44-8c0f-a772d458b353</item>
<item key="IdentifyCreateContact">3</item>
<item key="MediaType">chat</item><item key="TimeZone">120</item>
<item key="_data_id">139-e9826bf5-c5a4-40e5-a729-2cbdb4776a43</item>
<item key="firstName">John</item><item key="first_name">John</item>
<item key="lastName">Doe</item>
<item key="last_name">Doe</item>
<item key="location_lat">37.8197</item>
<item key="location_long">-122.4786</item>
<item key="userDisplayName">John Doe</item>
</userData>
</newParty>
<newParty userId="0079581AF56C0025" timeShift="20" visibility="ALL" eventId="2">
<userInfo personId="1" userNick="allendei" userType="AGENT" protocolType="BASIC" timeZoneOffset="120"/>
</newParty>
<message userId="007957F616780001" timeShift="25" visibility="ALL" eventId="3">
<msgText msgType="text" treatAs="NORMAL">This is message one.</msgText>
</message>
<message userId="0079581AF56C0025" timeShift="35" visibility="ALL" eventId="4">
<msgText msgType="text" treatAs="NORMAL">This is message two.</msgText>
</message>
<notice userId="0079581AF56C0025" timeShift="40" visibility="ALL" eventId="5">
<noticeText noticeType="USER_CUSTOM">This is notice one.</noticeText>
</notice>
<notice userId="007957F616780001" timeShift="58" visibility="ALL" eventId="6">
<noticeText noticeType="USER_CUSTOM">This is notice two.</noticeText>
</notice>
<partyLeft userId="007957F616780001" timeShift="90" visibility="ALL" eventId="4" askerId="007957F616780001">
<reason code="3">left due to disconnect</reason>
</partyLeft>
... and my xlt is:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:template match="/chatTranscript">
<html>
<header><xsl:value-of select="#sessionId" /></header>
<xsl:apply-templates select="newParty" />
<xsl:apply-templates select="message/msgText" />
<xsl:apply-templates select="notice/noticeText" />
<xsl:apply-templates select="partyLeft/reason" />
</html>
</xsl:template>
<xsl:template match="newParty[userInfo/#userType='CLIENT']">
<div class="Client" id="{#userId}">
<label>Client: <xsl:value-of select="userInfo/#userNick" /></label>
</div>
</xsl:template>
<xsl:template match="newParty[userInfo/#userType='AGENT']">
<div class="Client" id="{#userId}">
<label>Agent: <xsl:value-of select="userInfo/#userNick" /></label>
</div>
</xsl:template>
<xsl:template match="msgText">
<div class="Messages" id="{../#eventId}">
<label>Message: <xsl:value-of select="text()" /></label>
</div>
</xsl:template>
<xsl:template match="noticeText">
<div class="Notices" id="{../#eventId}">
<label>Notice: <xsl:value-of select="text()" /></label>
</div>
</xsl:template>
<xsl:template match="reason">
<div class="Notices" id="{../#eventId}">
<label>Reason: <xsl:value-of select="text()" /></label>
</div>
</xsl:template>
the desired output is as follows - where the class property(Agent or Client) of Messages and Notices are lookup up via the userID in the newParties at the top.
<html>
<header>0001GaBYC53D000K</header>
<div class="Client" id="007957F616780001"><label>Client: John Doe</label></div>
<div class="Client" id="0079581AF56C0025"><label>Agent: allendei</label></div>
<div class="Messages,Client" id="3"><label>Message: This is message one.</label></div>
<div class="Messages,Agent" id="4"><label>Message: This is message two.</label></div>
<div class="Notices,Agent" id="5"><label>Notice: This is notice one.</label></div>
<div class="Notices,Client" id="6"><label>Notice: This is notice two.</label></div>
<div class="Notices,Client" id="4"><label>Reason: left due to disconnect</label></div>
XSLT has a built-in mechanism for performing lookups. Start by defining a key as:
<xsl:key name="party" match="newParty" use="#userId" />
then use it as shown in the following example:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:key name="party" match="newParty" use="#userId" />
<xsl:template match="/chatTranscript">
<html>
<header>
<xsl:value-of select="#sessionId" />
</header>
<xsl:apply-templates/>
</html>
</xsl:template>
<xsl:template match="newParty[userInfo/#userType='CLIENT']">
<div class="Client" id="{#userId}">
<label>Client: <xsl:value-of select="userInfo/#userNick" /></label>
</div>
</xsl:template>
<xsl:template match="newParty[userInfo/#userType='AGENT']">
<div class="Client" id="{#userId}">
<label>Agent: <xsl:value-of select="userInfo/#userNick" /></label>
</div>
</xsl:template>
<xsl:template match="message">
<xsl:variable name="party-class">
<xsl:call-template name="lookup-class"/>
</xsl:variable>
<div class="Messages,{$party-class}" id="{#eventId}">
<label>Message: <xsl:value-of select="msgText" /></label>
</div>
</xsl:template>
<xsl:template match="notice">
<xsl:variable name="party-class">
<xsl:call-template name="lookup-class"/>
</xsl:variable>
<div class="Notices,{$party-class}" id="{#eventId}">
<label>Notice: <xsl:value-of select="noticeText" /></label>
</div>
</xsl:template>
<xsl:template match="partyLeft">
<xsl:variable name="party-class">
<xsl:call-template name="lookup-class"/>
</xsl:variable>
<div class="Notices,{$party-class}" id="{#eventId}">
<label>Reason: <xsl:value-of select="reason" /></label>
</div>
</xsl:template>
<xsl:template name="lookup-class">
<xsl:variable name="party-type" select="key('party', #userId)/userInfo/#userType" />
<xsl:choose>
<xsl:when test="$party-type='CLIENT'">Client</xsl:when>
<xsl:when test="$party-type='AGENT'">Agent</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Note that the output is not valid HTML.

Merging two XML files while transforming them using XSL

I have two XML files which I want to transform into one HTML file using XSL.
I transform them using xsltproc first.xml transform.xsl > output.html command in Linux terminal. Values from first.xml work perfectly and transform into HTML but I cannot force second.xml to work as well. It just didn't appear in file. I know there were questions like this on StackOverflow but I still couldn't figure out what I am doing wrong. It seems like something is wrong with match = "document('effects.xml')/effects" but I don't know what exactly.
first.xml
<elements>
<listOfElements>
<element>
*some data*
</element>
<element>
*some data*
</element>
</listOfElements>
</elements>
second.xml
<effects>
<effect>
<name> NAME1 </name>
<cost> COST1 </cost>
</effect>
<effect>
<name> NAME2 </name>
<cost> COST2 </cost>
</effect>
<effect>
<name> NAME3 </name>
<cost> COST3 </cost>
</effect>
</effect>
transform.xsl
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="/">
<xsl:text disable-output-escaping='yes'><!DOCTYPE html></xsl:text>
<html>
<head>
<meta charset="UTF-8"/>
<link rel="stylesheet" type="text/css" href="styl.css"/>
</head>
<body>
<xsl:apply-templates select="elements"/>
<xsl:apply-templates select="effects"/>
</body>
</html>
</xsl:template>
<xsl:template match="elements">
<div>
THIS WORKS
</div>
</xsl:template>
<xsl:template match="document('effects.xml')/effects">
<div>
<xsl:for-each select="effects/effect">
<div>
<p><xsl:value-of select="name"/></p>
</div>
</xsl:for-each>
</div>
</xsl:template>
Use <xsl:apply-templates select="document('effects.xml')/effects"/> and then in the match="effects" and <xsl:for-each select="effect">.

for-each-group text paragraph - xslt 2.0

I am looking for a solution to group text based on the title h1. I tried this with for-each-group, starts-with ="h1". The problem is that the h1 is not on the same level as the rest of the elements (div/h1).
Input html:
<!DOCTYPE html SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>test</title>
</head>
<body>
<div>
<h1><b>TRAIN</b></h1>
</div>
<p>text</p>
<p>In this field there is text</p>
<div>
<h1><b>nr1</b><b>CAR</b></h1>
</div>
<h2><b>1.</b><b>nr2</b><b>area</b></h2>
<p>infos about cars</p>
<p><b>more and</b>more infos about cars</p>
</body>
</html>
What I have so far is:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xpath-default-namespace="http://www.w3.org/1999/xhtml">
<xsl:output omit-xml-declaration="yes" method="xhtml" version="1.0" encoding="UTF-8"
indent="yes"/>
<xsl:template match="head"/>
<xsl:template match="body">
<xsl:for-each-group select = "*" group-starting-with = "h1">
<output>
<xsl:apply-templates select="current-group()"/>
</output>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{name()}">
<xsl:apply-templates select="node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
But the output is not working the way I want. I would like to have two output-blocks as this example output:
<html>
<output>
<div><h1><b>TRAIN</b></h1></div>
<p>text</p>
<p>In this field there is text</p>
</output>
<output>
<div><h1><b>nr1</b><b>CAR</b></h1></div>
<h2>
<b>1.</b>
<b>nr2</b>
<b>area</b>
</h2>
<p>infos about cars</p>
<p><b>more and</b>more infos about cars</p>
</output>
Thanks for any help!
You could use the descendant-or-self axis, to group starting on elements which have h1 as a descendant (or are h1 elements themselves)
<xsl:for-each-group select="*" group-starting-with="*[descendant-or-self::h1]">
Also note that in your XSLT you have used xpath-default-namespace, but your input XML does not use that namespace, so as it stands your body template in your XSLT won't match the input. Either you need to add the default namespace to your input, or remove the xpath-default-namespace from your XSLT.
How about:
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:template match="/html">
<xsl:copy>
<xsl:for-each-group select="body/*" group-starting-with="div[h1]">
<output>
<xsl:copy-of select="current-group()"/>
</output>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Dynamically change value of in an XSL file

Currently the displaying the XML in a browser along with the XSL data is correct except for one thing. In some colleges I have one department while in orders I have more than one (up to 9). How I can dynamically output the data based on the number of department for each college? Currently it only outputs one department per college.
College.xml File
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?xml-stylesheet type="text/xsl" href="colleges.xsl"?><colleges>
<college id="0">
<school>College of Education</school>
<mission>text</mission>
<department id="0">Educational Psychology and Leadership</department>
<department id="1">Health and Human Performance</department>
<department id="2">Language, Literacy and Intercultural Studies</department>
<department id="3">Teaching, Learning and Innovation</department>
</college>
<college id="1">
<school>College of Nursing</school>
<mission>text</mission>
<department id="0">Nursing</department>
</college>
<college id="2">
<school>School of Business</school>
<mission>text</mission>
<department id="0">Accounting and Management Information Systems</department>
<department id="1">Applied Business Technology</department>
</college></colleges>
College.xsl file:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body style="font-family:Arial;font-size:12pt;background-color:#EEEEEE">
<xsl:for-each select="colleges/college">
<div style="background-color:teal;color:white;padding:4px">
<span style="font-weight:bold"><xsl:value-of select="school"/></span> - <br /><xsl:value-of select="mission"/>
</div>
<div style="margin-left:20px;margin-bottom:1em;font-size:10pt">
<p>
<xsl:value-of select="department"/><br />
</p>
</div>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Try:
<xsl:template match="/"> ...
<xsl:for-each select="colleges/college">
...
<xsl:apply-templates select="department"/>
...
</xsl:for-each>
</xsl:template>
<xsl:template match="department">... what you want for each department</xsl:template>
Give this a shot...
<xsl:template match="/">
<html>
<body style="font-family:Arial;font-size:12pt;background-color:#EEEEEE">
<xsl:for-each select="colleges/college">
<div style="background-color:teal;color:white;padding:4px">
<span style="font-weight:bold"><xsl:value-of select="school"/></span> - <br /><xsl:value-of select="mission"/>
</div>
<div style="margin-left:20px;margin-bottom:1em;font-size:10pt">
<p>
<xsl:apply-templates select="department"/>
</p>
</div>
</xsl:for-each>
</body>
</html>
</xsl:template>
<xsl:template match="department">
<xsl:value-of select="."/><br />
</xsl:template>

Vertical output XSL and XML

I am working on a simple dictionary in XML, and now I'm trying to output some words vertical, but they all come out on a line without spaces.
This is some of the XML file
<thesaurus>
<dictionary>
<language>English</language>
<word type="1">word 1</word>
<word type="2">word 2</word>
<word type="3">word 3</word>
<word type="4">word 4</word>
<word type="5">word 5</word>
<word type="6">word 6</word>
</dictionary>
</thesaurus>
This is my first "almost" solution
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates select="//word">
<xsl:sort order="ascending"/>
</xsl:apply-templates>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
That solution only prints out all the word like this
AgentsColorFoundationsGrainPartialPogotypePretendSilentStrollTender
My second try is something like this
<xsl:for-each select="thesaurus">
<h1> <xsl:value-of select="//word"/></h1>
</xsl:for-each>
In that way I could style the words and they will print vertical, but the thing is that only the first of the words is printing. =/
Would be great with a hint :)
Thanks
Use this template:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates select="*/*/word">
<xsl:sort order="ascending"/>
</xsl:apply-templates>
</body>
</html>
</xsl:template>
<xsl:template match="word">
<xsl:value-of select="."/>
<br/>
</xsl:template>
</xsl:stylesheet>
Output:
<html>
<body>word 1<br />word 2<br />word 3<br />word 4<br />word 5<br />word 6<br /></body>
</html>