Merging two XML files while transforming them using XSL - html

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">.

Related

How to disable text escape on xsl:copy-of

I build a mechanism that takes all the <script /> tags and put them at the end of the page.
It works good except that Ampersant & characters are encode to & even those in JavaScript code which is not what I want.
How can I solve this?
XML
<?xml version="1.0" encoding="UTF-8"?>
<root></root>
XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns="http://www.w3.org/1999/xhtml" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" />
<xsl:template match="*">
<xsl:variable name="body">
<xsl:apply-templates select="." mode="body"></xsl:apply-templates>
</xsl:variable>
<xsl:apply-templates select="$body" mode="no-script" />
<xsl:copy-of select="$body//script" xpath-default-namespace="http://www.w3.org/1999/xhtml" />
</xsl:template>
<xsl:template match="script" mode="no-script">
</xsl:template>
<xsl:template match="*[not(self::script)] | #* |comment()" mode="no-script">
<xsl:if test="name() != 'script'">
<xsl:copy xpath-default-namespace="http://www.w3.org/1999/xhtml">
<xsl:apply-templates select="node()[not(self::script)] | #*" mode="no-script" />
</xsl:copy>
</xsl:if>
</xsl:template>
<xsl:template match="*" mode="body">
<script type="text/javascript">
// Ampersand <xsl:text disable-output-escaping="yes"><![CDATA[&]]></xsl:text>
var a = 'a';
</script>
<div>Hello World</div>
</xsl:template>
</xsl:stylesheet>
It outputs:
<div xmlns="http://www.w3.org/1999/xhtml">Hello World</div>
<script xmlns="http://www.w3.org/1999/xhtml" type="text/javascript">
// Ampersand &
var a = 'a';
</script>
I tried and it works but I wunder if there is a way to keep the <script> tags inside the variable $body.
<script type="text/javascript">
<xsl:value-of select="$body//script" xpath-default-namespace="http://www.w3.org/1999/xhtml" disable-output-escaping='yes'/>
</script>
The HTML output method in XSLT 2.0+ should not perform escaping for the text within a script element.
However, there's a difference between 2.0 and 3.0. In 2.0, this only applies to a script element in no namespace. In 3.0, provided you're outputting HTML5, it also applies to a script element in the XHTML namespace.

Capitalize all text inside of <title> tags for a group of html files in side a single folder

I'm currently using this code: (Linux Bash)
for x in *.html; do sed -i "a/(sudo grep -o '<title>.*</title>' $x)(sudo grep -o '<title>.*</title>' | sudo sed "s/\b[a-z]/\u&/g")//g" $x; echo moving $x; done
and it wont seem to be working, can anyone help me out, do I have to keep using (sed) or try using other plugins?
INTRO:
I have a like thousand html FILES all of them have tags inside but the text between the tags are all lower case,
I want to capitalize first letters for each word between tags, and do it all at once using loop in bash command.
EXAMPLE:
<title>sample title</title>
TO >>>
<title>Sample Title</title>
You can use the following XSLT stylesheet to solve your issue:
INPUT FILES:
$ more ?.html
::::::::::::::
1.html
::::::::::::::
<!DOCTYPE html>
<html>
<head>
<title>title reference 1.html</title>
</head>
<body>
The content of the document......
</body>
</html>
::::::::::::::
2.html
::::::::::::::
<!DOCTYPE html>
<html>
<head>
<title>title reference 2.html</title>
</head>
<body>
The content of the document......
</body>
</html>
STYLESHEET:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:redirect="http://xml.apache.org/xalan/redirect" extension-element-prefixes="redirect" xmlns:xalan="http://xml.apache.org/xslt" exclude-result-prefixes="xalan redirect ">
<xsl:output method="html" indent="yes" xalan:indent-amount="4" include-content-type="false" 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 name="TitleCase">
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($text,' ')">
<xsl:call-template name="TitleCaseWord">
<xsl:with-param name="text" select="substring-before($text,' ')"/>
</xsl:call-template>
<xsl:text> </xsl:text>
<xsl:call-template name="TitleCase">
<xsl:with-param name="text" select="substring-after($text,' ')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="TitleCaseWord">
<xsl:with-param name="text" select="$text"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="TitleCaseWord">
<xsl:param name="text"/>
<xsl:value-of select="translate(substring($text,1,1),'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')" /><xsl:value-of select="substring($text,2,string-length($text)-1)" />
</xsl:template>
<xsl:template match="//*[local-name()='title']">
<title>
<xsl:call-template name="TitleCase">
<xsl:with-param name="text" select="."/>
</xsl:call-template>
</title>
</xsl:template>
</xsl:stylesheet>
CMD:
$ for i in *.html; do java -classpath "./tagsoup-1.2.jar:./saxon9he.jar" net.sf.saxon.Transform --suppressXsltNamespaceCheck:on -x:org.ccil.cowan.tagsoup.Parser -s:1.html -xsl:title_upper.xsl -o:"new_${i}"; done
You can download the 2 jars at
https://mvnrepository.com/artifact/org.ccil.cowan.tagsoup/tagsoup/1.2
and http://saxon.sourceforge.net/
OUTPUT:
$ more new_*
::::::::::::::
new_1.html
::::::::::::::
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:html="http://www.w3.org/1999/xhtml">
<head>
<title xmlns="">Title Reference 1.html</title>
</head>
<body>
The content of the document......
</body>
</html>
::::::::::::::
new_2.html
::::::::::::::
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:html="http://www.w3.org/1999/xhtml">
<head>
<title xmlns="">Title Reference 1.html</title>
</head>
<body>
The content of the document......
</body>
</html>
If you have issue with saxon you can just use xsltproc as I use a version 1.0 stylesheet:
for i in *.html; do xsltproc --html title_upper.xsl ${i} > "new_${i}"; done

