I have a problem with transforming a XML (TEI P5) to PDF. My Stylesheet is from TEI https://github.com/TEIC/Stylesheets and I call the file fo/fo.xsl. When I transform my XML using Oxygen and Saxon there is no Problem, the PDF will be generated. But in my J2EE application the transform does not work. I think because of the XSLT 2.0. So i wanted to change to an other parser. I downloaded Saxon 9HE and included it in my project. Then I experimented and changed the java call and it looks now like this:
String inputXSL2 = "/de/we/parser/xslt/fo/fo.xsl";
String inputXML2 = "/de/we/parser/testfiles/Presentation.xml";
Source xsltTarget = new StreamSource(inputXSL2);
try {
TransformerFactory tFactory = TransformerFactory.newInstance();
tFactory.setURIResolver(new XsltURIResolver());
Transformer transformer = tFactory.newTransformer(xsltTarget);
Controller controller = (net.sf.saxon.Controller) transformer;
Configuration configuration = controller.getConfiguration();
configuration.setConfigurationProperty(FeatureKeys.PRE_EVALUATE_DOC_FUNCTION, Boolean.TRUE);
Source source = transformer.getURIResolver().resolve(inputXML2, "");
DocumentInfo newdoc = configuration.buildDocument(source);
configuration.getGlobalDocumentPool().add(newdoc, inputXML2);
Source streamSource = null;
StreamResult resultTarget = null;
streamSource = new StreamSource(new StringReader(inputXML2));
resultTarget = new StreamResult(new StringWriter());
transformer.transform(streamSource, resultTarget);
} catch (Exception e) {
System.out.println(e);
}
class XsltURIResolver implements URIResolver {
#Override
public Source resolve(String href, String base) throws TransformerException {
try {
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("/de/we/parser/xslt/fo/" + href);
StreamSource ss = new StreamSource(inputStream);
ss.setSystemId("/de/we/parser/xslt/fo/" + href);
return ss;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
}
The error is:
I/O error reported by XML parser processing /de/we/parser/xslt/fo/fo.xsl:
\de\we\parser\xslt\fo\fo.xsl (Das System kann den angegebenen Pfad nicht finden)
But the file exists so i don't know whats the problem.
Had a lot other version where I tried to use Saxon, but the result was never a generated pdf :(
Need some help please
Please don't crosspost to multiple lists.
You asked the same question here
https://saxonica.plan.io/boards/3/topics/5849?r=5851#message-5851
where I answered it.
Related
I have the following code which takes XML as input and produces a bunch of other files as output.
public void transformXml(InputStream inputFileStream, Path outputDir) {
try {
Resource resource = resourceLoader
.getResource("classpath:demo.xslt");
LOGGER.info("Creating output XMLs and Assessment Report in {}", outputDir);
final File outputFile = new File(outputDir.toString());
final Processor processor = getSaxonProcessor();
XsltCompiler compiler = processor.newXsltCompiler();
XsltExecutable stylesheet = compiler.compile(new StreamSource(resource.getFile()));
Xslt30Transformer transformer = stylesheet.load30();
Serializer out = processor.newSerializer(outputFile);
out.setOutputProperty(Serializer.Property.METHOD, "xml");
transformer.transform(new StreamSource(inputFileStream), out);
LOGGER.debug("Generated DTD XMLs and Assessment Report successfully in {}", outputDir);
} catch (SaxonApiException e) {
throw new XmlTransformationException("Error occured during transformation", e);
} catch (IOException e) {
throw new XmlTransformationException("Error occured during loading XSLT file", e);
}
}
private Processor getSaxonProcessor() {
final Configuration configuration = Configuration.newConfiguration();
configuration.disableLicensing();
Processor processor = new Processor(configuration);
return processor;
}
The XML input contains a DOCTYPE tag which resolves to a DTD that is not available to me. Hence why I am wanting to use a catalog to point it to a dummy DTD which is on my classpath.
I am struggling to find a way to this. Most examples that I find out there, are not using the s9api implementation. Any ideas?
Instead of
new StreamSource(inputFileStream)
you should instantiate a SAXSource, containing an XMLReader initialized to use the catalog resolver as its EntityResolver.
If you need to do the same thing for other source documents, such as those read using doc() or document(), you should supply a URIResolver which itself returns a SAXSource initialized in the same way.
There are other ways of doing it using Saxon configuration properties, but I think the above is the simplest.
The only Question i found that seemed similiar but sadly this didnt help
Hi so ive been stuck on this Problem for a few hours now.
I am trying to write new Data into an preexisting XML file (/creating it when it doesnt exist yet) for Data Collection for a small game i am programming for uni.
Ive created a DataRecorder class for this that works fine when testing with a main in that class
private Transformer transformer;
private Document documentWriter;
private DocumentBuilder documentBuilder;
private Element eventList;
private static final String RECORDED_DATA = "Game/src/main/resources/recordedData.xml";
public DataRecorder() {
recordedData = new ArrayList<Data>();
this.username = System.getProperty("user.name");
this.newData = false;
try {
TransformerFactory transformerFactory = TransformerFactory.newInstance();
this.transformer = transformerFactory.newTransformer();
} catch (Exception e) {
System.out.println("Error when creating DataRecorder: Transformer");
}
File tempFile = new File(RECORDED_DATA);
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
this.documentBuilder = dbf.newDocumentBuilder();
if (!tempFile.exists()) {
//Creates new Document if it doesnt exist
this.documentWriter = this.documentBuilder.newDocument();
} else {
this.documentWriter = this.documentBuilder.parse(RECORDED_DATA);
}
} catch (Exception e) {
System.out.println("Error when creating DataRecorder: Document Writer");
}
Element root;
if (!tempFile.exists()) {
//Creates new Document if it doesnt exist
root = this.documentWriter.createElement("recordedData");
this.documentWriter.appendChild(root);
} else {
root = this.documentWriter.getDocumentElement();
}
Element player = this.documentWriter.createElement("Player");
player.setAttribute("name", this.username);
root.appendChild(player);
this.eventList = this.documentWriter.createElement("EventList");
this.eventList.setAttribute("date", Date.from(java.time.ZonedDateTime.now().toInstant()).toString());
this.eventList.setAttribute("timezone", java.time.ZonedDateTime.now().getZone().toString());
player.appendChild(this.eventList);
DOMSource source = new DOMSource(this.documentWriter);
StreamResult result = new StreamResult(System.getProperty("user.dir") + "/src/main/resources/recordedData.xml");
try {
this.transformer.transform(source, result);
} catch (Exception e) {
System.out.println("Error when creating DataRecorder: Could not write System Name and Date");
System.out.println(e);
}
}
This creates an XML file that looks like this
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<recordedData>
<Player name="Name">
<EventList date="Wed Jun 24 21:34:46 CEST 2020" timezone="Europe/Berlin"/>
</Player>
</recordedData>
The problem is that when i execute this Class with its own Main it just adds some new Lines to (which is what i want it to do) while when executing with maven it completly rewrites the xml file.
The path you have is your resources folder, the resources folder is a maven convenience folder, that folder doesn't exist when you run maven build. The contents of that folder are copied to target/classes folder when maven build runs.
Try using a folder that is outside your source code repository
I'm always trying to print my xsl message in a JTextArea. With the Code of my last question I can print the xsl output in my TextArea, but not the message that is written in the xslt file.
Java Code:
public static void xslTransform(File xmlFile)throws IOException, TransformerException{
File xslFile = ...;
StreamSource xmlSource = new StreamSource(xmlFile);
StreamSource xslSource = new StreamSource(xslFile);
StreamResult result = new StreamResult (new StringWriter()); //maybe here is the problem?
TransformerFactory transformerFact = TransformerFactory.newInstance();
transformerFact.setAttribute(FeatureKeys.MESSAGE_EMITTER_CLASS, "MyMessageEmitter");
Transformer transformer = transformerFact.newTransformer(xslSource);
transformer.transform(xmlSource,result);
}
public class MyMessageEmitter extends net.sf.saxon.serialize.MessageEmitter{
String message;
private StringWriter stwriter = new StringWriter();
public void MyMessageEmitter() throws XPathException{
setWriter(stwriter);
}
#Override
public void close() throws XPathException{
super.close();
message=stwriter.toString();
myJTextArea.setText(message);
stwriter = new StringWriter();
}
}
The XSLT File:
<xsl:template match="/">
<xsl:for-each select="//#id">
<xsl:message>
<xsl:value-of select="name(parent::*)"/> <xsl:text></xsl:text><xsl:value-of select="."/>
</xslmessage>
</xsl:for-each>
</xsl:tempate>
with this Code message from java is empty because this xslt do not paste any output (for example a new xml-document. I checked this with another xslt that prints a new xml.
So how can i get the message that prints the xslt?
Thanks for all help
KaFu
If MyMessageEmitter has a reference to myJTextArea then presumably it is an inner class.
This means that its full name is not "MyMessageEmitter" but something more complex. With your code as written I get this:
Error
Failed to load MyMessageEmitter
net.sf.saxon.trans.XPathException: Failed to load MyMessageEmitter
at net.sf.saxon.trans.DynamicLoader.getClass(DynamicLoader.java:123)
...
Caused by: java.lang.ClassNotFoundException: MyMessageEmitter
If I change the class name in factory.setAttribute to the correct name I get this:
Error
Failed to instantiate class jaxptest.TransformMessageTest$MyMessageEmitter
net.sf.saxon.trans.XPathException: Failed to instantiate class jaxptest.TransformMessageTest$MyMessageEmitter
at net.sf.saxon.trans.DynamicLoader.getInstance(DynamicLoader.java:185)
...
Caused by: java.lang.InstantiationException: jaxptest.TransformMessageTest$MyMessageEmitter
This is because a non-static inner class cannot be instantiated except from its containing class.
Your next error is very subtle and took me a long time to diagnose. You've written
public void MyMessageEmitter() throws XPathException{
setWriter(stwriter);
}
but that makes MyMessageEmitter() an ordinary method, whereas you intended it to be a constructor. So it should have been written:
public MyMessageEmitter() throws XPathException{
setWriter(stwriter);
}
It works if I make the inner class static; but the problem now is that the static class can't simply refer to the jTextArea by name.
The next problem is also quite subtle, and this time it's Saxon that's at least half to blame: the close() method on the MessageEmitter is called twice. Unfortunately when this happens the first call creates a new empty StringWriter, and the second call writes the content of this empty StringWriter to your text area. So I changed it to create a new StringWriter in the open() method rather than in the close() method.
This now leaves the question of how to communicate between your MessageEmitter and your JTextArea. This isn't trivial. I did it by having the MessageEmitter write to a static variable, and having the callling application pick up this static variable. But obviously, use of global static variables isn't really acceptable in production applications. The problem is that Saxon creates an instance of your MessageEmitter class, but doesn't provide any direct way of communicating with it. The only solution I could find to this involved using some lower-level Saxon interfaces, like this:
public void testMessageCapture() {
try {
String stylesheet =
"<?xml version='1.0'?>" +
"<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform'" +
" version='2.0'>" +
" <xsl:template match='/'>" +
"<first/>" +
"<xsl:message>Hi!</xsl:message>" +
" </xsl:template>" +
"</xsl:stylesheet>";
TransformerFactory transFactory = new TransformerFactoryImpl();
//transFactory.setAttribute(FeatureKeys.MESSAGE_EMITTER_CLASS, "jaxptest.TransformMessageTest$MyMessageEmitter");
Templates templates = transFactory.newTemplates(
new SAXSource(new InputSource(new StringReader(stylesheet))));
StreamResult result = new StreamResult(new StringWriter());
final StringWriter messageOut = new StringWriter();
Transformer transformer = templates.newTransformer();
((net.sf.saxon.jaxp.TransformerImpl)transformer).getUnderlyingController().setMessageEmitter(
new MessageEmitter() {
#Override
public void open() throws XPathException {
setWriter(messageOut);
super.open();
}
}
);
transformer.transform(new StreamSource(new StringReader("<in/>")), result);
assertEquals("Hi!", messageOut.toString().trim());
} catch (TransformerException e) {
e.printStackTrace();
fail();
}
}
(where of course you could substitute the writing to the string writer with a direct write to your JTextArea).
This code is actually specific to Saxon 9.6; in earlier releases you would cast the JAXP Transformer to a Controller directly.
This rather illustrates the limitations of relying on JAXP interfaces. It would be a lot easier to do this using Saxon's native s9api interface.
I got it... Here is the solution:
Processor proc = new Processor(false);
XsltCompiler comp = new proc.newXsltCompiler();
StreamSource styleSource = new StreamSource(xsltFile);
StreamSource xmlSource = new StreamSource(xmlFile);
String msg="";
XsltExecutable templates = comp.compile(styleSource);
XsltTransformer transformer = templates.load();
transformer.setSource(xmlSource);
transformer.setMessageListener(new MessageListener(){
#Override
public void message (XdmNode content, boolean terminate, SourceLocator locator){
try{
if (content.getTypedValue!= null){
msg += content.getTypedValue().toString() + "\n";
}}
catch (SaxonApiException ex){
Logger.getLogger(...);
}
}
});
Writer write = new StringWriter();
Serializer out = proc.newSerializer(write);
transformer.setDestination(out);
transformer.transform();
textAreaOut.setText(msg);
thanks for your help!
I transform one xml to other using xslt.
The point is that in target xml I need shema to be resolved.
Where link to this schema should reside? Please, provide me with right approach for that.
Is the used API enough:
import javax.xml.transform.*;
public class Main implements URIResolver{
private File root;
public static void main(String[] args) throws TransformerException, TransformerConfigurationException,
FileNotFoundException, IOException {
TransformerFactory tFactory = TransformerFactory.newInstance();
tFactory.setURIResolver(new URIResolver() {
public Source resolve(String href, String base) {
if (href.endsWith("in.xml")) {
return new StreamSource(this.getClass().getResourceAsStream("in.xml"));
} else {
return null;
}
}
});
Transformer transformer = tFactory.newTransformer(new StreamSource("rules.xsl"));
transformer.transform(new StreamSource("in.xml"), new StreamResult(new FileOutputStream("out.xml")));
System.out.println("************* The result is in out.xml *************");
#Override
public Source resolve(String href, String base) throws TransformerException {
StreamSource source = new StreamSource(getInputStream(href));
// this works with saxon7/saxon6.5.2/xalan
source.setSystemId(href);
return source;
}
protected InputStream getInputStream(String path)
{
InputStream file = null;
try
{
// load from a dir
file = new FileInputStream(new File(this.root, path));
}
catch (FileNotFoundException e)
{
e.printStackTrace();
System.out.println("File not found");
}
return file;
}
in.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<enfinity
xmlns:dt="http://www.intershop.com/xml/ns/enfinity/6.1/core/impex-dt">..
rules.xsl:
<custom-attribute name="isPerformance" dt:dt="int">1</custom-attribute>
I got:
org.xml.sax.SAXParseException: The prefix "dt" for attribute "dt:dt" associated with an element type "custom-attribute" is not bound.
Exception in thread "main" java.lang.NullPointerException
First step is to solve the SAXParseException. This is caused because your rules.xsl file is not namespace-well-formed. You can't use the namespace prefix dt in your stylesheet unless it is declared in the stylesheet. So add xmlns:dt="http://www.intershop.com/xml/ns/enfinity/6.1/core/impex-dt" to the stylesheet.
If the file you have shown as rules.xsl is the entire file, then it's going to fail because this file is not an XSLT stylesheet.
I can't really help you with your first question, about the reference to a schema. It would help to show what output you want to produce. But you seem to have some much more basic problems to solve first.
I have a code which transform xml file using xsl, my peace of code as following. My problem is when i run the execution point it gives me following error.
StackTrace: javax.xml.transform.TransformerException: javax.xml.transform.TransformerException: com.sun.org.apache.xml.internal.utils.WrappedRuntimeException: /home/app/myapp/bin/xhtml11-flat.dtd (No such file or directory)
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:720)
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:313)
at com.core.util.XmlUtils.transform(XmlUtils.java:151)
at com.core.util.XmlUtils.transform(XmlUtils.java:147)
Long story short it is trying to find dtd file inside the bin directory from where i executed the application.
/home/app/myapp/bin/xhtml11-flat.dtd
I have the xhtml11-flat.dtd file if i copy this file in bin directory it works fine, instead of the bin directory i want to load it from classpath any idea how can i achieve this with minimum changes ?
I don't know from where it is laoding .dtd code so that i can set my path in it.
//Execution Point
function transform(){
Templates templates = getTemplates();
StringWriter result = new StringWriter();
XmlUtils.transform(templates.newTransformer(), input, new StreamResult(result));
...
}
private Templates getTemplates() throws Exception {
if (templates == null) {
templates = XmlUtils.createTemplates(XslRdcSourceDocTransformer.class.getResourceAsStream("/xsl/" + getXslFileName()));
}
return templates;
}
public static Templates createTemplates(InputStream stream) throws Exception {
TransformerFactory tfactory = TransformerFactory.newInstance();
return tfactory.newTemplates(new StreamSource(stream));
}
Your xml files are probably containing a doctype declaration containing a relative path to the dtd:
<!DOCTYPE html SYSTEM "xhtml11-flat.dtd">
The transformer api tries to resolve this path to the current working directory of the java program. To customize how the path is resolved you need to implement a EntityResolver. This EntityResolver can return an InputSource referring to a copy of the dtd loaded from the classpath.
public InputSource resolveEntity(final String publicId, final String systemId) throws SAXException {
if ("xhtml11-flat.dtd".equals(systemId)) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
InputSource is = new InputSource();
is.setSystemId(systemId);
is.setByteStream(cl.getResourceAsStream("/com/example/dtd/xhtml11-flat.dtd"));
return is;
} else {
return null;
}
}
How you use this class depends on the type of source for your transformation. For a DOMSource you have to configure the DocumentBuilder:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
DocumentBuilder builder = ...
builder.setEntityResolver(entityResolver);
Source source = new DOMSource(builder.parse(inputStream));
For a SAXSource the setting is on the XMLReader instance:
SAXParserFactory factory1 = SAXParserFactory.newInstance();
factory1.setValidating(false);
factory1.setNamespaceAware(true);
SAXParser parser = factory1.newSAXParser();
XMLReader xmlreader = parser.getXMLReader();
xmlreader.setEntityResolver(entityResolver);
Source source = new SAXSource(xmlreader, new InputSource(stream));
The code for the transformation is the same regardless of the source type and should look similar to the code you currently have in your XmlUtils class:
Templates templates = ...
Result result = new StreamResult(...);
Transformer transformer = templates.newTransformer();
transformer.transform(source, result);