ModelBuilderpublic class ModelBuilder extends DefaultHandler NOTE: need to change currentElement management so that there is only a
need to do a getParent() and cast to (ElementNode).
ModelBuilder is a SAX2 ContentHandler that
builds a Model (i.e. a tree of ModelNode s from
the SAX2 events.
This class also offers a static method to synchronously builds a
Model given a URI: {@link ModelBuilder#loadDocument loadDocument}. |
Fields Summary |
---|
public static final String | DTD_SUBSETThe default DTD subset used when resolving the DTD entities. | public static final String | LOAD_INTERRUPTEDThe message used in the SAXException when the
loading thread is interrupted | protected static String | dtdidsThe accepted DTD public IDs. | protected com.sun.perseus.model.ElementNode | currentElementObject keeping track of the current node, i.e., the
last node that was built from an element. | protected com.sun.perseus.model.DocumentNode | modelRootRoot of the model tree | protected Vector | entityStreamsKeeps track of opened streams (entities) so that
they can be closed when parsing completes. | protected Vector | pendingPrefixMappingKeeps pending namespaceURI to prefix mapping. This is used because
the prefix mapping is declared in the startPrefixMapping method
_before_ startElement is called. Therefore, the startElement method
will use this pendingPrefixMapping vector to declare prefix mappings
on the newly created element. | protected Vector | pendingPrefixMappingCacheUsed to allows quick check on pendingPrefixMapping. |
Constructors Summary |
---|
ModelBuilder(Vector modelFactoryIn, com.sun.perseus.model.DocumentNode modelRootIn)The modelFactory is used to build
ModelNode instances corresponding
to individual nodes in the parsed XML documents.
This ModelBuilder aggregates the nodes
manufactured by the modelFactory
if (modelFactoryIn == null) {
modelFactoryIn = SVGTinyModelFactory.getPrototypes(modelRootIn);
}
this.modelRoot = modelRootIn;
int n = modelFactoryIn.size();
for (int i = 0; i < n; i++) {
modelRoot.addPrototype((ElementNode) modelFactoryIn.elementAt(i));
}
|
Methods Summary |
---|
void | addToParent(com.sun.perseus.model.ElementNode child, com.sun.perseus.model.CompositeNode parent)Adds the input node to the given parent. If there is no
associated RunnableQueue , the child is simply
added to the parent in the calling thread. If there is
a RunnableQueue , the child is added to the parent
the RunnableQueue thread, by invoking a
Runnable on the queue.
invokeAndWait(new Runnable() {
public void run() {
parent.add(child);
}
});
// This may happen, for example, if the loading thread
// is interrupted by an update listener.
if (ThreadSupport.isInterrupted(Thread.currentThread())) {
throw new SAXException(LOAD_INTERRUPTED
+ Thread.currentThread());
}
| public final void | characters(char[] ch, int start, int length)Receive notification of character data inside an element.
By default, do nothing. Application writers may override this
method to take specific actions for each chunk of character data
(such as adding the data to a node or buffer, or printing it to
a file).
if (currentElement != null) {
final String text = new String(ch, start, length);
final UpdateListener ul = modelRoot.getUpdateListener();
invokeAndWait(new Runnable() {
public void run() {
currentElement.appendTextChild(text);
ul.textInserted(currentElement);
}
});
} else {
System.err.println(">>>>>>>>>>>>>> currentElement is null!!!!!!!");
}
| public final void | endDocument()Receive notification of the end of the document.
By default, do nothing. Application writers may override this
method in a subclass to take specific actions at the end
of a document (such as finalising a tree or closing an output
file).
// Validate the document.
try {
modelRoot.validate();
} catch (DOMException de) {
de.printStackTrace();
throw new SAXException(de.getMessage());
}
invokeAndWait(new Runnable() {
public void run() {
UpdateListener um = modelRoot.getUpdateListener();
modelRoot.setLoaded(true);
um.loadComplete(modelRoot);
}
});
| public final void | endElement(java.lang.String uri, java.lang.String localName, java.lang.String qName)Updates the currentElement.
//
// See beginElement: currentElement _cannot_ be null
//
// Update the Font data base if a new FontFace was loaded
if (currentElement instanceof Font) {
invokeAndWait(new Runnable() {
public void run() {
ModelNode fc
= currentElement.getFirstChildNode();
if (fc != null && fc instanceof FontFace) {
modelRoot.addFontFace
((FontFace) fc);
}
}
});
}
invokeAndWait(new Runnable() {
public void run() {
UpdateListener um = modelRoot.getUpdateListener();
currentElement.setLoaded(true);
um.loadComplete(currentElement);
}
});
// Move up the next content node
ModelNode parent = currentElement.getParent();
if (parent instanceof ElementNode) {
currentElement = (ElementNode) parent;
} else {
currentElement = null;
}
| public final void | fatalError(org.xml.sax.SAXParseException e)Report a fatal XML parsing error.
The default implementation throws a SAXParseException.
Application writers may override this method in a subclass if
they need to take specific actions for each fatal error (such as
collecting all of the errors into a single report): in any case,
the application must stop all regular processing when this
method is invoked, since the document is no longer reliable, and
the parser may no longer report parsing events.
throw e;
| final com.sun.perseus.model.DocumentNode | getModelRoot()
return modelRoot;
| protected void | invokeAndWait(java.lang.Runnable runnable)Utility method. Invokes the Runnable on the
modelRoot.invokeAndWait .
try {
if (!ThreadSupport.isInterrupted(Thread.currentThread())) {
modelRoot.invokeAndWait(runnable);
} else {
throw new InterruptedException();
}
} catch (InterruptedException ie) {
throw new SAXException(LOAD_INTERRUPTED
+ Thread.currentThread());
}
| public static com.sun.perseus.model.DocumentNode | loadDocument(java.lang.String svgURI)Loads an SVG Tiny document from a given URI
InputStream is = GZIPSupport.openHandleGZIP(svgURI);
DocumentNode doc = new DocumentNode();
doc.setLoaded(false);
doc.setDocumentURI(svgURI);
UpdateAdapter updateAdapter = new UpdateAdapter();
doc.setUpdateListener(updateAdapter);
loadDocument(is, doc);
if (!updateAdapter.loadSuccess()) {
Exception cause = updateAdapter.getLoadingFailedException();
if (cause == null) {
throw new IOException();
} else {
throw new IOException(cause.getMessage());
}
}
return doc;
| public static void | loadDocument(java.io.InputStream is, com.sun.perseus.model.DocumentNode root)Load an SVG Tiny document at the given input stream.
This method uses JAXP to define the SAX parser to use.
Any error is reported to the input DocumentNode 's
UpdateListener 's loadFailed method.
loadDocument(is, root, null);
| public static void | loadDocument(java.io.InputStream is, com.sun.perseus.model.DocumentNode root, java.util.Vector modelFactory)Load an SVG Tiny document at the given input stream.
This method uses JAXP to define the SAX parser to use.
Any error is reported to the input DocumentNode 's
UpdateListener 's loadFailed method.
if (root == null) {
throw new IllegalArgumentException();
}
root.setLoaded(false);
String svgURI = root.getURIBase();
if (is == null && svgURI == null) {
throw new IllegalArgumentException();
}
final UpdateListener updateListener = root.getUpdateListener();
if (updateListener == null) {
throw new IllegalArgumentException();
}
// Before parsing the file, we add a default mapping for the
// SVG and XLink namespaces.
root.addNamespacePrefix(SVGConstants.XLINK_PREFIX,
SVGConstants.XLINK_NAMESPACE_URI,
root);
root.addNamespacePrefix("",
SVGConstants.SVG_NAMESPACE_URI,
root);
ModelBuilder modelBuilder = null;
InputStream gzipIS = null;
try {
// Get a SAX parser through the JAXP API. The
// parser does not do validation and is namespace aware
SAXParserFactory factory = SAXParserFactory.newInstance();
// System.err.println(">>>>>>>>>>>>>>>> SAXParserFactory class: "
// + factory.getClass().getName());
factory.setNamespaceAware(true);
factory.setValidating(false);
SAXParser parser = null;
parser = factory.newSAXParser();
final SAXParser saxParser = parser;
// Check the input stream. If the input stream is not null, we
// load that stream. Otherwise, we build an input stream from
// the root's URI.
if (is == null) {
is = GZIPSupport.openHandleGZIP(svgURI);
}
// The following wraps the input stream, if necessary, to handle
// GZIP compression.
gzipIS = GZIPSupport.handleGZIP(is);
final InputStream fgzipIS = gzipIS;
root.invokeAndWait(new Runnable() {
public void run() {
// Parse the document now. Our modelBuilder
// handles the parser's SAX events.
updateListener.loadStarting(root, fgzipIS);
}
});
modelBuilder
= new ModelBuilder(modelFactory, root);
saxParser.parse(gzipIS, modelBuilder);
} catch (ParserConfigurationException pce) {
loadingFailed(updateListener, root, pce);
} catch (SAXParseException spe) {
loadingFailed(updateListener, root, spe);
} catch (SAXException se) {
loadingFailed(updateListener, root, se);
} catch (IOException ioe) {
loadingFailed(updateListener, root, ioe);
} catch (Exception e) {
loadingFailed(updateListener, root, e);
} finally {
try {
if (gzipIS != null) {
gzipIS.close();
}
} catch (IOException ioe) {
// Don't do anything if we got an exception
// while trying to close the stream.
}
if (modelBuilder != null) {
int n = modelBuilder.entityStreams.size();
for (int i = 0; i < n; i++) {
Reader r = (Reader) modelBuilder.entityStreams.elementAt(i);
try {
r.close();
} catch (IOException ioe) {
// Do nothing: this means the stream was
// closed by the SAX parser.
}
}
}
}
| protected static void | loadingFailed(com.sun.perseus.model.UpdateListener updateListener, com.sun.perseus.model.DocumentNode root, java.lang.Exception e)Utility method to report an exception to the UpdateListener
in the proper thread.
System.err.println(">>>>>>>>>>>>>>>>>>> +++++ Loading failed ...");
e.printStackTrace();
try {
root.invokeAndWait(new Runnable() {
public void run() {
updateListener.loadingFailed(root, e);
}
});
} catch (InterruptedException ie) {
// The current thread was interrupted. Loading Failed will
// not be reported...
return;
}
| private void | parseStyleAttribute(java.lang.String styleValue, com.sun.perseus.model.ElementNode elt)Utility method to parse a trait value.
SimpleTokenizer st = new SimpleTokenizer(styleValue,
SVGConstants.COMMA_STR);
while (st.hasMoreTokens()) {
String traitSpec = st.nextToken();
int ci = traitSpec.indexOf(':");
String traitName = null;
String traitValue = null;
if (ci != -1) {
traitName = traitSpec.substring(0, ci);
traitValue = traitSpec.substring(ci + 1);
} else {
traitName = "";
traitValue = "";
}
elt.setAttribute(traitName, traitValue);
}
| public final org.xml.sax.InputSource | resolveEntity(java.lang.String publicId, java.lang.String systemId)Resolve an external entity.
if (publicId == null || dtdids.indexOf(publicId) != -1) {
// If there is no declared publicId or if the publicId is
// one of the supported ones (the test is very lose but quick)
// we assume the input source is an SVG Tiny view and we just
// process a small DTD subset that includes the default
// namespace and xlink namespace declarations. Attribute defaulting
// is handled by the code, so are default attributes, so there is
// no need to process the DTDs.
InputSource is = new InputSource();
Reader reader = new InputStreamReader(
new ByteArrayInputStream(DTD_SUBSET.getBytes()));
is.setCharacterStream(reader);
// Keep track of opened streams as some SAX
// implementations do not close that stream.
entityStreams.addElement(reader);
return is;
}
// Let the SAX parser find the entity.
return null;
| public final void | setDocumentLocator(org.xml.sax.Locator locator)Receive a Locator object for document events.
By default, do nothing. Application writers may override this
method in a subclass if they wish to store the locator for use
with other document events.
// ctx.setLocator(locator);
| public final void | startDocument()SAX: Implements {@link
org.xml.sax.ContentHandler#startDocument() ContentHander.startDocument}.
| public final void | startElement(java.lang.String uri, java.lang.String localName, java.lang.String qName, org.xml.sax.Attributes attributes)Receive notification of the start of an element.
// ====================================================================
// First, build a new element from its XML descriptor.
// ====================================================================
ElementNode modelNode = null;
try {
modelNode = (ElementNode) modelRoot.createElementNS(uri, localName);
// Handle prefix mappings
if (pendingPrefixMapping != null) {
int n = pendingPrefixMapping.size();
for (int i = 0; i < n; i++) {
String[] mapping = (String[])
pendingPrefixMapping.elementAt(i);
modelRoot.addNamespacePrefix(mapping[0],
mapping[1],
modelNode);
}
pendingPrefixMapping.removeAllElements();
pendingPrefixMapping = null;
}
// Make sure we mark the node as _not_ loaded.
// This is important because we use the loaded bit to
// control certain behaviors, such as the progressive rendering
// behavior.
modelNode.setLoaded(false);
// ================================================
// Check that required traits have been specified.
// ================================================
String[] requiredTraits = modelNode.getRequiredTraits();
if (requiredTraits != null) {
for (int i = 0; i < requiredTraits.length; i++) {
if (attributes.getValue(requiredTraits[i]) == null) {
throw new SAXException(Messages.formatMessage(
Messages.ERROR_MISSING_ATTRIBUTE,
new Object[] {
requiredTraits[i],
modelNode.getNamespaceURI(),
modelNode.getLocalName()
}));
}
}
}
String[][] requiredTraitsNS = modelNode.getRequiredTraitsNS();
if (requiredTraitsNS != null) {
for (int i = 0; i < requiredTraitsNS.length; i++) {
if (attributes.getValue(requiredTraitsNS[i][0],
requiredTraitsNS[i][1]) == null) {
throw new SAXException(Messages.formatMessage(
Messages.ERROR_MISSING_ATTRIBUTE_NS,
new Object[] {
requiredTraitsNS[i][1],
requiredTraitsNS[i][0],
modelNode.getNamespaceURI(),
modelNode.getLocalName()
}));
}
}
}
// ================================================
// End of required traits check
// ================================================
// ================================================
// Apply all specified traits.
// ================================================
int n = attributes.getLength();
for (int i = 0; i < n; i++) {
modelNode.setAttributeNS(attributes.getURI(i),
attributes.getLocalName(i),
attributes.getValue(i));
}
// ================================================
// Apply default traits if they were not specified.
// ================================================
String[][] defaultTraits = modelNode.getDefaultTraits();
if (defaultTraits != null) {
for (int i = 0; i < defaultTraits.length; i++) {
if (attributes.getValue(defaultTraits[i][0]) == null) {
modelNode.setAttribute(defaultTraits[i][0],
defaultTraits[i][1]);
}
}
}
// ================================================
// IMPL NOTE
//
// If the style attribute is specified, apply the
// traits it specified. The reason for handling the
// style attribute at the parser level is that it
// is not a regular trait. It is an in-line
// stylesheet. We only support it in order to
// support imports from Adobe Illustrator which
// uses the style attribute on gradients, even
// when the option to export without the style
// attribute is selected.
// ================================================
String styleAttribute =
attributes.getValue(SVGConstants.SVG_STYLE_ATTRIBUTE);
if (styleAttribute != null) {
parseStyleAttribute(styleAttribute, modelNode);
}
// ================================================
// Apply trait aliases
// ================================================
String[][] traitAliases = modelNode.getTraitAliases();
if (traitAliases != null) {
for (int i = 0; i < traitAliases.length; i++) {
if (attributes.getValue(traitAliases[i][0]) == null) {
// The trait with alias was not specified.
// Check if its alias was specified.
String v = attributes.getValue(traitAliases[i][1]);
if (v != null) {
modelNode.setAttribute(traitAliases[i][0], v);
}
}
}
}
} catch (DOMException e) {
e.printStackTrace();
throw new SAXException(e.getMessage());
}
// ====================================================================
// Append new element to the tree
// ====================================================================
if (currentElement != null) {
addToParent(modelNode, currentElement);
} else {
addToParent(modelNode, modelRoot);
}
currentElement = modelNode;
// ====================================================================
// Notify application that load has begun on a new element
// ====================================================================
final ModelNode startedNode = modelNode;
final UpdateListener ul = modelRoot.getUpdateListener();
invokeAndWait(new Runnable() {
public void run() {
ul.loadBegun(startedNode);
}
});
// ====================================================================
// Check if there were any delayed exception. A delayed exception
// allows progressive rendering of bad path to happen before the
// exception which captured the bad path data is actually thrown (below)
// ====================================================================
try {
modelRoot.checkDelayedException();
} catch (DOMException e) {
throw new SAXException(e.getMessage());
}
| public void | startPrefixMapping(java.lang.String prefix, java.lang.String uri)Receive notification of the start of a Namespace mapping.
By default, do nothing. Application writers may override this
method in a subclass to take specific actions at the start of
each Namespace prefix scope (such as storing the prefix mapping).
pendingPrefixMappingCache.addElement(new String[] {prefix, uri});
pendingPrefixMapping = pendingPrefixMappingCache;
|
|