Re: Splitting XML using XSLT into Multiple StringBuffer
Christoffer Dam Bruun <[email protected]>
| Newsgroups | gmane.text.xml.xalan.java.user |
|---|---|
| Message-ID | <[email protected]> |
Hi,
I have done something similar when I wanted access to the parts of the
output within the same transformation.
What I did was that I copied the Xalan Redirect Extension called It
RedirectToMem and
modified the source to create a ByteArrayOutputStream instead of a file
- and then maintaining
a Map of named ByteArrayOutputStreams. I can the get the content of
each stream within
the transformation with a "contentOf(..)" extension method that simply
returns the contents of a named
ByteArrayOutputStream as text.
Source of RediectToMem is attached.
(Beware that it has not been documented for public consumption....)
example use:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0"
xmlns:xalan="http://xml.apache.org/xalan"
xmlns:redirectmem="http://xml.apache.org/xalan/redirectmem"
extension-element-prefixes="redirectmem">
<xalan:component prefix="redirectmem"
functions="contentOf" elements="write">
<xalan:script lang="javaclass"
src="xalan://dk.callis.xalanext.RedirectToMem"/>
</xalan:component>
.....
<redirectmem:write file="name">
<xsl:apply-templates select="."/>
</redirectmem:write>
<xsl:variable name="textRep" select="redirectmem:contentOf('name')"/>
<redirectmem:remove file="name"/>
.....
I hope this example might inspire you
Best regards
Christoffer Bruun
On 01-09-2009 21:55, TenzoR wrote:
> Hello,
>
> I want to know if I am going at this the right way or not. Any hint would be
> appreciated as well.
>
> My End Goal is to take XML Data, transform it with XSLT and split it
> accordingly to multiple Java StringBuffer.
>
> My Input: StringBuffer (XML data)
>
> My Output: Multiple StringBuffers
>
> So far what I've tried are
> 1. Splitting inputted XML to multiple XML files using the Xalan Redirect
> technique.
> 2. Transform a XML file and outputting it to a StringBuffer.
>
> All test are simple, but I'm just not sure if it's feasible to do what I
> want to do ...
>
RedirectToMem.java
(text/plain, 14.9 KB)
package dk.callis.xalanext;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Hashtable;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamResult;
import org.apache.xalan.extensions.XSLProcessorContext;
import org.apache.xalan.res.XSLTErrorResources;
import org.apache.xalan.templates.ElemExtensionCall;
import org.apache.xalan.templates.OutputProperties;
import org.apache.xalan.transformer.TransformerImpl;
import org.apache.xml.serializer.SerializationHandler;
import org.apache.xpath.XPath;
import org.apache.xpath.objects.XObject;
import org.xml.sax.ContentHandler;
/**
* Implements three extension elements to allow an XSLT transformation to
* redirect its output to multiple output files.
*
* It is accessed by specifying a namespace URI as follows:
*
* <pre>
* xmlns:redirect="http://xml.apache.org/xalan/redirect"
* </pre>
*
* <p>
* You can either just use redirect:write, in which case the file will be opened
* and immediately closed after the write, or you can bracket the write calls by
* redirect:open and redirect:close, in which case the file will be kept open
* for multiple writes until the close call is encountered. Calls can be nested.
*
* <p>
* Calls can take a 'file' attribute and/or a 'select' attribute in order to get
* the filename. If a select attribute is encountered, it will evaluate that
* expression for a string that indicates the filename. If the string evaluates
* to empty, it will attempt to use the 'file' attribute as a default. Filenames
* can be relative or absolute. If they are relative, the base directory will be
* the same as the base directory for the output document. This is obtained by
* calling getOutputTarget() on the TransformerImpl. You can set this base
* directory by calling TransformerImpl.setOutputTarget() or it is automatically
* set when using the two argument form of transform() or transformNode().
*
* <p>
* Calls to redirect:write and redirect:open also take an optional attribute
* append="true|yes", which will attempt to simply append to an existing file
* instead of always opening a new file. The default behavior of always
* overwriting the file still happens if you do not specify append.
* <p>
* <b>Note:</b> this may give unexpected results when using xml or html output
* methods, since this is <b>not</b> coordinated with the serializers - hence,
* you may get extra xml decls in the middle of your file after appending to it.
*
* <p>
* Example:
* </p>
*
* <PRE>
* <?xml version="1.0"?>
* <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
* version="1.0"
* xmlns:redirect="http://xml.apache.org/xalan/redirect"
* extension-element-prefixes="redirect">
*
* <xsl:template match="/">
* <out>
* default output.
* </out>
* <redirect:open file="doc3.out"/>
* <redirect:write file="doc3.out">
* <out>
* <redirect:write file="doc1.out">
* <out>
* doc1 output.
* <redirect:write file="doc3.out">
* Some text to doc3
* </redirect:write>
* </out>
* </redirect:write>
* <redirect:write file="doc2.out">
* <out>
* doc2 output.
* <redirect:write file="doc3.out">
* Some more text to doc3
* <redirect:write select="doc/foo">
* text for doc4
* </redirect:write>
* </redirect:write>
* </out>
* </redirect:write>
* </out>
* </redirect:write>
* <redirect:close file="doc3.out"/>
* </xsl:template>
*
* </xsl:stylesheet>
* </PRE>
*
* @author Scott Boag
* @version 1.0
* @see <a href="../../../../../../extensions.html#ex-redirect"
* target="_top">Example with Redirect extension</a>
*/
public class RedirectToMem {
/**
* List of formatter listeners indexed by filename.
*/
protected Hashtable m_formatterListeners = new Hashtable();
/**
* List of output streams indexed by filename.
*/
protected Hashtable m_outputStreams = new Hashtable();
/**
* Default append mode for bare open calls. False for backwards
* compatibility (I think).
*/
public static final boolean DEFAULT_APPEND_OPEN = false;
/**
* Default append mode for bare write calls. False for backwards
* compatibility.
*/
public static final boolean DEFAULT_APPEND_WRITE = false;
/**
* Open the given file and put it in the XML, HTML, or Text formatter
* listener's table.
*/
public void open(XSLProcessorContext context, ElemExtensionCall elem)
throws java.net.MalformedURLException,
java.io.FileNotFoundException, java.io.IOException,
javax.xml.transform.TransformerException {
String fileName = getFilename(context, elem);
Object flistener = m_formatterListeners.get(fileName);
if (null == flistener) {
// Whether to append to existing files or not, <[email protected]>
String appendExpr = elem.getAttribute("append", context
.getContextNode(), context.getTransformer());
boolean append = (appendExpr != null) ? (appendExpr.equals("true") || appendExpr
.equals("yes"))
: DEFAULT_APPEND_OPEN;
Object ignored = makeFormatterListener(context, elem, fileName,
true, append);
}
}
/**
* Write the evalutation of the element children to the given file. Then
* close the file unless it was opened with the open extension element and
* is in the formatter listener's table.
*/
public void write(XSLProcessorContext context, ElemExtensionCall elem)
throws java.net.MalformedURLException,
java.io.FileNotFoundException, java.io.IOException,
javax.xml.transform.TransformerException {
String fileName = getFilename(context, elem);
Object flObject = m_formatterListeners.get(fileName);
ContentHandler formatter;
boolean inTable = false;
if (null == flObject) {
// Whether to append to existing files or not, <[email protected]>
String appendExpr = elem.getAttribute("append", context
.getContextNode(), context.getTransformer());
boolean append = (appendExpr != null) ? (appendExpr.equals("true") || appendExpr
.equals("yes"))
: DEFAULT_APPEND_WRITE;
formatter = makeFormatterListener(context, elem, fileName, true,
append);
} else {
inTable = true;
formatter = (ContentHandler) flObject;
}
TransformerImpl transf = context.getTransformer();
startRedirection(transf, formatter); // for tracing only
transf.executeChildTemplates(elem, context.getContextNode(), context
.getMode(), formatter);
endRedirection(transf); // for tracing only
if (!inTable) {
OutputStream ostream = (OutputStream) m_outputStreams.get(fileName);
if (null != ostream) {
try {
formatter.endDocument();
} catch (org.xml.sax.SAXException se) {
throw new TransformerException(se);
}
ostream.close();
// Keep it so we can ask for the contents later - use <XX:remove file=".."> to delete
// m_outputStreams.remove(fileName);
m_formatterListeners.remove(fileName);
}
}
}
public String contentOf(String fileName) {
//System.out.println("fileName=" + fileName);
ByteArrayOutputStream ostream = (ByteArrayOutputStream) m_outputStreams
.get(fileName);
// TODO - fix selection of output encoding
try {
return ostream.toString("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
public void remove(XSLProcessorContext context, ElemExtensionCall elem)
throws TransformerException {
String fileName = elem.getAttribute("file", context.getContextNode(),
context.getTransformer());
String ignore = elem.getAttribute("ignore-missing", context
.getContextNode(), context.getTransformer());
boolean ign = true;
if (null == ignore || "false".equals(ignore)) {
ign = false;
} else if ("true".equals(ignore)) {
ign = true;
} else {
context.getTransformer().getMsgMgr().error(
elem,
elem,
context.getContextNode(),
this.getClass()
+ " ignore-missing attribute can be only 'true' or 'false' - not: "
+ ignore);
}
if (null == fileName) {
context.getTransformer().getMsgMgr().error(
elem,
elem,
context.getContextNode(),
this.getClass()
+ " missing file attribute in on remove element");
} else {
ByteArrayOutputStream ostream = (ByteArrayOutputStream) m_outputStreams
.get(fileName);
if (null == ostream) {
if (!ign) {
context.getTransformer().getMsgMgr().error(
elem,
elem,
context.getContextNode(),
this.getClass() + " no file: " + fileName
+ " is unknown");
}
} else {
m_outputStreams.remove(fileName);
}
}
}
/**
* Close the given file and remove it from the formatter listener's table.
*/
public void close(XSLProcessorContext context, ElemExtensionCall elem)
throws java.net.MalformedURLException,
java.io.FileNotFoundException, java.io.IOException,
javax.xml.transform.TransformerException {
String fileName = getFilename(context, elem);
Object formatterObj = m_formatterListeners.get(fileName);
if (null != formatterObj) {
ContentHandler fl = (ContentHandler) formatterObj;
try {
fl.endDocument();
} catch (org.xml.sax.SAXException se) {
throw new TransformerException(se);
}
OutputStream ostream = (OutputStream) m_outputStreams.get(fileName);
if (null != ostream) {
ostream.close();
m_outputStreams.remove(fileName);
}
m_formatterListeners.remove(fileName);
}
}
/**
* Get the filename from the 'select' or the 'file' attribute.
*/
private String getFilename(XSLProcessorContext context,
ElemExtensionCall elem) throws java.net.MalformedURLException,
java.io.FileNotFoundException, java.io.IOException,
javax.xml.transform.TransformerException {
String fileName;
String fileNameExpr = ((ElemExtensionCall) elem).getAttribute("select",
context.getContextNode(), context.getTransformer());
if (null != fileNameExpr) {
org.apache.xpath.XPathContext xctxt = context.getTransformer()
.getXPathContext();
XPath myxpath = new XPath(fileNameExpr, elem, xctxt
.getNamespaceContext(), XPath.SELECT);
XObject xobj = myxpath.execute(xctxt, context.getContextNode(),
elem);
fileName = xobj.str();
if ((null == fileName) || (fileName.length() == 0)) {
fileName = elem.getAttribute("file", context.getContextNode(),
context.getTransformer());
}
} else {
fileName = elem.getAttribute("file", context.getContextNode(),
context.getTransformer());
}
if (null == fileName) {
context.getTransformer().getMsgMgr().error(elem, elem,
context.getContextNode(),
XSLTErrorResources.ER_REDIRECT_COULDNT_GET_FILENAME);
// "Redirect extension: Could not get filename - file or select
// attribute must return vald string.");
}
return fileName;
}
/**
* Create a new ContentHandler, based on attributes of the current
* ContentHandler.
*/
private ContentHandler makeFormatterListener(XSLProcessorContext context,
ElemExtensionCall elem, String fileName, boolean shouldPutInTable,
boolean append) throws java.net.MalformedURLException,
java.io.FileNotFoundException, java.io.IOException,
javax.xml.transform.TransformerException {
TransformerImpl transformer = context.getTransformer();
// This should be worked on so that the output format can be
// defined by a first child of the redirect element.
OutputProperties format = transformer.getOutputFormat();
String omitXmlDecl = elem.getAttribute("omit-xml-declaration", context
.getContextNode(), context.getTransformer());
if (null != omitXmlDecl) {
if ("true".equals(omitXmlDecl)) {
format.setBooleanProperty(
javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION,
true);
} else if ("false".equals(omitXmlDecl)) {
format.setBooleanProperty(
javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION,
false);
}
} else {
format.setBooleanProperty(
javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION, true);
}
// FileOutputStream ostream = new FileOutputStream(file);
// Patch from above line to below by <[email protected]>
// Note that in JDK 1.2.2 at least, FileOutputStream(File)
// is implemented as a call to
// FileOutputStream(File.getPath, append), thus this should be
// the equivalent instead of getAbsolutePath()
ByteArrayOutputStream ostream = new ByteArrayOutputStream();
try {
SerializationHandler flistener = createSerializationHandler(
transformer, ostream, format);
try {
flistener.startDocument();
} catch (org.xml.sax.SAXException se) {
throw new TransformerException(se);
}
if (shouldPutInTable) {
m_outputStreams.put(fileName, ostream);
m_formatterListeners.put(fileName, flistener);
}
return flistener;
} catch (TransformerException te) {
throw new javax.xml.transform.TransformerException(te);
}
}
/**
* A class that extends this class can over-ride this public method and
* recieve a callback that redirection is about to start
*
* @param transf
* The transformer.
* @param formatter
* The handler that receives the redirected output
*/
public void startRedirection(TransformerImpl transf,
ContentHandler formatter) {
// A class that extends this class could provide a method body
}
/**
* A class that extends this class can over-ride this public method and
* receive a callback that redirection to the ContentHandler specified in
* the startRedirection() call has ended
*
* @param transf
* The transformer.
*/
public void endRedirection(TransformerImpl transf) {
// A class that extends this class could provide a method body
}
/**
* A class that extends this one could over-ride this public method and
* receive a callback for the creation of the serializer used in the
* redirection.
*
* @param transformer
* The transformer
* @param ostream
* The output stream that the serializer wraps
* @param file
* The file associated with the ostream
* @param format
* The format parameter used to create the serializer
* @return the serializer that the redirection will go to.
*
* @throws java.io.IOException
* @throws TransformerException
*/
public SerializationHandler createSerializationHandler(
TransformerImpl transformer, OutputStream ostream,
OutputProperties format) throws java.io.IOException,
TransformerException {
SerializationHandler serializer = transformer
.createSerializationHandler(new StreamResult(ostream), format);
return serializer;
}
}