0001: /*
0002: *
0003: *
0004: * Portions Copyright 2000-2007 Sun Microsystems, Inc. All Rights
0005: * Reserved. Use is subject to license terms.
0006: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0007: *
0008: * This program is free software; you can redistribute it and/or
0009: * modify it under the terms of the GNU General Public License version
0010: * 2 only, as published by the Free Software Foundation.
0011: *
0012: * This program is distributed in the hope that it will be useful, but
0013: * WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * General Public License version 2 for more details (a copy is
0016: * included at /legal/license.txt).
0017: *
0018: * You should have received a copy of the GNU General Public License
0019: * version 2 along with this work; if not, write to the Free Software
0020: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0021: * 02110-1301 USA
0022: *
0023: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0024: * Clara, CA 95054 or visit www.sun.com if you need additional
0025: * information or have any questions.
0026: */
0027:
0028: /*****************************************************************************
0029: * Copyright (C) The Apache Software Foundation. All rights reserved. *
0030: * ------------------------------------------------------------------------- *
0031: * This software is published under the terms of the Apache Software License *
0032: * version 1.1, a copy of which has been included with this distribution in *
0033: * the LICENSE file. *
0034: *****************************************************************************/package com.sun.perseus.builder;
0035:
0036: import com.sun.perseus.platform.ThreadSupport;
0037:
0038: import com.sun.perseus.model.CompositeNode;
0039: import com.sun.perseus.model.DocumentNode;
0040: import com.sun.perseus.model.ElementNode;
0041: import com.sun.perseus.model.Font;
0042: import com.sun.perseus.model.FontFace;
0043: import com.sun.perseus.model.ModelNode;
0044: import com.sun.perseus.model.UpdateAdapter;
0045: import com.sun.perseus.model.UpdateListener;
0046: import com.sun.perseus.model.Use;
0047:
0048: import com.sun.perseus.util.SimpleTokenizer;
0049:
0050: import com.sun.perseus.util.SVGConstants;
0051:
0052: import com.sun.perseus.platform.GZIPSupport;
0053:
0054: import org.xml.sax.helpers.DefaultHandler;
0055: import org.xml.sax.Attributes;
0056: import org.xml.sax.Locator;
0057: import org.xml.sax.InputSource;
0058: import org.xml.sax.SAXException;
0059: import org.xml.sax.SAXParseException;
0060:
0061: import org.w3c.dom.DOMException;
0062:
0063: import java.util.Vector;
0064:
0065: import java.io.ByteArrayInputStream;
0066: import java.io.InputStream;
0067: import java.io.InputStreamReader;
0068: import java.io.IOException;
0069: import java.io.Reader;
0070:
0071: import javax.xml.parsers.SAXParserFactory;
0072: import javax.xml.parsers.SAXParser;
0073: import javax.xml.parsers.ParserConfigurationException;
0074:
0075: /**
0076: * NOTE: need to change currentElement management so that there is only a
0077: * need to do a getParent() and cast to (ElementNode).
0078: *
0079: * <code>ModelBuilder</code> is a SAX2 <code>ContentHandler</code> that
0080: * builds a <b>Model</b> (i.e. a tree of <code>ModelNode</code>s from
0081: * the SAX2 events. <br />
0082: *
0083: * This class also offers a static method to synchronously builds a
0084: * <b>Model</b> given a URI: {@link ModelBuilder#loadDocument loadDocument}.
0085: *
0086: * @version $Id: ModelBuilder.java,v 1.10 2006/07/13 00:55:57 st125089 Exp $
0087: */
0088: public class ModelBuilder extends DefaultHandler {
0089: /**
0090: * The default DTD subset used when resolving the DTD entities.
0091: */
0092: public static final String DTD_SUBSET = "<!ATTLIST svg\n"
0093: + " xmlns CDATA #FIXED \"http://www.w3.org/2000/svg\"\n"
0094: + " xmlns:xlink CDATA #FIXED \"http://www.w3.org/1999/xlink\"\n"
0095: + ">";
0096:
0097: /**
0098: * The message used in the SAXException when the
0099: * loading thread is interrupted
0100: */
0101: public static final String LOAD_INTERRUPTED = "Load Interrupted : ";
0102:
0103: /**
0104: * The accepted DTD public IDs.
0105: */
0106: protected static String dtdids = "-//W3C//DTD SVG 1.1 Tiny//EN";
0107:
0108: /**
0109: * Object keeping track of the current node, i.e., the
0110: * last node that was built from an element.
0111: */
0112: protected ElementNode currentElement;
0113:
0114: /**
0115: * Root of the model tree
0116: */
0117: protected DocumentNode modelRoot;
0118:
0119: /**
0120: * Keeps track of opened streams (entities) so that
0121: * they can be closed when parsing completes.
0122: * @see #resolveEntity
0123: */
0124: protected Vector entityStreams = new Vector();
0125:
0126: /**
0127: * Keeps pending namespaceURI to prefix mapping. This is used because
0128: * the prefix mapping is declared in the startPrefixMapping method
0129: * _before_ startElement is called. Therefore, the startElement method
0130: * will use this pendingPrefixMapping vector to declare prefix mappings
0131: * on the newly created element.
0132: */
0133: protected Vector pendingPrefixMapping = null;
0134:
0135: /**
0136: * Used to allows quick check on pendingPrefixMapping.
0137: */
0138: protected Vector pendingPrefixMappingCache = new Vector();
0139:
0140: /**
0141: * The <code>modelFactory</code> is used to build
0142: * <code>ModelNode</code> instances corresponding
0143: * to individual nodes in the parsed XML documents.
0144: * This <code>ModelBuilder</code> aggregates the nodes
0145: * manufactured by the <code>modelFactory</code>
0146: *
0147: * @param modelFactoryIn the factory that contains element
0148: * prototypes for the supported element types
0149: * @param modelRootIn the DocumentNode that should be populated
0150: * with the result of the build process.
0151: */
0152: ModelBuilder(Vector modelFactoryIn, final DocumentNode modelRootIn) {
0153: if (modelFactoryIn == null) {
0154: modelFactoryIn = SVGTinyModelFactory
0155: .getPrototypes(modelRootIn);
0156: }
0157:
0158: this .modelRoot = modelRootIn;
0159:
0160: int n = modelFactoryIn.size();
0161: for (int i = 0; i < n; i++) {
0162: modelRoot.addPrototype((ElementNode) modelFactoryIn
0163: .elementAt(i));
0164: }
0165: }
0166:
0167: /**
0168: * @return the root of the tree built by this builder.
0169: * null is returned if no tree was built yet
0170: */
0171: final DocumentNode getModelRoot() {
0172: return modelRoot;
0173: }
0174:
0175: /**
0176: * Utility method. Invokes the <code>Runnable</code> on the
0177: * <code>modelRoot.invokeAndWait</code>.
0178: *
0179: * @param runnable the <code>Runnable</code> to run
0180: * @throws SAXException if the input <code>Runnable</code> is
0181: * interrupted while pending execution or while running.
0182: */
0183: protected void invokeAndWait(final Runnable runnable)
0184: throws SAXException {
0185: try {
0186: if (!ThreadSupport.isInterrupted(Thread.currentThread())) {
0187: modelRoot.invokeAndWait(runnable);
0188: } else {
0189: throw new InterruptedException();
0190: }
0191: } catch (InterruptedException ie) {
0192: throw new SAXException(LOAD_INTERRUPTED
0193: + Thread.currentThread());
0194: }
0195: }
0196:
0197: ////////////////////////////////////////////////////////////////////
0198: // Default implementation of the EntityResolver interface.
0199: ////////////////////////////////////////////////////////////////////
0200: /**
0201: * Resolve an external entity.
0202: *
0203: * @param publicId The public identifer, or null if none is
0204: * available.
0205: * @param systemId The system identifier provided in the XML
0206: * document.
0207: * @return The new input source, or null to require the
0208: * default behaviour.
0209: * @exception org.xml.sax.SAXException Any SAX exception, possibly
0210: * wrapping another exception.
0211: * @see org.xml.sax.EntityResolver#resolveEntity
0212: */
0213: public final InputSource resolveEntity(final String publicId,
0214: final String systemId) throws SAXException {
0215: if (publicId == null || dtdids.indexOf(publicId) != -1) {
0216: // If there is no declared publicId or if the publicId is
0217: // one of the supported ones (the test is very lose but quick)
0218: // we assume the input source is an SVG Tiny view and we just
0219: // process a small DTD subset that includes the default
0220: // namespace and xlink namespace declarations. Attribute defaulting
0221: // is handled by the code, so are default attributes, so there is
0222: // no need to process the DTDs.
0223: InputSource is = new InputSource();
0224: Reader reader = new InputStreamReader(
0225: new ByteArrayInputStream(DTD_SUBSET.getBytes()));
0226: is.setCharacterStream(reader);
0227:
0228: // Keep track of opened streams as some SAX
0229: // implementations do not close that stream.
0230: entityStreams.addElement(reader);
0231:
0232: return is;
0233: }
0234:
0235: // Let the SAX parser find the entity.
0236: return null;
0237: }
0238:
0239: ////////////////////////////////////////////////////////////////////
0240: // Default implementation of DTDHandler interface.
0241: ////////////////////////////////////////////////////////////////////
0242:
0243: /**
0244: * Receive notification of a notation declaration.
0245: *
0246: * <p>By default, do nothing. Application writers may override this
0247: * method in a subclass if they wish to keep track of the notations
0248: * declared in a document.</p>
0249: *
0250: * @param name The notation name.
0251: * @param publicId The notation public identifier, or null if not
0252: * available.
0253: * @param systemId The notation system identifier.
0254: * @exception org.xml.sax.SAXException Any SAX exception, possibly
0255: * wrapping another exception.
0256: * @see org.xml.sax.DTDHandler#notationDecl
0257: */
0258: /*
0259: public void notationDecl(String name, String publicId, String systemId)
0260: throws SAXException {
0261: // no op
0262: }
0263: */
0264:
0265: /**
0266: * Receive notification of an unparsed entity declaration.
0267: *
0268: * <p>By default, do nothing. Application writers may override this
0269: * method in a subclass to keep track of the unparsed entities
0270: * declared in a document.</p>
0271: *
0272: * @param name The entity name.
0273: * @param publicId The entity public identifier, or null if not
0274: * available.
0275: * @param systemId The entity system identifier.
0276: * @param notationName The name of the associated notation.
0277: * @exception org.xml.sax.SAXException Any SAX exception, possibly
0278: * wrapping another exception.
0279: * @see org.xml.sax.DTDHandler#unparsedEntityDecl
0280: */
0281: /*
0282: public void unparsedEntityDecl(String name, String publicId,
0283: String systemId, String notationName) throws SAXException {
0284: // no op
0285: }
0286: */
0287:
0288: ////////////////////////////////////////////////////////////////////
0289: // Default implementation of ContentHandler interface.
0290: ////////////////////////////////////////////////////////////////////
0291:
0292: /**
0293: * Receive a Locator object for document events.
0294: *
0295: * <p>By default, do nothing. Application writers may override this
0296: * method in a subclass if they wish to store the locator for use
0297: * with other document events.</p>
0298: *
0299: * @param locator A locator for all SAX document events.
0300: * @see org.xml.sax.ContentHandler#setDocumentLocator
0301: * @see org.xml.sax.Locator
0302: */
0303: public final void setDocumentLocator(final Locator locator) {
0304: // ctx.setLocator(locator);
0305: }
0306:
0307: /**
0308: * <b>SAX</b>: Implements {@link
0309: * org.xml.sax.ContentHandler#startDocument() ContentHander.startDocument}.
0310: */
0311: public final void startDocument() {
0312: }
0313:
0314: /**
0315: * Receive notification of the end of the document.
0316: *
0317: * <p>By default, do nothing. Application writers may override this
0318: * method in a subclass to take specific actions at the end
0319: * of a document (such as finalising a tree or closing an output
0320: * file).</p>
0321: *
0322: * @exception org.xml.sax.SAXException Any SAX exception, possibly
0323: * wrapping another exception.
0324: * @see org.xml.sax.ContentHandler#endDocument
0325: */
0326: public final void endDocument() throws SAXException {
0327: // Validate the document.
0328: try {
0329: modelRoot.validate();
0330: } catch (DOMException de) {
0331: de.printStackTrace();
0332: throw new SAXException(de.getMessage());
0333: }
0334:
0335: invokeAndWait(new Runnable() {
0336: public void run() {
0337: UpdateListener um = modelRoot.getUpdateListener();
0338: modelRoot.setLoaded(true);
0339: um.loadComplete(modelRoot);
0340: }
0341: });
0342: }
0343:
0344: /**
0345: * Receive notification of the start of a Namespace mapping.
0346: *
0347: * <p>By default, do nothing. Application writers may override this
0348: * method in a subclass to take specific actions at the start of
0349: * each Namespace prefix scope (such as storing the prefix mapping).</p>
0350: *
0351: * @param prefix The Namespace prefix being declared.
0352: * @param uri The Namespace URI mapped to the prefix.
0353: * @exception org.xml.sax.SAXException Any SAX exception, possibly
0354: * wrapping another exception.
0355: * @see org.xml.sax.ContentHandler#startPrefixMapping
0356: */
0357: public void startPrefixMapping(String prefix, String uri)
0358: throws SAXException {
0359: pendingPrefixMappingCache
0360: .addElement(new String[] { prefix, uri });
0361: pendingPrefixMapping = pendingPrefixMappingCache;
0362: }
0363:
0364: /**
0365: * Receive notification of the end of a Namespace mapping.
0366: *
0367: * <p>By default, do nothing. Application writers may override this
0368: * method in a subclass to take specific actions at the end of
0369: * each prefix mapping.</p>
0370: *
0371: * @param prefix The Namespace prefix being declared.
0372: * @exception org.xml.sax.SAXException Any SAX exception, possibly
0373: * wrapping another exception.
0374: * @see org.xml.sax.ContentHandler#endPrefixMapping
0375: */
0376: /*
0377: public void endPrefixMapping(String prefix) throws SAXException {
0378: // no op
0379: }
0380: */
0381:
0382: /**
0383: * Receive notification of the start of an element.
0384: *
0385: * @param uri The element's namespace uri
0386: * @param localName The element's local name, i.e., within the given
0387: * namespace
0388: * @param qName The element's qualified name, i.e., including the namespace
0389: * prefix
0390: * @param attributes The specified or defaulted attributes.
0391: * @exception org.xml.sax.SAXException Any SAX exception, possibly
0392: * wrapping another exception.
0393: * @see org.xml.sax.ContentHandler#startElement
0394: */
0395: public final void startElement(final String uri,
0396: final String localName, final String qName,
0397: final Attributes attributes) throws SAXException {
0398: // ====================================================================
0399: // First, build a new element from its XML descriptor.
0400: // ====================================================================
0401: ElementNode modelNode = null;
0402: try {
0403: modelNode = (ElementNode) modelRoot.createElementNS(uri,
0404: localName);
0405:
0406: // Handle prefix mappings
0407: if (pendingPrefixMapping != null) {
0408: int n = pendingPrefixMapping.size();
0409: for (int i = 0; i < n; i++) {
0410: String[] mapping = (String[]) pendingPrefixMapping
0411: .elementAt(i);
0412: modelRoot.addNamespacePrefix(mapping[0],
0413: mapping[1], modelNode);
0414: }
0415: pendingPrefixMapping.removeAllElements();
0416: pendingPrefixMapping = null;
0417: }
0418:
0419: // Make sure we mark the node as _not_ loaded.
0420: // This is important because we use the loaded bit to
0421: // control certain behaviors, such as the progressive rendering
0422: // behavior.
0423: modelNode.setLoaded(false);
0424:
0425: // ================================================
0426: // Check that required traits have been specified.
0427: // ================================================
0428: String[] requiredTraits = modelNode.getRequiredTraits();
0429: if (requiredTraits != null) {
0430: for (int i = 0; i < requiredTraits.length; i++) {
0431: if (attributes.getValue(requiredTraits[i]) == null) {
0432: throw new SAXException(Messages.formatMessage(
0433: Messages.ERROR_MISSING_ATTRIBUTE,
0434: new Object[] { requiredTraits[i],
0435: modelNode.getNamespaceURI(),
0436: modelNode.getLocalName() }));
0437: }
0438: }
0439: }
0440:
0441: String[][] requiredTraitsNS = modelNode
0442: .getRequiredTraitsNS();
0443: if (requiredTraitsNS != null) {
0444: for (int i = 0; i < requiredTraitsNS.length; i++) {
0445: if (attributes.getValue(requiredTraitsNS[i][0],
0446: requiredTraitsNS[i][1]) == null) {
0447: throw new SAXException(Messages.formatMessage(
0448: Messages.ERROR_MISSING_ATTRIBUTE_NS,
0449: new Object[] { requiredTraitsNS[i][1],
0450: requiredTraitsNS[i][0],
0451: modelNode.getNamespaceURI(),
0452: modelNode.getLocalName() }));
0453: }
0454: }
0455: }
0456:
0457: // ================================================
0458: // End of required traits check
0459: // ================================================
0460:
0461: // ================================================
0462: // Apply all specified traits.
0463: // ================================================
0464: int n = attributes.getLength();
0465: for (int i = 0; i < n; i++) {
0466: modelNode.setAttributeNS(attributes.getURI(i),
0467: attributes.getLocalName(i), attributes
0468: .getValue(i));
0469: }
0470:
0471: // ================================================
0472: // Apply default traits if they were not specified.
0473: // ================================================
0474: String[][] defaultTraits = modelNode.getDefaultTraits();
0475: if (defaultTraits != null) {
0476: for (int i = 0; i < defaultTraits.length; i++) {
0477: if (attributes.getValue(defaultTraits[i][0]) == null) {
0478: modelNode.setAttribute(defaultTraits[i][0],
0479: defaultTraits[i][1]);
0480: }
0481: }
0482: }
0483:
0484: // ================================================
0485: // IMPL NOTE
0486: //
0487: // If the style attribute is specified, apply the
0488: // traits it specified. The reason for handling the
0489: // style attribute at the parser level is that it
0490: // is not a regular trait. It is an in-line
0491: // stylesheet. We only support it in order to
0492: // support imports from Adobe Illustrator which
0493: // uses the style attribute on gradients, even
0494: // when the option to export without the style
0495: // attribute is selected.
0496: // ================================================
0497: String styleAttribute = attributes
0498: .getValue(SVGConstants.SVG_STYLE_ATTRIBUTE);
0499: if (styleAttribute != null) {
0500: parseStyleAttribute(styleAttribute, modelNode);
0501: }
0502:
0503: // ================================================
0504: // Apply trait aliases
0505: // ================================================
0506: String[][] traitAliases = modelNode.getTraitAliases();
0507: if (traitAliases != null) {
0508: for (int i = 0; i < traitAliases.length; i++) {
0509: if (attributes.getValue(traitAliases[i][0]) == null) {
0510: // The trait with alias was not specified.
0511: // Check if its alias was specified.
0512: String v = attributes
0513: .getValue(traitAliases[i][1]);
0514: if (v != null) {
0515: modelNode.setAttribute(traitAliases[i][0],
0516: v);
0517: }
0518: }
0519: }
0520: }
0521: } catch (DOMException e) {
0522: e.printStackTrace();
0523: throw new SAXException(e.getMessage());
0524: }
0525:
0526: // ====================================================================
0527: // Append new element to the tree
0528: // ====================================================================
0529: if (currentElement != null) {
0530: addToParent(modelNode, currentElement);
0531: } else {
0532: addToParent(modelNode, modelRoot);
0533: }
0534: currentElement = modelNode;
0535:
0536: // ====================================================================
0537: // Notify application that load has begun on a new element
0538: // ====================================================================
0539: final ModelNode startedNode = modelNode;
0540: final UpdateListener ul = modelRoot.getUpdateListener();
0541: invokeAndWait(new Runnable() {
0542: public void run() {
0543: ul.loadBegun(startedNode);
0544:
0545: }
0546: });
0547:
0548: // ====================================================================
0549: // Check if there were any delayed exception. A delayed exception
0550: // allows progressive rendering of bad path to happen before the
0551: // exception which captured the bad path data is actually thrown (below)
0552: // ====================================================================
0553: try {
0554: modelRoot.checkDelayedException();
0555: } catch (DOMException e) {
0556: throw new SAXException(e.getMessage());
0557: }
0558: }
0559:
0560: /**
0561: * Utility method to parse a trait value.
0562: *
0563: * @param styleValue the value of the style attribute to parse.
0564: * @param elt the ElementNode on which trait values should be set.
0565: */
0566: private void parseStyleAttribute(final String styleValue,
0567: final ElementNode elt) {
0568: SimpleTokenizer st = new SimpleTokenizer(styleValue,
0569: SVGConstants.COMMA_STR);
0570: while (st.hasMoreTokens()) {
0571: String traitSpec = st.nextToken();
0572: int ci = traitSpec.indexOf(':');
0573: String traitName = null;
0574: String traitValue = null;
0575: if (ci != -1) {
0576: traitName = traitSpec.substring(0, ci);
0577: traitValue = traitSpec.substring(ci + 1);
0578: } else {
0579: traitName = "";
0580: traitValue = "";
0581: }
0582: elt.setAttribute(traitName, traitValue);
0583: }
0584: }
0585:
0586: /**
0587: * Adds the input node to the given parent. If there is no
0588: * associated <code>RunnableQueue</code>, the child is simply
0589: * added to the parent in the calling thread. If there is
0590: * a <code>RunnableQueue</code>, the child is added to the parent
0591: * the <code>RunnableQueue</code> thread, by invoking a
0592: * <code>Runnable</code> on the queue.
0593: *
0594: * @param child node to add to the parent
0595: * @param parent node to which the child is added.
0596: *
0597: * @throws SAXException if the child cannot be added to the parent
0598: * because the thread was interrupted or if the thread
0599: * was interrupted to begin with.
0600: */
0601: void addToParent(final ElementNode child, final CompositeNode parent)
0602: throws SAXException {
0603:
0604: invokeAndWait(new Runnable() {
0605: public void run() {
0606: parent.add(child);
0607: }
0608: });
0609:
0610: // This may happen, for example, if the loading thread
0611: // is interrupted by an update listener.
0612: if (ThreadSupport.isInterrupted(Thread.currentThread())) {
0613: throw new SAXException(LOAD_INTERRUPTED
0614: + Thread.currentThread());
0615: }
0616: }
0617:
0618: /**
0619: * Debug: trace element to console
0620: *
0621: * @param uri the element's namespace uri
0622: * @param localName the element's local name
0623: * @param qName the element's qualified name
0624: * @param attributes the element's attributes
0625: */
0626: /*
0627: public final void traceAttributes(final String uri,
0628: final String localName,
0629: final String qName,
0630: final Attributes attributes) {
0631: System.out.println(">>>>> startElement <" + localName
0632: + "> \n\turi = " + uri + "\n\tqName = " + qName);
0633:
0634: int n = attributes.getLength();
0635: for (int i = 0; i < n; i++) {
0636: System.out.println("=============>");
0637: System.out.println(" uri[" + i + "] = " + attributes.getURI(i));
0638: System.out.println(" name[" + i + "] = local("
0639: + attributes.getLocalName(i) + ") qname("
0640: + attributes.getQName(i) + ")");
0641: System.out.println(" value[" + i + "] = ivalue("
0642: + attributes.getValue(i)
0643: + ") qvalue("
0644: + attributes.getValue(attributes.getQName(i))
0645: + ") urivalue("
0646: + attributes.getValue(attributes.getLocalName(i),
0647: attributes.getURI(i))
0648: + ")");
0649: System.out.println(attributes.getQName(i) + " = "
0650: + attributes.getValue(i));
0651: System.out.println("<=============");
0652: }
0653: }
0654: */
0655:
0656: /**
0657: * Updates the <tt>currentElement</tt>.
0658: *
0659: * @param uri The element's namespace uri.
0660: * @param localName The element's local name
0661: * @param qName The element's qualified name
0662: * @exception org.xml.sax.SAXException Any SAX exception, possibly
0663: * wrapping another exception.
0664: * @see org.xml.sax.ContentHandler#endElement
0665: */
0666: public final void endElement(final String uri,
0667: final String localName, final String qName)
0668: throws SAXException {
0669: //
0670: // See beginElement: currentElement _cannot_ be null
0671: //
0672:
0673: // Update the Font data base if a new FontFace was loaded
0674: if (currentElement instanceof Font) {
0675: invokeAndWait(new Runnable() {
0676: public void run() {
0677: ModelNode fc = currentElement.getFirstChildNode();
0678: if (fc != null && fc instanceof FontFace) {
0679: modelRoot.addFontFace((FontFace) fc);
0680: }
0681: }
0682: });
0683: }
0684:
0685: invokeAndWait(new Runnable() {
0686: public void run() {
0687: UpdateListener um = modelRoot.getUpdateListener();
0688: currentElement.setLoaded(true);
0689: um.loadComplete(currentElement);
0690: }
0691: });
0692:
0693: // Move up the next content node
0694: ModelNode parent = currentElement.getParent();
0695: if (parent instanceof ElementNode) {
0696: currentElement = (ElementNode) parent;
0697: } else {
0698: currentElement = null;
0699: }
0700: }
0701:
0702: /**
0703: * Receive notification of character data inside an element.
0704: *
0705: * <p>By default, do nothing. Application writers may override this
0706: * method to take specific actions for each chunk of character data
0707: * (such as adding the data to a node or buffer, or printing it to
0708: * a file).</p>
0709: *
0710: * @param ch The characters.
0711: * @param start The start position in the character array.
0712: * @param length The number of characters to use from the
0713: * character array.
0714: * @exception org.xml.sax.SAXException Any SAX exception, possibly
0715: * wrapping another exception.
0716: * @see org.xml.sax.ContentHandler#characters
0717: */
0718: public final void characters(final char[] ch, final int start,
0719: final int length) throws SAXException {
0720: if (currentElement != null) {
0721: final String text = new String(ch, start, length);
0722: final UpdateListener ul = modelRoot.getUpdateListener();
0723: invokeAndWait(new Runnable() {
0724: public void run() {
0725: currentElement.appendTextChild(text);
0726: ul.textInserted(currentElement);
0727: }
0728: });
0729: } else {
0730: System.err
0731: .println(">>>>>>>>>>>>>> currentElement is null!!!!!!!");
0732: }
0733: }
0734:
0735: /**
0736: * Receive notification of ignorable whitespace in element content.
0737: *
0738: * <p>By default, do nothing. Application writers may override this
0739: * method to take specific actions for each chunk of ignorable
0740: * whitespace (such as adding data to a node or buffer, or printing
0741: * it to a file).</p>
0742: *
0743: * @param ch The whitespace characters.
0744: * @param start The start position in the character array.
0745: * @param length The number of characters to use from the
0746: * character array.
0747: * @exception org.xml.sax.SAXException Any SAX exception, possibly
0748: * wrapping another exception.
0749: * @see org.xml.sax.ContentHandler#ignorableWhitespace
0750: */
0751: /*
0752: public void ignorableWhitespace(char ch[], int start, int length)
0753: throws SAXException {
0754: // no op
0755: }
0756: */
0757:
0758: /**
0759: * Receive notification of a processing instruction.
0760: *
0761: * <p>By default, do nothing. Application writers may override this
0762: * method in a subclass to take specific actions for each
0763: * processing instruction, such as setting status variables or
0764: * invoking other methods.</p>
0765: *
0766: * @param target The processing instruction target.
0767: * @param data The processing instruction data, or null if
0768: * none is supplied.
0769: * @exception org.xml.sax.SAXException Any SAX exception, possibly
0770: * wrapping another exception.
0771: * @see org.xml.sax.ContentHandler#processingInstruction
0772: */
0773: /*
0774: public void processingInstruction(String target, String data)
0775: throws SAXException {
0776: // no op
0777: }
0778: */
0779:
0780: /**
0781: * Receive notification of a skipped entity.
0782: *
0783: * <p>By default, do nothing. Application writers may override this
0784: * method in a subclass to take specific actions for each
0785: * processing instruction, such as setting status variables or
0786: * invoking other methods.</p>
0787: *
0788: * @param name The name of the skipped entity.
0789: * @exception org.xml.sax.SAXException Any SAX exception, possibly
0790: * wrapping another exception.
0791: * @see org.xml.sax.ContentHandler#processingInstruction
0792: */
0793: /*
0794: public void skippedEntity(String name) throws SAXException {
0795: // no op
0796: }
0797: */
0798:
0799: ////////////////////////////////////////////////////////////////////
0800: // Default implementation of the ErrorHandler interface.
0801: ////////////////////////////////////////////////////////////////////
0802: /**
0803: * Report a fatal XML parsing error.
0804: *
0805: * <p>The default implementation throws a SAXParseException.
0806: * Application writers may override this method in a subclass if
0807: * they need to take specific actions for each fatal error (such as
0808: * collecting all of the errors into a single report): in any case,
0809: * the application must stop all regular processing when this
0810: * method is invoked, since the document is no longer reliable, and
0811: * the parser may no longer report parsing events.</p>
0812: *
0813: * @param e The error information encoded as an exception.
0814: * @exception org.xml.sax.SAXException Any SAX exception, possibly
0815: * wrapping another exception.
0816: * @see org.xml.sax.ErrorHandler#fatalError
0817: * @see org.xml.sax.SAXParseException
0818: */
0819: public final void fatalError(final SAXParseException e)
0820: throws SAXException {
0821: throw e;
0822: }
0823:
0824: // =========================================================================
0825: // Utility method
0826: // =========================================================================
0827:
0828: /**
0829: * Loads an SVG Tiny document from a given URI
0830: *
0831: * @param svgURI the URI of the SVG document to load.
0832: * @return the <code>DocumentNode</code> built from the requested svgURI.
0833: * @throws IOException if the file cannot be loaded.
0834: */
0835: public static DocumentNode loadDocument(final String svgURI)
0836: throws IOException {
0837:
0838: InputStream is = GZIPSupport.openHandleGZIP(svgURI);
0839: DocumentNode doc = new DocumentNode();
0840: doc.setLoaded(false);
0841: doc.setDocumentURI(svgURI);
0842:
0843: UpdateAdapter updateAdapter = new UpdateAdapter();
0844: doc.setUpdateListener(updateAdapter);
0845:
0846: loadDocument(is, doc);
0847:
0848: if (!updateAdapter.loadSuccess()) {
0849: Exception cause = updateAdapter.getLoadingFailedException();
0850: if (cause == null) {
0851: throw new IOException();
0852: } else {
0853: throw new IOException(cause.getMessage());
0854: }
0855: }
0856:
0857: return doc;
0858: }
0859:
0860: /**
0861: * Load an SVG Tiny document at the given input stream.
0862: *
0863: * This method uses JAXP to define the SAX parser to use.
0864: *
0865: * Any error is reported to the input <code>DocumentNode</code>'s
0866: * <code>UpdateListener</code>'s <code>loadFailed</code> method.
0867: *
0868: * @param is the <code>InputStream</code> from which the SVG content is
0869: * read. This might be GZIPed compressed stream. If the input stream
0870: * is null, an input stream is opened from the root's document URI.
0871: * @param root the documentNode to populate with the document's content
0872: *
0873: *
0874: * @throws IllegalArgumentException if the root's URI is null,
0875: * if root is null or if the root's <code>UpdateListener</code> is
0876: * null
0877: */
0878: public static void loadDocument(final InputStream is,
0879: final DocumentNode root) {
0880: loadDocument(is, root, null);
0881: }
0882:
0883: /**
0884: * Load an SVG Tiny document at the given input stream.
0885: *
0886: * This method uses JAXP to define the SAX parser to use.
0887: *
0888: * Any error is reported to the input <code>DocumentNode</code>'s
0889: * <code>UpdateListener</code>'s <code>loadFailed</code> method.
0890: *
0891: * @param is the <code>InputStream</code> from which the SVG content is
0892: * read. This might be GZIPed compressed stream. If the input stream
0893: * is null, an input stream is opened from the root's document URI.
0894: * @param root the documentNode to populate with the document's content
0895: * @param modelFactory the <code>ModelFactory</code> used to turn XML
0896: * elements into <code>ModelNode</code> instances.
0897: *
0898: * @throws IllegalArgumentException if the root's URI is null,
0899: * if root is null or if the root's <code>UpdateListener</code> is
0900: * null
0901: */
0902: public static void loadDocument(InputStream is,
0903: final DocumentNode root, Vector modelFactory) {
0904:
0905: if (root == null) {
0906: throw new IllegalArgumentException();
0907: }
0908:
0909: root.setLoaded(false);
0910:
0911: String svgURI = root.getURIBase();
0912:
0913: if (is == null && svgURI == null) {
0914: throw new IllegalArgumentException();
0915: }
0916:
0917: final UpdateListener updateListener = root.getUpdateListener();
0918: if (updateListener == null) {
0919: throw new IllegalArgumentException();
0920: }
0921:
0922: // Before parsing the file, we add a default mapping for the
0923: // SVG and XLink namespaces.
0924: root.addNamespacePrefix(SVGConstants.XLINK_PREFIX,
0925: SVGConstants.XLINK_NAMESPACE_URI, root);
0926: root.addNamespacePrefix("", SVGConstants.SVG_NAMESPACE_URI,
0927: root);
0928:
0929: ModelBuilder modelBuilder = null;
0930: InputStream gzipIS = null;
0931:
0932: try {
0933: // Get a SAX parser through the JAXP API. The
0934: // parser does not do validation and is namespace aware
0935: SAXParserFactory factory = SAXParserFactory.newInstance();
0936: // System.err.println(">>>>>>>>>>>>>>>> SAXParserFactory class: "
0937: // + factory.getClass().getName());
0938: factory.setNamespaceAware(true);
0939: factory.setValidating(false);
0940:
0941: SAXParser parser = null;
0942: parser = factory.newSAXParser();
0943: final SAXParser saxParser = parser;
0944:
0945: // Check the input stream. If the input stream is not null, we
0946: // load that stream. Otherwise, we build an input stream from
0947: // the root's URI.
0948: if (is == null) {
0949: is = GZIPSupport.openHandleGZIP(svgURI);
0950: }
0951:
0952: // The following wraps the input stream, if necessary, to handle
0953: // GZIP compression.
0954: gzipIS = GZIPSupport.handleGZIP(is);
0955: final InputStream fgzipIS = gzipIS;
0956:
0957: root.invokeAndWait(new Runnable() {
0958: public void run() {
0959: // Parse the document now. Our modelBuilder
0960: // handles the parser's SAX events.
0961: updateListener.loadStarting(root, fgzipIS);
0962: }
0963: });
0964:
0965: modelBuilder = new ModelBuilder(modelFactory, root);
0966:
0967: saxParser.parse(gzipIS, modelBuilder);
0968: } catch (ParserConfigurationException pce) {
0969: loadingFailed(updateListener, root, pce);
0970: } catch (SAXParseException spe) {
0971: loadingFailed(updateListener, root, spe);
0972: } catch (SAXException se) {
0973: loadingFailed(updateListener, root, se);
0974: } catch (IOException ioe) {
0975: loadingFailed(updateListener, root, ioe);
0976: } catch (Exception e) {
0977: loadingFailed(updateListener, root, e);
0978: } finally {
0979: try {
0980: if (gzipIS != null) {
0981: gzipIS.close();
0982: }
0983: } catch (IOException ioe) {
0984: // Don't do anything if we got an exception
0985: // while trying to close the stream.
0986: }
0987:
0988: if (modelBuilder != null) {
0989: int n = modelBuilder.entityStreams.size();
0990: for (int i = 0; i < n; i++) {
0991: Reader r = (Reader) modelBuilder.entityStreams
0992: .elementAt(i);
0993: try {
0994: r.close();
0995: } catch (IOException ioe) {
0996: // Do nothing: this means the stream was
0997: // closed by the SAX parser.
0998: }
0999: }
1000: }
1001: }
1002: }
1003:
1004: /**
1005: * Utility method to report an exception to the UpdateListener
1006: * in the proper thread.
1007: *
1008: * @param updateListener the <code>UpdateListener</code> to which
1009: * the error should be reported.
1010: * @param root the <code>DocumentNode</code> which was being loaded.
1011: * @param e the <code>Exception</code> which caused the failure.
1012: */
1013: protected static void loadingFailed(
1014: final UpdateListener updateListener,
1015: final DocumentNode root, final Exception e) {
1016: System.err
1017: .println(">>>>>>>>>>>>>>>>>>> +++++ Loading failed ...");
1018: e.printStackTrace();
1019: try {
1020: root.invokeAndWait(new Runnable() {
1021: public void run() {
1022: updateListener.loadingFailed(root, e);
1023: }
1024: });
1025: } catch (InterruptedException ie) {
1026: // The current thread was interrupted. Loading Failed will
1027: // not be reported...
1028: return;
1029: }
1030: }
1031: }
|