need help with xsl transformation - unwanted text - xslt

I'm trying to match just the secondary-content xml tag. but it's outputting text "/ Main cotnent goes here." Why is it outputting text from the masthead and maincontent tag?
xmL:
<xml>
<system-data-structure>
<mastheads>
<masthead>
<image>
<path>/</path>
</image>
<alt/>
</masthead>
</mastheads>
<maincontent>
<content>
<p>Main content goes here. </p>
</content>
</maincontent>
<secondary-content>
<title>
<h2>Secondary Content Title</h2>
</title>
<block>
<path>/</path>
</block>
<content>
<p>Secondary main content goes here. </p>
</content>
</secondary-content>
<secondary-content>
<title></title>
<block>
<content>
<div class="aux-content-box">
<h2 class="aux-content-box">More Information</h2>
<ul>
<li>
Air Force Tuition Assistance
</li>
<li>
Army Tuition Assistance
</li>
<li>
Coast Guard Tuition Assistance
</li>
<li>
Marine Corps Tuition Assistance
</li>
<li>
Navy Tuition Assistance
</li>
<li>
National Guard State Tuition Assistance
</li>
<li>
National Guard Federal Tuition Assistance
</li>
<li>
Reserve Tuition Assistance
</li>
<li>
US Department of Veteran Affairs: Tuition Assistance Top-Up Program
</li>
</ul>
</div>
</content>
<path>/web/current-students/military/military-links-nav</path>
<name>military-links-nav</name>
</block>
<content/>
</secondary-content>
</system-data-structure>
</xml>
xsl:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="utf-8"/>
<xsl:template match="/system-data-structure">
<xsl:apply-templates select="secondary-content" />
</xsl:template>
<xsl:template match="secondary-content">
Found a learner!
</xsl:template>
</xsl:stylesheet>
output:
/ Main content goes here. Found a learner! Found a learner!

This is a frequently asked question. It's due to XSLT's built-in templates that implicitly drive the processing of various nodes.
Override the built-in template for text nodes with the following template:
<xsl:template match="text()"/>

One reason for the behaviour is with your first template matching line
<xsl:template match="/system-data-structure">
In your XML, the root element is xml, not system-data-structure. This means it doesn't match anything, and this is why the built-in templates are kicking in as described in the previous answer.
Try replacing the above line with this...
<xsl:template match="/xml/system-data-structure">
This should then yield the following output
Found a learner! Found a learner!

It's because in your first template, you're trying to match system-data-structure at the root level. However, xml is the root level in your XML example. Change the match to /xml/system-data-structure:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="utf-8"/>
<xsl:template match="/xml/system-data-structure">
<xsl:apply-templates select="secondary-content" />
</xsl:template>
<xsl:template match="secondary-content">
Found a learner!
</xsl:template>
</xsl:stylesheet>

Related

Looping in a specific structure with xsl:apply-templates

I'm trying to create list elements in the below structure using xsl:apply-templates. Is it possible to achieve the below output without using xsl:for-each?
i am able to acheive thee below structure with xsl:for-each but would like to know if it is possible with xsl:apply-templates.
Below is my XML
<Properties>
<Root>
<group-container>
<group-title>
<title-name>Packs1</title-name>
<title-sub-links>
<subtitle-name>sub1</subtitle-name>
</title-sub-links>
<title-sub-links>
<subtitle-name>sub2</subtitle-name>
</title-sub-links>
</group-title>
<group-title>
<title-name>Packs2</title-name>
<title-sub-links>
<subtitle-name>abc</subtitle-name>
</title-sub-links>
<title-sub-links>
<subtitle-name>xyz</subtitle-name>
</title-sub-links>
</group-title>
</group-container>
<group-title>
<title-name>link title 1</title-name>
</group-title>
<group-title>
<title-name>link xyz</title-name>
</group-title>
</Root>
</Properties>
XSL
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="/">
<div class="col-9 tab">
<ul>
<xsl:apply-templates select = "/Properties/Root/group-container/group-title"/>
<xsl:apply-templates select = "/Properties/Root/group-container/group-title/title-sub-links"/>
</ul>
</div>
</xsl:template>
<xsl:template match = "group-title">
<li>
<xsl:value-of select="title-name"/>
</li>
</xsl:template>
<xsl:template match = "title-sub-links">
<li>
<xsl:value-of select="subtitle-name"/>
</li>
</xsl:template>
</xsl:stylesheet>
Output received
<div class="col-9 tab">
<ul>
<li>Packs1</li>
<li>Packs2</li>
<li>sub1</li>
<li>sub2</li>
<li>abc</li>
<li>xyz</li>
</ul>
</div>
Expected output
<div class="col-9 tab">
<ul>
<li>Packs1</li>
<li>sub1</li>
<li>sub2</li>
</ul>
<ul>
<li>Packs2</li>
<li>abc</li>
<li>xyz</li>
</ul>
</div>
I think (!) you want to do:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="/">
<div class="col-9 tab">
<xsl:apply-templates select = "/Properties/Root/group-container/group-title"/>
</div>
</xsl:template>
<xsl:template match = "group-title">
<ul>
<li>
<xsl:value-of select="title-name"/>
</li>
<xsl:apply-templates select = "title-sub-links"/>
</ul>
</xsl:template>
<xsl:template match = "title-sub-links">
<li>
<xsl:value-of select="subtitle-name"/>
</li>
</xsl:template>
</xsl:stylesheet>