Combine values of two XML files by IDs using XSLT

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>

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>

How to convert all "LF" chars to "<br />" tag and show it on the HTML page

How to convert all LF chars to <br /> tags and show it on the HTML page?
I have the following example XML file:
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="data.xslt"?>
<data>
<lines>
Line 1
Line 2
Line 3
Line 4
Line 5
Line 6
</lines>
</data>
and I want to show all lines on the HTML page. For this I use the following XSLT transformation:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
<xsl:variable name="filtered">
<xsl:call-template name="replace">
<xsl:with-param name="string" select="./data/lines"/>
<xsl:with-param name="search" select="'
'"/>
<xsl:with-param name="new"><br /></xsl:with-param>
</xsl:call-template>
</xsl:variable>
<td align="left">
<xsl:value-of select="$filtered" disable-output-escaping="yes"/>
</td>
</body>
</html>
</xsl:template>
<xsl:template name="replace">
<xsl:param name="string"/>
<xsl:param name="search"/>
<xsl:param name="new"/>
<xsl:choose>
<xsl:when test="contains($string, $search)">
<xsl:value-of select="substring-before($string, $search)"/>
<xsl:value-of select="$new"/>
<xsl:call-template name="replace">
<xsl:with-param name="string" select="substring-after($string, $search)"/>
<xsl:with-param name="search" select="$search"/>
<xsl:with-param name="new" select="$new"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
When I open that XML file in Firefox (I use browser to show XSLT transformation) I will see that result:
Line 1Line 2Line 3Line 4Line 5Line 6
As you see, LF chars were not replaced by <br /> tags.
But when I use other string, for example EOL:
<xsl:with-param name="new">EOL</xsl:with-param>
I will see expected result:
EOLLine 1EOLLine 2EOLLine 3EOLLine 4EOLLine 5EOLLine 6EOL
The problem is with the convert/display <br /> tag.
You can pass a node fragment as a parameter value, as you do with <xsl:with-param name="new"><br /></xsl:with-param>, but to output that as a br element in your template you need to use <xsl:copy-of select="$new"/>, not xsl:value-of.
[edit] Here is an example: http://home.arcor.de/martin.honnen/xslt/test2012062801.xml. The stylesheet is at http://home.arcor.de/martin.honnen/xslt/test2012062801.xsl, I will also include it below:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" version="4.01" encoding="utf-8" indent="yes"/>
<xsl:template match="/">
<html>
<head>
<title>Example</title>
</head>
<body>
<xsl:variable name="filtered">
<xsl:call-template name="replace">
<xsl:with-param name="string" select="data/lines"/>
<xsl:with-param name="search" select="'
'"/>
<xsl:with-param name="new"><br /></xsl:with-param>
</xsl:call-template>
</xsl:variable>
<div>
<xsl:copy-of select="$filtered"/>
</div>
</body>
</html>
</xsl:template>
<xsl:template name="replace">
<xsl:param name="string"/>
<xsl:param name="search"/>
<xsl:param name="new"/>
<xsl:choose>
<xsl:when test="contains($string, $search)">
<xsl:value-of select="substring-before($string, $search)"/>
<xsl:copy-of select="$new"/>
<xsl:call-template name="replace">
<xsl:with-param name="string" select="substring-after($string, $search)"/>
<xsl:with-param name="search" select="$search"/>
<xsl:with-param name="new" select="$new"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
You have extra quotes in your parameter value. Try changing the line...
<xsl:with-param name="search" select="'
'"/>
To...
<xsl:with-param name="search" select="
"/>
UPDATE
As pointed out by the OP, the above is incorrect and will cause an XSLT transformation error.
I believe the answer by #banana to be the correct one.
IMHO, xsl:anaylze-string is the perfect fit for this problem. This XSLT 2.0 style-sheet run under Saxon ...
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:so="http://stackoverflow.com/questions/11222334"
xmlns:x="http://www.w3.org/1999/xhtml"
xmlns="http://www.w3.org/1999/xhtml"
exclude-result-prefixes="xsl fn xs so x">
<xsl:output method="xhtml" encoding="utf-8" indent="yes"
doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN"
omit-xml-declaration="yes" />
<xsl:template match="/">
<html>
<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"/></head>
<body>
<xsl:apply-templates select="data/lines"/>
</body>
</html>
</xsl:template>
<xsl:template match="lines">
<xsl:analyze-string select="." regex="\n">
<xsl:matching-substring>
<br />
<xsl:value-of select="'
'" />
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
</xsl:stylesheet>
... when applied to this input document ...
<?xml version="1.0" encoding="utf-8"?>
<data>
<lines>
Line 1
Line 2
Line 3
Line 4
Line 5
Line 6
</lines>
</data>
... will produce this html page ...
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body><br />
Line 1<br />
Line 2<br />
Line 3<br />
Line 4<br />
Line 5<br />
Line 6<br />
</body>
</html>
Try to replace:
<xsl:with-param name="new"><br /></xsl:with-param>
with:
<xsl:with-param name="new"><br /></xsl:with-param>
this will write current node value replacing \n with <br/>
<xsl:value-of select="replace(., '\n', '<br/>')"/>