regex/XSLT: replace specific string within boundaries [duplicate] - regex

This question already has answers here:
Why it's not possible to use regex to parse HTML/XML: a formal explanation in layman's terms
(10 answers)
Closed 3 years ago.
I am not sure how to use regex to do the following:
within the edge tags (), check if type="highway.secondary", and if yes, replace all values of speed to 40.
<edge id="-100396051#2" type="highway.unclassified">
<lane id="-100396051#2_0" index="0" speed="13.89">
<param key="origId" value="100396051"/>
</lane>
</edge>
<edge id="-101784374#0" type="highway.secondary">
<lane id="-101784374#0_0" index="0" speed="27.78" length="17.22" >
<param key="origId" value="101784374"/>
</lane>
<lane id="-101784374#0_1" index="1" speed="29.98" length="17.22" >
<param key="origId" value="101784374"/>
</lane>
</edge>
So far I got this:
(?<=type="highway\.secondary")(speed)(?=edge), but that doesn't find speed...
Thanks!

INPUT: edges.xml
<edges>
<edge id="-100396051#2" type="highway.unclassified">
<lane id="-100396051#2_0" index="0" speed="13.89">
<param key="origId" value="100396051"/>
</lane>
</edge>
<edge id="-101784374#0" type="highway.secondary">
<lane id="-101784374#0_0" index="0" speed="27.78" length="17.22" >
<param key="origId" value="101784374"/>
</lane>
<lane id="-101784374#0_1" index="1" speed="29.98" length="17.22" >
<param key="origId" value="101784374"/>
</lane>
</edge>
</edges>
STYLESHEET: change_speed.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="#speed[../parent::edge and ../../#type='highway.secondary']">
<xsl:attribute name="speed">
<xsl:value-of select="'40'"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
OUTPUT:
$ xsltproc change_speed.xsl edges.xml
<?xml version="1.0" encoding="utf-8"?>
<edges>
<edge id="-100396051#2" type="highway.unclassified">
<lane id="-100396051#2_0" index="0" speed="13.89">
<param key="origId" value="100396051"/>
</lane>
</edge>
<edge id="-101784374#0" type="highway.secondary">
<lane id="-101784374#0_0" index="0" speed="40" length="17.22">
<param key="origId" value="101784374"/>
</lane>
<lane id="-101784374#0_1" index="1" speed="40" length="17.22">
<param key="origId" value="101784374"/>
</lane>
</edge>
</edges>
Explanations:
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
Will copy all the nodes and attributes recursively
<xsl:template match="#speed[../parent::edge and ../../#type='highway.secondary']">
<xsl:attribute name="speed">
<xsl:value-of select="'40'"/>
</xsl:attribute>
</xsl:template>
When you reach an attribute named speed for which the parent node is named edge and has an attribute type whose value is at highway.secondary, change the value of this attribute to 40.

Related

Uncaught TypeError: Cannot read properties of null (reading 'hashCode') [SaxonJS]

