XProc p:store href variable when using xsl:result-document - xslt

I'm using XProc to run an XSLT that spits out numerous result-documents (using the xsl:result-document). I'm unsure how to go about adding a variable for the #href in the step. I've got the below XProc:
<p:declare-step xmlns:p="http://www.w3.org/ns/xproc"
xmlns:c="http://www.w3.org/ns/xproc-step" version="1.0">
<p:input port="source">
<p:empty/>
</p:input>
<p:xslt name="pagelist">
<p:input port="stylesheet">
<p:document href="file:/C:/page-list.xsl"/>
</p:input>
<p:input port="source">
<p:document href="file:/C:/toc.xml"/>
</p:input>
<p:input port="parameters">
<p:empty/>
</p:input>
</p:xslt>
<p:store name="pagelist" indent="true">
<p:with-option name="method" select="'xml'" />
<p:with-option name="href" select="" />
</p:store>
How do I add a variable in the XProc that will match the output filename from the xsl:result-document?
XSLT snippet, if needed:
<xsl:result-document href="{xhtml:a[#class='ref-uri']/#id}_pagelist.xml" method="xml" include-content-type="no" indent="no">

Using Calabash 1.1.30 from the command line I was able to get the following to work:
<?xml version="1.0" encoding="UTF-8"?>
<p:declare-step xmlns:p="http://www.w3.org/ns/xproc" xpath-version="2.0"
xmlns:c="http://www.w3.org/ns/xproc-step" version="1.0">
<p:input port="source">
<p:empty/>
</p:input>
<p:output port="result" primary="true" sequence="true">
<p:pipe port="result" step="secondary-storage"/>
</p:output>
<p:xslt name="xslt-pagelist" version="2.0">
<p:input port="stylesheet">
<p:document href="page-list.xsl"/>
</p:input>
<p:input port="source">
<p:document href="toc.xml"/>
</p:input>
<p:input port="parameters">
<p:empty/>
</p:input>
</p:xslt>
<p:store href="toc-list.html"/>
<p:for-each name="secondary-storage">
<p:iteration-source select=".">
<p:pipe port="secondary" step="xslt-pagelist"/>
</p:iteration-source>
<p:output port="result">
<p:pipe port="result" step="store"/>
</p:output>
<p:store name="store">
<p:with-option name="href" select="document-uri(.)"/>
</p:store>
</p:for-each>
</p:declare-step>
So basically a p:for-each over a p:iteration-source that uses the secondary result output port from the p:xslt step and then inside simply uses document-uri(.) to have the result URI. All this requires xpath-version="2.0".
Somehow oXygen 22 doesn't run the code but gives me an access denied error, it seems to be set up to write secondary documents to the oXygen installation directory which with normal Windows security settings is not allowed and is not a place where you want result files anyhow; to fix that problem I have adjusted the XProc to have the XML input as a input source to the whole XProc script, then in the p:xslt step I can use the function p:base-uri to set the output-base-uri for XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<p:declare-step xmlns:p="http://www.w3.org/ns/xproc" xpath-version="2.0"
xmlns:c="http://www.w3.org/ns/xproc-step" version="1.0">
<p:input port="source">
<p:document href="toc.xml"/>
</p:input>
<p:output port="result" primary="true" sequence="true">
<p:pipe port="result" step="secondary-storage"/>
</p:output>
<p:xslt name="xslt-pagelist" version="2.0">
<p:with-option name="output-base-uri" select="p:base-uri()"/>
<p:input port="stylesheet">
<p:document href="page-list.xsl"/>
</p:input>
<p:input port="parameters">
<p:empty/>
</p:input>
</p:xslt>
<p:store href="toc-list.html"/>
<p:for-each name="secondary-storage">
<p:iteration-source select=".">
<p:pipe port="secondary" step="xslt-pagelist"/>
</p:iteration-source>
<p:output port="result">
<p:pipe port="result" step="store"/>
</p:output>
<p:store name="store">
<p:with-option name="href" select="document-uri(.)"/>
</p:store>
</p:for-each>
</p:declare-step>
That way Calabash from the command line and within oXygen behave the same, writing the results to the same directory as the input source comes from.

Related

xsl:result-document instruction throws error when invoking stylesheet with Calabash

