updating pom.xml elements using xslt - xslt

I'm trying to update just finalName, artifactId and name in pom.xml using xslt. I don't understand xslt transformation, but I found this: how to modify xml file using xslt which seems very easy to follow, so based on that I created this template:
<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:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//artifactId">
<artifactId>aaaaaaaaaaaaaaaaaaaaaaaaaaa123</artifactId>
</xsl:template>
</xsl:stylesheet>
and building it using:
xsltproc template.xsl pom.xml > modified-pom.xml
however for some unknown to me reason, the xpath //artifactId does not seem to be matched and replaced. I tried also: artifactId only, project/artifactId, /project/artifactId etc. but none matches. Remembering lesson from using xmlstalet which requires specific namespaces, this seems to be maybe somehow namespace related, but I really don't know how to fix it, as man xsltproc seems to be somehow broken/not reflecting command reality. Can someone help where is the error?
EDIT: added partially solved state, based on request from comment:
as an minimally working example input file , we might consider this pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<artifactId>toUpdate</artifactId>
<name>toUpdate</name>
<properties>
<finalName>toUpdate</finalName>
</properties>
</project>
I'm using now template:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:m="http://maven.apache.org/POM/4.0.0">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/m:project/m:artifactId">
<artifactId>updated</artifactId>
</xsl:template>
<xsl:template match="/m:project/m:name">
<name>updated</name>
</xsl:template>
<xsl:template match="/m:project/m:properties/m:finalName">
<finalName>updated</finalName>
</xsl:template>
</xsl:stylesheet>
and replacing it using command: xsltproc template.xsl min.xml > out.xml
the output is:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<artifactId xmlns="" xmlns:m="http://maven.apache.org/POM/4.0.0">updated</artifactId>
<name xmlns="" xmlns:m="http://maven.apache.org/POM/4.0.0">updated</name>
<properties>
<finalName xmlns="" xmlns:m="http://maven.apache.org/POM/4.0.0">updated</finalName>
</properties>
</project>
where I'd like to get rid of part xmlns="" xmlns:m="http://maven.apache.org/POM/4.0.0"
if I update the replacement in template to: <m:finalName>updated</m:finalName> I'm not getting the correct result, but instead:
<m:finalName xmlns:m="http://maven.apache.org/POM/4.0.0">updated</m:finalName>
trying to declare global namespace instead of m also did not help me, as it even did not build.
Expected output is input with toUpdate replaced with updated

Consider the following minimized example:
XML
<project xmlns="http://maven.apache.org/POM/4.0.0">
<artifactId>toUpdate</artifactId>
<name>toUpdate</name>
<properties>
<finalName>toUpdate</finalName>
</properties>
</project>
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:m="http://maven.apache.org/POM/4.0.0"
exclude-result-prefixes="m">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="m:artifactId">
<artifactId>updated</artifactId>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<artifactId>updated</artifactId>
<name>toUpdate</name>
<properties>
<finalName>toUpdate</finalName>
</properties>
</project>
Note the two namespace declarations in the xsl:styleshhet start-tag. One is for addressing the elements in the source XML. The other puts any literal result elements in the stylesheet in the target namespace.
If you only to want to replace the text content of an element, without renaming it, you could simplify this to:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:m="http://maven.apache.org/POM/4.0.0"
exclude-result-prefixes="m">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="m:artifactId/text()">updated</xsl:template>
</xsl:stylesheet>

Related

XSLT Identity template overriding

