Reputation:
I'm trying to convert the following XML document:
<method>
<desc_signature>
<desc_name>
Some method name
</desc_name>
</desc_signature>
<desc_content>
<paragraph>Paragraph1</paragraph>
<image>Image1</image>
<paragraph>Paragraph2</paragraph>
<literal_block>Codesnippet1</literal_block>
<image>Image2</image>
<paragraph>Paragraph3</paragraph>
<image>Image3</image>
<literal_block>Codesnippet2</literal_block>
</desc_content>
</method>
To the following JSON format:
{
"title":"Some method name",
"elements":[
"Paragraph1",
"Paragraph2",
"Codesnippet1",
"Paragraph3",
"Codesnippet2",
]
}
What I basically want to achieve, is to comma separate a list, but only include paragraph and literal_blocks, and still keeping the original order.
My first attempt was an XSLT that looks like this, with this union selector:
<xsl:for-each select="desc_content/paragraph|desc_content/literal_block">
Like this:
<!-- Method template -->
<xsl:template name="method">
{
"title":"<xsl:value-of select="normalize-space(desc_signature/desc_name)" />",
"elements":[
<xsl:for-each select="desc_content/paragraph|desc_content/literal_block">
<xsl:choose>
<xsl:when test="self::paragraph">
<xsl:call-template name="paragraph"/>
</xsl:when>
<xsl:when test="self::literal_block">
<xsl:call-template name="code"/>
</xsl:when>
</xsl:choose>
<xsl:if test="position()!=last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
]
}
</xsl:template>
However, it seems like it is grouping paragraphs and then literal_blocks:
{
"title":"Some method name",
"elements":[
"Paragraph1",
"Paragraph2",
"Paragraph3",
"Codesnippet1",
"Codesnippet2",
]
}
Another approach was to select all:
<xsl:for-each select="desc_content/*">
That, however, yields too many commas, as position() accounts for all elements, even if not used:
{
"title":"Some method name",
"elements":[
"Paragraph1",,
"Paragraph2",
"Codesnippet1",,
"Paragraph3",,
"Codesnippet2",
]
}
How would I be able to achieve the desired behaviour?
Thanks for the help!
EDIT - XSLT 1.0 solution:
I've solved the issue with the solution from @Christian Mosz, using apply-templates and checking following-sibling:
<!-- Method template -->
<xsl:template name="method">
{
"title":"<xsl:value-of select="normalize-space(desc_signature/desc_name)" />",
"elements":[
<xsl:apply-templates select="desc_content/*"/>
]
}
</xsl:template>
<!-- Paragraph template -->
<xsl:template match="paragraph">
{"paragraph":"<xsl:value-of select="normalize-space()"/>"}
<xsl:if test="following-sibling::paragraph|following-sibling::literal_block">,</xsl:if>
</xsl:template>
<!-- Code snippet template -->
<xsl:template match="literal_block">
{"code":"<xsl:call-template name="code"/>"}
<xsl:if test="following-sibling::paragraph|following-sibling::literal_block">,</xsl:if>
</xsl:template>
For XSLT 3.0, please check approved answer.
Upvotes: 1
Views: 52
Reputation: 167506
Using XSLT 3 (e.g. with Saxon 9.8 or later or AltovaXML 2017 R3 or later) you have two options, you can either construct an XPath 3.1 map:
map {
'title' : normalize-space(desc_signature/desc_name),
'elements' : array {
data(desc_content/(paragraph | literal_block))
}
}
i.e.
<?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="#all"
version="3.0">
<xsl:output method="json" indent="yes" />
<xsl:template match="method">
<xsl:sequence
select="map {
'title' : normalize-space(desc_signature/desc_name),
'elements' : array {
data(desc_content/(paragraph | literal_block))
}
}"/>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/pNmC4HJ
or you transform to the XML representation of JSON the xml-to-json
functions supports:
<?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="http://www.w3.org/2005/xpath-functions"
expand-text="yes"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output method="text" indent="yes" />
<xsl:template match="/">
<xsl:variable name="json-xml">
<xsl:apply-templates/>
</xsl:variable>
<xsl:value-of select="xml-to-json($json-xml, map { 'indent' : true() })"/>
</xsl:template>
<xsl:template match="method">
<map>
<string key="title">{normalize-space(desc_signature/desc_name)}</string>
<array key="elements">
<xsl:apply-templates select="desc_content/(paragraph | literal_block)"/>
</array>
</map>
</xsl:template>
<xsl:template match="desc_content/*">
<string>{.}</string>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/pNmC4HJ/1
Upvotes: 1