I'm getting this error in Chrome console:
Seems to be a problem with XSLT or SEF as other SEF works.
I complie SEF with the xslt3 tool on Node.js version v14.17.3.
I can run the XSLT with Saxon 9.8.12-EE in oXygen with no problem.
Maybe the problem with maps, xsl:include or xsl:import?
Here is the XSLT code:
<stylesheet exclude-result-prefixes="xs xd dme functx dita mei map array" extension-element-prefixes="ixsl" version="3.0" xmlns="http://www.w3.org/1999/XSL/Transform" xmlns:array="http://www.w3.org/2005/xpath-functions/array" xmlns:dita="http://dita-ot.sourceforge.net" xmlns:dme="http://www.mozarteum.at/ns/dme" xmlns:functx="http://www.functx.com" xmlns:ixsl="http://saxonica.com/ns/interactiveXSLT" xmlns:map="http://www.w3.org/2005/xpath-functions/map" xmlns:mei="http://www.music-encoding.org/ns/mei" xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl" xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://www.music-encoding.org/ns/mei">
<xi:include href="docs.xsl" xpointer="element(/1/1)"/>
<import href="changeLog.xsl"/>
<!--This parameter is a needed for XSpec. Cf. https://github.com/xspec/xspec/wiki/Global-Context-Item-->
<param name="global-context-item" select="."/>
<param name="source"/>
<include href="../modules/identity-transform.xsl"/>
<xd:doc>
<xd:desc>
<xd:p>Replace current scoreDef with the alternative. Note that this should be defined in the following-sibling <choice>.</xd:p>
</xd:desc>
</xd:doc>
<template match="scoreDef[not(#corresp = concat('#', $source))]">
<copy-of select="following-sibling::choice[#type = 'scoring']/*[#corresp = concat('#', $source)]/scoreDef"/>
</template>
<xd:doc>
<xd:desc/>
</xd:doc>
<template match="choice[#type = 'scoring']"/>
<xd:doc>
<xd:desc>
<xd:p>Map staffDef#n of the new scoreDef and #dme.parts.</xd:p>
</xd:desc>
</xd:doc>
<variable as="map(xs:string, item())*" name="map_staves_order">
<for-each select="$global-context-item//choice[#type = 'scoring']/*[#corresp = concat('#', $source)]/scoreDef">
<map>
<for-each select="descendant::staffDef">
<map-entry key="string(#n)" select="#dme.parts"/>
</for-each>
</map>
</for-each>
</variable>
<xd:doc>
<xd:desc>
<xd:p>Get the maximum number of staves.</xd:p>
</xd:desc>
</xd:doc>
<variable as="xs:integer" name="count_staves" select="
max(for $a in map:keys($map_staves_order)
return
if (matches($a, '\d')) then
xs:integer($a)
else
())"/>
<xd:doc>
<xd:desc>
<xd:p>Key: old #n.</xd:p>
<xd:p>Value: new #n</xd:p>
</xd:desc>
</xd:doc>
<variable as="map(xs:string, xs:string)*" name="map_new_old_staves">
<map>
<for-each select="map:keys($map_staves_order)">
<variable name="current_key" select="."/>
<map-entry key="$global-context-item//measure[#n = 1]/staff[#dme.parts = map:get($map_staves_order, $current_key)]/#n/string()" select="."/>
</for-each>
</map>
</variable>
<xd:doc>
<xd:desc>
<xd:p>Copies elements before first staff without changes, e.g. <tempo></xd:p>
<xd:p>Copies staves accordingly to the new score order. Changes the #n-attribute accordingly</xd:p>
<xd:p>Copies ControlEvents and updates the #staff attribute if neccessary.</xd:p>
</xd:desc>
</xd:doc>
<template match="measure">
<copy>
<apply-templates select="#*"/>
<apply-templates select="child::staff[1]/preceding-sibling::*"/>
<variable as="element()" name="currentMeasure" select="."/>
<for-each select="1 to $count_staves">
<variable as="xs:string" name="currentItem" select="string()"/>
<for-each select="$currentMeasure/child::staff[map:get($map_staves_order, $currentItem) = #dme.parts]">
<copy>
<attribute name="n" select="$currentItem"/>
<apply-templates select="(#* except #n) | node()"/>
</copy>
</for-each>
</for-each>
<call-template name="update_staff_controlEvents">
<with-param name="elements" select="child::staff[last()]/following-sibling::*"/>
</call-template>
</copy>
</template>
<xd:doc>
<xd:desc>
<xd:p>Receives one or more elements (controlEvents) and updates their #staff according to the $map_new_old_staves variable.</xd:p>
<xd:p>Recursive template</xd:p>
</xd:desc>
<xd:param name="elements"/>
</xd:doc>
<template name="update_staff_controlEvents">
<param name="elements"/>
<for-each select="$elements">
<choose>
<when test="#staff">
<copy>
<attribute name="staff" select="map:get($map_new_old_staves, #staff)"/>
<apply-templates select="#* except #staff"/>
<if test="node()">
<call-template name="update_staff_controlEvents">
<with-param name="elements" select="node()"/>
</call-template>
</if>
</copy>
</when>
<otherwise>
<copy>
<apply-templates select="#*"/>
<if test="node()">
<call-template name="update_staff_controlEvents">
<with-param name="elements" select="node()"/>
</call-template>
</if>
</copy>
</otherwise>
</choose>
</for-each>
</template>
</stylesheet>
JS which I run on Console:
var options = {
stylesheetLocation: "./assets/xsl/origScoring.sef.json",
sourceText: xmlString,
stylesheetParams: {"source": "source_A"},
destination: "document"
}
var result = SaxonJS.transform(options);
result.principalResult
Owing to the Saxon developers I could easily solve my issue by adding namespace prefixes in the XPath expressions when creating maps. Originally, I used xpath-default-namespace. Fo instance, here on the elements measure and staff should be:
<map-entry key="$global-context-item//mei:measure[#n = 1]/mei:staff[#dme.parts = map:get($map_staves_order, $current_key)]/#n/string()" select="."/>
instead of
<map-entry key="$global-context-item//measure[#n = 1]/staff[#dme.parts = map:get($map_staves_order, $current_key)]/#n/string()" select="."/>

