As a continuation in my thought patterns from this question: Saxon in Java: XSLT for CSV to XML
Per Michael Kay's answer on that question, I eventually ended up with the following code for applying an XSLT to a document:
Processor processor = new Processor(false);
StringWriter stringWriter = new StringWriter();
Serializer serializer = new Serializer(stringWriter);
XsltCompiler compiler = processor.newXsltCompiler();
XsltExecutable executable = compiler.compile(new StreamSource(new File(transformLocation)));
XsltTransformer transformer = executable.load();
transformer.setInitialTemplate(new QName("main"));
transformer.setParameter(new QName("filePath"), new XdmAtomicValue("location/of/Test.csv"));
transformer.setDestination(serializer);
transformer.transform();
String transformedDocument = stringWriter.toString().trim();
This code makes use of the s9api in Saxon (I'm on version 9.4 HE). It allows me to set the initial template and dynamically inject the path to the document to be transformed, which allows me to transform non-XML files (such as CSV, in this particular case).
However, this somewhat obliterates my code-re-usability.
Let me explain: I have a transformDocument()
method. Originally, before I was trying to do crazy things like transform CSV and was working only with XML, it was called by both my marshalObjectToDocument()
and unmarshalDocumentToObject()
methods (there's the re-usability).
Here's a comparison of the two directions given a world of only XML:
unmarshalDocumentToObject()
new StreamSource(new File(documentLocation))
StreamSource
can be passed to transformDocument
as the "source" (XsltTransformer.setSource()
).marshalObjectToDocument()
new StreamSource(new StringReader(giantStringOfXML))
StreamSource
can be passed to transformDocument
as the "source" (XsltTransformer.setSource()
).In case 1 (unmarshalDocumentToObject()
) I have a file path coming in, so I could just change transformDocument()
to take a file path String and pass it that so it can manually inject it into the XSLT parameter. This would work for both XML and plain text.
In case 2 (marshalObjectToDocument()
) I have NO file path. I have an object, which gets converted to a giant String containing its XML representation. I can't pass a file path String to transformDocument()
because I don't have a file. Now I can't use transformDocument()
. Code re-usability destroyed.
My goal in all of this is both to be able to somehow treat both XML and plain text documents the same way in the code and to be able to re-use my code for applying XSLTs and XSDs whether I'm marshaling or unmarshaling. Is this a quixotic goal? Am I doomed to be relegated to writing different code for each document type and direction? Or can someone see a way around this?
Surely this is a standard and basic bit of software engineering? You've got three bits of code here: an application, which wants to run a transformation; and XSLT engine, which performs the transformation, and an interface layer, which provides an abstraction over the service offered by the XSLT engine, typically subsetting the functionality to provide only what the application needs, and doing it in a simpler form. The advantage of your interface layer is that it reduces the complexity of the transformation API; the disadvantage is that it also reduces the functionality. When your application starts wanting more of the functionality that was previously hidden, you have a number of choices: you can add functionality to the interface layer (eventually reaching the point where it ceases to add value), or you can bypass the interface layer for those parts of the application that get no value from it.
Reuse of code depends on identifying globs of functionality that can be used in more than one place. If different bits of your application are doing different things then it becomes harder for them to reuse code. What's new?