sylvain 2003/01/06 07:21:24 Modified: . changes.xml src/documentation/xdocs/userdocs/serializers serializers.xml ziparchive-serializer.xml src/java/org/apache/cocoon/serialization ZipArchiveSerializer.java Log: ZipArchiveSerializer now accepts inline content for archive entries Revision Changes Path 1.329 +5 -1 xml-cocoon2/changes.xml Index: changes.xml =================================================================== RCS file: /home/cvs/xml-cocoon2/changes.xml,v retrieving revision 1.328 retrieving revision 1.329 diff -u -r1.328 -r1.329 --- changes.xml 6 Jan 2003 06:10:24 -0000 1.328 +++ changes.xml 6 Jan 2003 15:21:24 -0000 1.329 @@ -40,6 +40,10 @@ </devs> <release version="@version@" date="@date@"> + <action dev="SW" type="update"> + ZipArchiveSerializer now accepts inline content for entries of the zip archive + and not only source URLs. + </action> <action dev="TC" type="update"> ImageReader extends now ResourceReader and therefor the "expire-time" parameter is now "expires". Also removed the 1.6 +1 -0 xml-cocoon2/src/documentation/xdocs/userdocs/serializers/serializers.xml Index: serializers.xml =================================================================== RCS file: /home/cvs/xml-cocoon2/src/documentation/xdocs/userdocs/serializers/serializers.xml,v retrieving revision 1.5 retrieving revision 1.6 diff -u -r1.5 -r1.6 --- serializers.xml 25 Dec 2002 07:05:55 -0000 1.5 +++ serializers.xml 6 Jan 2003 15:21:24 -0000 1.6 @@ -57,6 +57,7 @@ <li><link href="svgtiff-serializer.html">SVG/TIFF Serializer</link></li> <li><link href="vrml-serializer.html">VRML Serializer</link></li> <li><link href="link-serializer.html">Link Serializer</link></li> + <li><link href="ziparchive-serializer.html">Zip archive Serializer</link></li> </ul> </s1> </body> 1.3 +29 -15 xml-cocoon2/src/documentation/xdocs/userdocs/serializers/ziparchive-serializer.xml Index: ziparchive-serializer.xml =================================================================== RCS file: /home/cvs/xml-cocoon2/src/documentation/xdocs/userdocs/serializers/ziparchive-serializer.xml,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- ziparchive-serializer.xml 31 Oct 2002 16:48:55 -0000 1.2 +++ ziparchive-serializer.xml 6 Jan 2003 15:21:24 -0000 1.3 @@ -15,22 +15,36 @@ <s1 title="Zip archive Serializer"> <p>The Zip archive serializer generates a zip archive by aggregating several sources.</p> - <p>The input document should describe entries of the archive by means of - their name (which can be a path) in the archive and the source of the entry - contents. These are Cocoon sources, and as such can use any of the protocols - handled by Cocoon, including "cocoon:" to include dynamically generated - content in the archive. - </p> + <p>The input document should describe entries of the archive by means of + their name (which can be a path) and their content either as URLs or + inline data :</p> + <ul> + <li>URLs, given by the "src" attribute, are Cocoon sources and as such + can use any of the protocols handled by Cocoon, including "cocoon:" to + include dynamically generated content in the archive.</li> + <li>inline data is represented by an XML document that is serialized to the + zip entry using the serializer identified by the "serializer" attribute.</li> + </ul> + <p> + Example : + </p> +<source> +<zip:archive xmlns:zip="http://apache.org/cocoon/zip-archive/1.0"> + <zip:entry name="foo.html" src="cocoon://dynFoo.html"/> + <zip:entry name="images/bar.jpeg" src="bar.jpeg"/> + <zip:entry name="index.html" serializer="html"> + <html> + <head> + <title>Index page</title> + </head> + <body> + Please go <a href="foo.html">there</a> + </body> + </html> + </zip:entry> +</zip:archive:zip> +</source> - <p> - Example : - </p> - <source><![CDATA[ -<zip-archive:zip xmlns:zip-archive="http://apache.org/cocoon/zip-archive/1.0"> - <zip-archive:entry name="foo.html" src="cocoon://dynFoo.html"/> - <zip-archive:entry name="images/bar.jpeg" src="bar.jpeg"/> -</zip-archive:zip> -]]></source> <ul> <li>Name: zip</li> <li>Class: org.apache.cocoon.serialization.ZipArchiveSerializer</li> 1.4 +235 -73 xml-cocoon2/src/java/org/apache/cocoon/serialization/ZipArchiveSerializer.java Index: ZipArchiveSerializer.java =================================================================== RCS file: /home/cvs/xml-cocoon2/src/java/org/apache/cocoon/serialization/ZipArchiveSerializer.java,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- ZipArchiveSerializer.java 3 Dec 2002 10:20:01 -0000 1.3 +++ ZipArchiveSerializer.java 6 Jan 2003 15:21:24 -0000 1.4 @@ -51,16 +51,26 @@ package org.apache.cocoon.serialization; +import org.apache.avalon.excalibur.pool.Recyclable; import org.apache.avalon.framework.component.ComponentException; import org.apache.avalon.framework.component.ComponentManager; +import org.apache.avalon.framework.component.ComponentSelector; import org.apache.avalon.framework.component.Composable; -import org.apache.excalibur.source.Source; -import org.apache.excalibur.source.SourceResolver; + +import org.apache.cocoon.caching.CacheValidity; +import org.apache.cocoon.caching.Cacheable; +import org.apache.cocoon.components.CocoonComponentManager; +import org.apache.cocoon.environment.Source; +import org.apache.cocoon.environment.SourceResolver; + import org.xml.sax.Attributes; import org.xml.sax.SAXException; +import org.xml.sax.helpers.NamespaceSupport; +import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Enumeration; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -68,53 +78,88 @@ * A serializer that builds Zip archives by aggregating several sources. * <p> * The input document should describe entries of the archive by means of - * their name (which can be a path) in the archive and the source of the entry - * contents. These are Cocoon sources, and as such can use any of the protocols - * handled by Cocoon, including "cocoon:" to include dynamically generated - * content in the archive. + * their name (which can be a path) and their content either as URLs or + * inline data : + * <ul> + * <li>URLs, given by the "src" attribute, are Cocoon sources and as such + * can use any of the protocols handled by Cocoon, including "cocoon:" to + * include dynamically generated content in the archive.</li> + * <li>inline data is represented by an XML document that is serialized to the + * zip entry using the serializer identified by the "serializer" attribute.</li> + * </ul> * <p> * Example : * <pre> * <zip:archive xmlns:zip="http://apache.org/cocoon/zip-archive/1.0"> * <zip:entry name="foo.html" src="cocoon://dynFoo.html"/> * <zip:entry name="images/bar.jpeg" src="bar.jpeg"/> + * <zip:entry name="index.html" serializer="html"> + * <html> + * <head> + * <title>Index page</title> + * </head> + * <body> + * Please go <a href="foo.html">there</a> + * </body< + * </html> + * </zip:entry> * </zip:archive:zip> * </pre> * * @author <a href="http://www.apache.org/~sylvain">Sylvain Wallez</a> + * @version CVS $Id$ */ -// TODO (1) : handle more attributes on <zip> for properties of ZipOutputStream +// TODO (1) : handle more attributes on <archive> for properties of ZipOutputStream // such as comment or default compression method and level // TODO (2) : handle more attributes on <entry> for properties of ZipEntry // (compression method and level, time, comment, etc.) -// TODO (3) : allow the entry to be inlined by using "serializer" instead of "src", e.g. : -// <entry name="foo.png" serializer="svg2png"> -// <svg>...</svg> -// </entry> - -public class ZipArchiveSerializer - extends AbstractSerializer - implements Composable { +public class ZipArchiveSerializer extends AbstractSerializer implements Composable { /** * The namespace for elements handled by this serializer, * "http://apache.org/cocoon/zip-archive/1.0". */ - public static final String ZIP_NAMESPACE = - "http://apache.org/cocoon/zip-archive/1.0"; + public static final String ZIP_NAMESPACE = "http://apache.org/cocoon/zip-archive/1.0"; + + private static final int START_STATE = 0; + private static final int IN_ZIP_STATE = 1; + private static final int IN_CONTENT_STATE = 2; + + /** The component manager */ + protected ComponentManager manager; + + /** The serializer component selector */ + protected ComponentSelector selector; /** The Zip stream where entries will be written */ protected ZipOutputStream zipOutput; - /** Have we encountered the toplevel "zip" element ? */ - protected boolean inZip = false; + /** The current state */ + protected int state = START_STATE; + + /** The resolver to get sources */ + protected SourceResolver resolver; /** Temporary byte buffer to read source data */ protected byte[] buffer = new byte[1024]; - protected ComponentManager manager; + /** Serializer used when in IN_CONTENT state */ + protected Serializer serializer; + + /** Current depth of the serialized content */ + protected int contentDepth; + + /** Used to collect namespaces */ + private NamespaceSupport nsSupport = new NamespaceSupport(); + + /** + * @see org.apache.avalon.framework.component.Composable#compose(ComponentManager) + */ + public void compose(ComponentManager manager) throws ComponentException { + this.manager = manager; + } /** * Always return "application/x-zip" which is the default for Zip archives. @@ -127,42 +172,80 @@ * @see org.xml.sax.ContentHandler#startDocument() */ public void startDocument() throws SAXException { + this.state = START_STATE; this.zipOutput = new ZipOutputStream(this.output); - this.inZip = false; + this.resolver = CocoonComponentManager.getCurrentEnvironment(); } /** - * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes) + * Begin the scope of a prefix-URI Namespace mapping. + * + * @param prefix The Namespace prefix being declared. + * @param uri The Namespace URI the prefix is mapped to. */ - public void startElement(String namespaceURI, - String localName, - String qName, - Attributes atts) - throws SAXException { - if (!inZip) { - // expecting "zip" as the first element - if (namespaceURI.equals(ZIP_NAMESPACE) - && localName.equals("archive")) { - this.inZip = true; - } else { - throw new SAXException( - "Expecting 'archive' root element (got '" - + localName - + "')"); - } + public void startPrefixMapping(String prefix, String uri) throws SAXException { + if (state == IN_CONTENT_STATE) { + // Pass to the serializer + super.startPrefixMapping(prefix, uri); + } else { - // expecting "entry" element - if (namespaceURI.equals(ZIP_NAMESPACE) - && localName.equals("entry")) { - // Get the source - addEntry(atts); - } else { - throw new SAXException( - "Expecting 'entry' element (got '" + localName + "')"); + // Register it if it's not our own namespace (useless to content) + if (!uri.equals(ZIP_NAMESPACE)) { + this.nsSupport.declarePrefix(prefix, uri); } } } + // Note : no need to implement endPrefixMapping() as we just need to pass it through if there + // is a serializer, which is what the superclass does. + + /** + * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes) + */ + public void startElement(String namespaceURI, String localName, String qName, Attributes atts) + throws SAXException { + switch (state) { + case START_STATE : + // expecting "zip" as the first element + if (namespaceURI.equals(ZIP_NAMESPACE) && localName.equals("archive")) { + this.nsSupport.pushContext(); + this.state = IN_ZIP_STATE; + } else { + throw new SAXException( + "Expecting 'archive' root element (got '" + localName + "')"); + } + break; + + case IN_ZIP_STATE : + // expecting "entry" element + if (namespaceURI.equals(ZIP_NAMESPACE) && localName.equals("entry")) { + this.nsSupport.pushContext(); + // Get the source + addEntry(atts); + } else { + throw new SAXException("Expecting 'entry' element (got '" + localName + "')"); + } + break; + + case IN_CONTENT_STATE : + this.contentDepth++; + super.startElement(namespaceURI, localName, qName, atts); + break; + } + } + + /** + * @see org.xml.sax.ContentHandler#characters(char[], int, int) + */ + public void characters(char[] buffer, int offset, int length) throws SAXException { + // Propagate text to the serializer only if we have encountered the content's top-level + // element. Otherwhise, the serializer may be confused by some characters occuring between + // startDocument() and the first startElement() (e.g. Batik fails hard in that case) + if (this.state == IN_CONTENT_STATE && this.contentDepth > 0) { + super.characters(buffer, offset, length); + } + } + /** * Add an entry in the archive. * @param atts the attributes that describe the entry @@ -174,41 +257,117 @@ } String src = atts.getValue("src"); - if (src == null) { - throw new SAXException("No source given for the Zip entry"); + String serializerType = atts.getValue("serializer"); + + if (src == null && serializerType == null) { + throw new SAXException( + "No source nor serializer given for the Zip entry '" + name + "'"); + } + + if (src != null && serializerType != null) { + throw new SAXException( + "Cannot specify both 'src' and 'serializer' on a Zip entry '" + name + "'"); } - SourceResolver resolver = null; - Source source = null; try { - resolver = (SourceResolver)this.manager.lookup(SourceResolver.ROLE); - // Create a new Zip entry ZipEntry entry = new ZipEntry(name); this.zipOutput.putNextEntry(entry); - // Get the source and its data - source = resolver.resolveURI(src); - InputStream sourceInput = source.getInputStream(); - - // Copy the source to the zip - int len; - while ((len = sourceInput.read(this.buffer)) > 0) { - this.zipOutput.write(this.buffer, 0, len); - } + if (src != null) { + // Get the source and its data + Source source = resolver.resolve(src); + InputStream sourceInput = source.getInputStream(); + + // Copy the source to the zip + int len; + while ((len = sourceInput.read(this.buffer)) > 0) { + this.zipOutput.write(this.buffer, 0, len); + } + + // and close the entry + this.zipOutput.closeEntry(); + + } else { + // Serialize content + if (this.selector == null) { + this.selector = + (ComponentSelector) this.manager.lookup(Serializer.ROLE + "Selector"); + } + + // Get the serializer + this.serializer = (Serializer) this.selector.select(serializerType); + + // Direct its output to the zip file, filtering calls to close() + // (we don't want the archive to be closed by the serializer) + this.serializer.setOutputStream(new FilterOutputStream(this.zipOutput) { + public void close() { /*nothing*/ + } + }); + + // Set it as the current XMLConsumer + this.setConsumer(serializer); + + // start its document + this.serializer.startDocument(); + + // and give it any namespaces already declared + Enumeration prefixes = this.nsSupport.getPrefixes(); + while (prefixes.hasMoreElements()) { + + String prefix = (String) prefixes.nextElement(); + super.startPrefixMapping(prefix, this.nsSupport.getURI(prefix)); + } - // and close the entry - this.zipOutput.closeEntry(); + this.state = IN_CONTENT_STATE; + this.contentDepth = 0; + } } catch (RuntimeException re) { throw re; + } catch (SAXException se) { + throw se; } catch (Exception e) { throw new SAXException(e); - } finally { - if (resolver != null) { - resolver.release(source); + } + } + + /** + * @see org.xml.sax.ContentHandler#endElement(String, String, String) + */ + public void endElement(String namespaceURI, String localName, String qName) + throws SAXException { + if (state == IN_CONTENT_STATE) { + super.endElement(namespaceURI, localName, qName); + this.contentDepth--; + + if (this.contentDepth == 0) { + // End of this entry + + // close all declared namespaces. + Enumeration prefixes = this.nsSupport.getPrefixes(); + while (prefixes.hasMoreElements()) { + String prefix = (String) prefixes.nextElement(); + super.endPrefixMapping(prefix); + } + + super.endDocument(); + + try { + this.zipOutput.closeEntry(); + } catch (IOException ioe) { + throw new SAXException(ioe); + } + + super.setConsumer(null); + this.selector.release(this.serializer); + this.serializer = null; + + // Go back to listening for entries + this.state = IN_ZIP_STATE; } - this.manager.release(resolver); + } else { + this.nsSupport.popContext(); } } @@ -224,13 +383,16 @@ throw new SAXException(ioe); } } - /** - * @see org.apache.avalon.framework.component.Composable#compose(ComponentManager) + * @see org.apache.avalon.excalibur.pool.Recyclable#recycle() */ - public void compose(ComponentManager componentManager) - throws ComponentException { - this.manager = componentManager; + public void recycle() { + if (this.serializer != null) { + this.selector.release(this.serializer); + } + if (this.selector != null) { + this.manager.release(this.selector); + } + super.recycle(); } - }
---------------------------------------------------------------------- In case of troubles, e-mail: [EMAIL PROTECTED] To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]