Cannot Edit XSL attribute value using Java - java

I have an XSL file where I want to update my attribute tag value using java code.
This is my XSL file :-
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
version="2.0">
<xsl:attribute-set name="__frontmatter">
<xsl:attribute name="text-align">center</xsl:attribute>
</xsl:attribute-set>
</xsl:stylesheet>
I want to read parent tag "__frontmatter" , then under this child node tag "text-align" and update the value center.
I know to read Node name from XML file , but this is something confusing me, how will I read xsl:attribute-set and name="xyz" from java code?
EDIT:- Adding method.
private static void updateElementValue(Document doc) {
String a="right";
NodeList frontmatterr = doc.getElementsByTagName("text-align");
Element e = null;
for(int i=0; i<frontmatterr.getLength();i++){
e = (Element) frontmatterr.item(i);
Node name = emp.getElementsByTagName("text-align").item(0).getFirstChild();
name.setNodeValue(name.getNodeValue().valueOf(a));
}
}
My java code where I am trying to read xsl node.

Although you say in your comment that you "cannot use another XSLT file" I think XSLT is the right tool to manipulate XSLT so I post a suggestion here, with the XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:param name="new-text-align">right</xsl:param>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="xsl:attribute-set[#name = '__frontmatter']/xsl:attribute[#name = 'text-align']">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:value-of select="$new-text-align"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
online at https://xsltfiddle.liberty-development.net/bdxtqo/1, you can transform the original XSLT you have to
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:attribute-set name="__frontmatter">
<xsl:attribute name="text-align">right</xsl:attribute>
</xsl:attribute-set>
</xsl:stylesheet>
within the Java JAXP API you would simply set up a Transformer https://docs.oracle.com/javase/8/docs/api/javax/xml/transform/Transformer.html with above XSLT as the source (https://docs.oracle.com/javase/8/docs/api/javax/xml/transform/TransformerFactory.html#newTransformer-javax.xml.transform.Source-) and use the original XSLT as the input Source and would get the new XSLT as the Result of the transform method.

Related

using XSLT apply-template to descendents with different names

I am trying to use apply-templates for elements encapsulated within parent element but has different element name. In my example I want to apply it to /Author/Name/ elements and choose either of one whichever has a value. This task is continuation of another question I have asked.
I am using the following XML
<?xml version="1.0" encoding="UTF-8"?>
<Author>
<Info>
<Name>
<FirstName>#3 bb***</FirstName>
<LastName>test</LastName>
</Name>
<Input>
<Item>##### 3 ??***</Item>
</Input>
</Info>
<Custom>Test</Custom>
</Author>
I am applying the following XSLT 1.0 using java. In above example I want template applied to firstName and lastName but show value in firstName since it is the first with a value.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:variable name="allowed-start-chars">abcdefghijklmnopqrstuvwxyz</xsl:variable>
<xsl:variable name="allowed-follow-chars">0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ ?abcdefghijklmnopqrstuvwxyz-&apos;.,/#&()!+</xsl:variable>
<xsl:template match="/">
<html>
<body>
<!-- Not sure how to use `xsl:choose` just added to show my intentions-->
<xsl:choose>
<xsl:when test="..">
<div>
<xsl:apply-templates select="Author/Name/FirstName"/>
</div>
</xsl:when>
</xsl:choose>
</body>
</html>
</xsl:template>
<!--used pipe to apply for two different elements but it does not work-->
<xsl:template match="FirstName|LastName">
<!-- find the first character eligible to be starting character -->
<xsl:variable name="start-chars" select="translate(., translate(., $allowed-start-chars, ''), '')"/>
<xsl:variable name="start-char" select="substring($start-chars, 1, 1)"/>
<!-- get text after the chosen starting character -->
<xsl:variable name="tail" select="substring-after(., $start-char)"/>
<!-- remove unwanted characters from tail -->
<xsl:variable name="fo" select="translate($tail, translate($tail, $allowed-follow-chars, ''), '')"/> <xsl:choose>
<!--only show if character remain after removing bad ones, and not empty-->
<xsl:when test="string-length($start-char) > 0 and normalize-space(concat($start-char, $fo))">
<xsl:value-of select="normalize-space(concat($start-char, $fo))"/>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
The Java code is Stylizer class from Oracle tuorial page example, please refer the link for full code. I am running command-line sending xml and xslt files.
File stylesheet = new File(argv[0]);
File datafile = new File(argv[1]);
DocumentBuilder builder = factory.newDocumentBuilder();
document = builder.parse(datafile);
// Use a Transformer for output
TransformerFactory tFactory = TransformerFactory.newInstance();
StreamSource stylesource = new StreamSource(stylesheet);
Transformer transformer = tFactory.newTransformer(stylesource);
DOMSource source = new DOMSource(document);
StreamResult result = new StreamResult(System.out);
transformer.transform(source, result);
I would appreciate again help on this one. As I am learning XSLT and I am also not sure what performance issue using approach
From the context of:
<xsl:template match="/">
you can do:
<xsl:apply-templates select="(Author/Info/Name/FirstName | Author/Info/Name/LastName)[text()][1]"/>
to select the first element of the two that has a child text node. If there are only FirstName and LastName, you can shorten this to:
<xsl:apply-templates select="Author/Info/Name/*[text()][1]"/>
Note that your attempt:
<xsl:apply-templates select="Author/Name/FirstName"/>
cannot work because Name is not a child of Author.

