A transform (http://stackoverflow.com/questions/12862902/how-to-do-xslt-muenchian-grouping-with-some-null-attributes/12871809#12871809) groups based on an attribute (#group) and that attribute being null. Initially this was for just concat-ing strings but I now need to extend it so it uses the string as a file location and concats the docs if they are grouped - if not grouped it should use the string as a file it gets but doesn't need to join to anything else. The Transform:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="modules" match="module[#group]" use="concat(generate-id(..), '|', #group)"/>
<xsl:template match="root">
<AllSections>
<xsl:apply-templates />
</AllSections>
</xsl:template>
<!-- NON GROUPED PART -->
<xsl:template match="module[not(#group)]">
<page>
<content>
<xsl:value-of select="comp"/>
</content>
</page>
</xsl:template>
<!--GROUPED PART -->
<xsl:template match="module[#group][generate-id() = generate-id(key('modules', concat(generate-id(..), '|', #group))[1])]">
<xsl:variable name="modules" select="key('modules', concat(generate-id(..), '|', #group))"/>
<page>
<content>
<xsl:apply-templates select="$modules/comp/text()"/>
</content>
<count>
<xsl:value-of select="count($modules)" />
</count>
</page>
</xsl:template>
<xsl:template match="module"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
And Sample Input:
<root>
<section>
<subsection>
<module>
<comp>111</comp>
</module>
<module group='group01'>
<comp>222</comp>
</module>
<module group='group01'>
<comp>333</comp>
</module>
<module>
<comp>444</comp>
</module>
<module>
<comp>555</comp>
</module>
</subsection>
</section>
<section>
<subsection>
<module group ="group02">
<comp>666</comp>
</module>
<module group ="group02">
<comp>777</comp>
</module>
<module>
<comp>888</comp>
</module>
<module group ="group03">
<comp>999</comp>
</module>
<module group ="group03">
<comp>101010</comp>
</module>
</subsection>
<subsection>
<module group ="group04">
<comp>11111</comp>
</module>
<module group ="group04">
<comp>121212</comp>
</module>
<module group ="group05">
<comp>131313</comp>
</module>
<module group ="group05">
<comp>141414</comp>
</module>
<module group ="group06">
<comp>151515</comp>
</module>
<module group ="group06">
<comp>161616</comp>
</module>
<module>
<comp>171717</comp>
</module>
</subsection>
The transform at the moment concats the comp strings if they are of the same group... doing this for the non-grouped parts so the string is used as a file location:
<!-- NON GROUPED PART -->
<xsl:template match="module[not(#group)]">
<page>
<content>
<xsl:variable name="var">
<xsl:value-of select="comp"/>
</xsl:variable>
<xsl:copy-of select="document(concat('../myfile/', string($var)))"/>
</content>
</page>
</xsl:template>
At the moment the GROUPED PART will output:
...
<page>
<content>strgin1string2</content>
<count>2</count>
</page>
...
I need:
...
<page>
<content>
TEXT FROM FILE CALLED string1
TEXT FROM FILE CALLED string2
</content>
<count>2</count>
</page>
...
Thanks!
Assuming you simply want to pull in complete XML documents you can change your code to
<xsl:template match="module[#group][generate-id() = generate-id(key('modules', concat(generate-id(..), '|', #group))[1])]">
<xsl:variable name="modules" select="key('modules', concat(generate-id(..), '|', #group))"/>
<page>
<content>
<xsl:copy-of select="document($modules/comp)"/>
</content>
<count>
<xsl:value-of select="count($modules)" />
</count>
</page>
</xsl:template>
[edit] I think my suggestion above works if the comp elements contain the complete URL. Your comment however suggests you want to concatenate a constant with each comp, in that case with XSLT 1.0 you need
<xsl:template match="module[#group][generate-id() = generate-id(key('modules', concat(generate-id(..), '|', #group))[1])]">
<xsl:variable name="modules" select="key('modules', concat(generate-id(..), '|', #group))"/>
<page>
<content>
<xsl:for-each select="$modules/comp">
<xsl:copy-of select="document(concat('../myfile/', .))"/>
</xsl:for-each>
</content>
<count>
<xsl:value-of select="count($modules)" />
</count>
</page>
</xsl:template>
With XSLT 2.0 you could write a compact line like <xsl:copy-of select="document($modules/comp/(concat('../line/', .))"/> but with XSLT 1.0 the for-each should do.
Related
I already have an XSLT which takes XML input, transforms it and gives me XML output.
But is there a way where I can use same XSLT, and fetch the transformed XML output and convert it into JSON.
Here is a small and artificial example that you could use as your starting point:
Input XML
<root>
<item id="1">
<name>Alpha</name>
<value>101</value>
</item>
<item id="2">
<name>Bravo</name>
<value>2.25</value>
</item>
<item id="3">
<name>Charlie</name>
<value>33</value>
</item>
</root>
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:strip-space elements="*"/>
<xsl:template match="/">
<xsl:variable name="output-xml">
<xsl:apply-templates/>
</xsl:variable>
<!-- write XML to output file -->
<xsl:copy-of select="$output-xml"/>
<!-- produce a JSON file as additional output -->
<xsl:result-document href="json.txt" method="text">
<xsl:apply-templates select="$output-xml" mode="json"/>
</xsl:result-document>
</xsl:template>
<xsl:template match="root">
<array>
<xsl:apply-templates/>
</array>
</xsl:template>
<xsl:template match="item">
<object name="{name}">
<xsl:value-of select="value"/>
</object>
</xsl:template>
<xsl:template match="array" mode="json">
<!-- array -->
<xsl:text>[
</xsl:text>
<xsl:apply-templates mode="json"/>
<xsl:text>
]</xsl:text>
</xsl:template>
<xsl:template match="object" mode="json">
<!-- object -->
<xsl:text> {"</xsl:text>
<xsl:value-of select="#name"/>
<xsl:text>" : </xsl:text>
<xsl:value-of select="."/>
<xsl:text>}</xsl:text>
<xsl:if test="position()!=last()">
<xsl:text>,
</xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
XML Output
<?xml version="1.0" encoding="UTF-8"?>
<array>
<object name="Alpha">101</object>
<object name="Bravo">2.25</object>
<object name="Charlie">33</object>
</array>
Output file "json.txt"
[
{"Alpha" : 101},
{"Bravo" : 2.25},
{"Charlie" : 33}
]
I am trying to output only one line per unique value in my final text output after running an XML through an XSL stylesheet. In my research, I came upon the distinct-values function, but I'm unable to execute it the way that I want.
Here is my XML:
<Library>
<Book>
<Code>1</Code>
<Title>MANAGEMENT</Title>
</Book>
<Book>
<Code>1</Code>
<Title>MANAGEMENT</Title>
</Book>
<Book>
<Code>1</Code>
<Title>MANAGEMENT</Title>
</Book>
<Book>
<Code>1</Code>
<Title>MANAGEMENT</Title>
</Book>
<Book>
<Code>1</Code>
<Title>MANAGEMENT</Title>
</Book>
<Book>
<Code>10</Code>
<Title>MECHANICAL</Title>
</Book>
<Book>
<Code>106</Code>
<Title>TRANSPORTATION</Title>
</Book>
</Library>
And here is my current XSL (incorrect):
<xsl:template match="Book">
<xsl:value-of select="this:fixedOutput(Code)" />
<xsl:value-of select="this:fixedOutput(Title)" />
<xsl:value-of select="$linefeed" />
</xsl:template>
My output right now is:
1|MANAGEMENT| 1|MANAGEMENT| 1|MANAGEMENT| 1|MANAGEMENT| 1|MANAGEMENT|
10|MECHANICAL| 106|TRANSPORTATION|
But I want it to be this:
1|MANAGEMENT| 10|MECHANICAL| 106|TRANSPORTATION|
I'm not sure how to use the syntax of distinct values to get to where I need.
An XSLT 1.0 solution that uses key and the generate-id() function (Muenchian grouping) to get distinct values :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="bookCode" match="/Library/Book/Code" use="." />
<xsl:template match="/">
<xsl:for-each select="/Library/Book/Code[generate-id()
= generate-id(key('bookCode',.)[1])]">
<xsl:value-of select="this:fixedOutput(.)" />
<xsl:value-of select="this:fixedOutput(../Title)" />
<xsl:value-of select="$linefeed" />
</xsl:for-each>
</xsl:template>
An XSLT 2.0 solution which uses xsl:for-each-group as #michael.hor257k said :
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="Library">
<xsl:for-each-group select="Book" group-by="concat(Code,Title)">
<xsl:apply-templates select="." />
</xsl:for-each-group>
</xsl:template>
<xsl:template match="Book">
<xsl:value-of select="this:fixedOutput(Code)" />
<xsl:value-of select="this:fixedOutput(Title)" />
<xsl:value-of select="$linefeed" />
</xsl:template>
Note: As this:fixedOutput in your code doen't refer any namespace it has been used as it is.
Refer this: http://xsltransform.net/3MP2uBE
I've got the following XML:
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<?xml-stylesheet type="text/xsl" href="Test.xslt"?>
<test-results>
<test-case name="TestCase1" description="Descriptiontext">
<categories>
<category name="Dimension linked to measure group" />
</categories>
</test-case>
<test-case name="TestCase2" description="DescriptionText">
<categories>
<category name="Dimension linked to measure group" />
</categories>
</test-case>
<test-case name="TestCase3" description="DescriptionText">
<categories>
<category name="Default parameters" />
</categories>
</test-case>
<test-case name="TestCase4" description="DescriptionText">
<categories>
<category name="Default parameters" />
</categories>
</test-case>
<test-case name="TestCase5" description="DescriptionText">
<categories>
<category name="Referential Integrity" />
</categories>
<reason>
<message><![CDATA[Not testable, yet (v1.6.1)]]></message>
</reason>
</test-case>
<test-case name="TestCase6" description="DescriptionText">
<categories>
<category name="Referential Integrity" />
</categories>
<reason>
<message><![CDATA[Not testable, yet (v1.6.1)]]></message>
</reason>
</test-case>
</test-results>
With the following XSLT I try to use Muenchian grouping to order by category name (ascending) and within each category by test-case name (ascending).
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml">
<xsl:key name="cases-by-category" match="categories" use="category/#name" />
<xsl:template match="test-case">
<xsl:for-each select="categories[count(. | key('cases-by-category', category/#name)[1]) = 1]">
<xsl:sort select="category/#name" />
<xsl:value-of select="category/#name" /><br/>
<xsl:for-each select="key('cases-by-category', category/#name)">
<xsl:sort select="//test-case/#name" />
<xsl:value-of select="//test-case/#name"/><br/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
However, what I get is this:
Dimension linked to measure group
TestCase1
TestCase1
Default parameters
TestCase1
TestCase1
Referential Integrity
TestCase1
TestCase1
The number of test cases for each category is correct, but the sorting doesn't get applied and the first test-case name is always used. How can I fix this?
Given <xsl:key name="cases-by-category" match="categories" use="category/#name" /> the expression key('cases-by-category', category/#name) gives you a node-set of categories elements, if you want to sort them by the parent then I think you want to use <xsl:sort select="../#name" />.
I also think having
<xsl:template match="test-case">
<xsl:for-each select="categories[count(. | key('cases-by-category', category/#name)[1]) = 1]">
looks odd as you would process the categories of every matched test-case element, it seems more likely you want
<xsl:template match="test-results">
<xsl:for-each select="test-case/categories[count(. | key('cases-by-category', category/#name)[1]) = 1]">
instead.
Here is a complete sample:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml">
<xsl:output indent="yes"/>
<xsl:key name="cases-by-category" match="categories" use="category/#name" />
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="test-results">
<xsl:for-each select="test-case/categories[count(. | key('cases-by-category', category/#name)[1]) = 1]">
<xsl:sort select="category/#name" />
<xsl:value-of select="category/#name" /><br/>
<xsl:for-each select="key('cases-by-category', category/#name)">
<xsl:sort select="../#name" />
<xsl:value-of select="../#name"/><br/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When I run that with Saxon 6.5 against your input I get the following result:
<html xmlns="http://www.w3.org/1999/xhtml">
<body>Default parameters<br/>TestCase3<br/>TestCase4<br/>Dimension linked to measure group<br/>TestCase1<br/>TestCase2<br/>Referential Integrity<br/>TestCase5<br/>TestCase6<br/>
</body>
</html>
Slightly different to the standard Meunchain grouping as I'm dealing with a null(?) attribute for some of the tags that I'm transforming. I'd like the null groupings to be treated as their own individual group with the transformed output adding the grouped strings. Also if there is a grouping to do a count of how many there were.
<root>
<section>
<subsection>
<module>
<comp>111</comp>
</module>
<module group='group01'>
<comp>222</comp>
</module>
<module group='group01'>
<comp>333</comp>
</module>
<module>
<comp>444</comp>
</module>
<module>
<comp>555</comp>
</module>
</subsection>
</section>
<section>
<subsection>
<module group ="group02">
<comp>666</comp>
</module>
<module group ="group02">
<comp>777</comp>
</module>
<module>
<comp>888</comp>
</module>
<module group ="group03">
<comp>999</comp>
</module>
<module group ="group03">
<comp>101010</comp>
</module>
</subsection>
<subsection>
<module group ="group04">
<comp>11111</comp>
</module>
<module group ="group04">
<comp>121212</comp>
</module>
<module group ="group05">
<comp>131313</comp>
</module>
<module group ="group05">
<comp>141414</comp>
</module>
<module group ="group06">
<comp>151515</comp>
</module>
<module group ="group06">
<comp>161616</comp>
</module>
<module>
<comp>171717</comp>
</module>
</subsection>
</section>
Wanted output:
<AllSections>
<section>
<subsection>
<page>
<content>111</content>
</page>
<page>
<content>222333</content>
<count>2</count>
</page>
<page>
<content>444</content>
</page>
<page>
<content>555</content>
</page>
</subsection>
</section>
<section>
<subsection>
<page>
<content>666777</content>
<count>2</count>
</page>
<page>
<content>888</content>
</page>
<page>
<content>999101010</content>
<count>2</count>
</page>
</subsection>
<subsection>
<page>
<content>111111121212</content>
<count>2</count>
</page>
<page>
<content>131313141414161616</content>
<count>3</count>
</page>
<page>
<content>151515</content>
</page>
<page>
<content>171717</content>
</page>
</subsection>
</section>
Thanks!
For the elements with a group attribute, you are grouping by that attribute but also within the parent subsection element. Therefore you could start off by defining a key to group them this was
<xsl:key name="modules" match="module[#group]" use="concat(generate-id(..), '|', #group)" />
Next, you would need templates to match the various cases for the module elements. Firstly, you could have a template to match module elements with no group attribute, where you could format the output as required.
<xsl:template match="module[not(#group)]">
<page>
<content>
<xsl:value-of select="comp"/>
</content>
</page>
</xsl:template>
For modules, with group attributes you would need a match that checked that this particular module element occurred first in the group for the key defined above.
<xsl:template
match="module
[#group]
[generate-id() = generate-id(key('modules', concat(generate-id(..), '|', #group))[1])]">
Within this template, you could then easily define a variable to hold the elements in the group, using the key, and then either output the child comp elements, or count them
<xsl:variable name="modules" select="key('modules', concat(generate-id(..), '|', #group))"/>
<page>
<content>
<xsl:apply-templates select="$modules/comp/text()"/>
</content>
<count>
<xsl:value-of select="count($modules)" />
</count>
</page>
Finally, you would need a third template to match all other module elements (i.e. elements with a group attribute, but not first in the group) to ignore them, to ensure they don't get output twice. (The XSLT processor should always match more specific templates before this more generic one)
<xsl:template match="module"/>
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="modules" match="module[#group]" use="concat(generate-id(..), '|', #group)"/>
<xsl:template match="root">
<AllSections>
<xsl:apply-templates />
</AllSections>
</xsl:template>
<xsl:template match="module[not(#group)]">
<page>
<content>
<xsl:value-of select="comp"/>
</content>
</page>
</xsl:template>
<xsl:template match="module[#group][generate-id() = generate-id(key('modules', concat(generate-id(..), '|', #group))[1])]">
<xsl:variable name="modules" select="key('modules', concat(generate-id(..), '|', #group))"/>
<page>
<content>
<xsl:apply-templates select="$modules/comp/text()"/>
</content>
<count>
<xsl:value-of select="count($modules)" />
</count>
</page>
</xsl:template>
<xsl:template match="module"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output
<AllSections>
<section>
<subsection>
<page>
<content>111</content>
</page>
<page>
<content>222333</content>
<count>2</count>
</page>
<page>
<content>444</content>
</page>
<page>
<content>555</content>
</page>
</subsection>
</section>
<section>
<subsection>
<page>
<content>666777</content>
<count>2</count>
</page>
<page>
<content>888</content>
</page>
<page>
<content>999101010</content>
<count>2</count>
</page>
</subsection>
<subsection>
<page>
<content>11111121212</content>
<count>2</count>
</page>
<page>
<content>131313141414</content>
<count>2</count>
</page>
<page>
<content>151515161616</content>
<count>2</count>
</page>
<page>
<content>171717</content>
</page>
</subsection>
</section>
</AllSections>
I am looking to export from Filemaker using column names (instead of positions). Currently I export the following XSL stylesheet that exports by position with:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fm="http://www.filemaker.com/fmpxmlresult" exclude-result-prefixes="fm" >
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/">
<people>
<xsl:for-each select="fm:FMPXMLRESULT/fm:RESULTSET/fm:ROW">
<person>
<name>
<xsl:value-of select="fm:COL[01]/fm:DATA"/>
</name>
<location>
<xsl:value-of select="fm:COL[02]/fm:DATA"/>
</location>
</person>
</xsl:for-each>
</people>
</xsl:template>
</xsl:stylesheet>
Any ideas? Thanks.
If you just want to make the code more readable, then I'd suggest something simple, like:
<!-- expected columns -->
<xsl:variable name="NAME" value="1" />
<xsl:variable name="LOCATION" value="2" />
<!-- ... -->
<people>
<xsl:for-each select="fm:FMPXMLRESULT/fm:RESULTSET/fm:ROW">
<person>
<name>
<xsl:value-of select="fm:COL[$NAME]/fm:DATA"/>
</name>
<location>
<xsl:value-of select="fm:COL[$LOCATION]/fm:DATA"/>
</location>
</person>
</xsl:for-each>
</people>
BTW, with <xsl:value-of /> you can omit the fm:DATA, i.e. use:
<xsl:value-of select="fm:COL[$LOCATION] />
It will return the same result.
If you need something more sophisticated, please explain.
Update:
To refer to columns by column names is harder, but possible with something like that:
<!-- Define a key to get a field and all fields that precede it by the field name -->
<xsl:key name="N" match="/fm:FMPXMLRESULT/fm:METADATA/fm:FIELD" use="#NAME" />
<xsl:key name="N" match="/fm:FMPXMLRESULT/fm:METADATA/fm:FIELD"
use="following-sibling::fm:FIELD/#NAME" />
<!-- Then *count* them it in the code like that -->
<people>
<xsl:for-each select="fm:FMPXMLRESULT/fm:RESULTSET/fm:ROW">
<person>
<name>
<xsl:value-of select="fm:COL[count(key('N', 'name'))]" />
</name>
<location>
<xsl:value-of select="fm:COL[count(key('N', 'location'))]" />
</location>
</person>
</xsl:for-each>
</people>
Not utterly elegant, but works.