XSLT3 Streaming for appending integer position of node

I have a large XML file to transform using XSLT to append the integer position of sibling node . I’m using XSLT3 streaming and accumulators. I did get desired output. However, my code looks so lengthy that I’m unable to simplify my code. I also need to group same sibling nodes as sibling nodes in the source xml is not grouped always. Could someone help me here please?
Requirement: Sibling nodes such as Positions, Payments etc.. need to be appended with their corresponding integer position such as <Locations1>, <Locations2>etc.<Payments1>,< Payments2> etc..
Now that I have declared two accumulators, each for each sibling nodes. However, my source XML has many sibling nodes.. I’m not sure if I need to use as many accumulators and template match as my sibling nodes.
Input XML
``
<?xml version="1.0" encoding="UTF-8"?>
<Members>
<Member>
<Name>
<fname>Fred</fname>
<id>1234</id>
</Name>
<Locations>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations>
<Locations>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations>
<Payments>
<amount>1000</amount>
<currency>USD</currency>
</Payments>
<Payments>
<amount>1000</amount>
<currency>USD</currency>
</Payments>
<Locations>
<name>New York</name>
<days>5</days>
<hours>40</hours>
</Locations>
<Locations>
<name>Boston</name>
<days>4</days>
<hours>32</hours>
</Locations>
</Member>
<Member>
<Name>
<fname>Jack</fname>
<id>4567</id>
</Name>
<Locations>
<name>New York</name>
<days>5</days>
<hours>30</hours>
</Locations>
<Locations>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations>
<Payments>
<amount>1500</amount>
<currency>USD</currency>
</Payments>
<Payments>
<amount>1800</amount>
<currency>USD</currency>
</Payments>
</Member>
</Members>
``
Expected Output
``
<?xml version="1.0" encoding="UTF-8"?>
<Members>
<Member>
<Name>
<fname>Fred</fname>
<id>1234</id>
</Name>
<Locations_1>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Locations_3>
<name>New York</name>
<days>5</days>
<hours>40</hours>
</Locations_3>
<Locations_4>
<name>Boston</name>
<days>4</days>
<hours>32</hours>
</Locations_4>
<Payments_1>
<amount>1000</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1000</amount>
<currency>USD</currency>
</Payments_2>
</Member>
<Member>
<Name>
<fname>Jack</fname>
<id>4567</id>
</Name>
<Locations_1>
<name>New York</name>
<days>5</days>
<hours>30</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Payments_1>
<amount>1500</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1800</amount>
<currency>USD</currency>
</Payments_2>
</Member>
</Members>
``
Current 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="3.0">
<xsl:output method="xml" indent="yes"/>
<xsl:mode streamable="yes" on-no-match="shallow-copy" use-accumulators="#all"/>
<xsl:accumulator name="loc-count" as="xs:integer" initial-value="0" streamable="yes">
<xsl:accumulator-rule match="Member" select="0"/>
<xsl:accumulator-rule match="Member/Locations" select="$value + 1"/>
</xsl:accumulator>
<xsl:accumulator name="pay-count" as="xs:integer" initial-value="0" streamable="yes">
<xsl:accumulator-rule match="Member" select="0"/>
<xsl:accumulator-rule match="Member/Payments" select="$value + 1"/>
</xsl:accumulator>
<xsl:template match="Locations">
<xsl:element name="Locations_{accumulator-before('loc-count')}">
<xsl:copy-of select="#* | node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="Payments">
<xsl:element name="Payments_{accumulator-before('pay-count')}">
<xsl:copy-of select="#* | node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
``
Current Output
<?xml version="1.0" encoding="UTF-8"?>
<Members>
<Member>
<Name>
<fname>Fred</fname>
<id>1234</id>
</Name>
<Locations_1>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Payments_1>
<amount>1000</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1000</amount>
<currency>USD</currency>
</Payments_2>
<Locations_3>
<name>New York</name>
<days>5</days>
<hours>40</hours>
</Locations_3>
<Locations_4>
<name>Boston</name>
<days>4</days>
<hours>32</hours>
</Locations_4>
</Member>
<Member>
<Name>
<fname>Jack</fname>
<id>4567</id>
</Name>
<Locations_1>
<name>New York</name>
<days>5</days>
<hours>30</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Payments_1>
<amount>1500</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1800</amount>
<currency>USD</currency>
</Payments_2>
</Member>
</Members>
If you want to group the Member child elements by node-name() then I think you need to wrap the xsl:for-each-group into xsl:fork:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="#all" version="3.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:mode on-no-match="shallow-copy" streamable="yes" use-accumulators="counters"/>
<xsl:accumulator name="counters" as="map(xs:QName, xs:integer)" initial-value="map{}" streamable="yes">
<xsl:accumulator-rule match="Member" select="map{}"/>
<xsl:accumulator-rule match="Member/*"
select="map:put($value, node-name(), if (map:contains($value, node-name())) then map:get($value, node-name()) + 1 else 1)"/>
</xsl:accumulator>
<xsl:template match="Member">
<xsl:copy>
<xsl:fork>
<xsl:for-each-group select="*" group-by="node-name()">
<xsl:apply-templates select="current-group()"/>
</xsl:for-each-group>
</xsl:fork>
</xsl:copy>
</xsl:template>
<xsl:template match="Member/*">
<xsl:element name="{node-name()}_{accumulator-before('counters')(node-name())}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
This approach only shows the grouping, it doesn't try to special case Name elements or some other way to not output an index if there is only one such element.
Firstly, my sympathy. XML that uses names like Payments_1 and Payments_2 is really bad news, someone is going to hate you for generating it like this. But if that's the kind of XML you've been told to produce, I guess it's not your job to question it.
As far as the requirements are concerned, you haven't made it clear whether the various kinds of sibling nodes are always grouped as in your example (all Locations, then all Payments, etc), or whether they can be interleaved.
One way you might be able to reduce the volume of code is by having a single accumulator holding a map. The map would use element names as the key and the current sibling count for that element as the value.
<accumulator name="counters" as="map(xs:QName, xs:integer)" initial-value="map{}">
<xsl:accumulator-rule match="Member" select="map{}"/>
<xsl:accumulator-rule match="Member/*" select="map:put($value, node-name(.), if (map:contains($value, node-name(.)) then map:get($value, node-name(.))+1 else 1"/>
</accumulator>
<xsl:template match="Members/*">
<xsl:element name="{name()}_{accumulator-before('counters')(node-name(.))}">
....
Another way to do the conditional map:put is
map:put($value, node-name(.), ($value(node-name(.)), 0)[1] + 1)