I have an XSL stylesheet that looks like this:
<xsl:template name="xsl:initial-template">
<xsl:for-each-group select="collection($doc-collection)//dr:description" group-by="format-date(dr:date, '[Mn]-[Y]')">
<xsl:result-document href="{current-grouping-key()}.html" method="html" indent="yes">
<xsl:call-template name="page">
<xsl:with-param name="desc" select="current-group()"/>
</xsl:call-template>
</xsl:result-document>
</xsl:for-each-group>
</xsl:template>
(Variables and other templates omitted for brevity.)
When I invoke this using Saxon, everything runs fine and I end up with x documents created through xsl:result-document.
When it is run as part of an XProc pipeline with Calabash as follows:
<p:xslt name="transformation" template-name="xsl:initial-template">
<p:input port="stylesheet">
<p:document href="../xslt/main.xsl"/>
</p:input>
<p:input port="source">
<p:empty/>
</p:input>
<p:with-param name="doc-collection" select="resolve-uri($source)"/>
</p:xslt>
I get a run-time error during the transformation: Cannot execute xsl:result-document while evaluating xsl:variable. I tried stripping down the code to a bare minimum, but I cannot prevent the error from occuring when calling xsl:result-document.
I don't know what variable it is referring to, I can only assume it is created somewhere inside the pipeline?
Working with Saxon EE 9.9.1.5 and Calabash 1.1.30-99 inside Oxygen XML Developer.

Trying to understand XSLT Muenchian Method transformation

I came across the Muenchian Method when looking for a way of grouping elements in an XML file that has been produced by converted a CSV file.
Source
<file>
<patient>
<Lab_Specimen_Number>L,18.1342718.Y</Lab_Specimen_Number>
<Patient_Number>LOC0000015</Patient_Number>
<ORG/>
<Specimen/>
<Antibiotic_Amox_Ampicillin/>
</patient>
<patient>
<Lab_Specimen_Number>L,18.1342727.V</Lab_Specimen_Number>
<Patient_Number>LOC0000001</Patient_Number>
<ORG>Coliform</ORG>
<Specimen>L,18.1342727.VA</Specimen>
<Antibiotic_Amox_Ampicillin>S</Antibiotic_Amox_Ampicillin>
</patient>
<patient>
<Lab_Specimen_Number/>
<Patient_Number/>
<ORG>Staphylococcus aureus</ORG>
<Specimen>L,18.1342727.VA</Specimen>
<Antibiotic_Amox_Ampicillin>S</Antibiotic_Amox_Ampicillin>
</patient>
<patient>
<Lab_Specimen_Number>L,18.1346290.T</Lab_Specimen_Number>
<Patient_Number>LOC0000001</Patient_Number>
<ORG>Coliform</ORG>
<Specimen>L,18.1346290.TA</Specimen>
<Antibiotic_Amox_Ampicillin>S</Antibiotic_Amox_Ampicillin>
</patient>
<patient>
<Lab_Specimen_Number>L,18.1342713.X</Lab_Specimen_Number>
<Patient_Number>LOC0000009</Patient_Number>
<ORG/>
<Specimen/>
<Antibiotic_Amox_Ampicillin/>
</patient>
</file>
Based on the article I have changed the match when assigning a key to patient[Specimen != ''] instead of just patient as it is possible for the Specimen value to be blank and these would be missing from the final output if just patient was used.
Transformation
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="patients-by-specimen" match="patient[Specimen != '']" use="Specimen" />
<xsl:template match="file">
<file>
<xsl:for-each select="patient[count(. | key('patients-by-specimen', Specimen)[1]) = 1]">
<patient>
<xsl:copy-of select="Lab_Specimen_Number" />
<xsl:copy-of select="Patient_Number" />
<Specimen>
<xsl:copy-of select="Specimen" />
<Organisms>
<xsl:for-each select="key('patients-by-specimen', Specimen)">
<Organism>
<xsl:copy-of select="ORG"/>
<xsl:copy-of select="Antibiotic_Amox_Ampicillin"/>
</Organism>
</xsl:for-each>
</Organisms>
</Specimen>
</patient>
</xsl:for-each>
</file>
</xsl:template>
</xsl:stylesheet>
Whilst the transformation above gives me the desired output I don't fully understand how this line is working:
<xsl:for-each select="patient[count(. | key('patients-by-specimen', Specimen)[1]) = 1]">
Could someone explain this iteration in the context of my source file?
Output
<file>
<patient>
<Lab_Specimen_Number>L,18.1342718.Y</Lab_Specimen_Number>
<Patient_Number>LOC0000015</Patient_Number>
<Specimen>
<Specimen/>
<Organisms/>
</Specimen>
</patient>
<patient>
<Lab_Specimen_Number>L,18.1342727.V</Lab_Specimen_Number>
<Patient_Number>LOC0000001</Patient_Number>
<Specimen>
<Specimen>L,18.1342727.VA</Specimen>
<Organisms>
<Organism>
<ORG>Coliform</ORG>
<Antibiotic_Amox_Ampicillin>S</Antibiotic_Amox_Ampicillin>
</Organism>
<Organism>
<ORG>Staphylococcus aureus</ORG>
<Antibiotic_Amox_Ampicillin>S</Antibiotic_Amox_Ampicillin>
</Organism>
</Organisms>
</Specimen>
</patient>
<patient>
<Lab_Specimen_Number>L,18.1346290.T</Lab_Specimen_Number>
<Patient_Number>LOC0000001</Patient_Number>
<Specimen>
<Specimen>L,18.1346290.TA</Specimen>
<Organisms>
<Organism>
<ORG>Coliform</ORG>
<Antibiotic_Amox_Ampicillin>S</Antibiotic_Amox_Ampicillin>
</Organism>
</Organisms>
</Specimen>
</patient>
<patient>
<Lab_Specimen_Number>L,18.1342713.X</Lab_Specimen_Number>
<Patient_Number>LOC0000009</Patient_Number>
<Specimen>
<Specimen/>
<Organisms/>
</Specimen>
</patient>
</file>

