I'm trying to add hierarchy to some grotty extruded typesetting XML. I can't seem to manage grouping several kinds of groups in the same parent element at once.
What I have (simplified, obviously):
<article>
<h1>A section title here</h1>
<p>A paragraph.</p>
<p>Another paragraph.</p>
<bl>Bulleted list item.</bl>
<bl>Another bulleted list item.</bl>
<h1>Another section title</h1>
<p>Yet another paragraph.</p>
</article>
What I want:
<article>
<sec>
<h1>A section title here</h1>
<p>A paragraph.</p>
<p>Another paragraph.</p>
<list>
<list-item>Bulleted list item.</list-item>
<list-item>Another bulleted list item.</list-item>
</list>
</sec>
<sec>
<h1>Another section title</h1>
<p>Yet another paragraph.</p>
</sec>
</article>
This almost works for the list items:
<xsl:for-each-group select="*" group-adjacent="boolean(self::BL)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<list><xsl:apply-templates select="current-group()"/></list>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
but it only handles the very first list in an article; and as soon as I try to add another xsl:for-each-group to cover the sections, the list-item one stops working.
Ideas? Many thanks in advance!
Here is a sample stylesheet that produces the output you posted for the input sample you posted:
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="article">
<xsl:copy>
<xsl:for-each-group select="*" group-starting-with="h1">
<sec>
<xsl:copy-of select="."/>
<xsl:for-each-group select="current-group() except ." group-adjacent="boolean(self::bl)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<list>
<xsl:apply-templates select="current-group()"/>
</list>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</sec>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="bl">
<list-item>
<xsl:apply-templates/>
</list-item>
</xsl:template>
</xsl:stylesheet>
Related
I am very new in xsl. I was trying to add the <quote> tag in between the <para>.tag. but the output printing twice.
Here is my xsl code
<?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="2.0">
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*" mode="pretrans">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="p[count(child::node())=0]" mode="pretrans"/>
<xsl:template match="doc">
<poc>
<xsl:apply-templates/>
</poc>
</xsl:template>
<xsl:template match="text">
<chapter>
<xsl:variable name="pos" select="count(child::node()[#style='H5']/preceding-sibling::p)+1"/>
<xsl:apply-templates select="child::node()[position()<$pos]" mode="presec"/>
<section>
<xsl:variable name="nodesets" >
<xsl:apply-templates select="child::node()[position()>=$pos]" mode="pretrans"/>
</xsl:variable>
<xsl:apply-templates select="$nodesets" mode="postsec"/> <!---->
</section>
</chapter>
</xsl:template>
<xsl:template match="p" mode="presec">
<xsl:choose>
<xsl:when test="#style='H2'">
<title><xsl:apply-templates/></title>
</xsl:when>
<xsl:when test="#style='H4'">
<subdivision>
<title><xsl:apply-templates/></title>
</subdivision>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template match="p" mode="postsec">
<xsl:variable name="pos" select="count(preceding-sibling::p[#style='H5'][1]/preceding-sibling::p)+1"/>
<xsl:variable name="pos" select="count(preceding-sibling::p)+1"/>
<xsl:variable name="styleblock" select="count(preceding-sibling::p[#style='BlockStyle'][1]/preceding-sibling::p)+1"/>
<xsl:choose>
<xsl:when test="#style='H5'">
<title><xsl:apply-templates/></title>
</xsl:when>
<xsl:when test="count(child::node())=0"/>
<xsl:otherwise>
<paragraph>
<xsl:if test="#style='BlockStyle'">
<quotes>
<xsl:apply-templates/>
</quotes>
</xsl:if>
<xsl:apply-templates/>
</paragraph>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
expected output:
<poc>
<chapter>
<section>
<paragraph>
<quote>
Hi welcome to new year 2022
</quote>
Hi welcome to new year 2022
</paragraph>
</section>
</chapter>
</poc>
The message is printing twice.
can anyone help me in this.
Depending on your needs delete the first or the second
<paragraph>
<xsl:if test="#style='BlockStyle'">
<quotes>
<xsl:apply-templates/><!-- First -->
</quotes>
</xsl:if>
<xsl:apply-templates/><!-- Second -->
</paragraph>
I am trying to create an xsl-stylesheet that outputs my xml-contents in the correct order.
Here is an example:
XML:
...<p>This is<mark> a nested <b>text</b></mark></p>...
XSL:
<?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="html" indent="yes"/>
<xsl:template match="/">
<html>
<body>
<h2>
<xsl:value-of select="html/head/title"/>
</h2>
<div style="border:1px solid black;margin:30px;padding:30px;box-sizing:border-box;">
<xsl:for-each select="html/body/div[#class='toc']/table/tr/td/a">
<p><a>
<xsl:attribute name="href" namespace="uri">
<xsl:value-of select="current()/#href"/>
</xsl:attribute>
<xsl:value-of select="current()"/>
</a></p>
</xsl:for-each>
</div>
<xsl:for-each select="html/body/div[#class='chapter']">
<div style="border:1px solid black;margin:30px;padding:30px;box-sizing:border-box;">
<xsl:attribute name="id" namespace="uri"><xsl:value-of select ="current()/#id"/></xsl:attribute>
<p><xsl:value-of select ="current()/#id"/></p>
<xsl:call-template name="rec">
<xsl:with-param name="parents" select="current()"/>
</xsl:call-template>
</div>
</xsl:for-each>
</body>
</html>
</xsl:template>
<xsl:template name="rec">
<xsl:param name="parents"></xsl:param>
<xsl:for-each select="$parents/*">
<xsl:if test="name() = 'img'">
<img class="{#class}" src="{#src}" style="max-width:100%;"/>
</xsl:if>
<xsl:if test="name() != 'img'">
<xsl:element name="{local-name()}">
<xsl:if test="name() != 'figure'">
<xsl:value-of select ="current()"/>
</xsl:if>
<xsl:call-template name="rec">
<xsl:with-param name="parents" select="current()"/>
</xsl:call-template>
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This outputs:
This is a nested text
a nested text
text
What I am trying to get:
<p>This is<mark> a nested <b>text</b></mark></p>
I have tried just to include a CSS-Stylesheet (which would get rid of this particular problem), however this does not seem to work with images (e.g.), which won´t be displayed but will occure inside most documents.
The XSL-Stylesheet is supposed to be working with multiple documents (I wrote an exporter, that creates xml-files, that roughly follow the same syntax). The important part should only be the recursive function inside <xsl:template name="rec">.
Help would be greatly appreciated. Thanks!
Basic push style, structure and order preserving processing usually relies on the identity transformation template plus custom templates for each node you need to transform e.g.
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="img">
<img class="{#class}" src="{#src}" style="max-width:100%;"/>
</xsl:template>
The duplicated text in your wrong output is created by the repeated use of xsl:value-of in the recursive, named template. If you treat text as nodes and let any copying be handled through adequate templates, like the identity transformation template, you don't output text values several times.
I need to format some xml data with the following structure
<list>
<item>
Test
</item>
<item>
Testt
</item>
<or-item>
TestOr
</or-item>
<or-item>
TestOrr
</or-item>
<item>
Testtt
</item>
<or-item>
TestOrrr
</or-item>
<item>
Testttt
</item>
</list>
with xsl:number the or-item must be formatted with the second level count on that position. I know it would be better to structure the or-item inside that item but the data is given like that.
I need a way to count the or-item next to the current or-item to calculate the numbering for xsl:number
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" version="1.1"
xmlns:axf="http://www.antennahouse.com/names/XSL/Extensions" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output encoding="UTF-8" method="html" indent="yes"/>
<xsl:template match="list">
<div>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="item">
<div>
<xsl:number count="item"/>
<xsl:value-of select="."/>
</div>
</xsl:template>
<xsl:template match="or-item">
<div style="padding-left: 10px">
<xsl:number value="count(//or-item)" format="a) "/>
<xsl:value-of select="."/>
</div>
</xsl:template>
</xsl:stylesheet>
Edit
I am using XSLT 1.1 with xsltproc on linux but 2.0 whould be possible if neccessary
As the target format is HTML, it seems you could rely on creating the appropriate nested HTML ordered lists by using xsl:for-each-group and group-starting-with="item":
<xsl:template match="list">
<ol>
<xsl:for-each-group select="*" group-starting-with="item">
<li>
<xsl:value-of select="."/>
<xsl:where-populated>
<ol>
<xsl:apply-templates select="tail(current-group())"/>
</ol>
</xsl:where-populated>
</li>
</xsl:for-each-group>
</ol>
</xsl:template>
<xsl:template match="or-item">
<li>
<xsl:value-of select="."/>
</li>
</xsl:template>
https://xsltfiddle.liberty-development.net/ejivJrM
That example uses some XSLT/XPath 3 stuff like were-populated and tail but in case that XSLT 2 compatility is needed then it could be replaced by <xsl:if test="subsequence(current-group(), 2)"><ol><xsl:apply-templates select="subsequence(current-group(), 2)"/></xsl:if>.
And of course the use of HTML ordered lists is not necessary, if needed/wanted you could just transform the input to nested divs with the used grouping approach and then in a second step use format-number as you seem to want to do:
<xsl:template match="list">
<xsl:variable name="nested-list">
<xsl:for-each-group select="*" group-starting-with="item">
<xsl:copy>
<xsl:value-of select="."/>
<xsl:copy-of select="tail(current-group())"/>
</xsl:copy>
</xsl:for-each-group>
</xsl:variable>
<div>
<xsl:apply-templates select="$nested-list"/>
</div>
</xsl:template>
<xsl:template match="item">
<div>
<xsl:number/>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="or-item">
<div style="padding-left: 10px">
<xsl:number format="a) "/>
<xsl:value-of select="."/>
</div>
</xsl:template>
https://xsltfiddle.liberty-development.net/ejivJrM/1
You can produce the expected output by simply adjusting the xsl:number instruction:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="utf-8"/>
<xsl:template match="/list">
<div>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="item">
<div>
<xsl:number/>
<xsl:value-of select="."/>
</div>
</xsl:template>
<xsl:template match="or-item">
<div style="padding-left: 10px">
<xsl:number level="any" from="item" format="a) "/>
<xsl:value-of select="."/>
</div>
</xsl:template>
</xsl:stylesheet>
I am trying to transform a <p> element with <br/> elements within into several <alinea>subtext</alinea>. For instance :
<p>subtext<br/>some more text<br/> some more subtext</p>
From the previous <p> I was hoping to simply replace all <br/> into </alinea><alinea> since the template for <p> opens a <alinea> element already.
<xsl:template match="p">
<para><alinea><xsl:apply-templates/></alinea></para>
</xsl:template>
<xsl:template match="br">
</alinea><xsl:apply-templates/><alinea>
</xsl:template>
But it is doesn't validate.
Expected result :
<para>
<alinea>
subtext
</alinea>
<alinea>
some more text
</alinea>
<alinea>
some more subtext
</alinea>
</para>
This is quite easy to do in XSLT 2.0:
<xsl:template match="p">
<para>
<xsl:for-each-group select="node()" group-starting-with="br">
<alinea>
<xsl:copy-of select="current-group()[not(self::br)]" />
</alinea>
</xsl:for-each-group>
</para>
</xsl:template>
Demo: https://xsltfiddle.liberty-development.net/6r5Gh2Q
Prob you can use something like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="p">
<para>
<xsl:apply-templates/>
</para>
</xsl:template>
<xsl:template match="text()">
<aline>
<xsl:value-of select="."/>
</aline>
</xsl:template>
<xsl:template match="br"/>
<xsl:template match="div">
<blockquote>
<xsl:value-of select="."/>
</blockquote>
</xsl:template>
</xsl:stylesheet>
I found a (dirty?) way to replace <br> with </alinea><alinea> :
<xsl:template match="br">
<xsl:value-of disable-output-escaping="yes">
</alinea><alinea>
</xsl:value-of>
</xsl:template>
Anything prettier ?
Namespace getting added to the inner element <i>,<b>, <mpval>. I want to get rid of this namespace.
My XML:
<Container xmlns="http://www.sss.org/schema/"
xmlns:meta="http://www.sss.org/schema/tangier/metadata">
<cs-properties>
My Parent level text 1
<mp>
text1 of first child <b> in bold</b>
<mpval>36-37</mpval>
text2 of child <i> in italic </i>
</mp>
My Parent level text2 in <i>italic</i> also in <b>bold </b>
</cs-properties>
</Container>
When I apply below XSL, I get namespace added to <i> element. Want to get rid of it.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:sub="http://www.sss.org/schema"
exclude-result-prefixes="xsl sub">
<xsl:variable name="ns" select="'http://www.sss.org/schema/'" />
<xsl:output indent="no" omit-xml-declaration="yes"/>
<xsl:variable name="inlineElements" select="'b','i','sub','sup'"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="sub:cs-properties">
<!--<properties xmlns= "{$ns}">-->
<xsl:element name="cs-properties" namespace="{$ns}" >
<xsl:for-each-group select="node()" group-adjacent="self::text() or self::node()
[name()=$inlineElements]">
<xsl:choose>
<xsl:when test="current-grouping-key()=true()">
<parenttext>
<xsl:copy-of select="current-group()" copy-namespaces="no" />
</parenttext>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
<!--</properties>-->
</xsl:element>
</xsl:template>
<xsl:template match="sub:mp|sub:abs-max">
<xsl:element name="{name()}">
<xsl:for-each-group select="node()" group-adjacent="self::text() or self::node()
[name()=$inlineElements]">
<xsl:choose>
<xsl:when test="current-grouping-key()=true()">
<childtext>
<xsl:copy-of select="current-group()" copy-namespaces="no"/>
</childtext>
</xsl:when>
<xsl:otherwise>
<!--<xsl:apply-templates select="."/>-->
<xsl:copy-of select="current-group()" copy-namespaces="no"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Result:
<Container xmlns="http://www.sss.org/schema"
xmlns:meta="http://www.sss.org/schema/tangier/metadata"><cs-properties><parenttext
xmlns="">
My Parent level text 1
</parenttext><mp xmlns=""><childtext>
text1 of first child <b xmlns="http://www.sss.org/schema"> in
bold</b></childtext><mpval xmlns="http://www.sss.org/schema">36-
37</mpval><childtext>
text2 of child <i xmlns="http://www.sss.org/schema"> in italic </i>
</childtext></mp><parenttext xmlns="">
My Parent level text2 in <i
xmlns="http://www.sss.org/schema">italic</i> also in <b
xmlns="http://www.sss.org/schema">bold </b></parenttext></cs-
properties></Container>
You can avoid the <parenttext xmlns=""> either by using <parenttext xmlns="http://www.sss.org/schema/"> in your markup or by putting xmlns="http://www.sss.org/schema/" on the styleheet's root element. The latter would affect all result elements which might be needed if you have them in more places in the stylesheet.