XSLT: get relative URI instead of absolute uri

I want to:
Create a file in the same folder where I am running my XSLT stylesheet against.
This new file has a list of href to files with a certain value in the copyrholder element.
Have a relative path in the href.
This is what I currently have:
I create a new topic in the same folder
List of href with absolute uri
Problem: make the absolute path relative to file I just created.
Example
This is the folder I am referencing to and all files have that particular element I want in the list:
C:/dita/file1.dita
C:/dita/file2.dita
C:/dita/file3.dita
C:/dita/file4.dita
C:/dita/en/file5.dita
This is the XSLT I use
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:template match="/">
<xsl:result-document href="newtopic.dita" doctype-public="-//OASIS//DTD DITA Topic//EN" doctype-system="topic.dtd" indent="yes">
<topic id="to_new_topics">
<xsl:element name="title">New topics</xsl:element>
<xsl:element name="body">
<xsl:variable name="folderURI" select="resolve-uri('.',base-uri())"/>
<ul>
<xsl:for-each select="collection(concat($folderURI, '?select=*.dita;recurse=yes'))//copyrholder[contains(., 'value')]">
<li>
<xsl:element name="xref">
<xsl:attribute name="href">
<xsl:value-of select="base-uri()" />
</xsl:attribute>
</xsl:element>
</li>
</xsl:for-each>
</ul>
</xsl:element>
</topic>
</xsl:result-document>
</xsl:template>
</xsl:stylesheet>
This is the current result:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE topic
PUBLIC "-//OASIS//DTD DITA Topic//EN" "topic.dtd">
<topic id="to_new_topics">
<title>New topics</title>
<body>
<ul>
<li><xref href="file:/C:/´dita/file1.dita"/></li>
<li><xref href="file:/C:/´dita/file2.dita"/></li>
<li><xref href="file:/C:/´dita/file3.dita"/></li>
<li><xref href="file:/C:/´dita/file4.dita"/></li>
<li><xref href="file:/C:/dita/en/file5.dita"/></li>
</ul>
</body>
</topic>
This what I would like to have:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE topic
PUBLIC "-//OASIS//DTD DITA Topic//EN" "topic.dtd">
<topic id="to_new_topics">
<title>New topics</title>
<body>
<ul>
<li><xref href="file1.dita"/></li>
<li><xref href="file2.dita"/></li>
<li><xref href="file3.dita"/></li>
<li><xref href="file4.dita"/></li>
<li><xref href="en/file5.dita"/></li>
</ul>
</body>
</topic>
Anyone whou can help me to make the path relative?
Could this work:
<xsl:value-of select="replace(base-uri(), $folderURI, '') "/>
(did not see this at the comments), since if the problem is the absolute path at the xref, replacing the path with empty value should solve this problem. Or I just understand something terrible wrong. :) This is my tests end result:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE topic
PUBLIC "-//OASIS//DTD DITA Topic//EN" "topic.dtd">
<topic id="to_new_topics">
<title>New topics</title>
<body>
<ul>
<li>
<xref href="en/file3.dita"/>
</li>
<li>
<xref href="file1.dita"/>
</li>
<li>
<xref href="file2.dita"/>
</li>
</ul>
</body>
</topic>
Tested with: XSL, newtopic.xml (to avoid problems and renamed this .xml, not .dita) and all file*.dita files are in the same folder or subfolder.

catch the first occurrence value