How to copy parent node namespace to child element using xslt?

My xml looks like which I created using Java JAXBContext and Marshaller.
I want to format some part of xml only not the whole xml.
<?xml version="1.0" encoding="UTF-8"?>
<ns4:Requests xmlns:ns2="http://www.dummy.com/xsd/tublu/murmur_001" xmlns:ns3="http://www.dummy.com/xsd/CommonObjects_001" xmlns:ns4="http://www.dummy.com/xsd/naku_001">
<ns4:RequestSetId>fhskgvseruigiu</ns4:RequestSetId>
<ns4:RequestStream>CHAPP</ns4:RequestStream>
<ns4:Request>
<ns4:TrackAndTrace>
<ns4:CPAId>003</ns4:CPAId>
<ns4:CorrelationId>ytuty</ns4:CorrelationId>
</ns4:TrackAndTrace>
</ns4:Request>
<ns4:Request>
<ns4:TrackAndTrace>
<ns4:CPAId>003</ns4:CPAId>
<ns4:CorrelationId>cyuri7</ns4:CorrelationId>
</ns4:TrackAndTrace>
</ns4:Request>
</ns4:Requests>
I want to format like
<?xml version="1.0" encoding="UTF-8"?>
<ns4:Requests xmlns:ns2="http://www.dummy.com/xsd/tublu/murmur_001" xmlns:ns4="http://www.dummy.com/xsd/naku_001" xmlns:ns3="http://www.dummy.com/xsd/CommonObjects_001">
<ns4:RequestSetId>fhskgvseruigiu</ns4:RequestSetId>
<ns4:RequestStream>CHAPP</ns4:RequestStream>
<ns4:Request xmlns:ns4="http://www.dummy.com/xsd/naku_001"><ns4:TrackAndTrace><ns4:CPAId>003</ns4:CPAId><ns4:CorrelationId>ytuty</ns4:CorrelationId></ns4:TrackAndTrace></ns4:Request>
<ns4:Request xmlns:ns4="http://www.dummy.com/xsd/naku_001"><ns4:TrackAndTrace><ns4:CPAId>003</ns4:CPAId><ns4:CorrelationId>cyuri7</ns4:CorrelationId></ns4:TrackAndTrace></ns4:Request>
</ns4:Requests>
Here is the solution (by Transforming the XML Data using Java's XSLT APIs),
As you may also have noticed.. JAXB alone cannot meet this requirement, but after marshalling the object to a formatted XML String (as u have shown) you can then post process/transform it accordingly using a suitable XSLT file
So to get a linearized 'Request' element, just make use of the xsl shown below:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="TrackAndTrace"/>
<xsl:strip-space elements="Request"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note: Also tested that above method/approach is working properly - used the Stylizer sample code (from https://docs.oracle.com/javase/tutorial/jaxp/xslt/transformingXML.html)
Cheers!
Update: If you want a solution that also preserves the original namespace prefix as shown in your question, follow this variation
Add factory.setNamespaceAware(true); in the Stylizer code
& Use this tweaked XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="w3.org/1999/XSL/Transform" xmlns:ns4="dummy.com/xsd/naku_001">
<xsl:strip-space elements="ns4:TrackAndTrace"/>
<xsl:strip-space elements="ns4:Request"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Java XML and XSLT transformation error: Namespace for prefix 'm' has not been declared

I'm trying to transform an XML document with the javax.xml.transform.Transformer and XSLT, and am having trouble with a namespace prefix that is not recognized when I call the transform method.
Here is the XML document which has the "m" namespace prefix defined in it:
<?xml version="1.0" encoding="UTF-8"?>
<edmx:Edmx xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx" Version="1.0">
<edmx:DataServices xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:DataServiceVersion="1.0">
<Schema xmlns="http://schemas.microsoft.com/ado/2008/09/edm" Namespace="SITUATION">
</Schema>
</edmx:DataServices>
</edmx:Edmx>
Here is the XSL that currently just copies the whole XML document (later I'd like to extend it to merge in another XML document, similar to what is described here: https://stackoverflow.com/a/5706319/208011):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Copy everything including attributes as default action -->
<xsl:template match="*">
<xsl:element name="{name()}">
<xsl:apply-templates select="#*" />
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{name()}"><xsl:value-of select="." /></xsl:attribute>
</xsl:template>
</xsl:stylesheet>
Here is the java code:
Source xsltSource = new StreamSource(new File(getClass().getClassLoader().getResource("merge-metadata.xsl").getFile()));
transformer = transFact.newTransformer(xsltSource);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document input = dbf.newDocumentBuilder().parse(new InputSource(new StringReader(s1)));
StringWriter out = new StringWriter();
transformer.transform(new DOMSource(input), new StreamResult(out));
Here's the stack trace:
Caused by: java.lang.RuntimeException: Namespace for prefix 'm' has not been declared.
at com.sun.org.apache.xml.internal.serializer.SerializerBase.getNamespaceURI(SerializerBase.java:915)
at com.sun.org.apache.xml.internal.serializer.SerializerBase.addAttribute(SerializerBase.java:431)
at com.sun.org.apache.xml.internal.serializer.ToUnknownStream.addAttribute(ToUnknownStream.java:316)
Eventually I want to merge this XML document with another, but I can't get past the namespace issue to just copy this one.
If I don't use the xsl stylesheet in the transformer factory newTransformer() method then I don't get the namespace error and the output of the transformation is exactly the same as the original XML document.
If I set the DocumentBuilderFactory to not be namespace aware then I don't get the exception, but the output of the transformation is missing the namespaces.
Thanks for your help.
Your hand-crafted, pseudo-identity transform is losing the namespace declarations and failing when it tries to name an element with an undeclared namespace prefix:
<xsl:element name="{name()}">
Use the standard identity transformation instead:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

How to update a range of an XMl file tag?

My problem is I don't know How to update a XML file. In the following XML file I want to include some tags inside another tag which are already exist in the file.
**My XML file is as following: **
<?xml version="1.0" encoding="UTF-8"?>
<root>
<PayrunDetails>
<PayrunNumber>000777</PayrunNumber>
</PayrunDetails>
<PayLocation>
<LocationCode>ACT</LocationCode>
<LocationDescription>ACT</LocationDescription>
<CompanyDetails>
<CName>APPLE Limited</CName>
<Payslip>
<StaffNumber>12345</StaffNumber>
<PayDetails>
<AmountGross>9999</AmountGross>
<ComponentDetails>
<ComponentType>SALARY</ComponentType>
<Amount>1999</Amount>
<YTDAmount>10616</YTDAmount>
</ComponentDetails>
<ComponentDetails>
<ComponentType>SALARY</ComponentType>
<Amount>7305</Amount>
<YTDAmount>76703</YTDAmount>
</ComponentDetails>
</PayDetails>
</Payslip>
</CompanyDetails>
</PayLocation>
</root>
My desired output file is as following:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<PayrunDetails>
<PayrunNumber>000777</PayrunNumber>
</PayrunDetails>
<PayLocation>
<LocationCode>ACT</LocationCode>
<LocationDescription>ACT</LocationDescription>
<CompanyDetails>
<CName>APPLE Limited</CName>
<Payslip>
<StaffNumber>12345</StaffNumber>
<PayDetails>
<AmountGross>9999</AmountGross>
<ComponentDetails>
<ComponentType ID="SALARY">
<Amount>1999</Amount>
<YTDAmount>10616</YTDAmount>
</ComponentType>
</ComponentDetails>
<ComponentDetails>
<ComponentType ID="TAX">
<Amount>7305</Amount>
<YTDAmount>76703</YTDAmount>
</ComponentType>
</ComponentDetails>
</PayDetails>
</Payslip>
</CompanyDetails>
</PayLocation>
</root>
In the above desired file you will find that ComponentType tag has included the rest of the tags exist inside the ComponentDetails tag.
For the above said problem I want to use XSLT but I don't know what code should I write to get the solution.
I'm fairly new to XSLT so please excuse the potential novice question. Any guidance would be appreciated here.
Thanks in advance.
First read up on the identity transform in XSLT, which involves this template
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
(If you could use XSLT 3.0, you could just write <xsl:mode on-no-match="shallow-copy"/> instead)
This will copy across all your nodes and attributes as-is, which in your case gets you almost there.
There are a number of ways you could the transform of the nodes you want. One way is to match the ComponentDetails tag, to create a new ComponentType in the output, along with code to select the other child nodes.
<xsl:template match="ComponentDetails">
<xsl:copy>
<ComponentType ID="{ComponentType}">
<xsl:apply-templates />
</ComponentType>
</xsl:copy>
</xsl:template>
This makes use of Attribute Value Templates to create the ID attribute.
Note that <xsl:apply-templates /> is short-hand for <xsl:apply-templates select="node()" /> and so this will still select the existing ComponentType element in the input document, which will then be matched by the identity template. To stop ComponentType being output twice, you need to add a template to match and ignore it.
<xsl:template match="ComponentType" />
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" html-version="5"/>
<xsl:strip-space elements="*" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ComponentDetails">
<xsl:copy>
<ComponentType ID="{ComponentType}">
<xsl:apply-templates />
</ComponentType>
</xsl:copy>
</xsl:template>
<xsl:template match="ComponentType" />
</xsl:stylesheet>

Create xmlns attribute in the XML using XSLT Transformation

I am trying to add the xmlns attribute to the resulting XML with a value passed by parameter during XSLT transformation using JDK Transformer (Oracle XML v2 Parser or JAXP) but it always defaults to http://www.w3.org/2000/xmlns/
My source XML
<test/>
My XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://example.com">
<xsl:param name="myNameSpace" select="'http://neilghosh.com'"/>
<xsl:template match="/">
<process>
<xsl:attribute name="xmlns:neil">
<xsl:value-of select="$myNameSpace"/>
</xsl:attribute>
</process>
</xsl:template>
</xsl:stylesheet>
My Result
<?xml version="1.0"?>
<process xmlns="http://www.w3.org/2000/xmlns/" xmlns:neil="neilghosh.com">
</process>
My Desired Result
<?xml version="1.0"?>
<process xmlns="http://example.com" xmlns:neil="neilghosh.com">
</process>
Firstly, in the XSLT data model, you don't want to create an attribute node, you want to create a namespace node.
Namespace nodes are usually created automatically: if you create an element or attribute in a particular namespace, the requisite namespace node (and hence, when serialized, the namespace declaration) are added automatically by the processor.
If you want to create a namespace node that isn't necessary (because it's not used in the name of any element or attribute) then in XSLT 2.0 you can use xsl:namespace. If you're stuck with XSLT 1.0 then there's a workaround, that involves creating an element in the relevant namespace and then copying its namespace node:
<xsl:variable name="ns">
<xsl:element name="neil:dummy" namespace="{$param}"/>
</xsl:variable>
<process>
<xsl:copy-of select="$ns/*/namespace::neil"/>
</process>
Michael Kay provided you with the correct answer, but based on your comments, you aren't sure how to use it in your transformation.
Here is a complete transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pNamespace" select="'neilghosh.com'"/>
<xsl:variable name="vDummy">
<xsl:element name="neil:x" namespace="{$pNamespace}"/>
</xsl:variable>
<xsl:template match="/*">
<xsl:element name="process" namespace="http://example.com">
<xsl:copy-of select="namespace::*"/>
<xsl:copy-of select="ext:node-set($vDummy)/*/namespace::*[.=$pNamespace]"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<test/>
the wanted, correct result is produced:
<process xmlns="http://example.com" xmlns:neil="neilghosh.com" />
Namespace declarations in XML are not attributes even though they look like attributes. In XSLT 2.0 you can use <xsl:namespace name="neil" select="$myNameSpace" /> to add a namespace declaration to the result tree dynamically but that feature is not available in XSLT 1.0.
Don't try to create "xmlns" attributes yourself. Create the namespaces in the XSLT and they will be done automatically.
This XSLT works (tested with Saxon 9.4):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:neil="neilghosh.com"
xpath-default-namespace="http://example.com"
xmlns="http://example.com" version="2.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:param name="myDynamicNamespace" select="'http://neilghosh.com'"/>
<xsl:template match="/">
<xsl:element name="process">
<xsl:namespace name="neil" select="$myDynamicNamespace"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
And gives the following output:
<?xml version="1.0" encoding="UTF-8"?>
<process xmlns="http://example.com" xmlns:neil="http://neilghosh.com"/>
Finally got an workaround which worked with my XSLT Processor (Oracle XML V2 Parser)
I had to transform it to a DOM Document and then persist that DOM to filesystem instead of outputting directly to StreamResult
I used DOMResult in the transform method
Following XSLT fragment worked but there was an extra xmlns:xmlns="http://www.w3.org/2000/xmlns/" which was probably absorbed by Document and did not appear in the final output when I persisted to file system.
<process>
<xsl:attribute name="xmlns">
<xsl:value-of select="'http://example.com'"/>
</xsl:attribute>
<process>
I know this is not the best way to do but given the parse constraint this is the only choice I have now.

Categories