XSLT: How to find source and target Xpath for the edge?

I want to write an xslt file to transfer an xmi file in a graphical file. But I meet the problem that the edge can not connect the right source node and target node. I have tried already two weeks. But I am still confused. Please help me. Thanks a million.
The original code is:
<?xml version="1.0" encoding="UTF-8"?>
<xml xmlns:xmi="#">
<element xmi:id="BasicElement-Line1" name="Line1" xmi:type="association"/>
<element xmi:id="BasicElement-Line2" name="Line2" xmi:type="association"/>
<element xmi:id="BasicElement-Object1" name="Object1" xmi:type="class">
<ownedAttribute xmi:type="Property" name="input" type="BasicElement-Object2" association="BasicElement-Line1"/>
<ownedAttribute xmi:type="Property" name="output" type="BasicElement-Object3" association="BasicElement-Line2"/>
</element>
<element xmi:id="BasicElement-Object2" name="Object2" xmi:type="class">
</element>
<element xmi:id="BasicElement-Object3" name="Object3" xmi:type="class">
</element>
</xml>
and my aim code is:
<?xml version="1.0" encoding="UTF-8"?>
<xmi xmlns:y="##">
<edge target="N1002D" source="N1001B" id="N10005">
<y:PolyLineEdge>
<y:Arrows target="none" source="none" />
</y:PolyLineEdge>
</edge>
<edge target="N1002D" source="N1001B" id="N10010">
<y:PolyLineEdge>
<y:Arrows target="none" source="none" />
</y:PolyLineEdge>
</edge>
<node id="N1001B">
<y:NodeLabel>BasicElement-Object1</y:NodeLabel>
</node>
<node id="N1002D">
<y:NodeLabel>BasicElement-Object2</y:NodeLabel>
</node>
<node id="N10033">
<y:NodeLabel>BasicElement-Object3</y:NodeLabel>
</node>
</xmi>
Because there will be more "class" element in the future. So I used "{generate-id()}" to define the node IDs. But when I do that, I found the edge can not find the way of source node and target node. So I have already worked on it two weeks and have no idea on it. Please help me, I really appreciate.
I'm not really familiar with XMI and the target format, but here's something that should fit your description.
Source:
<?xml version="1.0" encoding="UTF-8"?>
<xml xmlns:xmi="#">
<element xmi:id="BasicElement-Line1" name="Line1" xmi:type="association">
<ownedEnd xmi:type="Property" type="BasicElement-Object1" association="BasicElement-Line1"/>
</element>
<element xmi:id="BasicElement-Line2" name="Line2" xmi:type="association">
<ownedEnd xmi:type="Property" type="BasicElement-Object1" association="BasicElement-Line2"/>
</element>
<element xmi:id="BasicElement-Object1" name="Object1" xmi:type="class">
<ownedAttribute xmi:type="Property" name="input" type="BasicElement-Object2" association="BasicElement-Line1"/>
<ownedAttribute xmi:type="Property" name="output" type="BasicElement-Object3" association="BasicElement-Line2"/>
</element>
<element xmi:id="BasicElement-Object2" name="Object2" xmi:type="class">
</element>
<element xmi:id="BasicElement-Object3" name="Object3" xmi:type="class">
</element>
</xml>
Transformed with (adjust the namespaces to the correct uris):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xmi="#" xmlns:y="##"
exclude-result-prefixes="xmi" version="1.0">
<xsl:output indent="yes"/>
<xsl:template match="xml">
<xmi>
<xsl:apply-templates select="element"/>
</xmi>
</xsl:template>
<xsl:template match="element[#xmi:type='class']">
<node id="{generate-id()}">
<y:NodeLabel>
<xsl:value-of select="#xmi:id"/>
</y:NodeLabel>
<y:UMLClassNode/>
</node>
</xsl:template>
<xsl:template match="element[#xmi:type='association']">
<!-- association name -->
<xsl:variable name="association" select="ownedEnd/#association"/>
<!-- id of source -->
<xsl:variable name="ownedEnd-type" select="ownedEnd/#type"/>
<!-- using association variable to select the correct id of target -->
<xsl:variable name="ownedAttribute-type"
select="//element[#xmi:id = $ownedEnd-type]/ownedAttribute[#association = $association]/#type"/>
<edge id="{ generate-id() }"
source="{ generate-id( /xml/element[#xmi:id = $ownedEnd-type] ) }"
target="{ generate-id( /xml/element[#xmi:id = $ownedAttribute-type] ) }">
<y:PolyLineEdge>
<y:Arrows source="none" target="none"/>
</y:PolyLineEdge>
</edge>
</xsl:template>
</xsl:stylesheet>
gives you:
<xmi xmlns:y="##">
<edge id="d0e3" source="d0e13" target="d0e20">
<y:PolyLineEdge>
<y:Arrows source="none" target="none"/>
</y:PolyLineEdge>
</edge>
<edge id="d0e8" source="d0e13" target="d0e23">
<y:PolyLineEdge>
<y:Arrows source="none" target="none"/>
</y:PolyLineEdge>
</edge>
<node id="d0e13">
<y:NodeLabel>BasicElement-Object1</y:NodeLabel>
<y:UMLClassNode/>
</node>
<node id="d0e20">
<y:NodeLabel>BasicElement-Object2</y:NodeLabel>
<y:UMLClassNode/>
</node>
<node id="d0e23">
<y:NodeLabel>BasicElement-Object3</y:NodeLabel>
<y:UMLClassNode/>
</node>
</xmi>