Flat xml to hierarchical XML transformation using XSLT

I am trying to create a transformation that will map a flat structure ( with parent/child ids) into a hierarchical structure. I have included a simple request and responses examples below. The actual data has over 500 elements. Any help would be really appreciated! Thanks!
Validation Rules:
Offer can be in top level
Offer can be under package
Offer can be under group
Request:
<!-- offer with no parents(group or package) if No package_id or group ID in ffer element-->
<offer>
<namevaluepair>
<name>type</name>
<value>offer</value>
</namevaluepair>
<namevaluepair>
<name>id</name>
<value>OFR1</value>
</namevaluepair>
</offer>
<!-- type package P1 & P2 representation-->
<offer>
<namevaluepair>
<name>type</name>
<value>package</value>
</namevaluepair>
<namevaluepair>
<name>id</name>
<value>P1</value>
</namevaluepair>
</offer>
<offer>
<namevaluepair>
<name>type</name>
<value>package</value>
</namevaluepair>
<namevaluepair>
<name>id</name>
<value>P2</value>
</namevaluepair>
</offer>
<!-- offer under package but not in group-->
<offer>
<namevaluepair>
<name>type</name>
<value>offer</value>
</namevaluepair>
<namevaluepair>
<name>id</name>
<value>OFRP1</value>
</namevaluepair>
<namevaluepair>
<name>package_id</name>
<value>P1</value>
</namevaluepair>
</offer>
<!-- offers under package but not in group-->
<offer>
<namevaluepair>
<name>type</name>
<value>offer</value>
</namevaluepair>
<namevaluepair>
<name>id</name>
<value>OFRP2</value>
</namevaluepair>
<namevaluepair>
<name>package_id</name>
<value>P2</value>
</namevaluepair>
</offer>
<!-- type groups G1P1 and G1P2 representations,
group G1P1 is part of package P1 and this group G1P2 is part of package P2-->
<offer>
<namevaluepair>
<name>type</name>
<value>group</value>
</namevaluepair>
<namevaluepair>
<name>id</name>
<value>G1P1</value>
</namevaluepair>
<namevaluepair>
<name>package_id</name>
<value>P1</value>
</namevaluepair>
</offer>
<offer>
<namevaluepair>
<name>type</name>
<value>group</value>
</namevaluepair>
<namevaluepair>
<name>id</name>
<value>G2P2</value>
</namevaluepair>
<namevaluepair>
<name>package_id</name>
<value>P2</value>
</namevaluepair>
</offer>
<!-- offer OFRG1P1 under group G1P1 as parent AND G1P1 is under P1 package-->
<offer>
<namevaluepair>
<name>type</name>
<value>offer</value>
</namevaluepair>
<namevaluepair>
<name>id</name>
<value>OFRG1P1</value>
</namevaluepair>
<namevaluepair>
<name>group_id</name>
<value>G1P1</value>
</namevaluepair>
</offer>
<!-- offer OFRG1P1 under group G2P2 as parent AND grp G2P2 is under P2 package-->
<offer>
<namevaluepair>
<name>type</name>
<value>offer</value>
</namevaluepair>
<namevaluepair>
<name>id</name>
<value>OFRG2P2</value>
</namevaluepair>
<namevaluepair>
<name>group_id</name>
<value>G2P2</value>
</namevaluepair>
</offer>
Output:
<offers>
<!-- offer with no parents(group or package)-->
<offer ID="OFR1"/>
<packages>
<package ID="P1">
<groups>
<group ID="G1P1">
<offer ID="OFRG1P1"/>
</group>
</groups>
<offer ID="OFR1P1"/>
</package>
<package ID="P2">
<groups>
<group ID="G2P2">
<offer ID="OFRG2P2"/>
</group>
</groups>
<offer ID="OFR1P2"/>
</package>
</packages>
</offers>
Here is a XSLT 1.0 solution, utilizing keys.
The stylesheet declares four keys for finding different objects:
top level objects by their type (package or offer),
groups by their package id,
offers belonging to a package by their package id, and
offers belonging to a group by their group id.
At the top level, the stylesheet processes the top-level offers and packages. At the package level, the stylesheet processes the groups and offers belonging to that package. At the group level, the system processes the offers belonging to that group.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="top-level-objects-by-type"
match="offer[not(namevaluepair[name='package_id'])]
[not(namevaluepair[name='group_id'])]"
use="namevaluepair[name='type']/value"/>
<xsl:key name="groups-by-package-id"
match="offer[namevaluepair[name='type' and value='group']]"
use="namevaluepair[name='package_id']/value"/>
<xsl:key name="offers-by-package-id"
match="offer[namevaluepair[name='type' and value='offer']]"
use="namevaluepair[name='package_id']/value"/>
<xsl:key name="offers-by-group-id"
match="offer[namevaluepair[name='type' and value='offer']]"
use="namevaluepair[name='group_id']/value"/>
<xsl:template match="/">
<offers>
<xsl:apply-templates select="key('top-level-objects-by-type', 'offer')"/>
<packages>
<xsl:apply-templates select="key('top-level-objects-by-type', 'package')"/>
</packages>
</offers>
</xsl:template>
<xsl:template match="offer[namevaluepair[name='type' and value='package']]">
<xsl:variable name="id" select="namevaluepair[name='id']/value"/>
<package ID="{$id}">
<groups>
<xsl:apply-templates select="key('groups-by-package-id', $id)"/>
</groups>
<xsl:apply-templates select="key('offers-by-package-id', $id)"/>
</package>
</xsl:template>
<xsl:template match="offer[namevaluepair[name='type' and value='group']]">
<xsl:variable name="id" select="namevaluepair[name='id']/value"/>
<group ID="{$id}">
<xsl:apply-templates select="key('offers-by-group-id', $id)"/>
</group>
</xsl:template>
<xsl:template match="offer[namevaluepair[name='type' and value='offer']]">
<offer ID="{namevaluepair[name='id']/value}"/>
</xsl:template>
</xsl:stylesheet>