I've the below XML.
<chapter num="1">
<section level="sect2">
<page>22</page>
</section>
<section level="sect3">
<page>23</page>
</section>
</chapter>
here I'm trying to get the first occurrence of <page>.
I'm using the below XSLT.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ntw="Number2Word.uri" exclude-result-prefixes="ntw">
<xsl:output method="html"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="ThisDocument" select="document('')"/>
<xsl:template match="/">
<xsl:text disable-output-escaping="yes"><![CDATA[<!DOCTYPE html>]]></xsl:text>
<html>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="chapter">
<section class="tr_chapter">
<xsl:value-of select="//page[1]/text()"/>
<div class="chapter">
</div>
</section>
</xsl:template>
</xsl:stylesheet>
but the output that I get all the page valyes printed. I only want the first one.
Current output.
<!DOCTYPE html>
<html>
<body>
<section class="tr_chapter">2223
<div class="chapter">
</div>
</section>
</body>
</html>
the page values are printed here after <section class="tr_chapter">, i want only 22 but I'm getting 2223
here I'm using //page[1]/text(), because I'm not sure that the page comes within the section, it is random.
please let me know how I can get only the first page value.
here is the transformation http://xsltransform.net/3NzcBsR
Try:
<xsl:value-of select="(//page)[1]"/>
http://xsltransform.net/3NzcBsR/1
Note that this gets the value of the first page element in the entire document.
If you want to search the contents of the chapter context element in your template for the first page descendant then use <xsl:value-of select="descendant::page[1]"/> or <xsl:value-of select="(.//page)[1]"/>.

Compare attributes from different levels during xslt transformation

Below I created a simplyfied XML example of what my xml looks like. I have an attribute which contains a number that I would like to watch. It's sort of a counter and during the transformation I would like to add something whenever the counter ++.
The problem is the number of levels in my xml file. Here I only made three but I actually have like 8 or maybe even more. I need to find a way to compare the current node against the previous one (or vice versa) but with the levels taken into account. So for instance in the example below the lvl2 node with the id of 4 needs to be compared with the lvl3 node with id 3 simply to find out if the id attribute has been raised.
xml:
<lvl1 id="1">
<lvl2 id="1">
<lvl3 id="1"></lvl3>
<lvl3 id ="2"></lvl3>
</lvl2>
<lvl2 id="2">
<lvl3 id="3"></lvl3>
</lvl2>
<lvl2 id="4"></lvl2>
</lvl1>
Since global counter variables are out of the question with xslt Im currently out of ideas and can't seem to find any here or anywhere else..
the output would be something like:
<ul>
<div>id 1</div>
<li>
<ul>
<li>
<ul>
<li></li>
<div>id 2</div>
<li></li>
</ul>
</li>
<li>
<ul>
<div>id 3</div>
<li></li>
</ul>
</li>
<div>id 4</div>
<li></li>
</ul>
</li>
here the stylesheet which transforms the xml into the html output but without the divs:
<?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="/">
<ul>
<xsl:value-of select="#id"/>
<xsl:apply-templates select="lvl1"/>
</ul>
</xsl:template>
<xsl:template match="lvl1">
<li class="{#id}">
lvl 1
<ul>
<xsl:apply-templates select="lvl2"/>
</ul>
</li>
</xsl:template>
<xsl:template match="lvl2">
<li class="{#id}">lvl 2
<ul>
<xsl:apply-templates select="lvl3"/>
</ul>
</li>
</xsl:template>
<xsl:template match="lvl3">
<li class="{#id}">lvl 3
</li>
</xsl:template>
If you apply this XSLT
<?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" />
<xsl:template match='/'>
<ul>
<xsl:apply-templates select='*' />
</ul>
</xsl:template>
<xsl:template match="*">
<li>
<div>
<xsl:value-of select='#id' />
<xsl:if test='count(*)>0'>
<ul>
<xsl:apply-templates select='*' />
</ul>
</xsl:if>
</div>
</li>
</xsl:template>
</xsl:stylesheet>
you get something that may be what you ask:
<ul>
<li>
<div>1<ul>
<li>
<div>1<ul>
<li>
<div>1</div>
</li>
<li>
<div>2</div>
</li>
</ul>
</div>
</li>
<li>
<div>2<ul>
<li>
<div>3</div>
</li>
</ul>
</div>
</li>
<li>
<div>4</div>
</li>
</ul>
</div>
</li>
</ul>
The question is a bit more complex than it seems, as preceding:: does not always work here to find last #id before current element. You need also check #id on parent and consider root position as well. This code produces the desired result with the XML given in the question. See my comments inside.
<xsl:template match="/">
<ul>
<xsl:apply-templates/>
</ul>
</xsl:template>
<xsl:template match="*">
<!-- get preceding (or parent) id -->
<xsl:variable name="lastId">
<xsl:choose>
<!-- try to get id from first preceding element -->
<xsl:when test="preceding::*[1]/#id">
<xsl:value-of select="preceding::*[1]/#id"/>
</xsl:when>
<!-- there could still be ids in parent elements -->
<xsl:when test="../#id">
<xsl:value-of select="../#id"/>
</xsl:when>
<!-- or this is the root element -->
<xsl:otherwise>
<xsl:value-of select="0"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- now compare current and last id -->
<xsl:if test="#id > $lastId">
<div>
<xsl:text>id </xsl:text>
<xsl:value-of select="#id"/>
</div>
</xsl:if>
<!-- create list item -->
<li>
<!-- check for subelements -->
<xsl:if test="*">
<ul>
<xsl:apply-templates/>
</ul>
</xsl:if>
</li>
</xsl:template>
Depending on your real scenario, you might want to limit match="*" to match="*[starts-with(local-name(),'lvl')]", as proposed in a comment by LarsH.

