I have multiple XSL's or style sheets for schematron validation of an XML (where one is the root and others being imported to the root). The below code is for one stylesheet.
public XMLSaxonTransformer(InputStream styleSheet) {
final TransformerFactory transformerFactory = TransformerFactory.newInstance("net.sf.saxon.TransformerFactoryImpl", null);
try {
styleSheetTemplate = transformerFactory.newTemplates(new StreamSource(styleSheet), new StreamSource(styleSheet));
} catch (TransformerConfigurationException e) {
throw new Exception("Exception caught while parsing StyleSheet", e);
}
}
The above works for single stylesheet. but, my requirement is for multiple stylesheets with one root and others being included in the root.
My code should look something like below.
public XMLSaxonTransformer(List<InputStream> styleSheetList) {
final TransformerFactory transformerFactory = TransformerFactory.newInstance("net.sf.saxon.TransformerFactoryImpl", null);
try {
// do something to add the XSL's list to the transformer
} catch (TransformerConfigurationException e) {
throw new Exception("Exception caught while parsing StyleSheet", e);
}}
You should only specify the top-level stylesheet to the TransformerFactory.newTemplates() method. The top-level stylesheet will pull in other stylesheet modules using xsl:include or xsl:import. If those stylesheet modules exist only in memory, you will need to nominate a URIResolver to the TransformerFactory. Your URIResolver will be called when an xsl:include or xsl:import declaration is encountered, and it should return a Source object (for example a StreamSource) to deliver the contents of the stylesheet module.
Related
I have an action that turns XML data into an XSL based report that is viewed on a web page. A separate action called by a user can be used to transform this report into a PDF and save it to a location.
I am looking to use the Quartz Scheduler to run and save the report as a PDF every day. I have confirmed that the Quartz Scheduler runs successfully, however when it attempts to transform the data into a PDF report is fails.
public byte[] render(Action action, String location) throws Exception {
// Transform the source XML to System.out.
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
// configure fopFactory as desired
FopFactory fopFactory = FopFactory.newInstance();
// configure foUserAgent as desired
FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
// Create a transformer for the stylesheet.
Templates templates = null;
Transformer transformer;
if (location != null) {
templates = getTemplates(location);
transformer = templates.newTransformer();
} else {
transformer = TransformerFactory.newInstance().newTransformer();
}
transformer.setURIResolver(getURIResolver());
Object result = action;
Source xmlSource = getDOMSourceForStack(result);
// Construct fop with desired output format
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, out);
Result res = new SAXResult(fop.getDefaultHandler());
transformer.transform(xmlSource, res);
return out.toByteArray();
} catch (Exception e) {
throw e;
} finally {
out.close(); // ...and flush...
}
}
protected Templates getTemplates(String path) throws TransformerException, IOException {
if (path == null) {
throw new TransformerException("Stylesheet path is null");
}
Templates templates = null;
URL resource = ServletActionContext.getServletContext().getResource(path);
if (resource == null) {
throw new TransformerException("Stylesheet " + path + " not found in resources.");
}
TransformerFactory factory = TransformerFactory.newInstance();
templates = factory.newTemplates(new StreamSource(resource.openStream()));
return templates;
}
protected Source getDOMSourceForStack(Object value)
throws IllegalAccessException, InstantiationException {
return new DOMSource(getAdapterFactory().adaptDocument("result", value));
}
protected AdapterFactory getAdapterFactory() {
if (adapterFactory == null) {
adapterFactory = new AdapterFactory();
}
return adapterFactory;
}
protected void setAdapterFactory(AdapterFactory adapterFactory) {
this.adapterFactory = adapterFactory;
}
protected URIResolver getURIResolver() {
return new ServletURIResolver(
ServletActionContext.getServletContext());
}
}
The action parameter is the action that runs the report that will be transformed and the location parameter is the location of the XSL Stylesheet that formats the report. This action functions when called by the user, but when Quartz tries to call it on a scheduled basis it throws a NullPointerException error at the
URL resource = ServletActionContext.getServletContext().getResource(path);
line. Is there a way to get Quartz to work with this transformation action?
Your code throws NPE because it is executed by Quartz outside of a Struts action and therefore ServletActionContext.getServletContext() returns null. You need to find out a different way to get hold of the ServletContext. The easiest would be implementing a ServletContextListener that would save the ServletContext instance in a private static field when its contextInitialized method is invoked. Then in your Quartz job code, you would use something like this:
MyServletContextListener.getServletContext().getResource(path);
getServletContext is a static method that you will need to add to your ServletContextListener and it will simply return the saved ServletContext instance.
You need to make sure Quartz is started AFTER your ServletContextListener's contextInitialized method has been called. The easiest would be starting Quartz from the contextInitialized method. This ensures that when Quartz executes any of your jobs, the ServletContext field would be initialized and MyServletContextListener.getServletContext() would not return null.
For a project at university, I need to parse a GML file. GML files are XML based so I use JDOM2 to parse it. To fit my purposes, I extended org.jdom2.Document like so:
package datenbank;
import java.io.File;
// some more imports
public class GMLDatei extends org.jdom2.Document {
public void saveAsFile() {
// ...
}
public GMLKnoten getRootElement(){
return (GMLKnoten) this.getDocument().getRootElement();
}
public void setRootElement(GMLKnoten root){
this.getDocument().setRootElement(root);
}
}
I also extended org.jdom2.Element and named the subclass GMLKnoten but this does not matter too much for my question.
When testing, I try to load a GML file. When using the native document and element classes, it loads fine, but when using my subclasses, I get the following scenario:
I load the file using:
SAXBuilder saxBuilder = new SAXBuilder();
File inputFile = new File("gml/Roads_Munich_Route_Lines.gml");
GMLDatei document = null;
ArrayList<String> types = new ArrayList<String>();
try {
document = (GMLDatei) saxBuilder.build(inputFile);
} catch (JDOMException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
In the line
document = (GMLDatei) saxBuilder.build(inputFile);
I get a Cast-Exception:
Exception in thread "main" java.lang.ClassCastException: org.jdom2.Document cannot be cast to datenbank.GMLDatei
at datenbank.GMLTest.main(GMLTest.java:27)
I thought that casting schould be no problem as I am subclassing org.jdom2.document. What am I missing?
vat
In general I want to "challenge" your requirement to extend Document - what value do you get from your custom classes that are not already part of the native implementation? I ask this for 2 reasons:
as the maintainer of JDOM, should I be adding some new feature?
I am just curious.....
JDOM has a system in place for allowing you to extend it's core classes and have a different implementation of them when parsing a document. It is done by extending the JDOMFactory.
Consider this code here: JDOMFactory interface. When SAXParser parses a document it uses those methods to build the document.
There is a default, overridable implementation in DefaultJDOMFactory that you can extend, and, for example, in your implementation, you must override the non-final "Element" methods like:
#Override
public Element element(final int line, final int col, final String name,
String prefix, String uri) {
return new Element(name, prefix, uri);
}
and instead have:
#Override
public Element element(final int line, final int col, final String name,
String prefix, String uri) {
return new GMLKnoten (name, prefix, uri);
}
Note that you will have to override all methods that are non-final and return content that is to be customised (for example, you will have to override 4 Element methods by my count.
With your own GMLJDOMFactory you can then use SAXBuilder by either using the full constructor new SAXBuilder(null, null, new GMPJDOMFactory()) or by setting the JDOMFactory after you have constructred it with setJDOMFactory(...)
I am using javax.xml.transform.* to do XSLT transformation. Since the xslt file to be used comes from the outside world there could be errors in that file, and I am going to give back some meaningful response to the user.
Although I can easily catch the TransformationExceptions, I found no way to obtain enough information from it. For example, if there is a tag to be terminated by an end-tag, printStackTrace() gives scarring message
javax.xml.transform.TransformerConfigurationException: Could not compile stylesheet
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl.newTemplates(Unknown Source)
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl.newTransformer(Unknown Source)
... (100 lines)
and getMessage() gives only
Could not compile stylesheet
None of them gives the real reason of the error.
I noticed that in Eclipse test console I can see the following
[Fatal Error] :259:155: The element type "sometag" must be terminated by the matching end-tag "</sometag>".
ERROR: 'The element type "sometag" must be terminated by the matching end-tag "</sometag>".'
FATAL ERROR: 'Could not compile stylesheet'
This is exactly what I want. Unfortunately, since this is a web application, the user cannot see this.
How can I display the correct error message to the user?
Put your own ErrorListener on your Transformer instance using Transformer.setErrorListener, like so:
final List<TransformationException> errors = new ArrayList<TransformationException>();
Transformer transformer = ... ;
transformer.setErrorListener(new ErrorListener() {
#Override
public void error(TransformerException exception) {
errors.add(exception);
}
#Override
public void fatalError(TransformerException exception) {
errors.add(exception);
}
#Override
public void warning(TransformerException exception) {
// handle warnings as well if you want them
}
});
// Any other transformer setup
Source xmlSource = ... ;
Result outputTarget = ... ;
try {
transformer.transform(xmlSource, outputTarget);
} catch (TransformerException e) {
errors.add(e); // Just in case one is thrown that isn't handled
}
if (!errors.isEmpty()) {
// Handle errors
} else {
// Handle output since there were no errors
}
This will log all the errors that occur into the errors list, then you can use the messages off those errors to get what you want. This has the added benefit that it will try to resume the transformation after the errors occur. If this causes any problems, just rethrow the exception by doing:
#Override
public void error(TransformerException exception) throws TransformationException {
errors.add(exception);
throw exception;
}
#Override
public void fatalError(TransformerException exception) throws TransformationException {
errors.add(exception);
throw exception;
}
Firstly, it's likely that any solution will dependent on your choice of XSLT processor. Different implementations of the JAXP interface might well provide different information in the exceptions they generate.
It's possible that the error from the XML parser is available in a wrapped exception. For historic reasons, TransformerConfigurationException offers both getException() and getCause() to access wrapped exceptions, and it may be worth checking them both.
Alternatively it's possible that the information was supplied in a separate call to the ErrorListener.
Finally, this particular error is detected by the XML parser (not the XSLT processor) so in the first instance it will be handled by the parser. It may well be worth setting the parser's ErrorHandler and catching parsing errors at that level. If you want explicit control over the XML parser used by the transformation, use a SAXSource whose XMLReader is suitably initialized.
You can configure System.out to write in your own OutputStream.
Use of ErrorListener don't catch all output.
If you work with threads you can look here (http://maiaco.com/articles/java/threadOut.php) to avoid change of System.out for other threads.
example
public final class XslUtilities {
private XslUtilities() {
// only static methods
}
public static class ConvertWithXslException extends Exception {
public ConvertWithXslException(String message, Throwable cause) {
super(message, cause);
}
}
public static String convertWithXsl(String input, String xsl) throws ConvertWithXslException {
ByteArrayOutputStream systemOutByteArrayOutputStream = new ByteArrayOutputStream();
PrintStream oldSystemOutPrintStream = System.out;
System.setOut(new PrintStream(systemOutByteArrayOutputStream));
ByteArrayOutputStream systemErrByteArrayOutputStream = new ByteArrayOutputStream();
PrintStream oldSystemErrPrintStream = System.err;
System.setErr(new PrintStream(systemErrByteArrayOutputStream));
String resultXml;
try {
System.setProperty("javax.xml.transform.TransformerFactory", "net.sf.saxon.TransformerFactoryImpl");
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer(new StreamSource(new StringReader(xsl)));
StringWriter stringWriter = new StringWriter();
transformer.transform(new StreamSource(new StringReader(input)), new StreamResult(stringWriter));
resultXml = stringWriter.toString();
} catch (TransformerException e) {
System.out.flush();
final String systemOut = systemOutByteArrayOutputStream.toString();
System.err.flush();
final String systemErr = systemErrByteArrayOutputStream.toString();
throw new ConvertWithXslException("TransformerException - " + e.getMessageAndLocation()
+ (systemOut.length() > 0 ? ("\nSystem.out:" + systemOut) : "")
+ (systemErr.length() > 0 ? ("\nSystem.err:" + systemErr) : ""), e);
} finally {
System.setOut(oldSystemOutPrintStream);
System.setErr(oldSystemErrPrintStream);
}
return resultXml;
}
}
Hey guys so I am brand new to the world of Java-XML parsing and found that the StaX API is probably my best bet as I need to both read and write XML files. Alright so I have a very short (and should be very simple) program that (should) create an XMLInputFactory and use that to create a XMLStreamReader. The XMLStreamReader is created using a FileInputStream attached to an XML file in the same directory as the source file. However even though the FileInputStream compiled properly, the XMLInputFactory cannot access it and without the FileInputStream it cannot creat the XMLStreamReader. Please help as I have no idea what to and am frustrated to the point of giving up!
import javax.xml.stream.*;
import java.io.*;
public class xml {
static String status;
public static void main(String[] args) {
status = "Program has started";
printStatus();
XMLInputFactory inFactory = XMLInputFactory.newInstance();
status = "XMLInputFactory (inFactory) defined"; printStatus();
try { FileInputStream fIS = new FileInputStream("stax.xml"); }
catch (FileNotFoundException na) { System.out.println("FileNotFound"); }
status = "InputStream (fIS) declared"; printStatus();
try { XMLStreamReader xmlReader = inFactory.createXMLStreamReader(fIS); } catch (XMLStreamException xmle) { System.out.println(xmle); }
status = "XMLStreamReader (xmlReader) created by 'inFactory'"; printStatus();
}
public static void printStatus(){ //this is a little code that send notifications when something has been done
System.out.println("Status: " + status);
}
}
also here is the XML file if you need it:
<?xml version="1.0"?>
<dennis>
<hair>brown</hair>
<pants>blue</pants>
<gender>male</gender>
</dennis>
Your problem has to do w/ basic java programming, nothing to do w/ stax. your FileInputStream is scoped within a try block (some decent code formatting would help) and therefore not visible to the code where you are attempting to create the XMLStreamReader. with formatting:
XMLInputFactory inFactory = XMLInputFactory.newInstance();
try {
// fIS is only visible within this try{} block
FileInputStream fIS = new FileInputStream("stax.xml");
} catch (FileNotFoundException na) {
System.out.println("FileNotFound");
}
try {
// fIS is not visible here
XMLStreamReader xmlReader = inFactory.createXMLStreamReader(fIS);
} catch (XMLStreamException xmle) {
System.out.println(xmle);
}
on a secondary note, StAX is a nice API, and a great one for highly performant XML processing in java. however, it is not the simplest XML api. you would probably be better off starting with the DOM based apis, and only using StAX if you experience performance issues using DOM. if you do stay with StAX, i'd advise using XMLEventReader instead of XMLStreamReader (again, an easier api).
lastly, do not hide exception details (e.g. catch them and print out something which does not include the exception itself) or ignore them (e.g. continue processing after the exception is thrown without attempting to deal with the problem).
There are a lot of examples on the internet of "reading" files but I can't find anything on "editing" a node value and writing it back out to the original file.
I have a non-working xml writer class that looks like this:
import org.w3c.dom.Document;
public class RunIt {
public static Document xmlDocument;
public static void main(String[] args)
throws TransformerException, IOException {
try {
xmlDocument = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().parse("thor.xml");
} catch (IOException ex) {
ex.printStackTrace();
} catch (SAXException ex) {
ex.printStackTrace();
} catch (ParserConfigurationException ex) {
ex.printStackTrace();
}
addElement("A", "New");
writeDoc();
}
public static void addElement(String path, String val){
Element e = xmlDocument.createElement(path);
e.appendChild(xmlDocument.createTextNode(val));
xmlDocument.getDocumentElement().appendChild(e);
}
public static void writeDoc() throws TransformerException, IOException {
StringWriter writer = new StringWriter();
Transformer tf;
try {
tf = TransformerFactory.newInstance().newTransformer();
tf.transform(new DOMSource(xmlDocument), new StreamResult(writer));
writer.close();
} catch (TransformerConfigurationException e) {
e.printStackTrace();
} catch (TransformerFactoryConfigurationError e) {
e.printStackTrace();
}
}
}
For this example, lets say this is the XML and I want to add a "C" node (inside the A node) that contains the value "New" :
<A>
<B>Original</B>
</A>
You use the Document object to create new nodes. Adding nodes as you suggest involves creating a node, setting its content and then appending it to the root element. In this case your code would look somehting like this:
Element e = xmlDocument.createElement("C");
e.appendChild(xmlDocument.createTextNode("new"));
xmlDocument.getDocumentElement().appendChild(e);
This will add the C node as a new child of A right after the B node.
Additionally, Element has some convenience functions that reduce the amount of required code. The second line above could have been replaced with
e.setTextContent("new");
More complicated efforts involving non root elements will involve you using XPath to fetch the target node to be edited. If you do start to use XPath to target nodes, bear in mind that the JDK XPath performance is abysmal. Avoid using an XPath of "#foo" in favor of constructs like e.getAttribute("foo") whenever you can.
EDIT: Formatting the document back to a string which can be written to a file can be done with the following code.
Document xmlDocument;
StringWriter writer = new StringWriter();
TransformerFactory.newInstance().transform(new DOMSource(xmlDocument), new StreamResult(writer));
writer.close();
String xmlString = writer.toString();
EDIT: Re: updated question with code.
Your code doesn't work because you're conflating 'path' and 'element name'. The parameter to Document.createElement() is the name of the new node, not the location in which to place it. In the example code I wrote I didn't get into locating the appropriate node because you were asking specifically about adding a node to the document parent element. If you want your addElement() to behave the way I think you're expecting it to behave, you'd have to add another parameter for the xpath of the target parent node.
The other problem with your code is that your writeDoc() function doesn't have any output. My example shows writing the XML to a String value. You can write it to any writer you want by adapting the code, but in your example code you use a StringWriter but never extract the written string out of it.
I would suggest rewriting your code something like this
public static void main(String[] args) {
File xmlFile = new File("thor.xml");
Document xmlDocument = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().parse(xmlFile);
// this is effective because we know we're adding to the
// document root element
// if you want to write to an arbitrary node, you must
// include code to find that node
addTextElement(xmlDocument.getDocumentElement(), "C", "New");
writeDoc(new FileWriter(xmlFile);
}
public static Element addTextElement(Node parent, String element, String val){
Element e = addElement(parent, element)
e.appendChild(xmlDocument.createTextNode(val));
return e;
}
public static Element addElement(Node parent, String element){
Element e = xmlDocument.createElement(path);
parent.appendChild(e);
return e;
}
public static void writeDoc(Writer writer) {
try {
Transformer tf = TransformerFactory.newInstance().newTransformer();
tf.transform(new DOMSource(xmlDocument), new StreamResult(writer));
} finally {
writer.close();
}
}
In order to write your document back to a file, you'll need an XML serializer or write your own. If you are using the Xerces library, check out XMLSerializer. For sample usage, you can also check out the DOMWriter samples page.
For more information on Xerces, read this