XSLT copy/copy-of without keeping namespaces

Hi I have issue in the default namespace declaration. output xml elements are appended with the default namespace.
The input XML look like
<m:Request xmlns:m="http://www.NeededNamespace/1.4.0">
<Details>
<Records>50</Records>
<Start>1</Start>
<sortName>sortName</sortName>
</Details>
<search>
<criteria>
<comparative>
<Comparative>exactMatch</Comparative>
</comparative>
<name>STATECODE</name>
<value>CO</value>
</criteria>
<criteria>
<comparative>
<Comparative>exactMatch</Comparative>
</comparative>
<name>Version</name>
<value>4.0</value>
</criteria>
<criteria>
<comparative>
<Comparative>contains</Comparative>
</comparative>
<name>LEGALNAME</name>
<value>Citizens State Bank</value>
</criteria>
</search>
</m:Request>
The XSLT look like
<xsl:stylesheet version="1.0" exclude-result-prefixes="t" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:t="http://www.NotRequirednamespace.com">
<xsl:output indent="yes" method="xml" encoding="utf-8" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<!--Stylesheet to remove all namespaces from a document-->
<!--NOTE: this will lead to attribute name clash, if an element contains
two attributes with same local name but different namespace prefix-->
<!--Nodes that cannot have a namespace are copied as such-->
<xsl:template match="/">
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soap:Header>
<xys:To xmlns:xys="http://services.xys.com/framework/xysHeader/v2">
<xys:version>9.0</xys:version>
<xys:serviceName>DetailsManagement</xys:serviceName>
<xys:QOS>DEFAULT</xys:QOS>
<xys:operation>GetDetails</xys:operation>
</xys:To>
<ConsumerInfo xmlns="http://services.xys.com/framework/xysHeader/v2">
<xysApplicationName>SAP</xysApplicationName>
<xysCheckPermission>-1</xysCheckPermission>
<xysConsumerPlatform>CS</xysConsumerPlatform>
<xysLanguage>en</xysLanguage>
<xysLocale>US</xysLocale>
<xysLogLevel>false</xysLogLevel>
</ConsumerInfo>
<HeaderMetadata xmlns="http://services.xys.com/framework/xysHeader/v2">
<metadataContractVersion>2.0</metadataContractVersion>
<Id>414</Id>
<Timestamp>2014-11-20T14:17:30.908-0500</Timestamp>
</HeaderMetadata>
<xys:favouriteSausage xmlns:xys="http://services.xys.com/framework/xysHeader/v2">cumberland</xys:favouriteSausage>
</soap:Header>
<soap:Body>
<GetDetails xmlns="http://www.NeededNamespace/1.4.0">
<Message id="" version="" bodyType="FS-XML" timeStampCreated="2015-10-11T10:15:25.9144403-04:00" sourceLogicalId="" xmlns="http://www.ibm.com/industries/xys">
<ACGroup bodyCategory="" TPMode="RespondError"/>
<COMMAND>
<xsl:apply-templates/>
</COMMAND>
</Message>
</GetDetails>
</soap:Body>
</soap:Envelope>
</xsl:template>
<!--template to copy elements-->
<xsl:template match="*">
<xsl:element name="{local-name()}" namespace="http://www.NeededNamespace/1.4.0">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
<!--template to copy attributes-->
<xsl:template match="#*">
<xsl:attribute name="{local-name()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
<!--template to copy the rest of the nodes-->
<xsl:template match="comment() | text() | processing-instruction()">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
The output XML what i am getting is
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soap:Header>
<xys:To xmlns:xys="http://services.xys.com/framework/xysHeader/v2">
<xys:version>9.0</xys:version>
<xys:serviceName>DetailsManagement</xys:serviceName>
<xys:QOS>DEFAULT</xys:QOS>
<xys:operation>GetDetails</xys:operation>
</xys:To>
<ConsumerInfo xmlns="http://services.xys.com/framework/xysHeader/v2">
<xysApplicationName>SAP</xysApplicationName>
<xysCheckPermission>-1</xysCheckPermission>
<xysConsumerPlatform>CS</xysConsumerPlatform>
<xysLanguage>en</xysLanguage>
<xysLocale>US</xysLocale>
<xysLogLevel>false</xysLogLevel>
</ConsumerInfo>
<HeaderMetadata xmlns="http://services.xys.com/framework/xysHeader/v2">
<metadataContractVersion>2.0</metadataContractVersion>
<Id>414</Id>
<Timestamp>2014-11-20T14:17:30.908-0500</Timestamp>
</HeaderMetadata>
<xys:favouriteSausage xmlns:xys="http://services.xys.com/framework/xysHeader/v2">cumberland</xys:favouriteSausage>
</soap:Header>
<soap:Body>
<GetDetails xmlns="http://www.NeededNamespace/1.4.0">
<Message xmlns="http://www.ibm.com/industries/xys" id="" version="" bodyType="FS-XML" timeStampCreated="2015-10-11T10:15:25.9144403-04:00" sourceLogicalId="">
<ACGroup bodyCategory="" TPMode="RespondError"/>
<COMMAND>
<Request xmlns="http://www.NeededNamespace/1.4.0">
<Details>
<Records>50</Records>
<Start>1</Start>
<sortName>sortName</sortName>
</Details>
<search>
<criteria>
<comparative>
<Comparative>exactMatch</Comparative>
</comparative>
<name>STATECODE</name>
<value>CO</value>
</criteria>
<criteria>
<comparative>
<Comparative>exactMatch</Comparative>
</comparative>
<name>Version</name>
<value>4.0</value>
</criteria>
<criteria>
<comparative>
<Comparative>contains</Comparative>
</comparative>
<name>LEGALNAME</name>
<value>Citizens State Bank</value>
</criteria>
</search>
</Request>
</COMMAND>
</Message>
</GetDetails>
</soap:Body>
</soap:Envelope>
But in the result I am getting the element as
<Request xmlns="http://www.NeededNamespace/1.4.0">
But i want the result tag as like below
<Request>
I dont want to redeclare the namespace which is already declared in the root tag of the same. I have tried all the option i have known and tried for last few days. can you please help me on this.
The input element has expanded name (local-part="Request", namespace="http://www.NeededNamespace/1.4.0"). If you don't want the output Request element to have a namespace declaration, then presumably you want it to be in the same namespace as its parent, that is you want its expanded name to be (local-part="Request", namespace="http://www.ibm.com/industries/xys"). An xsl:copy or xsl:copy-of instruction will never (even in 2.0) change the expanded name of the element being copied. So you can't achieve your desired output using xsl:copy or xsl:copy-of. You will need to create a new element with the same local name but a different namespace from the original, using <xsl:element name="{local-name()}" namespace="http://www.ibm.com/industries/xys"/>.

