PROBLEM
I am working on a problem where I am required to take JSON input file and convert to a XML file. I have provided an example below of what i am deaing with.
In JSON :
"playerStats": [
{ "jerseyNumber": "23", "fgPercentage": 60, "plusMinus": "plus" },
{ "jerseyNumber": "24", "fgPercentage": 40, "plusMinus": "minus" }
] }
In XML :
<BallerStats>
<BallerStat>
<Baller><BallerJersey>23</BallerJersey><Type>Jersey</Type></Baller>
<fgPercentage><Type>PERCENT</Type><Value>60</Value></fgPercentage>
</BallerStat>
<BallerStat>
<Baller><BallerJersey>23</BallerJersey><Type>Jersey</Type></Baller>
<fgPercentage><Type>PERCENT</Type><Value>60</Value></fgPercentage>
</BallerStat>
</BallerStats>
As you can see it is not a 1 to 1 ratio meaning, in JSON we represent fgPercentage as 60 but in xml we separate it by "value" and "type"
Also certain tag names are different. In JSON we call them "playerStats" and in XML we call the equivalent tag "BallerStats"
SOLUTION
Keep in mind the JSON file could have many other fields apart from playerstats like coachstats or fanstats but we only care about playerstats
The following is what I have come up with but again I could be wrong.
Scan Json and look for "playerStats"
If not found , Do nothing.
If found then grab valuable information like jerseyNumber , fgPercentage, ignore plusMinus and store them in a object of a template class with these Values.
Then use the Object to generate appropriate XML tags.
I am not sure if my approach is effective or efficient. I would love to hear suggestions and I am doing this in Java so feel free to refer to any reliable and popular libraries. I would love to see all approaches and even better if you provide code snippet.
One option is to use json-to-xml() in XSLT 3.0.
You'll need an XSLT 3.0 processor; I used Saxon-HE 9.8 in my example below.
You can pass the JSON in as a param.
The results of json-to-xml() will look something like this:
<map xmlns="http://www.w3.org/2005/xpath-functions">
<array key="playerStats">
<map>
<string key="jerseyNumber">23</string>
<number key="fgPercentage">60</number>
<string key="plusMinus">plus</string>
</map>
<map>
<string key="jerseyNumber">24</string>
<number key="fgPercentage">40</number>
<string key="plusMinus">minus</string>
</map>
</array>
</map>
You can process that XML to get your target XML.
Example...
Java
package so.test1;
import java.io.File;
import java.io.OutputStream;
import javax.xml.transform.stream.StreamSource;
import net.sf.saxon.s9api.XsltTransformer;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.QName;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.Serializer;
import net.sf.saxon.s9api.XdmAtomicValue;
import net.sf.saxon.s9api.XsltCompiler;
import net.sf.saxon.s9api.XsltExecutable;
/**
*
* #author dhaley
*
*/
public class SOTest1 {
public static void main(String[] args) throws SaxonApiException {
final String XSLT_PATH = "src/so/test1/test.xsl";
final String JSON = "{\"playerStats\": [\n" +
" {\"jerseyNumber\": \"23\", \"fgPercentage\": 60, \"plusMinus\": \"plus\"},\n" +
" {\"jerseyNumber\": \"24\", \"fgPercentage\": 40, \"plusMinus\": \"minus\"}\n" +
"]}";
OutputStream outputStream = System.out;
Processor processor = new Processor(false);
Serializer serializer = processor.newSerializer();
serializer.setOutputStream(outputStream);
XsltCompiler compiler = processor.newXsltCompiler();
XsltExecutable executable = compiler.compile(new StreamSource(new File(XSLT_PATH)));
XsltTransformer transformer = executable.load();
transformer.setInitialTemplate(new QName("init")); //<-- SET INITIAL TEMPLATE
transformer.setParameter(new QName("json"), new XdmAtomicValue(JSON)); //<-- PASS JSON IN AS PARAM
transformer.setDestination(serializer);
transformer.transform();
}
}
XSLT 3.0 (test.xsl)
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xpath-default-namespace="http://www.w3.org/2005/xpath-functions">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="json"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template name="init">
<!--Only process playerStats-->
<xsl:apply-templates select="json-to-xml($json)//array[#key='playerStats']"/>
</xsl:template>
<xsl:template match="array">
<BallerStats>
<xsl:apply-templates/>
</BallerStats>
</xsl:template>
<xsl:template match="map">
<BallerStat>
<xsl:apply-templates/>
</BallerStat>
</xsl:template>
<xsl:template match="*[#key='jerseyNumber']">
<Baller>
<BallerJersey xsl:expand-text="true">{.}</BallerJersey>
<Type>Jersey</Type>
</Baller>
</xsl:template>
<xsl:template match="*[#key='fgPercentage']">
<fgPercentage>
<Type>PERCENT</Type>
<Value xsl:expand-text="true">{.}</Value>
</fgPercentage>
</xsl:template>
<xsl:template match="*[#key=('plusMinus')]"/>
</xsl:stylesheet>
Output (stdout)
<?xml version="1.0" encoding="UTF-8"?>
<BallerStats>
<BallerStat>
<Baller>
<BallerJersey>23</BallerJersey>
<Type>Jersey</Type>
</Baller>
<fgPercentage>
<Type>PERCENT</Type>
<Value>60</Value>
</fgPercentage>
</BallerStat>
<BallerStat>
<Baller>
<BallerJersey>24</BallerJersey>
<Type>Jersey</Type>
</Baller>
<fgPercentage>
<Type>PERCENT</Type>
<Value>40</Value>
</fgPercentage>
</BallerStat>
</BallerStats>
Underscore-java library can convert json to xml. I am the maintainer of the project. Live example
import com.github.underscore.U;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class MyClass {
#SuppressWarnings("unchecked")
public static void main(String args[]) {
String json = "{\"playerStats\": [\n"
+ "{ \"jerseyNumber\": \"23\", \"fgPercentage\": 60, \"plusMinus\": \"plus\" },"
+ "{ \"jerseyNumber\": \"24\", \"fgPercentage\": 40, \"plusMinus\": \"minus\" }"
+ "] }";
String xml = "<BallerStats>"
+ "<BallerStat>"
+ " <Baller><BallerJersey>23</BallerJersey><Type>Jersey</Type></Baller>"
+ " <fgPercentage><Type>PERCENT</Type><Value>60</Value></fgPercentage>"
+ "</BallerStat>"
+ "</BallerStats>";
List<Map<String, Object>> jsonArray = U.get((Map<String, Object>) U.fromJson( json ), "playerStats");
Map<String, Object> map = new LinkedHashMap<>();
List<Map<String, Object>> ballerStats = new ArrayList<>();
Map<String, Object> ballerStat = new LinkedHashMap<>();
map.put("BallerStats", ballerStat);
ballerStat.put("BallerStat", ballerStats);
for (Map<String, Object> jsonItem : jsonArray) {
Map<String, Object> newBallerStat = (Map<String, Object>) ((Map<String, Object>) ((Map<String, Object>) U.fromXml( xml )).get("BallerStats")).get("BallerStat");
((Map<String, Object>) newBallerStat.get("Baller")).put("BallerJersey", jsonItem.get("jerseyNumber"));
((Map<String, Object>) newBallerStat.get("fgPercentage")).put("Value", jsonItem.get("fgPercentage"));
ballerStats.add(newBallerStat);
}
System.out.println(U.toXml(map));
}
}
Related
I have an xml file like the following:
<?xml version="1.0"?>
<Book>
<Title>Ulysses</Title>
<Author>James <b>Joyce</b></Author>
</Book>
I need to parse this using Java into a pojo like
title="Ulysses"
author="James <b>Joyce</b>"
In other words, I need the html or possible custom xml tags to remain as plain text rather than xml elements, when parsing.
I can't edit the XML at all but it would be ok for me to create a custom xslt file to transform the xml.
I've got the following Java code for using xslt to assist with the reading of the xml,
TransformerFactory factory = TransformerFactory.newInstance();
Source stylesheetSource = new StreamSource(new File(stylesheetPathname).getAbsoluteFile());
Transformer transformer = factory.newTransformer(stylesheetSource);
Source inputSource = new StreamSource(new File(inputPathname).getAbsoluteFile());
Result outputResult = new StreamResult(new File(outputPathname).getAbsoluteFile());
transformer.transform(inputSource, outputResult);
This does apply my xslt to the file which is written out but I can't come up with the correct xslt to do it. I had a look at Add CDATA to an xml file but this does not work for me.
Essentially, I believe I want the file to look like
<?xml version="1.0"?>
<Book>
<Title>Ulysses</Title>
<Author><![CDATA[James <b>Joyce</b>]]></Author>
</Book>
Then I can extract
"James <b>Joyce</b>". I tried the approach suggested here: Add CDATA to an xml file
But it did not work for me.
I used the following xslt:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="no"/>
<xsl:template match="Author">
<xsl:copy>
<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>
<xsl:copy-of select="*"/>
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</xsl:copy>
</xsl:template>
and this produced:
<?xml version="1.0" encoding="UTF-8"?>
Ulysses
<Author><![CDATA[
<b>Joyce</b>]]></Author>
Can you please help with this? I want the original document to be written out in it's entirety but with the CDATA surrounding everything within the author element.
Thanks
Isn't using a simple html/xml parser like Jsoup a better way of solving this?
Using Jsoup you can try something like this:
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.parser.Parser;
import org.jsoup.select.Elements;
public class Example {
public static void main(String[] args) {
String xml = "<?xml version=\"1.0\"?>\n"
+ "<Book>\n"
+ " <Title>Ulysses</Title>\n"
+ " <Author>James <b>Joyce</b></Author>\n"
+ "</Book>";
Document doc = Jsoup.parse(xml, "", Parser.xmlParser());
doc.outputSettings().prettyPrint(false);
Elements books = doc.select("Book");
for(Element e: books){
Book b = new Book(e.select("Title").html(),e.select("Author").html());
System.out.println(b.title);
System.out.println(b.author);
}
}
public static class Book{
String title;
String author;
public Book(String title, String author) {
this.title = title;
this.author = author;
}
}
}
With XSLT 3.0 as supported by Saxon 9.8 HE (available on Maven and Sourceforge) you can use XSLT as follows:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
exclude-result-prefixes="xs math"
version="3.0">
<xsl:output cdata-section-elements="Author"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="Author">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:value-of select="serialize(node())"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
As for your attempt, you basically need to "implement" the identity transformation template concisely written in XSLT 3.0 as <xsl:mode on-no-match="shallow-copy"/> as a template
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
in XSLT 1.0 so that those nodes not handled by more specialized template (like the one for Author elements) are recursively copied through.
Then, with the copy-of selecting all child nodes node() and not only the element nodes * you get
<xsl:template match="Author">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>
<xsl:copy-of select="node()"/>
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</xsl:copy>
</xsl:template>
I am able to generate CSV file from XML file using XSLT, but the only header of XML file header is only showing on CSV file. The Values are not showing up.
Here is my java code:-
import java.io.File;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.w3c.dom.Document;
public class xml2csv {
public static void main() throws Exception {
File stylesheet = new File("C:/Users/Admin/Desktop/out.xslt");
File xmlSource = new File("C:/Users/Admin/Desktop/out.xml");
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(xmlSource);
StreamSource stylesource = new StreamSource(stylesheet);
Transformer transformer = TransformerFactory.newInstance().newTransformer(stylesource);
Source source = new DOMSource(document);
Result outputTarget = new StreamResult(new File("C:/Users/Admin/Desktop/out.csv"));
transformer.transform(source, outputTarget);
}
}
the XML file:-
<root>
<header>Symbol</header>
<row>NIFTY 50</row>
<row>LUPIN</row>
<header>Open</header>
<row>9,670.35</row>
<row>1,082.90</row>
<header>High</header>
<row>9,684.25</row>
<row>1,137.00</row>
</root>
XSLT file:-
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" >
<xsl:output method="text" omit-xml-declaration="yes" indent="no"/>
<xsl:template match="/">
Symbol,Open,High
<xsl:for-each select="//header">
<xsl:value-of select="concat(Symbol, ',', Open, ',', High)"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
So, I am getting only header of XML using this XSLT, where am I going wrong?
If I am guessing correctly at what you're trying to accomplish here, you will need to do something like:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="text"/>
<xsl:template match="/root">
<!-- header -->
<xsl:text>Symbol,Open,High
</xsl:text>
<!-- data -->
<xsl:variable name="n" select="count(row) div 3" />
<xsl:for-each select="row[position() <= $n]">
<xsl:variable name="i" select="position()" />
<xsl:text>"</xsl:text>
<xsl:value-of select="."/>
<xsl:text>","</xsl:text>
<xsl:value-of select="../row[$n + $i]"/>
<xsl:text>","</xsl:text>
<xsl:value-of select="../row[2 * $n + $i]"/>
<xsl:text>"
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Applied to your input example, the result will be:
Symbol,Open,High
"NIFTY 50","9,670.35","9,684.25"
"LUPIN","1,082.90","1,137.00"
I have added quotes around the values because some of them contain commas - but I did not handle the possibility of some them containing a quote.
As I mentioned in a comment to your question, this could be a lot easier if your XML were structured in a more friendly way.
I made a minimal example to reproduce the problem. This is the transformation (mini.xsl):
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<foo>
<xsl:copy-of select="."/>
</foo>
</xsl:template>
</xsl:stylesheet>
and this is the input (mini.xml):
<?xml version="1.0" encoding="utf-8"?>
<bar xmlns:x="baz">
<x:baz/>
</bar>
When I apply the transformation with
xsltproc mini.xsl mini.xml
the result looks as expected:
<?xml version="1.0"?>
<foo>
<bar xmlns:x="baz">
<x:baz/>
</bar>
</foo>
However, when I run the transformation with the following Java program,
import java.io.FileInputStream;
import java.io.IOException;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
public class Program
{
public static void main(String[] args)
{
try
{
Source transform = new StreamSource(new FileInputStream(args[0]));
Templates templates = TransformerFactory.newInstance().newTemplates(transform);
Document input = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new FileInputStream(args[1]));
Result result = new StreamResult(System.out);
templates.newTransformer().transform(new DOMSource(input), result);
}
catch (TransformerFactoryConfigurationError | ParserConfigurationException | SAXException | IOException | TransformerException e)
{
e.printStackTrace();
}
}
}
the result looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<bar xmlns:x="baz">
<baz/>
</bar>
</foo>
(notice that the x: prefix in front of baz is missing.)
Why is that?
And what can I do about it (to preserve the namespace prefix)?
For what I can only assume are historical reasons, DocumentBuilderFactory is non-namespace-aware by default. You need to explicitly switch on namespaces before you do newDocumentBuilder().
It would also be better to use the parse method that takes a File directly rather than creating your own FileInputStream (which your code is not closing once the parse is finished), and likewise with the StreamSource from which the Transformer is built.
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document input = dbf.newDocumentBuilder().parse(new File(args[1]));
What I want to do?
I'm working on a Apache Cocoon Project and want to find solution to return paramaters in HTML pages.
I need to get the parameter, which has ArrayList type, and use it in HTML page in order to fill a table. How can I do this? Is it correct to set a request parameter? If yes, then how to use it inside HTML code? If no, then how to return the parameter correctly?
ActionClass.java
public class ActionClass implements Action, ThreadSafe{
public Map act(Redirector rdrctr, org.apache.cocoon.environment.SourceResolver sr, Map map, String string, Parameters params) throws Exception {
// READ REQUEST
Request request = ObjectModelHelper.getRequest(map);
// DO SOMETHING XQUERY VIA BASEX, SPARQL RDFSTORE WHATEVER
ArrayList<ResultBean> results = xquery();
Map sitemapParams = new HashMap();
// SET REQUEST PARAMETER
request.setAttribute("results",results);
return sitemapParams;
}
}
ResultBean.java
package com.kiddo.grlegislation;
public class ResultBean {
private String id;
private String title;
private String type;
public void setId(String i){
this.id = i;
};
public void setTitle(String t){
this.title = t;
};
public String getId(){
return this.id;
};
public String getTitle(){
return this.title;
};
}
First of all, are you sure that you need an Action? Actions are meant to act somehow (update something in the database, invoke a web service, etc). If you just need to generate content, a Generator class could be a better fit for you...
Anyway... How could you return something from an Action into HTML? Lets see it with an example:
Action class: because it extends Action, it must return a Map. Just add there whatever data you need to pass to your HTML:
package com.stackoverflow;
public class ActionClass extends Action {
public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters params) {
Map<String, String> sitemapParams = new HashMap<String, String>();
sitemapParams.put("myVariable", "hello world!");
return sitemapParams;
}
}
sitemap.xmap: in your sitemap file, you can access any data returned by the Action, by placing it's key between brackets. Then you can pass it to your HTML generator:
<map:components>
<map:actions>
<map:action name="myAction" src="com.stackoverflow.ActionClass" />
</map:actions>
</map:components>
...
<map:match ...>
<map:generate ... />
<map:act type="myAction">
<map:transform src="myTransformation.xsl">
<map:parameter name="something" value="{myVariable}"/>
</map:transform>
</map:act>
<map:serialize .../>
</map:match>
myTransformation.xsl: your XSLT file should read the data and embed it into your HTML:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="something" select="'default value if you wish to specify one'"/>
<xsl:template match="xxx">
<html><body>...
<xsl:value-of select="$something" />
...</body></html>
</xsl:template>
</xsl:stylesheet>
You can get more information about Actions, Generators and the sitemap in this page. It's from Apache Cocoon 2.1 documentation, but it also applies to 2.2.
Alternative approach, with a Generator:
Generator class: this file builds a XML document, which is then passed into the pipeline. You could have something like this:
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
...
public class GeneratorClass extends AbstractGenerator {
private String foo;
#Override
public void setup(SourceResolver resolver, Map objectModel, String src, Parameters params) throws ProcessingException, SAXException, IOException {
super.setup(resolver, objectModel, src, params);
// you can read input parameters in here:
foo = params.getParameter("someParameter");
}
public void generate() throws IOException, SAXException, ProcessingException {
ArrayList<ResultBean> beans = xQuery(foo);
// Let's build the XML document. I'll do it by manually appending text strings,
// but there is no need, we could just use Xstream or any similar library
StringBuilder xml = new StringBuilder();
xml.append("<results>");
// Iterate through the array list...
for (ResultBean b : beans) {
xml.append("<result>");
xml.append("<id>").append(b.getId()).append("</id>");
xml.append("<title>").append(b.getTitle()).append("</title>");
xml.append("</result>");
}
// ... and we end the XML string
xml.append("</results>");
// Return the XML to Cocoon's pipeline
XMLReader xmlreader = XMLReaderFactory.createXMLReader();
xmlreader.setContentHandler(super.xmlConsumer);
InputSource source = new InputSource(new StringReader(xml.toString()));
xmlreader.parse(source);
try {
this.finalize();
} catch (Throwable e) {
}
}
}
Sitemap.xmap: you just need to call your generator, and then apply your XSLT to the generated XML:
<map:components>
<map:generators>
<map:generator type="myGenerator" src="com.stackoverflow.GeneratorClass" />
</map:generators>
/<map:components>
<map:generate type="myGenerator">
<!-- if you need to pass input data to the generator... -->
<map:parameter name="someParameter" select="{request-param:something}" />
</map:generate>
<map:transform src="myTransformation.xsl" />
<map:serialize type="html"/>
myTransformation.xsl:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<head></head>
<body>
<table>
<xsl:for-each select="results/result">
<tr>
<td><xsl:value-of select="id/text()"/></td>
<td><xsl:value-of select="title/text()"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
You can get more info about Cocoon generators here. Once again, it's an official tutorial for Cocoon 2.1, but it's also valid for Cocoon 2.2.
I'm trying to do something like this and it seems to work:
<map:pipeline id="pd-version">
<map:match pattern="pd/*/*">
<map:aggregate element="foo">
<map:part src="cocoon:/version-{1}-{2}.xml"/>
<map:part src="http://localhost:8888/GRLegislation/pd/{1}/{2}/data.xml"/>
</map:aggregate>
<map:transform src="legislation_updated.xslt" type="xslt-saxon"/>
<map:transform src="legislation.xslt" type="xslt-saxon">
</map:transform>
<map:serialize type="xhtml"/>
</map:match>
</map:pipeline>
<map:pipeline>
<map:match pattern="version-*-*">
<map:generate type="versiongen">
<map:parameter name="type" value="pd"/>
<map:parameter name="year" value="{1}"/>
<map:parameter name="id" value="{2}"/>
</map:generate>
<map:serialize type="xml"/>
</map:match>
</map:pipeline>
Parameters are not loaded correctly from <map:part> to <map:match>. Also I have some XSLT issues, because now we have a different root of XML.
I have a xml structure "Filter" that get unmarshalled into in a java class called "Filter".
The XML state looks roughly like:
<filter>
<propertyType>
<propertyName>prop1</propertyName>
<propertyValue>val1</propertyValue>
</propertyType>
<propertyType>
<propertyName>prop2</propertyName>
<propertyValue>val2</propertyValue>
</propertyType>
</filter>
Ordinarily, it works great.
However, there are certain situations where one of these property values itself contains xml structure (see second propertyValue below):
<filter>
<propertyType>
<propertyName>prop1</propertyName>
<propertyValue>val1</propertyValue>
</propertyType>
<propertyType>
<propertyName>prop2</propertyName>
<propertyValue><nodeA><nodeB>valB</nodeB></nodeA></propertyValue>
</propertyType>
</filter>
The problem here is that after unmarshalling this structure, the propertyValue is null.
I would like to simply be able to have the unmarshalling ignore this xml-looking code and treat it as a simple string value.
Does anyone know how I can accomplish this? Thanks for any reply!
How about the annotation of using "#XmlAnyElement"?
You can get the instance of org.w3c.dom.Element.
The text data should be able to be obtained by operating this instance.
class PropertyType {
private String propertyName;
// private String propertyValue; // comment out
#XmlAnyElement(lax=true)
private List<org.w3c.dom.Element> propertyValue; // Adding this
}
exsample of to get text data.
// It is assumed that the child node is one.
org.w3c.dom.Node nd = propertyValue.get(0).getFirstChild();
while(true) {
if (nd.hasChildNodes()) {
nd = nd.getFirstChild();
} else {
System.out.println(nd.getNodeValue()); // this is text data
break;
}
}
For this use case I would create an XSLT that will convert the XML document. Then using the javax.xml.transform.* APIs, transform the XML to a JAXBResult to unmarshal the object:
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.util.JAXBResult;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamSource;
public class Demo {
public static void main(String[] args) throws Exception {
TransformerFactory tf = TransformerFactory.newInstance();
File xsltFile = new File("transform.xsl");
StreamSource xsltSource = new StreamSource(xsltFile);
Transformer transformer = tf.newTransformer(xsltSource);
File xml = new File("input.xml");
StreamSource xmlSource = new StreamSource(xml);
JAXBContext jc = JAXBContext.newInstance(Filter.class);
JAXBResult jaxbResult = new JAXBResult(jc);
transformer.transform(xmlSource, jaxbResult);
Filter filter = (Filter) jaxbResult.getResult();
}
}
transform.xsl
<?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:template match="propertyValue"> <xsl:value-of select="descendents"/>
<xsl:element name="propertyValue">
<xsl:value-of select="node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
AFAIK the JAXB work on xml schema for unmarshalling XML into Java object. So if schema defines element as simple element, it can only contain text. If you need to store XML as simple text. You might need to escape it using CDATA construct. Try enclosing the same as shown below, after unmarshling you will get the XML as it is.
<filter>
<propertyType>
<propertyName>prop1</propertyName>
<propertyValue>val1</propertyValue>
</propertyType>
<propertyType>
<propertyName>prop2</propertyName>
<propertyValue><![CDATA[<nodeA><nodeB>valB</nodeB></nodeA>]]></propertyValue>
</propertyType>
</filter>