I want to transform
<entry>
<parent1>
<object_id>1580</object_id>
</parent1>
<parent1>
<object_id>1586</object_id>
</parent1>
<parent2>
<object_id>1582</object_id>
</parent2>
<parent2>
<object_id>1592</object_id>
</parent2>
</entry>
into
<entry>
<parent1>1580-1586</parent1>
<parent2>1582-1592</parent2>
</entry>
Top-level entry name is unknown. Parent names are unknown, and the number of parent nodes with the same name can vary.
Child nodes are known "object_id".
So, I would like to group the unknown parents in an abstract way, and concatenate child node values, delimited by "-".
Merge XML nodes using XSLT comes close to answering the question, as does Group/merge childs of same nodes in xml/xslt , but they're not quite what I need.
So far I have:
<xsl:key name="groupName" match="*[object_id]" use="."/>
<xsl:template match="*[generate-id(.) = generate-id(key('groupName', .))]">
<xsl:copy>
<xsl:call-template name="join">
<xsl:with-param name="list" select="object_id" />
<xsl:with-param name="separator" select="'-'" />
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="join">
<xsl:param name="list" />
<xsl:param name="separator"/>
<xsl:for-each select="$list">
<xsl:value-of select="." />
<xsl:if test="position() != last()">
<xsl:value-of select="$separator" />
</xsl:if>
</xsl:for-each>
</xsl:template>
Thanks in advance!
Here is a slightly different solution, developed before I noticed Dimitre's post.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*" />
<xsl:key name="kParents" match="*[object_id]" use="local-name()" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="*[*/object_id]">
<xsl:variable name="grandparent-id" select="generate-id()" />
<xsl:copy>
<xsl:apply-templates select="#* | node()[not(object_id)] |
*[generate-id()=
generate-id(
key('kParents',local-name())[generate-id(..)=$grandparent-id][1])]"
mode="group-head" />
</xsl:copy>
</xsl:template>
<xsl:template match="*[object_id]" mode="group-head">
<xsl:variable name="grandparent-id" select="generate-id(..)" />
<xsl:copy>
<xsl:apply-templates select="#* | node()[not(self::object_id)]" />
<xsl:for-each select="key('kParents',local-name())[generate-id(..)=$grandparent-id]/object_id">
<xsl:value-of select="." />
<xsl:if test="position() != last()"> - </xsl:if>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Update
I updated the style-sheet to reflect the OP's comment about '-' being a delimiter, rather that a separator between first and last values.
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kObjByValAndParent" match="object_id"
use="name(..)"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*/*"/>
<xsl:template priority="2" match=
"/*/*[generate-id(object_id)
=
generate-id(key('kObjByValAndParent',name())[1])
]
">
<xsl:copy>
<xsl:value-of select=
"concat(object_id, ' - ',
key('kObjByValAndParent',name())[last()]
)
"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<entry>
<parent1>
<object_id>1580</object_id>
</parent1>
<parent1>
<object_id>1586</object_id>
</parent1>
<parent2>
<object_id>1582</object_id>
</parent2>
<parent2>
<object_id>1592</object_id>
</parent2>
</entry>
produces the wanted, correct result:
<entry>
<parent1>1580 - 1586</parent1>
<parent2>1582 - 1592</parent2>
</entry>
Explanation:
Proper use and overriding of the identity rule.
Proper use of the Muenchian grouping method.
II. In case all values must be concatenated together, use this slightly modified solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kObjByValAndParent" match="object_id"
use="name(..)"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*/*"/>
<xsl:template priority="2" match=
"/*/*[generate-id(object_id)
=
generate-id(key('kObjByValAndParent',name())[1])
]
">
<xsl:copy>
<xsl:for-each select="key('kObjByValAndParent',name())">
<xsl:if test="not(position()=1)"> - </xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Related
I have an xml as below:
<?xml version="1.0" encoding="UTF-8"?>
<mdb:MD_Metadata xmlns:mdb="http://standards.iso.org/iso/19115/-3/mdb/1.0"
xmlns:cit="http://standards.iso.org/iso/19115/-3/cit/1.0"
xmlns:lan="http://standards.iso.org/iso/19115/-3/lan/1.0"
xmlns:mcc="http://standards.iso.org/iso/19115/-3/mcc/1.0"
xmlns:gco="http://standards.iso.org/iso/19115/-3/gco/1.0">
<mdb:metadataIdentifier>
<mcc:MD_Identifier>
<mcc:authority>
<cit:CI_Citation>
<cit:title>
<gco:CharacterString>Old UUID</gco:CharacterString>
</cit:title>
</cit:CI_Citation>
</mcc:authority>
<mcc:code>
<gco:CharacterString>3796749d-c8a5-46ae-af11-24a977cb1d9a</gco:CharacterString>
</mcc:code>
<mcc:codeSpace>
<gco:CharacterString>urn:uuid</gco:CharacterString>
</mcc:codeSpace>
</mcc:MD_Identifier>
</mdb:metadataIdentifier>
</mdb:MD_Metadata>
I want to update the namespaces (mdb and cit to2.0) before applying other templates:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:cit1="http://standards.iso.org/iso/19115/-3/cit/1.0"
xmlns:cit="http://standards.iso.org/iso/19115/-3/cit/2.0"
xmlns:lan="http://standards.iso.org/iso/19115/-3/lan/1.0"
xmlns:mcc="http://standards.iso.org/iso/19115/-3/mcc/1.0"
xmlns:mco="http://standards.iso.org/iso/19115/-3/mco/1.0"
xmlns:mda="http://standards.iso.org/iso/19115/-3/mda/1.0"
xmlns:mdb1="http://standards.iso.org/iso/19115/-3/mdb/1.0"
xmlns:mdb="http://standards.iso.org/iso/19115/-3/mdb/2.0"
xmlns:gco="http://standards.iso.org/iso/19115/-3/gco/1.0"
xmlns:gcx="http://standards.iso.org/iso/19115/-3/gcx/1.0"
xmlns:gml="http://www.opengis.net/gml/3.2"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
exclude-result-prefixes="#all">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
<xsl:strip-space elements="*"/>
<xsl:template match="mdb1:*">
<xsl:element name="{name()}" namespace="http://standards.iso.org/iso/19115/-3/mdb/2.0">
<xsl:if test="count(ancestor::*) = 0">
<xsl:call-template name="add-iso19115-3.2018-namespaces"/>
</xsl:if>
</xsl:element>
</xsl:template>
<xsl:template match="cit1:*" >
<xsl:element name="{name()}" namespace="http://standards.iso.org/iso/19115/-3/cit/2.0">
<xsl:apply-templates select="#*|*"/>
</xsl:element>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<!--Identity template that will copy every attribute, element, comment, and processing instruction to the output-->
<xsl:template match="#*|node()">
<xsl:copy copy-namespaces="no">
<!-- Including any attributes it has and any child nodes -->
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="mdb:MD_Metadata" mode="main">
<xsl:copy>
<xsl:apply-templates select="#* , node()"/>
<!-- add mdb:defaultLocale after mdb:metadataIdentifier -->
<mdb:defaultLocale>
<lan:PT_Locale id="EN">
<lan:language>
<lan:LanguageCode codeList="http://www.loc.gov/standards/iso639-2/" codeListValue="eng"/>
</lan:language>
<lan:characterEncoding>
<lan:MD_CharacterSetCode codeList="http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml#MD_CharacterSetCode" codeListValue="utf8"/>
</lan:characterEncoding>
</lan:PT_Locale>
</mdb:defaultLocale>
</xsl:copy>
</xsl:template>
<!-- replace cit:title -->
<xsl:template match="mdb:metadataIdentifier/mcc:MD_Identifier/mcc:authority/cit:CI_Citation/cit:title/gco:CharacterString/text()">
<xsl:value-of select="'UUID Test'"/>
</xsl:template>
<!-- add mcc:description -->
<xsl:template match="mdb:metadataIdentifier/mcc:MD_Identifier">
<xsl:apply-templates/>
<mcc:description>
<gco:CharacterString>Data was submitted
</gco:CharacterString>
</mcc:description>
</xsl:template>
<!-- update attribute 'codeLIst' value of mdb:metadataScope > mdb:MD_MetadataScope > mdb:resourceScope > mcc:MD_ScopeCode -->
<xsl:template match="mdb:metadataScope/mdb:MD_MetadataScope/mdb:resourceScope/mcc:MD_ScopeCode">
<xsl:if test="#codeListValue">
<xsl:attribute name="codeList">
<xsl:value-of select="'https://schemas.isotc211.org/19115/resources/Codelist/cat/codelists.xml#MD_ScopeCode'" />
</xsl:attribute>
</xsl:if>
</xsl:template>
<xsl:template name="add-iso19115-3.2018-namespaces">
<xsl:namespace name="xsi" select="'http://www.w3.org/2001/XMLSchema-instance'"/>
<xsl:namespace name="cit" select="'http://standards.iso.org/iso/19115/-3/cit/2.0'"/>
<xsl:namespace name="lan" select="'http://standards.iso.org/iso/19115/-3/lan/1.0'"/>
<xsl:namespace name="mcc" select="'http://standards.iso.org/iso/19115/-3/mcc/1.0'"/>
<xsl:namespace name="gco" select="'http://standards.iso.org/iso/19115/-3/gco/1.0'"/>
<xsl:namespace name="xlink" select="'http://www.w3.org/1999/xlink'"/>
</xsl:template>
</xsl:stylesheet>
The stylesheet above only work if the namespaces associated with mdb and cit in the original file are as below:
<mdb:MD_Metadata xmlns:mdb="http://standards.iso.org/iso/19115/-3/mdb/2.0"
xmlns:cit="http://standards.iso.org/iso/19115/-3/cit/2.0"
xmlns:lan="http://standards.iso.org/iso/19115/-3/lan/1.0"
xmlns:mcc="http://standards.iso.org/iso/19115/-3/mcc/1.0"
xmlns:gco="http://standards.iso.org/iso/19115/-3/gco/1.0">
....
How can I first update the namespaces and then apply the rest templates against the updated xml?
Perhaps the following is what you want, performing the namespace transformations and the other changes in one transformation step:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:cit1="http://standards.iso.org/iso/19115/-3/cit/1.0"
xmlns:cit="http://standards.iso.org/iso/19115/-3/cit/2.0"
xmlns:lan="http://standards.iso.org/iso/19115/-3/lan/1.0"
xmlns:mcc="http://standards.iso.org/iso/19115/-3/mcc/1.0"
xmlns:mco="http://standards.iso.org/iso/19115/-3/mco/1.0"
xmlns:mda="http://standards.iso.org/iso/19115/-3/mda/1.0"
xmlns:mdb1="http://standards.iso.org/iso/19115/-3/mdb/1.0"
xmlns:mdb="http://standards.iso.org/iso/19115/-3/mdb/2.0"
xmlns:gco="http://standards.iso.org/iso/19115/-3/gco/1.0"
xmlns:gcx="http://standards.iso.org/iso/19115/-3/gcx/1.0"
xmlns:gml="http://www.opengis.net/gml/3.2"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
exclude-result-prefixes="#all">
<xsl:output method="xml" indent="yes"/>
<!--Identity template that will copy every attribute, element, comment, and processing instruction to the output-->
<xsl:template match="#*|node()">
<xsl:copy copy-namespaces="no">
<!-- Including any attributes it has and any child nodes -->
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/mdb1:*">
<xsl:element name="mdb:{local-name()}">
<xsl:call-template name="add-iso19115-3.2018-namespaces"/>
<xsl:apply-templates select="#* , node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="mdb1:*">
<xsl:element name="mdb:{local-name()}">
<xsl:apply-templates select="#* , node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="cit1:*">
<xsl:element name="cit:{local-name()}">
<xsl:apply-templates select="#* , node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="mdb1:MD_Metadata">
<xsl:copy>
<xsl:apply-templates select="#* , node()"/>
<!-- add mdb:defaultLocale after mdb:metadataIdentifier -->
<mdb:defaultLocale>
<lan:PT_Locale id="EN">
<lan:language>
<lan:LanguageCode codeList="http://www.loc.gov/standards/iso639-2/" codeListValue="eng"/>
</lan:language>
<lan:characterEncoding>
<lan:MD_CharacterSetCode codeList="http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml#MD_CharacterSetCode" codeListValue="utf8"/>
</lan:characterEncoding>
</lan:PT_Locale>
</mdb:defaultLocale>
</xsl:copy>
</xsl:template>
<!-- replace cit:title -->
<xsl:template match="mdb1:metadataIdentifier/mcc:MD_Identifier/mcc:authority/cit1:CI_Citation/cit1:title/gco:CharacterString/text()">
<xsl:value-of select="'UUID Test'"/>
</xsl:template>
<!-- add mcc:description -->
<xsl:template match="mdb1:metadataIdentifier/mcc:MD_Identifier">
<xsl:apply-templates/>
<mcc:description>
<gco:CharacterString>Data was submitted
</gco:CharacterString>
</mcc:description>
</xsl:template>
<!-- update attribute 'codeLIst' value of mdb:metadataScope > mdb:MD_MetadataScope > mdb:resourceScope > mcc:MD_ScopeCode -->
<xsl:template match="mdb1:metadataScope/mdb1:MD_MetadataScope/mdb1:resourceScope/mcc:MD_ScopeCode">
<xsl:if test="#codeListValue">
<xsl:attribute name="codeList">
<xsl:value-of select="'https://schemas.isotc211.org/19115/resources/Codelist/cat/codelists.xml#MD_ScopeCode'" />
</xsl:attribute>
</xsl:if>
</xsl:template>
<xsl:template name="add-iso19115-3.2018-namespaces">
<xsl:namespace name="xsi" select="'http://www.w3.org/2001/XMLSchema-instance'"/>
<xsl:namespace name="cit" select="'http://standards.iso.org/iso/19115/-3/cit/2.0'"/>
<xsl:namespace name="lan" select="'http://standards.iso.org/iso/19115/-3/lan/1.0'"/>
<xsl:namespace name="mcc" select="'http://standards.iso.org/iso/19115/-3/mcc/1.0'"/>
<xsl:namespace name="gco" select="'http://standards.iso.org/iso/19115/-3/gco/1.0'"/>
<xsl:namespace name="xlink" select="'http://www.w3.org/1999/xlink'"/>
</xsl:template>
</xsl:stylesheet>
I'm struggling to get this abomination called XSLT to work. I need to get an EXACT attribute at EXACT path, pass its original value to a template and rewrite this value with the result from the template.
I'm having a file like this:
<?xml version="1.0" encoding="windows-1251"?>
<File>
<Document ReportYear="17">
...
...
</Document>
</File>
So I made an XSLT like this:
<?xml version="1.0" encoding="windows-1251"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" encoding="windows-1251" indent="yes" />
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template name="formatYear">
<xsl:param name="year" />
<xsl:value-of select="$year + 2000" />
</xsl:template>
<xsl:template match="File/Document">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:attribute name="ReportYear">
<xsl:call-template name="formatYear">
<xsl:with-param name="year" select="#ReportYear" />
</xsl:call-template>
</xsl:attribute>
</xsl:copy>
<xsl:apply-templates />
</xsl:template>
</xsl:stylesheet>
This works fine except it closes the <Document> tag immediately and places its content immediately after itself.
Also, can I address the ReportYear attribute value without repeating it twice? I tried current() but it didn't work.
If you're closing <xsl:copy> before applying templates to the remainder of the content of <Document>, then of course <Document> will be closed before the remainder of the content of <Document> appears in the output.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" encoding="windows-1251" indent="yes" />
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="Document">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:attribute name="ReportYear">
<xsl:value-of select="#ReportYear + 2000" />
</xsl:attribute>
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
outputs
<?xml version="1.0" encoding="windows-1251"?>
<File>
<Document ReportYear="2017">
...
...
</Document>
</File>
I don't think an extra template just for adding 2000 to #ReportYear is necessary. But if you must, you can streamline the whole thing like so
<xsl:template name="formatYear">
<xsl:param name="year" select="#ReportYear" /> <!-- you can define a default value -->
<xsl:value-of select="$year + 2000" />
</xsl:template>
and
<xsl:attribute name="ReportYear">
<xsl:call-template name="formatYear" /> <!-- ...and can use it implicitly here -->
</xsl:attribute>
If you need to process the contents of the Document element with apply-templates and want to keep the result of the applied templates as the children then you need to move the apply-templates inside of the copy:
<xsl:template match="File/Document">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="ReportYear">
<xsl:call-template name="formatYear">
<xsl:with-param name="year" select="#ReportYear"/>
</xsl:call-template>
</xsl:attribute>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
Not sure why you haven't simply used
<xsl:template match="File/Document/#ReportYear">
<xsl:attribute name="{name()}">
<xsl:value-of select=". + 2000"/>
</xsl:attribute>
</xsl:template>
together with the identity transformation template.
I am getting stuck at a point where I need to remove an element from the input XML:
<message
xmlns="http://www.origoservices.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>
<m_control>
<control_timestamp>2013-06-06T14:55:37</control_timestamp>
<initiator_id>ASL</initiator_id>
</m_control>
<m_content>
<b_control>
<quote_type>Single Company</quote_type>
<quote_or_print>Quote And Print</quote_or_print>
<generic_quote_ind>Yes</generic_quote_ind>
<tpsdata>
<tps_quote_type>Comparison</tps_quote_type>
</tpsdata>
</b_control>
<application>
<product>
<tpsdata>
<service_type>QuickQuote</service_type>
<quote_type>Standard</quote_type>
</tpsdata>
</product>
</application>
</m_content>
</message>
if <tps_quote_type> is 'Comparison' then change the value of <quote_type> to 'Comparison' and the <tpsdata> field should be removed. The output should look like below.
<message
xmlns="http://www.origoservices.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>
<m_control>
<control_timestamp>2013-06-06T14:55:37</control_timestamp>
<initiator_id>ASL</initiator_id>
</m_control>
<m_content>
<b_control>
<quote_type>Comparison</quote_type>
<quote_or_print>Quote And Print</quote_or_print>
<generic_quote_ind>Yes</generic_quote_ind>
</b_control>
<application>
<product>
<tpsdata>
<service_type>QuickQuote</service_type>
<quote_type>Standard</quote_type>
</tpsdata>
</product>
</application>
</m_content>
</message>
So far I have tried this XSLT, but I don't know how to remove <tpsdata> field from the output. Could anyone help me in this?
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dp="http://www.datapower.com/extensions"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:date="http://exslt.org/dates-and-times"
extension-element-prefixes="dp"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="*">
<!-- identity with closing tags -->
<xsl:element name="{name()}">
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
<xsl:variable name="quoteType">
<xsl:value-of select="/*[namespace-uri()='http://www.origoservices.com' and local- name()='message']/*[namespace-uri()='http://www.origoservices.com' and local-name() ='m_content']/*[namespace-uri()='http://www.origoservices.com' and local-name()='b_control']/*[namespace-uri()='http://www.origoservices.com' and local-name()='quote_type']"/>
</xsl:variable>
<xsl:variable name="tpsQuoteType">
<xsl:value-of select="/*[namespace-uri()='http://www.origoservices.com' and local-name()='message']/*[namespace-uri()='http://www.origoservices.com' and local-name()='m_content']/*[namespace-uri()='http://www.origoservices.com' and local-name()='b_control']/*[namespace-uri()='http://www.origoservices.com' and local-name()='tpsdata']/*[namespace-uri()='http://www.origoservices.com' and local-name()='tps_quote_type']"/>
</xsl:variable>
<xsl:template match="/*[namespace-uri()='http://www.origoservices.com' and local-name()='message']/*[namespace-uri()='http://www.origoservices.com' and local-name()='m_content']/*[namespace-uri()='http://www.origoservices.com' and local-name()='b_control']/*[namespace-uri()='http://www.origoservices.com' and local-name()='quote_type']">
<xsl:choose>
<xsl:when test="$tpsQuoteType = 'Comparison' ">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:text>Comparison</xsl:text>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="*|comment()|processing-instruction()">
<xsl:copy>
<xsl:copy-of select="#*|namespace::*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Maybe you noticed that your handling of those elements with a namespace is a little painful. Just add the http://www.origoservices.com namespace to your XSLT and the pain goes away.
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:o="http://www.origoservices.com"
xmlns:dp="http://www.datapower.com/extensions"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:date="http://exslt.org/dates-and-times"
extension-element-prefixes="dp"
exclude-result-prefixes="fn date"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="o:b_control/o:quote_type[../o:tpsdata/o:tps_quote_type = 'Comparison']">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:text>Comparison</xsl:text>
</xsl:copy>
</xsl:template>
<xsl:template match="o:tpsdata[o:tps_quote_type = 'Comparison']" />
</xsl:stylesheet>
Notes
Most of your "plumbing" is not necessary.
Template match expressions don't need to be a full path.
Use match expressions rather than <xsl:choose> to pinpoint elements you want to change.
Start with a basic identity template, overriding it as needed with more specific templates. This makes your live much easier than starting with a modified identity template.
Use empty templates to remove specific elements.
<xsl:stylesheet version="1.0" extension-element-prefixes="dp" exclude-result-prefixes="dp regexp fn dpconfig" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:dp="http://www.datapower.com/extensions" xmlns:dpconfig="http://www.datapower.com/param/config" xmlns:dpfunc="http://www.datapower.com/extensions/functions" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:regexp="http://exslt.org/regular-expressions" >
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[local-name()='tpsdata']/*[local-name()='quote_type']">
<xsl:message dp:priority="debug"> Found quote_type </xsl:message>
<xsl:variable name = "First">
<xsl:value-of select="/*[local-name()='message']/*[local-name()='m_content']/*[local-name()='b_control']/*[local-name()='tpsdata']/*[local-name()='tps_quote_type']/text()"/>
</xsl:variable>
<xsl:variable name = "Second">
<xsl:value-of select = "."/>
</xsl:variable>
<xsl:message dp:priority="debug"> Second:<xsl:value-of select = "$Second"/></xsl:message>
<xsl:message dp:priority="debug"> First: <xsl:value-of select = "$First"/> </xsl:message>
<xsl:choose>
<xsl:when test="$Second = $First">
<xsl:message dp:priority="debug"> Stand and Comp are same </xsl:message>
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:message dp:priority="debug"> Stand and Comp are different </xsl:message>
<xsl:copy>
<xsl:value-of select="regexp:replace(*[local-name()='quote_type'],'','',$First)"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="*[local-name()='b_control']/*[local-name()='tpsdata']"/>
</xsl:stylesheet>
I have this XML:
<root>
<tab name="Detail">
<section name="mysection">
<items level="1">
<Idx_name>9</Idx_name>
<Type>mytype</Type>
<item name="myname">
<Grams um="(g)">9,0</Grams>
<Pre-infusion>Max</Pre-infusion>
</item>
<Std._Mode>On</Std._Mode>
<Price>100</Price>
</items>
</section>
</tab>
</root>
and this XSLT:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="items/*">
<xsl:choose>
<xsl:when test="not(name()='item')">
<xsl:attribute name="{name()}"><xsl:value-of select="."/></xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:value-of select="."/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Now, what I want is:
<root>
<tab name="Detail">
<section name="mysection">
<items level="1" Idx_name="9" Type="mytype" Std._Mode="On" Price="100">
<item name="myname">9,0Max</item>
</items>
</section>
</tab>
</root>
I obtain the error: "An attribute cannot be added after a child"
Unluckily, I can not change the order of elements in the node items of my original XML
How can I do it ?
Thanks
Ivan
Make sure you process the elements first you want to transform into attributes e.g. with XSLT 2.0 where you can process sequences which have a order you can simply do
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#*, node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="items">
<xsl:copy>
<xsl:apply-templates select="#*, * except item, item"/>
</xsl:copy>
</xsl:template>
<xsl:template match="items/*[not(self::item)]">
<xsl:attribute name="{name()}" select="."/>
</xsl:template>
<xsl:template match="items/item">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:value-of select="."/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
With XSLT 1.0 you would need to spell out several apply-templates in the order you want.
Now I have an XML file as below:
<DM Name="A DM">
<DV id="SQL:Select something from db" Name="DV 1">
<Sample aid="SQL:Select something from db" />
</DV>
<DV id="SQL:Select something from db" Name="DV 2">
<Sample aid="SQL:Select something from db" name ="DC">
good
</Sample>
</DV>
</DM>
I want to use an XSLT to transform it, there is a parameter in this tamplet to determine which DV should be transformed: if the parameter($dvIndex = 0), then just keep all the elements and attributes, just transform the attriform the attritributes with the value started with "SQL:", if ($dvindext > 0), just transform the specific DV,(remove other DV). Now I write the XSLT as below, but it miss the DM's attributes, I don't know how to copy DM's attributes. I donot know if there is better solution. XML File:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
xmlns:user="urn:my-scripts"
>
<xsl:output method="xml" indent="yes"/>
<msxsl:script language="C#" implements-prefix="user">
<![CDATA[
public string UpperCase(string value){
return value.ToUpper();
}
]]>
</msxsl:script>
<xsl:param name="dvIndex" select="2" />
<xsl:template match="DM" >
<xsl:copy>
<xsl:choose>
<xsl:when test="$dvIndex > 0">
<xsl:apply-templates select="DV[$dvIndex]"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!--[starts-with(translate(substring(.,1,4),'SQL:','sql:'),'sql:')]-->
<xsl:template match="#*[user:UpperCase(substring(.,1,4))='SQL:']">
<xsl:attribute name="{name()}">
<xsl:value-of select="'parsedSQL'"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
this question is also related to my question 2# (How to only convert an XML file's attribute using XSLT, and leave the other content?)
Thanks very much!
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="dvIndex" select="2" />
<xsl:template match="DM" >
<xsl:copy>
<xsl:apply-templates select="#*|DV[$dvIndex]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*[starts-with(translate(substring(.,1,4),'SQL:','sql:'),'sql:')]">
<xsl:attribute name="{name()}">
<xsl:value-of select="'parsedSQL'"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
Result:
<DM Name="A DM">
<DV id="parsedSQL" Name="DV 2">
<Sample aid="parsedSQL" name="DC">
good
</Sample>
</DV>
</DM>
With param dvIndex in 0:
<DM Name="A DM"></DM>
Note: Avoid scripting: it's not standar, it'll force to load script engine every time is used.
EDIT: If you want to process every DV when $dvIndex is 0, then this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="dvIndex" select="2" />
<xsl:template match="DM" >
<xsl:copy>
<xsl:apply-templates select="#*|DV[$dvIndex]|DV[not($dvIndex)]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*[starts-with(translate(substring(.,1,4),'SQL:','sql:'),'sql:')]">
<xsl:attribute name="{name()}">
<xsl:value-of select="'parsedSQL'"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
With $dvIndex is 0, output:
<DM Name="A DM">
<DV id="parsedSQL" Name="DV 1">
<Sample aid="parsedSQL"></Sample>
</DV>
<DV id="parsedSQL" Name="DV 2">
<Sample aid="parsedSQL" name="DC">
good
</Sample>
</DV>
</DM>
The following probably does what you need. Note the additional <xsl:apply-templates select="#*" /> to copy the attributes.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
xmlns:user="urn:my-scripts">
<xsl:output method="xml" indent="yes"/>
<msxsl:script language="C#" implements-prefix="user">
<![CDATA[
public string UpperCase(string value){
return value.ToUpper();
} ]]>
</msxsl:script>
<xsl:param name="dvIndex" select="0" />
<xsl:template match="DM" >
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:choose>
<xsl:when test="$dvIndex > 0">
<xsl:apply-templates select="DV[$dvIndex]"/>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="DV"/>
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!--[starts-with(translate(substring(.,1,4),'SQL:','sql:'),'sql:')]-->
<xsl:template match="#*[user:UpperCase(substring(.,1,4))='SQL:']">
<xsl:attribute name="{name()}">
<xsl:value-of select="'parsedSQL'"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>