how to select two nodes at one select statement

I have a xml file like this
<netcdf xmlns="http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2" location="file:/dev/null" iosp="lasp.tss.iosp.ValueGeneratorIOSP" start="0" increment="1">
<attribute name="title" value="Vector time series"/>
<dimension name="time" length="100"/>
<variable name="time" shape="time" type="double">
<attribute name="units" type="String" value="seconds since 1970-01-01T00:00"/>
</variable>
<group name="Vector" tsdsType="Structure" shape="time">
<variable name="x" shape="time" type="double"/>
<variable name="y" shape="time" type="double"/>
<variable name="z" shape="time" type="double"/>
</group>
</netcdf>
And I want to the value of the nodes whose name is either variable or group, so what's the right syntax to do things like?
<xsl:value-of select="/netcdf/variable or /netcdf/group"/>
Thanks in advance
Use (namespace declared with prefix x):
"/x:netcdf/*[self::x:variable or self::x:group]"
Do note that XSLT 1.0 xsl:value-of will return always the text value of the first element found. use better xsl:copy-of to show all returned elements.
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:d="http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:copy-of select="/*/*[self::d:variable or self::d:group]"/>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<netcdf xmlns="http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2"
location="file:/dev/null" iosp="lasp.tss.iosp.ValueGeneratorIOSP"
start="0" increment="1">
<attribute name="title" value="Vector time series"/>
<dimension name="time" length="100"/>
<variable name="time" shape="time" type="double">
<attribute name="units" type="String"
value="seconds since 1970-01-01T00:00"/>
</variable>
<group name="Vector" tsdsType="Structure" shape="time">
<variable name="x" shape="time" type="double"/>
<variable name="y" shape="time" type="double"/>
<variable name="z" shape="time" type="double"/>
</group>
</netcdf>
produces (what I guess is) the wanted result:
<variable xmlns="http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2" name="time" shape="time" type="double">
<attribute name="units" type="String" value="seconds since 1970-01-01T00:00"/>
</variable>
<group xmlns="http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2" name="Vector" tsdsType="Structure" shape="time">
<variable name="x" shape="time" type="double"/>
<variable name="y" shape="time" type="double"/>
<variable name="z" shape="time" type="double"/>
</group>
Do note: <xsl:value-of> outputs the string value, while <xsl:copy-of> outputs the node(s). In your case the string value of either element is empty on white-space only, so you probably want the elements themselves.
This is really an XPath question and there are different possible solutions:
/*/*[self::d:variable or self::d:group]
(the above is used in the transformation above), or:
/*/d:variable | /*d:group
This one uses the XPath union operator /

XSLT, sort and group by year-date

Regarding Umbraco XSLT version 1.
I have aprox. 150 news items in XML. Lets say like this (all is pseudocode until I get more familiar with this xml/xslt):
<news>
<data alias=date>2008-10-20</data>
</news>
<news>
<data alias=date>2009-11-25</data>
</news><news>
<data alias=date>2009-11-20</data>
</news> etc. etc....
I would like to run through the XML and create html-output as a news archive. Something like (tags not important):
2008
Jan
Feb
...
2009
Jan
Feb
Mar
etc. etc.
I can only come up with a nested for-each (pseudocode):
var year_counter = 2002
var month_counter = 1
<xsl:for-each select="./data [#alias = 'date']=year_counter">
<xsl:for-each select="./data [#alias = 'date']=month_counter">
<xsl:value-of select="data [#alias = 'date']>
"...if month_counter==12 end, else month_counter++ ..."
</xsl:for-each>
"... year_counter ++ ..."
</xsl:for-each>
But a programmer pointet out that looping through 10 years will give 120 loops and that is bad coding. Since I think Umbraco caches the result I am not so concerned, plus in this case there will be a max. of 150 records.
Any clues on how to sort and output many news items and group them in year and group each year in months?
Br. Anders
For the following solution I used this XML file:
<root>
<news>
<data alias="date">2008-10-20</data>
</news>
<news>
<data alias="date">2009-11-25</data>
</news>
<news>
<data alias="date">2009-11-20</data>
</news>
<news>
<data alias="date">2009-03-20</data>
</news>
<news>
<data alias="date">2008-01-20</data>
</news>
</root>
and this XSLT 1.0 transformation:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:cfg="http://tempuri.org/config"
exclude-result-prefixes="cfg"
>
<xsl:output method="xml" encoding="utf-8" />
<!-- index news by their "yyyy" value (first 4 chars) -->
<xsl:key
name="kNewsByY"
match="news"
use="substring(data[#alias='date'], 1, 4)"
/>
<!-- index news by their "yyyy-mm" value (first 7 chars) -->
<xsl:key
name="kNewsByYM"
match="news"
use="substring(data[#alias='date'], 1, 7)"
/>
<!-- translation table (month number to name) -->
<config xmlns="http://tempuri.org/config">
<months>
<month id="01" name="Jan" />
<month id="02" name="Feb" />
<month id="03" name="Mar" />
<month id="04" name="Apr" />
<month id="05" name="May" />
<month id="06" name="Jun" />
<month id="07" name="Jul" />
<month id="08" name="Aug" />
<month id="09" name="Sep" />
<month id="10" name="Oct" />
<month id="11" name="Nov" />
<month id="12" name="Dec" />
</months>
</config>
<xsl:template match="root">
<xsl:copy>
<!-- group news by "yyyy" -->
<xsl:apply-templates mode="year" select="
news[
generate-id()
=
generate-id(key('kNewsByY', substring(data[#alias='date'], 1, 4))[1])
]
">
<xsl:sort select="data[#alias='date']" order="descending" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<!-- year groups will be enclosed in a <year> element -->
<xsl:template match="news" mode="year">
<xsl:variable name="y" select="substring(data[#alias='date'], 1, 4)" />
<year num="{$y}">
<!-- group this year's news by "yyyy-mm" -->
<xsl:apply-templates mode="month" select="
key('kNewsByY', $y)[
generate-id()
=
generate-id(key('kNewsByYM', substring(data[#alias='date'], 1, 7))[1])
]
">
<xsl:sort select="data[#alias='date']" order="descending" />
</xsl:apply-templates>
</year>
</xsl:template>
<!-- month groups will be enclosed in a <month> element -->
<xsl:template match="news" mode="month">
<xsl:variable name="ym" select="substring(data[#alias='date'], 1, 7)" />
<xsl:variable name="m" select="substring-after($ym, '-')" />
<!-- select the label of the current month from the config -->
<xsl:variable name="label" select="document('')/*/cfg:config/cfg:months/cfg:month[#id = $m]/#name" />
<month num="{$m}" label="{$label}">
<!-- process news of the current "yyyy-mm" group -->
<xsl:apply-templates select="key('kNewsByYM', $ym)">
<xsl:sort select="data[#alias='date']" order="descending" />
</xsl:apply-templates>
</month>
</xsl:template>
<!-- for the sake of this example, news elements will just be copied -->
<xsl:template match="news">
<xsl:copy-of select="." />
</xsl:template>
</xsl:stylesheet>
When the transformation is applied, the following output is produced:
<root>
<year num="2009">
<month num="11" label="Nov">
<news>
<data alias="date">2009-11-25</data>
</news>
<news>
<data alias="date">2009-11-20</data>
</news>
</month>
<month num="03" label="Mar">
<news>
<data alias="date">2009-03-20</data>
</news>
</month>
</year>
<year num="2008">
<month num="10" label="Oct">
<news>
<data alias="date">2008-10-20</data>
</news>
</month>
<month num="01" label="Jan">
<news>
<data alias="date">2008-01-20</data>
</news>
</month>
</year>
</root>
It has the right structure already, you can adapt actual appearance to your own needs.
The solution is a two-phase Muenchian grouping approach. In the first phase, news items are grouped by year, in the second phase by year-month.
Please refer to my explanation of <xsl:key> and key() over here. You don't need to read the other question, though it is a similar problem. Just read the lower part of my answer.
What you need is the so-called Muenchian Grouping method, which addresses exactly this problem/pattern for XSLT.
Basically, it groups by finding unique keys and looping over the entries contained in the key being used.
in addition to lucero, check out Xsl grouping duplicates problem for avoiding problems with month names being removes
You can't do month_counter++ in XSLT, it's not a procedural language and it's not how XSLT works. So, it's kind of pointless to worry about this being inefficient if this does not work this way.
This looks like a major pain in the neck in XSLT. My XSLT is not fresh enough to try and actually implement it. But here are two ways:
1)
use xsl:key to extract all unique years-
then iterate through these years. For each year do
use xsl:key to extract all months
For each month do
2) (seems easier, if it works.)
sort them by date, save sorted array in variable
iterate this variable (it's important that variable holds sorted array)
each time look at preceding-sibling. If its year/month not equal to the current element, write the appropriate header
3) Forget XSLT, use a real programming language.