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 /
Related
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="."/>
Suppose I have the following input file:
<root>
<container_items>
<item Id="a">
<Content Name="red_dark" />
</item>
<item Id="b">
<Content Name="yellow" />
</item>
<item Id="c">
<Content Name="blue_dark" />
</item>
<item Id="d">
<Content Name="green" />
</item>
</container_items>
<container_refs>
<item_ref Id="a" />
<item_ref Id="b" />
<item_ref Id="c" />
<item_ref Id="d" />
</container_refs>
</root>
The real file is a little more complicated, but I will make it look simpler with closer criteria here to remove those 'item' elements that have 'Content' child element with a Name attribute that ends with "_dark". I managed to remove the 'item' elements that I don't need, however, the corresponding 'item_ref' elements left. Let's say I removed the 'item' elements that match my criteria. My goal is the 'item_ref' elements with Id="a" or ="c" also to be removed (those are the Id's of the matched and removed 'item' elements). So the expected end result is.
<root>
<container_items>
<item Id="b">
<Content Name="yellow" />
</item>
<item Id="d">
<Content Name="green" />
</item>
</container_items>
<container_refs>
<item_ref Id="b" />
<item_ref Id="d" />
</container_refs>
</root>
Apparently, I need to remove all 'item_ref' elements that have Id attribute with a value in the list of values collected from certain 'item' elements' Id attributes (that match my existing criteria for 'item' elements).
My XSL file is the following (focusing only on the criteria):
<xsl:template match="//item[./Content[substring(#Name, string-length(#Name)- string-length('_dark') + 1) = '_dark']]" />
Based on my criteria the 'item' elements matching the criteria are removed, but then the associated 'item_ref' elements remain in the input file, causing the following result:
<root>
<container_items>
<item Id="b">
<Content Name="yellow" />
</item>
<item Id="d">
<Content Name="green" />
</item>
</container_items>
<container_refs>
<item_ref Id="a" />
<item_ref Id="b" />
<item_ref Id="c" />
<item_ref Id="d" />
</container_refs>
</root>
Thanks in advance for your support.
You can achieve this with a xsl:key and two empty templates:
<xsl:key name="items" match="container_items/item" use="#Id" />
and the two empty templates are
<xsl:template match="container_items/item[substring(Content/#Name, string-length(Content/#Name)-string-length('_dark') + 1) = '_dark']" />
<xsl:template match="container_refs/item_ref[substring(key('items',#Id)/Content/#Name,string-length(key('items',#Id)/Content/#Name)-string-length('_dark') + 1) = '_dark']" />
The first one removes the items from container_items and the second one removes the item_refs from the container_refs.
Define a key as:
<xsl:key name="item" match="item" use="#Id" />
then use:
<xsl:template match="item_ref[substring(key('item', #Id)/#Name, string-length(key('item', #Id)/#Name) - string-length('_dark') + 1) = '_dark']"/>
to remove the item_ref nodes.
There's probably a more efficient way to do this by storing the relevant Ids in a variable, but that's the general idea.
Here's a way to do it.
You could optimize the //item[...] by using a key call.
<?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"
version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="item[contains(#Name,'_dark')]"/>
<xsl:template match="item_ref">
<xsl:variable name="ir" select="."/>
<xsl:if test="//item[#Id=$ir/#Id and not(contains(#Name,'_dark'))]">
<xsl:copy>
<xsl:attribute name="Id"><xsl:value-of select="#Id"/></xsl:attribute>
</xsl:copy>
</xsl:if>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
See it working here : https://xsltfiddle.liberty-development.net/93dFepP
I have an XML file with this structure:
<DetailTxt>
<Text>
<span>Some Text</span>
</Text>
<TextComplement Kind="Owner" MarkLbl="1">
<ComplCaption>
Caption 1
</ComplCaption>
<ComplBody>
Body 1
</ComplBody>
</TextComplement>
<Text>
<span>More Text</span>
</Text>
</DetailTxt>
Here is the part of the XSLT that is relevant here:
<xsl:template match="*[local-name() = 'DetailTxt']">
<xsl:apply-templates select="*[local-name() = 'Text']"/>
</xsl:template>
<xsl:template match="*[local-name() = 'Text']">
<item name="{local-name()}">
<richtext>
<par>
<run>
<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>
<xsl:apply-templates/>
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</run>
</par>
</richtext>
</item>
<item name="{local-name()}">
<richtext>
<par>
<run>
<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>
<xsl:value-of select="concat('[', ../TextComplement/#Kind, ../TextComplement/#MarkLbl,']')" />
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</run>
</par>
</richtext>
</item>
</xsl:template>
I expect the output to look like this:
<item name="Text">
<richtext>
<par>
<run><![CDATA[
<span>Some Text</span>
</p>]]></run>
</par>
</richtext>
</item>
<item name="Text">
<richtext>
<par>
<run><![CDATA[[Owner1]]]></run>
</par>
</richtext>
</item>
But the line using the TextComplement XPath looks like this:
<run><![CDATA[[]]]></run>
All values from TextComplement are missing. Whats wrong with the XPath here?
EDIT: I completely reworked my question and put in a CONCRETE question resulting from the first answer. That kind of invalidates the first answer but IMHO improves the question.
Not sure how the XSLT looks like but you can try adding the following template with the concat() function for getting the output.
<xsl:template match="Text">
<document version="9.0" form="Form1">
<item name="{local-name()}">
<xsl:copy-of select="span" />
</item>
<item name="{local-name()}">
<span>
<xsl:value-of select="concat('[', ../TextComplement/#Kind, ../TextComplement/#MarkLbl, ']')" />
</span>
</item>
</document>
</xsl:template>
This template is applied to the <Text> node and the ../ is used to go up one level and then access the attributes of <TextComplement> using the XPath.
The output of the template when applied to your XML will look like.
<document form="Form1" version="9.0">
<item name="Text">
<span>Some Text</span>
</item>
<item name="Text">
<span>[Owner1]</span>
</item>
</document>
The same template will also get applied to the <Text> node having More Text content and produce similar output.
I found a solution myself for the concrete question. I quess this is IBM Notes / LotusScript specific issue.
When using the selector
../TextComplement/#Kind
the parser returned an empty string. I changed to
../*[local-name() = 'TextComplement']/#Kind
and later (more concrete) to:
./following-sibling::*[local-name() = 'TextComplement']/#Kind
And that worked. I personally see no difference in these notations, but it seams that internally they are handled differently.
I've been struggling to get the output I want using the xsl:value-of select, however I seem not to be on the right track with this.
This is the XML data:
<?xml version="1.0" encoding="utf-8"?>
<ENTRY NAME="CustomerSite" CLASS="Site">
<ENTRY NAME="Production" CLASS="Configuration Set">
<ATTRIBUTE NAME="VersionMinor">
<VALUE>0</VALUE>
</ATTRIBUTE>
<ATTRIBUTE NAME="VersionMajor">
<VALUE>3</VALUE>
</ATTRIBUTE>
<ATTRIBUTE NAME="Site Identifier">
<VALUE>1</VALUE>
</ATTRIBUTE>
<ATTRIBUTE NAME="VersionPoint">
<VALUE>0</VALUE>
</ATTRIBUTE>
<ATTRIBUTE NAME="Note ID Dial String Digits">
<VALUE>*90</VALUE>
</ATTRIBUTE>
<ENTRY NAME="Account Codes" CLASS="Account Codes">
<ATTRIBUTE NAME="Enable">
<VALUE>No</VALUE>
</ATTRIBUTE>
</ENTRY>
</ENTRY>
And this is the XLS code I use:
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<html>
<body>
<xsl:for-each select="ENTRY">
<xsl:if test="#CLASS='SITE'">
<xsl:value-of select="./ENTRY/#NAME"/>
</xsl:if>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
The data I want to extract is the 'CustomerSite' used as the NAME value in the first Entry field.
Thanks
Niteowls
The first problem here is, that the value of attribute CLASS is "Site" (not "SITE").
But than your output will be "Production" because the for-each select the first (root) ENTRY and than the value of select the child ENTRY NAME attribute.
To get "CustomerSite" use.
<xsl:if test="#CLASS='Site'">
<xsl:value-of select="#NAME"/>
</xsl:if>
Or much better (no for-each needed):
<xsl:value-of select="/ENTRY[#CLASS='Site']/#NAME"/>
I've got an XML file with the following structure (multiple "entity" nodes):
<!-- entities.xml -->
<root>
<entity template="foo-template" kind="foo" name="bar">
<groups>
<group id="1">
<definition id="1" name="foobar" />
</group>
</groups>
</entity>
</root>
Many entity nodes have similar attributes and children nodes. I'd like to allow users to create entity templates in a separate file. Referencing the template will be done as follows:
<entity template="foo-template" kind="foo" ... />
Every attribute and child node from "foo-template" should be copied into the entity, except for those that already exist (i.e. allow overriding the template).
I'm not very familiar with XSLT. Is it the right tool for this task, or am I better off implementing this without it?
I'm using C++ and RapidXml, but can use other XML libraries.
Edit: example.
Template file:
<!-- templates.xml -->
<templates>
<entity template="foo-template" name="n/a" model="baz">
<groups>
<group id="1">
<definition id="1" name="def1" />
<definition id="2" name="def2" />
</group>
<group id="2">
<definition id="1" name="def3" />
<definition id="2" name="def4" />
</group>
</groups>
</entity>
</templates>
Output file:
<!-- output.xml -->
<root>
<entity kind="foo" name="bar" model="baz">
<groups>
<group id="1">
<definition id="1" name="foobar" />
</group>
<group id="2">
<definition id="1" name="def3" />
<definition id="2" name="def4" />
</group>
</groups>
</entity>
</root>
So the output contains group 1 from "entities.xml" and group 2 from "templates.xml". No need to merge group nodes with the same id.
If you have a file templates.xml that looks like
<templates>
<entity template="foo-template" kind="foo" name="bar" model="baz" />
<!-- and other entity elements with different template="..." values -->
</templates>
then an XSLT such as the following would achieve what you're after
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:key name="kEntityTemplate" match="entity" use="#template" />
<!-- identity template - copy everything not overridden by another template -->
<xsl:template match="#*|node">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
<xsl:template match="entity[#template]">
<xsl:variable name="thisEntity" select="." />
<!-- switch to templates doc -->
<xsl:for-each select="document('templates.xml')">
<xsl:variable name="template"
select="key('kEntityTemplate', $thisEntity/#template)" />
<entity>
<!-- copy template attributes that are not overridden -->
<xsl:for-each select="$template/#*">
<xsl:if test="not($thisEntity/#*[name() = name(current())])">
<!-- if not, copy the one from the template -->
<xsl:apply-templates select="." />
</xsl:if>
</xsl:for-each>
<!-- copy source attributes -->
<xsl:apply-templates select="$thisEntity/#*[name() != 'template']" />
<!-- deal with elements -->
<xsl:if test="$thisEntity/groups/group | $template/groups/group">
<groups>
<!-- here we select all group elements from the source plus
those group elements from the template that do not also exist
in the source, and sort the whole lot by id -->
<xsl:apply-templates select="$thisEntity/groups/group
| $template/groups/group[not(#id = $thisEntity/groups/group/#id)]">
<xsl:sort select="#id" data-type="number" />
</xsl:apply-templates>
</groups>
</xsl:if>
</entity>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The templates.xml file needs to be in the same directory as the stylesheet.
One option that you have outside of doing any kind of XML transformation is importing the other XML file and then referencing it from within the tags. See here for an example.
This would require your users to have separate template files for each template type which you may not want. However I would prefer the import approach because of the kiss principle. If you're not familiar with XSLT then importing is probably a better way to go as well.
I hope this helps!