I need to remove some unwanted fields over a big response XML using XSL 1.0, and want to use apply-templates instead of for-each. Below is the code I tried but is not giving expected results i.e., unwanted fields are removed successfully but few other fields outside match also disappeared. Am I using identity template incorrectly.
Input XML:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:object xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:array name="BankDetails">
<one>field1</one>
<two>field2</two>
<xsl:object>
<date>1234-56-78</date>
<Time>12345678</Time>
</xsl:object>
<xsl:object>
<date>0000-00-00</date>
<Time>00000000</Time>
</xsl:object>
<xsl:object>
<date>0000-00-00</date>
<Time>00000000</Time>
</xsl:object>
<xsl:object>
<date>0000-00-00</date>
<Time>00000000</Time>
</xsl:object>
<three>field3</three>
<four>field4</four>
<five>field5</five>
<six>field6</six>
</xsl:array>
</xsl:object>
Applied XSLT:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:axsl="http://www.w3.org/1999/XSL/TransformAlias"
xmlns:str="http://exslt.org/strings">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:namespace-alias stylesheet-prefix="axsl" result-prefix="xsl" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="xsl:object/xsl:array">
<xsl:for-each select="xsl:object[Time !='00000000']">
<xsl:copy-of select = "."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
What I got:
<?xml version="1.0" encoding="UTF-8"?><xsl:object xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:object>
<date>1234-56-78</date>
<Time>12345678</Time>
</xsl:object>
</xsl:object>
What is expected:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:object xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:array name="BankDetails">
<one>field1</one>
<two>field2</two>
<xsl:object>
<date>1234-56-78</date>
<Time>12345678</Time>
</xsl:object>
<three>field3</three>
<four>field4</four>
<five>field5</five>
<six>field6</six>
</xsl:array>
</xsl:object>
The problem with your attempt is that you are copying only xsl:object elements that have the right Time - but not doing anything (i.e. copy or apply templates) for the other elements that are also children of the context xsl:array element (one, two etc). If you do not apply templates to them, then the identity transform template will not be applied to them.
You are also not copying the context xsl:array element.
I would suggest you approach it from the opposite point-of-view:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:axsl="http://www.w3.org/1999/XSL/TransformAlias">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:namespace-alias stylesheet-prefix="axsl" result-prefix="xsl" />
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="xsl:object[Time ='00000000']"/>
</xsl:stylesheet>

Skip first node and Copy rest of the nodes using XSLT 3.0

I will need to skip first Worker node and copy rest of the nodes using XSLT 3.0
My source XML is
<?xml version="1.0" encoding="UTF-8"?>
<Workers>
<Worker>
<Employee>Emp</Employee>
<ID>Identifier</ID>
</Worker>
<Worker>
<Employee>12344</Employee>
<ID>1245599</ID>
</Worker>
<Worker>
<Employee>25644</Employee>
<ID>7823565</ID>
</Worker>
</Workers>
and desired output is
<?xml version="1.0" encoding="utf-8"?>
<Workers>
<Worker>
<Employee>12344</Employee>
<ID>1245599</ID>
</Worker>
<Worker>
<Employee>25644</Employee>
<ID>7823565</ID>
</Worker>
</Workers>
and XSLT that i have is
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="Worker[position() = 1]"/>
</xsl:stylesheet>
Above XSLT produces the output that I was expecting but I'm looking to see if there is a better way to skip the first node without using postion()as I'm not sure how efficient my current code is to process large files( approximately 800 MB)
I had to use following to remove white spaces from my result XML
<xsl:strip-space elements="*"/>
Can anyone check my code and provide any suggestion to improvise my code please?
===============
With Michael Kay's suggestion, my code looks like this
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes"/>
<!-- <xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template> -->
<!-- Removed above Identity Transformation -->
<xsl:mode streamable="yes" on-no-match="shallow-copy"/>
<xsl:template match="Workers">
<xsl:copy>
<xsl:apply-templates select="tail(Worker)"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I would write it
<xsl:template match="Worker[1]"/>
for readability, but it's all the same.
Match patterns with positional predicates can perform badly, so you're right to be cautious, but a simple one like this should be OK. In fact the main adverse consequence is probably that Saxon will allocate preceding-sibling pointers in the TinyTree, so that it can compute the node's sibling position.
Saxon effectively implements it as
<xsl:template match="Worker[not(preceding-sibling::Worker)]"/>
and you might prefer to write it that way. However, neither form is streamable.
To make it streamable, you could drop the unwanted nodes by not selecting them:
<xsl:template match="Workers">
<xsl:copy>
<xsl:apply-templates select="tail(Worker)"/>
</xsl:copy>
</xsl:template>
which might also be fractionally faster in the non-streaming case; and it saves memory because preceding-sibling pointers aren't needed.

