I have downloaded the latest 9ee version of saxon api for dotnet.
I've got a simple program that transforms an xml from an xslt.
it works for simple xslts, but I cannot seem to see how to enable schema validation (of the xslt document).
here is my C# (thanks to How to use XSLT 3.0 using Saxon-HE 9.8 in .NET)
public static string TransformXml(string xmlData, string xslData)
{
var xsltProcessor = new Processor(true);
var documentBuilder = xsltProcessor.NewDocumentBuilder();
documentBuilder.BaseUri = new Uri("file://");
var xdmNode = documentBuilder.Build(new StringReader(xmlData));
var xsltCompiler = xsltProcessor.NewXsltCompiler();
var xsltExecutable = xsltCompiler.Compile(new StringReader(xslData));
var xsltTransformer = xsltExecutable.Load();
xsltTransformer.InitialContextNode = xdmNode;
var results = new XdmDestination();
xsltTransformer.Run(results);
return results.XdmNode.OuterXml;
}
here is my xslt (taken largely from "mastering xslt" Doug Tidwell)
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="text" />
<xsl:import-schema schema-location="file:///C:/Users/m_r_n/source/repos/SaxonEEExample/ValidateXslt/po1.xsd" />
<xsl:template match="schema-element(PurchaseOrder)">
<xsl:for-each select="item">
<xsl:value-of select="#id" />: <xsl:value-of select="title" />
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template match="*">
<xsl:message terminate="yes">Source document is not a purchase order
</xsl:message>
</xsl:template>
</xsl:stylesheet>
when I run it, I get
"Source document is not purchase order"....
If I remove the "schema-element" function, then I get the desired
100: Water for Elephants
101: Glass Castle: A Memoir
200: 5 Amp Electric plug
my hunch is the xslt is correct, but I'm missing some setting on API (to be fair the saxon examples, don't include this sort of thing)
edit
actually to be honest, I want to be able to do 2 things, 1 validate an xslt against an input schema (import-schema) AND execute an XSLT against an xslt with these constructs, as 2 seperate operations.
edit
so taking the feedback from Michael Kay in the comments.
I've gone back to the saxon examples, and borrowed the validator example.
Rather than validating an instance document, I've tried to validate the XSLT itself.
var samplesDir = new Uri(AppDomain.CurrentDomain.BaseDirectory);
Processor processor = new Processor(true);
processor.SetProperty("http://saxon.sf.net/feature/timing", "true");
processor.SetProperty("http://saxon.sf.net/feature/validation-warnings", "false"); //Set to true to suppress the exception
SchemaManager manager = processor.SchemaManager;
manager.XsdVersion = "1.1";
manager.ErrorList = new List<StaticError>();
Uri schemaUri = new Uri(samplesDir, "po1.xsd");
try
{
manager.Compile(schemaUri);
}
catch (Exception e)
{
Console.WriteLine(e);
Console.WriteLine("Schema compilation failed with " + manager.ErrorList.Count + " errors");
foreach (StaticError error in manager.ErrorList)
{
Console.WriteLine("At line " + error.LineNumber + ": " + error.Message);
}
return;
}
// Use this to validate an instance document
SchemaValidator validator = manager.NewSchemaValidator();
XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Ignore;
String inputFileName = new Uri(samplesDir, "po.xsl").ToString();
XmlReader xmlReader = XmlReader.Create(inputFileName, settings);
validator.SetSource(xmlReader);
Console.WriteLine("Validating input file " + inputFileName);
validator.ErrorList = new List<ValidationFailure>();
XdmDestination psvi = new XdmDestination();
validator.SetDestination(psvi);
try
{
validator.Run();
}
catch (Exception e)
{
Console.WriteLine(e);
Console.WriteLine("Instance validation failed with " + validator.ErrorList.Count + " errors");
foreach (ValidationFailure error in validator.ErrorList)
{
Console.WriteLine("At line " + error.GetLineNumber() + ": " + error.GetMessage());
}
return;
}
the result on the console is
Loading schema document file:///C:/Users/m_r_n/source/repos/SaxonEEExample/ValidateXslt/bin/Debug/po1.xsd
Using parser org.apache.xerces.jaxp.SAXParserImpl$JAXPSAXParser
Found registry key at HKEY_LOCAL_MACHINE\Software\Saxonica\SaxonEE-N\Settings
Software installation path: c:\Program Files\Saxon\SaxonEE9.9N
Finished loading schema document file:///C:/Users/m_r_n/source/repos/SaxonEEExample/ValidateXslt/bin/Debug/po1.xsd
Validating input file file:///C:/Users/m_r_n/source/repos/SaxonEEExample/ValidateXslt/bin/Debug/po.xsl
One validation error was reported: Cannot validate <Q{.../Transform}stylesheet>: no element declaration available
Instance validation failed with 1 errors
At line 2: Cannot validate <Q{.../Transform}stylesheet>: no element declaration available
There are two approaches.
You can use xsl:import-schema to load the schema, but then it's only the XSLT processor that knows about the schema, so the input document needs to be built and validated under the control of XSLT. If you set SchemaValidationMode on the XsltTransformer, and supply the input to the XsltTransformer as an unparsed Stream, then it will be parsed with schema validation.
The alternative is to use the DocumentBuilder as you are doing, and make sure that it does the validation. To do this, you need to load the schema using SchemaManager.Compile, then create a SchemaValidator from this schema, and supply this to the DocumentBuilder.
To be fair Michael Kay answered the question I'm just putting this in for completeness, and this is pretty much the minimal example.
This will fail (with an exception) during "compile", if the XSLT is referencing an invalid (I used "PurchaseOrder1") node that isnt referenced in the schema.
var samplesDir = new Uri(AppDomain.CurrentDomain.BaseDirectory);
// Create a Processor instance.
Processor processor = new Processor(true);
// Create a transformer for the stylesheet.
//Xslt30Transformer transformer = processor.NewXsltCompiler().Compile(new Uri(samplesDir, "poNoValidation.xsl")).Load30();
Xslt30Transformer transformer = processor.NewXsltCompiler().Compile(new Uri(samplesDir, "po.xsl")).Load30();
// Create a serializer, with output to the standard output stream
Serializer serializer = processor.NewSerializer();
serializer.SetOutputWriter(Console.Out);
// Transform the source XML and serialize the result document
transformer.SchemaValidationMode = SchemaValidationMode.Strict;
transformer.ApplyTemplates(File.OpenRead("po.xml"), serializer);
Related
I migrate from saxon-CE to saxonJS (v1.2.0)
The output of the XSLT transformation need to be captured as an XML Document object as it was in saxon-CE:
var xslPath = './*.xsl';
var xsl = Saxon.requestXML(xslPath);
var proc = Saxon.newXSLT20Processor(xsl);
var xmlDoc;
var xmlDocTransformed;
var xmlStr;
xmlDoc = Saxon.parseXML(app.getLoadedMEI());
xmlDocTransformed = proc.transformToDocument(xmlDoc);
It tried to apply SaxonJS this way:
var result;
result = SaxonJS.transform({
stylesheetLocation: "./*.sef.xml",
sourceLocation: "./*.xml",
destination: "application"
});
and expected to get a transformation results object where I can access the principalResult property as described in the official documentation (#destination) and in this presentation.
When running the code I obtain the following:
There is no problem with transformation itself: when destination is set to replaceBody it works as expected.
Eventually I solved my task with the following code (Saxon JS 2.3):
var options = {
stylesheetLocation: xslPath,
sourceText: inputXmlStr,
stylesheetParams: params,
destination: "document"
};
var result = SaxonJS.transform(options);
var transformedXmlStr = SaxonJS.serialize(result.principalResult);
The SEF could be produced by xslt3 tool.
Note that you might use the alternative command for this (windows 10 power shell):
node node_modules/xslt3/xslt3.js "-t" "-xsl:stylesheet.xsl" "-export:stylesheet.sef.json" "-nogo"
One way is, of course, to use the fn:transform function with "normal" XSLT code:
const xml = `<root>
<item>a</item>
</root>`;
const xslt = `<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:output method="xml"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="/" name="xsl:initial-template">
<xsl:next-match/>
<xsl:comment>Run with {system-property('xsl:product-name')} {system-property('xsl:product-version')} {system-property('Q{http://saxon.sf.net/}platform')}</xsl:comment>
</xsl:template>
</xsl:stylesheet>`;
const resultString = SaxonJS.XPath.evaluate(`transform(map { 'source-node' : parse-xml($xml), 'stylesheet-text' : $xslt, 'delivery-format' : 'serialized' })?output`, null, { params : { 'xml' : xml, 'xslt' : xslt } });
console.log(resultString);
<script src="https://martin-honnen.github.io/Saxon-JS-2.3/SaxonJS2.js"></script>
Not recommended for performance reason but perhaps a workaround that avoid the hassle of creating an SEF. If you want to create an SEF, note, that the Node.js xslt3 tool from Saxonica can also do that, you don't need a current version of Saxon EE, just xslt3 -t -export:my-sheet.sef.json -nogo -xsl:my-sheet.xsl.
How do I define the equivalent of the xpath-default-namespace, for example:
<xsl:stylesheet version="2.0" xpath-default-namespace="http://schemas.xmlsoap.org/soap/envelope/"
in the following Java code snippet for Saxon EE 9.5?
public String transform(String request) {
try {
ProfessionalConfiguration config = new ProfessionalConfiguration();
config.setExtensionElementNamespace("http://yeah.com", "com.MyFactory");
config.registerExtensionFunction(new MyVariable());
EnterpriseTransformerFactory factory = new EnterpriseTransformerFactory();
factory.setConfiguration(config);
Source xslt = new StreamSource(new File("text.xsl"));
Transformer transformer = factory.newTransformer(xslt);
Source input = new StreamSource(new File("test.xml"));
StringWriter result = new StringWriter();
transformer.transform(input, new StreamResult(result));
return result.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
You can't change the default xpath namespace from your Java code, only from your XSLT code.
You say you can't edit the XSLT stylesheets but you can. They are XML documents and can be transformed, and you have a transformation language and transformation engine available to you. If there's no other way of solving this, transform the stylesheets before executing them.
When using JAXP pipelin (using Saxon HE), the comments created with don't appear in the resulted .xml.
First I set the system properties to get info and use Saxon and define the input/output:
System.setProperty("jaxp.debug", "1");
System.setProperty("javax.xml.transform.TransformerFactory", "net.sf.saxon.TransformerFactoryImpl");
StreamSource xsl = ...
StreamResult output = ...
InputSource input = ...
Then I have the following construction with dummy Pre and Post filters:
TransformerFactory factory = TransformerFactory.newInstance();
SAXTransformerFactory saxFactory = (SAXTransformerFactory) factory;
SAXParserFactory parserFactory = SAXParserFactory.newInstance();
parserFactory.setNamespaceAware(true);
XMLReader parser = parserFactory.newSAXParser().getXMLReader();
XMLFilter pre = new XMLFilterImpl(parser);
XMLFilter xslFilter = saxFactory.newXMLFilter(xsl);
xslFilter.setParent(pre);
XMLFilter post = new XMLFilterImpl(xslFilter);
TransformerHandler serializer = saxFactory.newTransformerHandler();
serializer.setResult(output);
Transformer trans = serializer.getTransformer();
trans.setOutputProperty(OutputKeys.METHOD, "xml");
post.setContentHandler(serializer);
post.parse(input);
When run with the following stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:comment>Nice comment</xsl:comment>
<test>[<xsl:value-of select="system-property('xsl:vendor')" />]
(<xsl:value-of select="system-property('xsl:version')" />
)[<xsl:value-of select="system-property('xsl:vendor-url')" />]</test>
</xsl:template>
</xsl:stylesheet>
I get the follwing output.xml without the comment:
<?xml version="1.0" encoding="UTF-8"?>
<test>[Saxonica]
(2.0
)[http://www.saxonica.com/]
</test>
And the following console log:
JAXP: find factoryId =javax.xml.transform.TransformerFactory
JAXP: found system property, value=net.sf.saxon.TransformerFactoryImpl
JAXP: created new instance of class net.sf.saxon.TransformerFactoryImpl using ClassLoader: null
JAXP: find factoryId =javax.xml.parsers.SAXParserFactory
JAXP: loaded from fallback value: com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl
JAXP: created new instance of class com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl using ClassLoader: null
JAXP: find factoryId =javax.xml.parsers.SAXParserFactory
JAXP: loaded from fallback value: com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl
JAXP: created new instance of class com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl using ClassLoader: null
When run without the whole pipeline, I do get the coment:
javax.xml.transform.TransformerFactory tFactory = javax.xml.transform.TransformerFactory.newInstance();
javax.xml.transform.Transformer transformer = tFactory.newTransformer(xsl);
transformer.transform(input, output);
results in
<?xml version="1.0" encoding="UTF-8"?><!--Nice comment -->
<test>[Saxonica]
(2.0
)[http://www.saxonica.com/]
</test>
Does anyone know why the JAXP pipeline omits the comments?
The SAX2 ContentHandler interface does not receive notification of comments. For that you need a LexicalHandler. But the SAX2 helper class XMLFilterImpl does not implement LexicalHandler, so it effectively drops the comments.
Switch to s9api in place of JAXP - it does these things much better.
I am trying to create a PDF from the contents of an xpage. I am following the format that Paul Calhoun used in Notes in 9 #102. I am able to create PDF's for views, but having trouble creating one for a document. I do not think the error is in Paul's code so I am not including it here, although I can if need be.
To generate the XML to display I use the generateXML() method of the document class in java. I get a handle to the backend document and then return the XML. The XML appears well formed, the top level tab is <document>. I pass this XML to the transformer which is using Apache FOP. All of my code is contained in the beforeRenderResponse of an xAgent.
The XSL stylesheet that I am using is a stripped down version to just get a proof of concept to work. I am going to include it, because the problem likely resides with this code. I am totally new to XSL.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
version="1.1"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
exclude-result-prefixes="fo">
<xsl:output
method="xml"
version="1.0"
omit-xml-declaration="no"
indent="yes" />
<xsl:param
name="versionParam"
select="'1.0'" />
<xsl:template match="document">
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:layout-master-set>
<fo:simple-page-master
master-name="outpage"
page-height="11in"
page-width="8.5in"
margin-top="1in"
margin-bottom="1in"
margin-left="1in"
margin-right="1in">
<fo:region-body />
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="A4">
<fo:block
font-size="16pt"
font-weight="bold"
space-after="5mm">
Apache FOP Proof of Concept.
</fo:block>
</fo:page-sequence>
</fo:root>
</xsl:template>
</xsl:stylesheet>
In the log file I get the message:
FATAL ERROR: 'com.ibm.xtq.common.utils.WrappedRuntimeException: D:\Program Files\IBM\Domino\<document form='PO'>
The error echos the entire XML in the log that it is trying to transform and ends with:
(The filename, directory name, or volume label syntax is incorrect.)'
Notice that Domino is trying to include the XML in a path. I know this is wrong, but don't know what to do to fix it.
EDIT: This is the Java class that runs the transformation. This code is from Paul Calhoun's demo.
import java.io.OutputStream;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
public class DominoXMLFO2PDF {
public static void getPDF(OutputStream pdfout,String xml,String xslt, Boolean authReq, String usernamepass) {
try {
//System.out.println("Transforming...");
Source xmlSource,xsltSource; //Source xmlSource = new StreamSource("http://localhost/APCC.nsf/Main?ReadViewEntries&count=999&ResortAscending=2");
xmlSource = new StreamSource(xml); //Source xsltSource = new StreamSource("http://localhost/APCC.nsf/viewdata.xsl");
xsltSource = new StreamSource(xslt);// configure fopFactory as desired
final FopFactory fopFactory = FopFactory.newInstance();
FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
// configure foUserAgent as desired
// Setup output
// OutputStream out = pdfout;
// out = new java.io.BufferedOutputStream(out);
try {
// Construct fop with desired output format
Fop fop = fopFactory.newFop(org.apache.xmlgraphics.util.MimeConstants.MIME_PDF,foUserAgent, pdfout);
// Setup XSLT
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(xsltSource);
//transformer.setParameter("versionParam", "Company List"); // Set the value of a <param> in the stylesheet
Source src = xmlSource; // Setup input for XSLT transformation
Result res = new SAXResult(fop.getDefaultHandler()); // Resulting SAX events (the generated FO) must be piped through to FOP
transformer.transform(src, res); // Start XSLT transformation and FOP processing
} catch (Exception e) {
} finally {
}
} catch (Exception e) {
e.printStackTrace(System.err);
}
}
}
This code is called in the xAgent using this line:
var retOutput = jce.getPDF(pageOutput, xmlsource, xsltsource, authReq, usernamepass);
The xmlsource is set with this line where the method returns XML using Document.generateXML():
var xmlsource = statusBean.generateXML(POdata, sessionScope.unidPDF);
Your problem is the XMLSource! When you look at Paul's code:
Source xmlSource = new StreamSource("http://localhost/APCC.nsf/Main?ReadViewEntries");
This points to an URL where to retrieve XML.On the other hand your code:
xmlsource = statusBean.generateXML(POdata, sessionScope.unidPDF);
contains the XML. So you need to change to:
String xmlstring = statusBean.generateXML(POdata, sessionScope.unidPDF);
Source xmlsource = new StreamSource(new java.io.StringReader(xmlstring));
I strongly suggest you try to keep all the Java in a java class, so you don't need to wrap/upwrap the objects in SSJS. Have a look at my series on FO too.
Here is my code which i wrote in onRequestscript
def groovyUtils = new com.eviware.soapui.support.GroovyUtils(context)
path = "D:\\Service\\something2.xml";
log.info("path = "+ path);
if (mockRequest.method == "POST" )
{
mockRunner.returnFile( mockRequest.httpResponse, new File(path))
return new com.eviware.soapui.impl.wsdl.mock.WsdlMockResult(mockRequest)
}
But this script changes my XML entirely... I want to modify an existing XML(something.xml)..
i was actually Not able to modify the xml so i thought of changinf the xml instead.But according to my business logic its wrong... So can any one help me to modify the xml
in onRequestscript....
XML like
<Something>
<Data1>
<value>100</value>
<Data1>
<Data2>
<value>200</value>
<Data2>
</Something>
to a modified like this
<Something>
<Data1>
<value>101</value>
<Data1>
<Data2>
<value>201</value>
<Data2>
</Something>
You can use XmlSlurper to parse and update values from XML file. Then generate a string from updated XML and set it to the response of your mock service.
I use free SoapUI 3.6.1 but it seems that its output object differs from your example. Revise the code for your needs.
// get and parse XML file content
path = "D:\\Service\\something2.xml";
def doc = new XmlSlurper().parse(path)
// update values
doc.Data1.value[0] = 101
doc.Data2.value[0] = 201
// generate and return XML string as service response
import groovy.xml.StreamingMarkupBuilder
def result = new StreamingMarkupBuilder().bind{ mkp.yield doc }.toString()
mockResponse.setResponseContent(result)