Converting plain text into html style lists using xstl, or grouping elements according to their contents and their positions using xslt

Trying to convert a plain text document into a html document using xslt, I am struggling with unordered lists.
I have:
<item>some text</item>
<item>- a list item</item>
<item>- another list item</item>
<item>more plain text</item>
<item>more and more plain text</item>
<item>- yet another list item</item>
<item>even more plain text</item>
What I want:
<p>some text</p>
<ul>
<li>a list item</li>
<li>another list item</li>
</ul>
<p>more plain text</p>
<p>more and more plain text</p>
<ul>
<li>yet another list item</li>
</ul>
<p>even more plain text</p>
I was looking at the Muenchian grouping but it would combine all list items into one group and all the plain text items into another. Then I tried to do select only items which preceding elements first char is different from its first char. But when I try to combine everything, I still get all the li in one ul.
Do you have any hints for me?
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kFollowing"
match="item[contains(., 'list')]
[preceding-sibling::item[1][contains(.,'list')]]"
use="generate-id(preceding-sibling::item
[not(contains(.,'list'))]
[1]
/following-sibling::item[1]
)"/>
<xsl:template match="item[contains(.,'list')]
[preceding-sibling::item[1][not(contains(.,'list'))]]">
<ul>
<xsl:apply-templates mode="list"
select=".|key('kFollowing',generate-id())"/>
</ul>
</xsl:template>
<xsl:template match="item" mode="list">
<li><xsl:value-of select="."/></li>
</xsl:template>
<xsl:template match="item[not(contains(.,'list'))]">
<p><xsl:value-of select="."/></p>
</xsl:template>
<xsl:template match="item[contains(.,'list')]
[preceding-sibling::item[1][contains(.,'list')]]"/>
</xsl:stylesheet>
when applied on the provided XML document (corrected from severely malformed into a well-formed XML document):
<t>
<item>some text</item>
<item>- a list item</item>
<item>- another list item</item>
<item>more plain text</item>
<item>more and more plain text</item>
<item>- yet another list item</item>
<item>even more plain text</item>
</t>
produces the wanted, correct result:
<p>some text</p>
<ul>
<li>- a list item</li>
<li>- another list item</li>
</ul>
<p>more plain text</p>
<p>more and more plain text</p>
<ul>
<li>- yet another list item</li>
</ul>
<p>even more plain text</p>
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()">
<xsl:apply-templates select="node()[1]|following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="item">
<p>
<xsl:value-of select="."/>
</p>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="item[starts-with(.,'- ')]">
<ul>
<xsl:call-template name="open"/>
</ul>
<xsl:apply-templates
select="following-sibling::node()
[not(self::item[starts-with(.,'- ')])][1]"/>
</xsl:template>
<xsl:template match="node()" mode="open"/>
<xsl:template match="item[starts-with(.,'- ')]" mode="open" name="open">
<li>
<xsl:value-of select="substring-after(.,'- ')"/>
</li>
<xsl:apply-templates select="following-sibling::node()[1]" mode="open"/>
</xsl:template>
</xsl:stylesheet>
Output:
<p>some text</p>
<ul>
<li>a list item</li>
<li>another list item</li>
</ul>
<p>more plain text</p>
<p>more and more plain text</p>
<ul>
<li>yet another list item</li>
</ul>
<p>even more plain text</p>
Note: This is like wrapping adjacents. Ussing fine grained traversal.