How can I add a CSS declaration to an XML document using XSLT?

I have many XML files that I want to process with XSLT. I want the result to include custom CSS for the purpose of displaying the files a distinct way in Oxygen’s Author mode.
Input:
<?xml version="1.0" encoding="utf-8"?>
<alto xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.loc.gov/standards/alto/ns-v2# http://www.loc.gov/standards/alto/alto-v2.0.xsd" xmlns="http://www.loc.gov/standards/alto/ns-v2#">
<!—more XML-->
</alto>
XSL:
<?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"
xpath-default-namespace="http://www.loc.gov/standards/alto/ns-v2#"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<!—A series of templates that transform the XML-->
</xsl:stylesheet>
Desired Output:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="my-style.css"?>
<alto xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.loc.gov/standards/alto/ns-v2#"
xsi:schemaLocation="http://www.loc.gov/standards/alto/ns-v2# http://www.loc.gov/standards/alto/alto-v2.0.xsd">
<!—more XML-->
</alto>
What do I need to add to my stylesheet to get the declaration to display in each XML file?
Use the xsl:processing-instruction instruction.
So your stylesheet could look like this:
<?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"
xpath-default-namespace="http://www.loc.gov/standards/alto/ns-v2#"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:processing-instruction name="xml-stylesheet">href="my-style.css"</xsl:processing-instruction>
<xsl:apply-templates select="node()|#*" />
</xsl:template>
<!-- Identity template -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output is:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="my-style.css"?>
<alto xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.loc.gov/standards/alto/ns-v2#"
xsi:schemaLocation="http://www.loc.gov/standards/alto/ns-v2# http://www.loc.gov/standards/alto/alto-v2.0.xsd"/>
Add
<xsl:template match="/">
<xsl:processing-instruction name="xml-stylesheet">href="my-style.css"</xsl:processing-instruction>
<xsl:next-match/>
</xsl:template>
at the end of the stylesheet or make sure you edit the template you have for match="/" and insert the <xsl:processing-instruction name="xml-stylesheet">href="my-style.css"</xsl:processing-instruction> there.

XSLT remove unwanted elements

I have XML
<getInquiryAboutListReturn xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<inquiryAbouts>
<inquiryAbout>
<code>Code</code>
<nameKk>Something</nameKk>
<nameRu>Something</nameRu>
<documents xsi:nil="true"/>
</inquiryAbout>
</inquiryAbouts>
</getInquiryAboutListReturn>
And I want to process it with XSLT to copy all XML
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes" />
<xsl:template match="/">
<xsl:copy-of select="//getInquiryAboutListReturn/inquiryAbouts"/>
</xsl:template>
</xsl:stylesheet>
How could I copy all XML without <documents xsi:nil="true"/> or without xsi:nil="true"?
Desired output XML
<getInquiryAboutListReturn xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<inquiryAbouts>
<inquiryAbout>
<code>Code</code>
<nameKk>Something</nameKk>
<nameRu>Something</nameRu>
</inquiryAbout>
</inquiryAbouts>
</getInquiryAboutListReturn>
This simple XSLT:
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="1.0">
<xsl:output omit-xml-declaration="no" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- TEMPLATE #1 -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- TEMPLATE #2 -->
<xsl:template match="*[#xsi:nil = 'true']" />
</xsl:stylesheet>
...when applied to the OP's source XML:
<?xml version="1.0"?>
<getInquiryAboutListReturn xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<inquiryAbouts>
<inquiryAbout>
<code>Code</code>
<nameKk>Something</nameKk>
<nameRu>Something</nameRu>
<documents xsi:nil="true"/>
</inquiryAbout>
</inquiryAbouts>
</getInquiryAboutListReturn>
...produces the expected result XML:
<?xml version="1.0"?>
<getInquiryAboutListReturn xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<inquiryAbouts>
<inquiryAbout>
<code>Code</code>
<nameKk>Something</nameKk>
<nameRu>Something</nameRu>
</inquiryAbout>
</inquiryAbouts>
</getInquiryAboutListReturn>
EXPLANATION:
The first template -- the Identity Template -- copies all nodes and attributes from the source XML document as-is.
The second template, which matches all elements with the specified, namespaced attribute equalling "true", effectively removes those elements.