Adapting XSLT to exclude attribute from comparison before merge, but still use the value

I am having trouble adapting an XSLT to handle some extra attribute rules/logic.
I have 'general' and 'specific' XML files that I am merging with this XSLT:
http://www2.informatik.hu-berlin.de/~obecker/XSLT/merge/merge.xslt.html
Source Files
general.xml
<?xml version="1.0" encoding="utf-8"?>
<settings>
<phone-settings e="2">
<language perm="RW">English</language>
<codec1_name idx="1" perm="">0</codec1_name>
<codec1_name idx="2" perm="">0</codec1_name>
</phone-settings>
</settings>
specific.xml
<?xml version="1.0" encoding="utf-8"?>
<settings>
<phone-settings e="2">
<language perm="RW">German</language>
<codec1_name idx="1" perm="R">8</codec1_name>
</phone-settings>
</settings>
XML documents are merged, specific replaces general.
If the attributes & their values match exactly the specific data is used.
To this point the transformation works well, but I have to make adjustments:
Some elements have a 'idx' attribute which identifies that element uniquely
Some elements have a 'perm' attribute which determines if it is read/write by the user.
If the same element exists in both source files, but with a different 'perm' attribute, the XSLT considers it unique and a duplicate element is introduced:
<?xml version="1.0" encoding="utf-8"?>
<settings>
<phone-settings e="2">
<language perm="RW">German</language>
<codec1_name idx="1" perm="">0</codec1_name>
<codec1_name idx="2" perm="">0</codec1_name>
<codec1_name idx="1" perm="R">8</codec1_name>
</phone-settings>
</settings>
Here is the XSLT template that handles the single-node comparision:
<xslt:template name="m:compare-nodes">
<xslt:param name="node1" />
<xslt:param name="node2" />
<xslt:variable name="type1">
<xslt:apply-templates mode="m:detect-type" select="$node1" />
</xslt:variable>
<xslt:variable name="type2">
<xslt:apply-templates mode="m:detect-type" select="$node2" />
</xslt:variable>
<xslt:choose>
<!-- Are $node1 and $node2 element nodes with the same name? -->
<xslt:when test="$type1='element' and $type2='element' and local-name($node1)=local-name($node2) and namespace-uri($node1)=namespace-uri($node2) and name($node1)!=$dontmerge and name($node2)!=$dontmerge">
<!-- Comparing the attributes -->
<xslt:variable name="diff-att">
<!-- same number ... -->
<xslt:if test="count($node1/#*)!=count($node2/#*)">.</xslt:if>
<!-- ... and same name/content -->
<xslt:for-each select="$node1/#*">
<xslt:if test="not($node2/#* [local-name()=local-name(current()) and namespace-uri()=namespace-uri(current()) and .=current()])">.</xslt:if>
</xslt:for-each>
</xslt:variable>
<xslt:choose>
<xslt:when test="string-length($diff-att)!=0">!</xslt:when>
<xslt:otherwise>=</xslt:otherwise>
</xslt:choose>
</xslt:when>
<!-- Other nodes: test for the same type and content -->
<xslt:when test="$type1!='element' and $type1=$type2 and name($node1)=name($node2) and ($node1=$node2 or ($normalize='yes' and normalize-space($node1)= normalize-space($node2)))">=</xslt:when>
<!-- Otherwise: different node types or different name/content -->
<xslt:otherwise>!</xslt:otherwise>
</xslt:choose>
</xslt:template>
I can exclude the 'perm' attribute from the match by doing this:
<!-- ... and same name/content -->
<xslt:for-each select="$node1/#* [name(.)!='perm']">
That solves the duplicate element issue, unfortunately it also means that the value for that attribute in the specific XML file - 'R' in this case is not merged:
<?xml version="1.0" encoding="utf-8"?>
<settings>
<phone-settings e="2">
<language perm="RW">German</language>
<codec1_name idx="1" perm="">8</codec1_name>
<codec1_name idx="2" perm="">0</codec1_name>
</phone-settings>
</settings>
How can I exclude the 'perm' attribute from the uniqueness test while still ensuring its value in the specific XML file is used in the merge?
The desired result from merging the two example files at the top is:
<?xml version="1.0" encoding="utf-8"?>
<settings>
<phone-settings e="2">
<language perm="RW">German</language>
<codec1_name idx="1" perm="R">8</codec1_name>
<codec1_name idx="2" perm="">0</codec1_name>
</phone-settings>
</settings>
Any help with this would be much appreciated!
Thanks.
I can exclude the 'perm' attribute
from the match by doing this:
<!-- ... and same name/content -->
<xslt:for-each select="$node1/#*[name(.)!='perm']">
That solves the duplicate element
issue, unfortunately it also means
that the value for that attribute in
the specific XML file - 'R' in this
case is not merged
How can I exclude the 'perm' attribute
from the uniqueness test while still
ensuring its value in the specific XML
file is used in the merge?
From a brief reading of the quite complex merge-code, it seems that you can achieve your goal by changing this (at line 190):
<xsl:copy-of select="$first1/#*" />
with this:
<xsl:copy-of select="$first2/#*" />
Do note: A better solution than what you are currently doing is to introduce a new global paramere, say $ignore-attributes-in-comparison and specify as value a space-separated string with the names of all attributes that should be ignored in a node comparison.
With other approach (identify elements by name and key attributes), this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="pSpecific" select="document('specific.xml')"/>
<xsl:key name="kElemByName-Attr"
match="*"
use="concat(name(),'+',#idx,'+',#perm)"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(*)]">
<xsl:param name="pSource" select="$pSpecific"/>
<xsl:param name="pCopy" select="true()"/>
<xsl:variable name="vCurrent" select="."/>
<xsl:for-each select="$pSource">
<xsl:variable name="vMatch"
select="key('kElemByName-Attr',
concat(name($vCurrent),'+',
$vCurrent/#idx,'+',
$vCurrent/#perm))"/>
<xsl:for-each select="$vMatch[$pCopy]">
<xsl:call-template name="identity"/>
</xsl:for-each>
<xsl:for-each select="$vCurrent[not($vMatch)]">
<xsl:call-template name="identity"/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
<xsl:template match="*[*[not(*)]]">
<xsl:variable name="vCurrent" select="."/>
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
<xsl:for-each select="$pSpecific">
<xsl:variable name="vMatch"
select="key('kElemByName-Attr',
concat(name($vCurrent),'+',
$vCurrent/#idx,'+',
$vCurrent/#perm))"/>
<xsl:apply-templates select="$vMatch/node()">
<xsl:with-param name="pSource" select="$vCurrent"/>
<xsl:with-param name="pCopy" select="false()"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output:
<settings>
<phone-settings e="2">
<language perm="RW">German</language>
<codec1_name idx="1" perm="">0</codec1_name>
<codec1_name idx="2" perm="">0</codec1_name>
<codec1_name idx="1" perm="R">8</codec1_name>
</phone-settings>
</settings>