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!
Related
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
XSL stylesheet is generating repeated output. Below is an example of it. The same thing is repeated thrice. In the first set of xml, i am only getting value of first attribute and in the secound from second attribute and so on.
<?xml version="1.0" encoding="UTF-8"?>
<obj>
<desc value="113662176"/>
<index value="" name="MATERIALNUMMER"/>
<index value="" name="DOKUMENTENART"/>
</obj>
<obj>
<desc value=""/>
<index value="66260383180" name="MATERIALNUMMER"/>
<index value="" name="DOKUMENTENART"/>
</obj>
<obj>
<desc value=""/>
<index value="" name="MATERIALNUMMER"/>
<index value="Fertigungsauftrag" name="DOKUMENTENART"/>
</obj>
I have also tired with xsl when and choose but the output was same. below is an example input xml with some attributes.
<?xml version = "1.0" encoding = "utf-8"?>
<root>
<document>
<field level = "document" name = "Fertigungsauftragsnummer" value = "113662176"/>
<field level = "document" name = "Materialnummer" value = "66260383180"/>
<field level = "document" name = "Dokumentenart" value = "Fertigungsauftrag"/>
</document>
</root>
below is the xsl style sheet which i am using for conversion. In the xsl template if i use match="/*" i dont get repeated output neither do i get values of xml attributes. It seems that for every xsl if we have one specific output. How can I make xsl style sheet to compile only once the input xml for all the xsl if statements?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version = "1.0" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/root/document/*">
<xsl:text>
</xsl:text><xsl:text disable-output-escaping="yes"><</xsl:text><xsl:text>obj</xsl:text><xsl:text disable-output-escaping="yes">></xsl:text><xsl:text>
</xsl:text>
<xsl:text disable-output-escaping="yes"><</xsl:text><xsl:text>desc value="</xsl:text>
<xsl:if test="#name='Fertigungsauftragsnummer'">
<xsl:value-of select="#value" />
<xsl:if test="#name='Materialnummer'">
<xsl:value-of select="#value" />
<xsl:if test="#name='Dokumentenart'">
<xsl:value-of select="#value" />
</xsl:if>
</xsl:if>
</xsl:if>
<xsl:text disable-output-escaping="yes">"/></xsl:text><xsl:text>
</xsl:text>
<xsl:text disable-output-escaping="yes"><</xsl:text><xsl:text>/obj</xsl:text><xsl:text disable-output-escaping="yes">></xsl:text><xsl:text>
</xsl:text>
</xsl:template>
</xsl:transform>
Expected output is shown below
<?xml version="1.0" encoding="UTF-8"?>
<obj>
<desc value="113662176"/>
<index value="66260383180" name="MATERIALNUMMER"/>
<index value="Fertigungsauftrag" name="DOKUMENTENART"/>
</obj>
You current approach of using xsl:text with disable-output-escaping is not the right approach when building XML. You should be writing out the XML tags directly. It looks like you want to convert document elements into obj elements so you should have a template like this
<xsl:template match="/root/document">
<obj>
<xsl:apply-templates select="field" />
</obj>
</xsl:template>
(In your current XSLT, you were converting field into obj which is why you got three of them).
It also looks like you want fields with name "Fertigungsauftragsnummer" into a desc element, so just create a template for that.
<xsl:template match="field[#name='Fertigungsauftragsnummer']">
<desc value="{#value}"/>
</xsl:template>
Note the syntax for the attributes using curly braces. These are known as Attribute Value Templates.
For the other two fields, you can share a common template, as it looks like you just want to make the name upper-case in both cases
<xsl:template match="field">
<index value="{#value}" name="{translate(#name, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')}"/>
</xsl:template>
Try this XSLT
<xsl:stylesheet version = "1.0" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/root/document">
<obj>
<xsl:apply-templates select="field" />
</obj>
</xsl:template>
<xsl:template match="field[#name='Fertigungsauftragsnummer']">
<desc value="{#value}"/>
</xsl:template>
<xsl:template match="field">
<index value="{#value}" name="{translate(#name, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')}"/>
</xsl:template>
</xsl:stylesheet>
Using XSLT 1.0
Is it possible to filter many to many attributes, i mean as below example:
"../../../../fieldmap/field[#name" i.e. more then 1 elements as fieldmap containing "field/#name" attribute are exists and it is comparing with definition/#title and there too more then one definition element exists containing #title.
EXAMPLE:
<xsl:for-each select="../../../../fieldmaps/field[#name=../destination/#title]">
Can you please suggest me how it could possible to achieve -- if field containing #name existed in any of defination/#title then only those records should be process within for-each loop?
(as now, it looks, it will just compare with first #title attribute and consider all fieldmaps/field/#name attributes)
Thanks
You can achieve that using a variable:
<xsl:variable name="titles" select="../destination/#title"/>
<!--now "titles" contains a nodeset with all the titles -->
<xsl:for-each select="../../../../fieldmaps/field[#name=$titles]">
<!-- you process each field with a name contained inside the titles nodeset -->
</xsl:for-each>
Here you have a simplified example:
INPUT:
<parent>
<fieldmaps>
<field name="One"/>
<field name="Two"/>
<field name="Three"/>
</fieldmaps>
<destinations>
<destination title="One"/>
<destination title="Two"/>
</destinations>
</parent>
TEMPLATE:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- ++++++++++++++++++++++++++++++++ -->
<xsl:template match="parent">
<Results>
<xsl:variable name="titles" select="destinations/destination/#title"/>
<xsl:for-each select="fieldmaps/field[#name=$titles]">
<Result title="{#name}"/>
</xsl:for-each>
</Results>
</xsl:template>
<!-- ++++++++++++++++++++++++++++++++ -->
</xsl:stylesheet>
OUTPUT:
<Results>
<Result title="One"/>
<Result title="Two"/>
</Results>
I hope this helps!
I have two tags in the input file, variable and type:
<variable baseType="int" name="X">
</variable>
<type baseType="structure" name="Y">
<variableInstance name="X" />
</type>
And I need to generate the following output file:
<Item name="Y">
<Field name="X" type="Long" />
</Item>
So conceptually my approach here has been to convert the type tag into the Item tage, the variable instance to the Field. That's working fine:
<xsl:for-each select="type[#baseType='structure']">
<Item>
<xsl:attribute name="name"><xsl:value-of select="#name" /></xsl:attribute>
<xsl:for-each select="variableInstance">
<Field>
<xsl:attribute name="name"><xsl:value-of select="#name" /></xsl:attribute>
<xsl:attribute name="type">**THIS IS WHERE I'M STUCK**</xsl:attribute>
</Field>
</xsl:for-each>
</Item>
</xsl:for-each>
The problem I'm stuck on is:
I don't know how to get the variableInstance/Field tag to match on the variable tag by name, so I can access the baseType.
I need to map "int" to "Long" once I'm able to do 1.
Thanks in advance!
PROBLEM 1.
For the first problem that you have you can use a key:
<xsl:key name="variable-key" match="//variable" use="#name" />
That key is going to index all variable elements in the document, using their name. So now, we can access any of those elements by using the following XPath expression:
key('variable-key', 'X')
Using this approach is efficient when you have a lot of variable elements.
NOTE: this approach is not valid if each variable has its own scope (i.e. you have local variables which are not visible in different parts of the document). In that case this approach should be modified.
PROBLEM 2.
For mapping attributes you could use a template like the following:
<xsl:template match="#baseType[. = 'int']">
<xsl:attribute name="baseType">
<xsl:value-of select="'Long'" />
</xsl:attribute>
</xsl:template>
The meaning of this transformation is: each time that we match a baseType attribute with int value, it has to be replaced by a Long value.
This transformation would be in place for each #baseType attribute in the document.
Using the described strategies a solution could be:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<!-- Index all variable elements in the document by name -->
<xsl:key name="variable-key"
match="//variable"
use="#name" />
<!-- Just for demo -->
<xsl:template match="text()" />
<!-- Identity template: copy attributes by default -->
<xsl:template match="#*">
<xsl:copy>
<xsl:value-of select="." />
</xsl:copy>
</xsl:template>
<!-- Match the structure type -->
<xsl:template match="type[#baseType='structure']">
<Item>
<xsl:apply-templates select="*|#*" />
</Item>
</xsl:template>
<!-- Match the variable instance -->
<xsl:template match="variableInstance">
<Field>
<!-- Use the key to find the variable with the current name -->
<xsl:apply-templates select="#*|key('variable-key', #name)/#baseType" />
</Field>
</xsl:template>
<!-- Ignore attributes with baseType = 'structure' -->
<xsl:template match="#baseType[. = 'structure']" />
<!-- Change all baseType attributes with long values to an attribute
with the same name but with an int value -->
<xsl:template match="#baseType[. = 'int']">
<xsl:attribute name="baseType">
<xsl:value-of select="'Long'" />
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
That code is going to transform the following XML document:
<!-- The code element is present just for demo -->
<code>
<variable baseType="int" name="X" />
<type baseType="structure" name="Y">
<variableInstance name="X" />
</type>
</code>
into
<Item name="Y">
<Field baseType="Long" name="X"/>
</Item>
Oki I've got a solution for point 1 xsltcake slice or with use of templates. For point two I would probably use similar template to the one that Pablo Pozo used in his answer
I have an xml document that contains some "Item" elements with ids. I want to make a list of the unique Item ids. The Item elements are not in a list though - they can be at any depth within the xml document - for example:
<Node>
<Node>
<Item id="1"/>
<Item id="2"/>
</Node>
<Node>
<Item id="1"/>
<Node>
<Item id="3"/>
</Node>
</Node>
<Item id="2"/>
</Node>
I would like the output 1,2,3 (or a similar representation). If this can be done with a single xpath then even better!
I have seen examples of this for lists of sibling elements, but not for a general xml tree structure. I'm also restricted to using xslt 1.0 methods. Thanks!
Selecting all unique items with a single XPath expression (without indexing, beware of performance issues):
//Item[not(#id = preceding::Item/#id)]
Try this (using Muenchian grouping):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="item-id" match="Item" use="#id" />
<xsl:template match="/Node">
<xsl:for-each select="//Item[count(. | key('item-id', #id)[1]) = 1]">
<xsl:value-of select="#id" />,
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Not sure if this is what you mean, but just in case.
In the html
<xsl:apply-templates select="item"/>
The template.
<xsl:template match="id">
<p>
<xsl:value-of select="#id"/> -
<xsl:value-of select="."/>
</p>
</xsl:template>