XSLT (XML to XML) output contains non-matching data from source

Using Visual Studio to perform the transform during development the resulting xml contains text from the source xml in the destination that was contained in tags that do not match my template criteria
I was expecting that my select Group in the first template to find any elements named Group that are immediate children of CrystalReport and pass them along in the apply template call. I understood that the match filter on my second template would only take in Group's that have an attribute of Level=1 and write them out. I'd expect everything else to be ignored.
Why does "not this" appear in my output?
source
<?xml version="1.0" encoding="utf-8" ?>
<!--
UPDATE: Note that adding the xmlns attribute causes all output
to disappear unless you use Chris's second solution.
-->
<CrystalReport xmlns="urn:crystal-reports:schemas:report-detail" >
<Group Level="1">
<GroupHeader>
<Section>
<Field FieldName="apple" />
</Section>
</GroupHeader>
<Group Level="2">
not this
</Group>
</Group>
</CrystalReport>
transform
<?xml version="1.0" encoding="utf-8"?>
<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:template match="/CrystalReport">
<root>
<xsl:apply-templates select="Group"/>
</root>
</xsl:template>
<xsl:template match="Group[#Level='1']/GroupHeader">
<tag1><xsl:value-of select="Section/Field/#FieldName"/></tag1>
</xsl:template>
</xsl:stylesheet>
output
<?xml version="1.0" encoding="utf-8"?>
<root>
<tag1>apple</tag1>
not this
</root>
You are facing the problem where the built in templates of the XML parser are coming into play. You are apply templates to all of the Group elements, but only catching one of them with your own templates. The other is handled by the default templates which out put the values of all nodes. I suggest that you change
<xsl:template match="/CrystalReport">
into
<xsl:template match="/">
This will override the root default templates which are producing the extra output. You can find more on the built in template rules at http://www.w3.org/TR/xslt#built-in-rule
Then override the basic text() template so you final XSLT looks a bit like this
<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:template match="/">
<root>
<xsl:apply-templates select="CrystalReport/Group"/>
</root>
</xsl:template>
<xsl:template match="Group[#Level='1']/GroupHeader">
<tag1>
<xsl:value-of select="Section/Field/#FieldName"/>
</tag1>
</xsl:template>
<xsl:template match="text()"/>
UPDATE
Or even simpler you could just match the desired elements and use something like this
<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:template match="/CrystalReport">
<root>
<xsl:apply-templates select="Group[#Level='1']/GroupHeader"/>
</root>
</xsl:template>
<xsl:template match="GroupHeader">
<tag1>
<xsl:value-of select="Section/Field/#FieldName"/>
</tag1>
</xsl:template>
This will leave the default text() templates in place which can be very handy.
Try
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:crystal="urn:crystal-reports:schemas:report-detail">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/crystal:CrystalReport">
<root>
<xsl:apply-templates select="crystal:Group[#Level='1']/crystal:GroupHeader"/>
</root>
</xsl:template>
<xsl:template match="crystal:GroupHeader">
<tag1>
<xsl:value-of select="crystal:Section/crystal:Field/#FieldName"/>
</tag1>
</xsl:template>