/*
* Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
package com.sun.perseus.model;
import com.sun.perseus.util.RunnableQueue;
import com.sun.perseus.util.RunnableQueue.RunnableHandler;
import com.sun.perseus.j2d.GraphicsProperties;
import com.sun.perseus.j2d.ViewportProperties;
import com.sun.perseus.j2d.RenderGraphics;
import com.sun.perseus.j2d.TextProperties;
import com.sun.perseus.j2d.Transform;
import com.sun.perseus.builder.DefaultFontFace;
import com.sun.perseus.parser.ColorParser;
import com.sun.perseus.parser.ClockParser;
import com.sun.perseus.parser.LengthParser;
import com.sun.perseus.parser.NumberListParser;
import com.sun.perseus.parser.PathParser;
import com.sun.perseus.parser.TimeConditionParser;
import com.sun.perseus.parser.TransformListParser;
import com.sun.perseus.parser.UnicodeParser;
import com.sun.perseus.parser.ViewBoxParser;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.DOMException;
import java.io.PrintStream;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Hashtable;
import java.util.Vector;
import com.sun.perseus.util.SVGConstants;
/**
* A <code>DocumentNode</code> represents the root of an SVG document model.
* A DocumentNode is a <code>Viewport</code> and centralizes some functions such
* as the font data base, tree updates management and event dispatching.
* The <code>DocumentNode</code> also manages the <code>RunnableQueue</code>
* used to synchronize all access to the SVG document.
*
* @version $Id: DocumentNode.java,v 1.24 2006/06/29 10:47:30 ln156897 Exp $
*/
public class DocumentNode extends Viewport implements Document {
/**
* Default spatial resolution value (96dpi);
*/
public static final float DEFAULT_PIXEL_MM_SIZE
= 0.26458333333333333333333333333333f; // 96dpi
/**
* Size of a pixel, in millimeters. This is needed in unit
* conversion as well.
*/
protected float pxMMSize = DEFAULT_PIXEL_MM_SIZE;
/**
* Coordinate in user space coordinates. This value is used as a working
* array for all nodes in the document tree.
*/
protected float[] upt = {0, 0};
/**
* A map itself containing map of ElementHandlers
* namespaceURI -> Hashtable
*
* the contained Hashtable maps:
* localName -> ElementNode prototype.
*/
protected Hashtable namespaceMap = new Hashtable();
/**
* Used to store additional traits NS., i.e., traits
* which are not naturally supported by the element.
* elt -> Hashtable.
*
* The contained Hashtable maps:
* namespace -> Hashtable.
*
* The contained Hashtable maps:
* localname -> value.
*/
protected Hashtable unknownTraitsNS = null;
/**
* Parser used to convert clock values
*/
protected final ClockParser clockParser = new ClockParser();
/**
* Parser used to convert unit and unitless floating point
* values to floats.
*/
protected final LengthParser lengthParser = new LengthParser();
/**
* Parser used to convert time condition values
*/
protected final TimeConditionParser timeConditionParser =
new TimeConditionParser();
/**
* Parser used to convert number list values
*/
protected NumberListParser numberListParser
= new NumberListParser();
/**
* Parser used to convert transform values
*/
protected TransformListParser transformListParser
= new TransformListParser();
/**
* Parser used to convert color values
*/
protected final ColorParser colorParser = new ColorParser();
/**
* Parser used to convert viewBox values
*/
protected final ViewBoxParser viewBoxParser = new ViewBoxParser();
/**
* Parser used to convert path data
*/
protected final PathParser pathParser = new PathParser();
/**
* Parser used to convert unicode ranges.
*/
protected UnicodeParser unicodeParser
= new UnicodeParser();
/**
* The <code>ImageLoader</code> handles the desired policy
* for loading image resources.
*/
protected ImageLoader imageLoader;
/**
* The default FontFace used by this RenderGraphics.
* @see com.sun.perseus.builder.DefaultFontFace
*/
protected FontFace defaultFontFace;
/**
* Exceptions can be delayed and later thrown. This is used, for example,
* to keep processing an invalid d attribute on a <path> element and only
* throw the exception after the corresponding model node has been hooked
* to the tree (while the exception was thrown by the path parser during the
* processing of the d attribute). This is only used when loading a document
* to comply to the error processing required for <path>, <polyline> and
* <polygon> processing of the 'd' and 'points' attributes.
*/
protected DOMException delayedException;
/**
* The default namespace URI for elements created in that document,
* in case the namespace URI in createElementNS is empty.
*/
protected String defaultNamespaceURI = SVGConstants.SVG_NAMESPACE_URI;
/**
* The Document URI
*/
protected String docURI;
/**
* The set of initial <code>FontFace</code> used by this
* <code>RenderingGraphics</code>.
* <br />
* <b>NOTE</b><br />It is the responsibility of the user of the
* Perseus software to make sure that the <code>RenderGraphics</code>
* initial <code>fontFamiliy</code> value is indeed the same as that
* of the various fonts in the <code>initialFontFaces</code> array.
* <br />
* If there is a mismatch, then text content that does not
* specify the <code>font-family</code> property will not match against the
* <code>initialFontFaces</code> instances and only the defaultFont will be
* used.
*
* @see com.sun.perseus.builder.DefaultFontFace
*/
protected FontFace[] initialFontFaces;
/**
* The FontFace data base used by this RenderGraphics.
* The Hashtable maps font-family names to a Vector
* of fonts with that font-family value.
*/
protected Hashtable fontFaceDB = null;
/**
* Set of currently active TraitAnimations.
*/
protected Vector activeTraitAnims = new Vector(0);
/**
* Vector of elements marked by discard for removal.
*/
protected Vector elementsToDiscard = new Vector(0);
/**
* Set of currently active MediaElements.
*/
protected Vector activeMediaElements = new Vector(0);
/**
* Controls whether the document is 'playing'. This is used to disable the
* media elements like audio when the document is not playing and allows
* sampling the document without having the audio stream played.
*/
protected boolean playing;
/**
* Flag to determine whether applyAnimations() has been called. For a
* detailed explanation on the use of this flag, please refer to the
* documentation for the Discard.nodeUnhookedFromDocumentTree() method.
*
* @see Discard#nodeUnhookedFromDocumentTree()
*/
protected boolean applyAnimationsCalled = false;
/**
* EventSupport is used for listener registration and event
* dispatching.
*/
protected EventSupport eventSupport = new EventSupport();
/**
* The associated <code>UpdateListener</code> is notified of
* all mutation events on this <code>DocumentNode</code> tree
*/
protected UpdateListener updateListener;
/**
* The associated <code>RunnableQueue</code>, if any, is
* managing updates to the <code>DocumentNode</code> and any of
* its descendants. In effect, the updateQueue provides the
* synchronization needed for updates to a document tree.
*/
protected RunnableQueue updateQueue;
/**
* The associated <code>RunnableHandler</code> which should be
* notified when Runnable acting on this DocumentNode tree are
* run.
*/
protected RunnableHandler runHandler;
/**
* Maps ids to ElementNode instances
*/
protected Hashtable idToElement = new Hashtable();
/**
* Used to store nodes with ids before they are inserted into
* the document tree.
*/
protected Hashtable reservedIds = new Hashtable();
/**
* Maps prefixes to namespace entries.
*/
protected Hashtable prefixes = new Hashtable();
/**
* Maps namespaces to prefixes.
*/
protected Hashtable namespaces = new Hashtable();
/**
* A DocumentNode is a root container. This object provides
* support for root time container behavior.
*/
protected TimeContainerRootSupport timeContainerRootSupport
= new TimeContainerRootSupport();
/**
* Map of unresolved IDRefs (id -> Vector of referencing IDRefs)
*/
protected Hashtable unresolvedIDRefs = new Hashtable();
/**
* Map of resolved IDRefs (id -> Vector of referencing IDRefs)
*/
protected Hashtable resolvedIDRefs = new Hashtable();
/**
* Vector of timedElementNodes. This is used only at parse time.
* @see #validate
*/
protected Vector timedElementNodes = new Vector(0);
/**
* We use a single instance of ModelEvent per DocumentNode instance.
* This allows multiple instances of DocumentNodes to co-exist.
*/
protected ModelEvent engineEvent = null;
/**
* Transform used to perform hit detection on text chunks.
*/
protected Transform hitChunkTxf = new Transform(null);
/**
* Transform used to compute bounding boxes on text chunks.
*/
protected Transform bboxChunkTxf = new Transform(null);
/**
* Transform used to render text chunks.
*/
protected Transform paintChunkTxf = new Transform(null);
/**
* Transform used to render glyphs.
*/
protected Transform paintGlyphTxf = new Transform(null);
/**
* Transform used to compute bounding boxes on glyphs.
*/
protected Transform bboxGlyphTxf = new Transform(null);
/**
* Transform used to perform hit testing on glyphs
*/
protected Transform hitGlyphTxf = new Transform(null);
/**
* Default constructor
*/
public DocumentNode() {
ownerDocument = this;
engineEvent = new ModelEvent("", this);
// Clear the 'in document tree' bit
canRenderState &= CAN_RENDER_IN_DOCUMENT_TREE_MASK;
// Clear the renderable bit
canRenderState &= CAN_RENDER_RENDERABLE_MASK;
}
/**
* Returns the initial value of the given Object-valued property.
* @return the initial value of the given property, null if the property is
* unknown.
*/
protected Object getInitialPropertyState(final int propertyIndex) {
switch (propertyIndex) {
case GraphicsNode.PROPERTY_FILL:
return GraphicsProperties.INITIAL_FILL;
case ViewportNode.PROPERTY_VIEWPORT_FILL:
return ViewportProperties.INITIAL_VIEWPORT_FILL;
case GraphicsNode.PROPERTY_STROKE:
return GraphicsProperties.INITIAL_STROKE;
case GraphicsNode.PROPERTY_COLOR:
return GraphicsProperties.INITIAL_COLOR;
case GraphicsNode.PROPERTY_STROKE_DASH_ARRAY:
return GraphicsProperties.INITIAL_STROKE_DASH_ARRAY;
case TextNode.PROPERTY_FONT_FAMILY:
return TextNode.INITIAL_FONT_FAMILY;
default:
return null;
}
}
/**
* Returns the initial value of the given float-valued property.
* @return the initial value of the given property, 0 if the property is
* unknown.
*/
protected float getInitialFloatPropertyState(final int propertyIndex) {
switch (propertyIndex) {
case GraphicsNode.PROPERTY_STROKE_WIDTH:
return GraphicsNode.INITIAL_STROKE_WIDTH;
case GraphicsNode.PROPERTY_STROKE_MITER_LIMIT:
return GraphicsNode.INITIAL_STROKE_MITER_LIMIT;
case GraphicsNode.PROPERTY_STROKE_DASH_OFFSET:
return GraphicsNode.INITIAL_STROKE_DASH_OFFSET;
case TextNode.PROPERTY_FONT_SIZE:
return TextNode.INITIAL_FONT_SIZE;
case ViewportNode.PROPERTY_VIEWPORT_FILL_OPACITY:
return ViewportNode.INITIAL_VIEWPORT_FILL_OPACITY;
default:
return 0;
}
}
/**
* Returns the initial value of the given packed property.
* @return the initial value of the given packed property, zero if the
* property is unknown.
*/
protected int getInitialPackedPropertyState(final int propertyIndex) {
switch (propertyIndex) {
case GraphicsNode.PROPERTY_FILL_RULE:
return CompositeGraphicsNode.INITIAL_FILL_RULE_IMPL;
case GraphicsNode.PROPERTY_STROKE_LINE_JOIN:
return CompositeGraphicsNode.INITIAL_STROKE_LINE_JOIN_IMPL;
case GraphicsNode.PROPERTY_STROKE_LINE_CAP:
return CompositeGraphicsNode.INITIAL_STROKE_LINE_CAP_IMPL;
case GraphicsNode.PROPERTY_DISPLAY:
return CompositeGraphicsNode.INITIAL_DISPLAY_IMPL;
case GraphicsNode.PROPERTY_VISIBILITY:
return CompositeGraphicsNode.INITIAL_VISIBILITY_IMPL;
case GraphicsNode.PROPERTY_FILL_OPACITY:
return CompositeGraphicsNode.INITIAL_FILL_OPACITY_IMPL;
case GraphicsNode.PROPERTY_STROKE_OPACITY:
return CompositeGraphicsNode.INITIAL_STROKE_OPACITY_IMPL;
case GraphicsNode.PROPERTY_OPACITY:
return CompositeGraphicsNode.INITIAL_OPACITY_IMPL;
case TextNode.PROPERTY_FONT_STYLE:
return StructureNode.INITIAL_FONT_STYLE_IMPL;
case TextNode.PROPERTY_FONT_WEIGHT:
return StructureNode.INITIAL_FONT_WEIGHT_IMPL;
case TextNode.PROPERTY_TEXT_ANCHOR:
return StructureNode.INITIAL_TEXT_ANCHOR_IMPL;
default:
return 0;
}
}
/**
* Returns the value of the given Object-valued property.
*
* @return the value of the given property, null if the property is
* unknown.
*/
protected Object getPropertyState(final int propertyIndex) {
return getInitialPropertyState(propertyIndex);
}
/**
* Returns the value of the given float property.
*
* @return the value of the given property, null if the property is
* unknown.
*/
protected float getFloatPropertyState(final int propertyIndex) {
return getInitialFloatPropertyState(propertyIndex);
}
/**
* Returns the value of the given packed property.
*
* @return the value of the given property, null if the property is
* unknown.
*/
protected int getPackedPropertyState(final int propertyIndex) {
return getInitialPackedPropertyState(propertyIndex);
}
/**
* Paints this node into the input <code>RenderGraphics</code>.
*
* @param rg the <tt>RenderGraphics</tt> where the node should paint itself
*/
public void paint(final RenderGraphics rg) {
if (canRenderState != 0) {
return;
}
paint(getFirstChildNode(), rg);
}
/**
* Initializes the ModelEvent's singleton object with the input data.
*
* @param type the event type
* @param target the event target
* @param time the event time
*/
ModelEvent initEngineEvent(final String type,
final ModelNode target) {
ModelEvent engineEvent = new ModelEvent(type, target);
engineEvent.type = type;
engineEvent.target = target;
engineEvent.currentTarget = null;
engineEvent.anchor = null;
engineEvent.stopPropagation = false;
engineEvent.repeatCount = 0;
engineEvent.keyChar = ModelEvent.CHAR_UNDEFINED;
return engineEvent;
}
/**
* When the document finishes loading, it notifies the ImageLoader so that
* it can start loading raster images, in case it waited for the load
* completion to start (e.g., as in SVGImageLoader).
*
* @param isLoaded the new loaded state
*/
public final void setLoaded(final boolean isLoaded) {
super.setLoaded(isLoaded);
if (isLoaded == true) {
getImageLoader().documentLoaded(this);
}
}
/**
* @return true if the DocumentNode is in the playing state.
*/
public boolean isPlaying() {
return playing;
}
/**
* @param isPlaying the new playing state.
*/
public void setPlaying(final boolean isPlaying) {
this.playing = isPlaying;
}
/**
* @throws DOMException the delayed exception if one was set.
*/
public void checkDelayedException() throws DOMException {
if (delayedException != null) {
throw delayedException;
}
}
/**
* @return the delayed exception, if any
*/
public DOMException getDelayedException() {
return delayedException;
}
/**
* @param de the new delayedException
*/
protected void setDelayedException(DOMException de) {
delayedException = de;
}
/**
* @return the size of a px CSS unit in millimeters.
*/
public float getPixelMMSize() {
return pxMMSize;
}
/**
* Controls the size of a pixel in millimeters
* @param newPxMMSize the new pixel size value, in millimeter
*/
public void setPixelMMSize(final float newPxMMSize) {
this.pxMMSize = newPxMMSize;
}
/**
* @return true if this node is hooked to the document tree, i.e., if it top
* most ancestor is the DocumentNode.
*/
boolean inDocumentTree() {
return true;
}
/**
* Invoked by <code>IDRef</code> instances when they need the given
* input id reference to be resolved to an <code>ElementNode</code>
* reference. If there is a known <code>ElementNode</code> with the
* requested id, the <code>IDRef</code>'s <code>resolve()</code>
* method is invoked immediately. Otherwise, the method will be called
* as soon as the id reference is resolved.
*
* @param idRef the IDRef which needs to be resolved.
* @param id the id the IDRef needs to resolve to an
* <code>ElementNode</code> reference.
*/
public void resolveIDRef(final IDRef idRef,
final String id) {
ElementNode ref = (ElementNode) getElementById(id);
if (ref != null) {
idRef.resolveTo(ref);
// Add this idRef to the resolvedIDRefs Hashtable
Vector idRefs = (Vector)resolvedIDRefs.get(id);
if (idRefs == null) {
idRefs = new Vector(1);
resolvedIDRefs.put(id, idRefs);
}
idRefs.addElement(idRef);
} else {
if (unresolvedIDRefs != null) {
Vector idRefs = (Vector) unresolvedIDRefs.get(id);
if (idRefs == null) {
idRefs = new Vector(1);
unresolvedIDRefs.put(id, idRefs);
}
idRefs.addElement(idRef);
}
}
}
/**
* Should be called when a document node is no longer needed
* and could be garbage collected.
*/
public void dispose() {
clearLayouts();
}
/**
* Gets the <code>ImageLoader</code> instance.
*
* @return the <code>ImageLoader</code> associated to this
* <code>DocumentNode</code>.
*/
public ImageLoader getImageLoader() {
if (imageLoader == null) {
imageLoader = new DefaultImageLoader();
}
return imageLoader;
}
/**
* Sets the <code>ImageLoader</code> for this document.
*
* @param imageLoader the new <code>ImageLoader</code> this
* <code>DocumentNode</code> should use.
*/
public void setImageLoader(final ImageLoader imageLoader) {
this.imageLoader = imageLoader;
}
/**
* A <code>DocumentNode</code> has no expanded content, so this
* returns null.
*
* @return a reference to the node's last expanded child, or null if there
* are no expanded children. This forces the computation of expanded
* content if needed.
*/
ModelNode getLastExpandedChild() {
return null;
}
/**
* A <code>DocumentNode</code> has no expanded content, so this
* returns null.
*
* @return a reference to the node's first expanded child, or null if there
* are no expanded children. This forces the computation of expanded
* content if needed.
*/
ModelNode getFirstExpandedChild() {
return null;
}
/**
* Some node types (such as <code>ElementNodeProxy</code>) have
* expanded children that they compute in some specific
* way depending on the implementation.
*
* @return a reference to the node's first expanded child, or null if there
* are no expanded children.
*/
public ModelNode getFirstComputedExpandedChild() {
return null;
}
/**
* Utility method. Unhooks the expanded content.
*/
protected void unhookExpandedQuiet() {
}
/**
* The node's URI base to use to resolve URI references
* If a URI base value was set on this node, then that value
* is returned. Otherwise, this method returns the parent's
* URI base. If there is not URI base on this node and if there
* is not parent, then this method returns null.
*
* @return the node's URI base to use to resolve relative URI references.
*/
public String getURIBase() {
return docURI;
}
/**
* Sets this document's URI
*
* @param docURI the new document URI
*/
public void setDocumentURI(final String docURI) {
if (ElementNode.equal(docURI, this.docURI)) {
return;
}
modifyingNode();
this.docURI = docURI;
modifiedNode();
}
/**
* @return the <code>UpdateListener</code> associated with this viewport.
* @see #setUpdateListener
*/
public UpdateListener getUpdateListener() {
if (parent == null || parent == this) {
return updateListener;
} else {
return parent.getUpdateListener();
}
}
/**
* @return the <code>RunnableQueue</code> which managers updates to this
* <code>DocumentNode</code> hierarchy.
*/
public RunnableQueue getUpdateQueue() {
return updateQueue;
}
/**
* @return the <code>RunnableQueue.RunnableHandler</code> which is notified
* of <code>Runnable</code> instances ran against this DocumentNode.
*/
public RunnableHandler getRunnableHandler() {
return runHandler;
}
/**
* @param updateQueue the <code>RunnableQueue</code> which manages
* updates to this document tree.
*/
public void setUpdateQueue(final RunnableQueue updateQueue) {
this.updateQueue = updateQueue;
}
/**
* @param runHandler the <code>RunnableHandler</code> which listens to
* <code>Runnable</code> execution for this
* <code>DocumentNode</code>.
*/
public void setRunnableHandler(final RunnableHandler runHandler) {
this.runHandler = runHandler;
}
/**
* Sets the <code>UpdateListener</code> associated with this viewport.
* All updates made to this tree will be reported to the input
* <code>UpdateListener</code>
*
* @param updateListener the new <code>UpdateListener</code> which will
* receive notifications for updates on this tree.
*/
public void setUpdateListener(final UpdateListener updateListener) {
this.updateListener = updateListener;
}
/**
* @return The <code>EventSupport</code> instance associated
* with this <code>DocumentNode</code>
*/
public EventSupport getEventSupport() {
return eventSupport;
}
/**
* If no such element exists, this returns null.
* If more than one element has an id attribute with that value, what
* is returned is undefined.
*
*/
public Element getElementById(final String id) {
return (ElementNode) idToElement.get(id);
}
/**
* Return the <code>Element</code> in the current document with
* the given unique ID. The difference with getElementById is
* that this method may return a node that is not inserted in
* the document tree. This is used in ElementNode.setId().
*
* @param id the ID of the object to be retrieved.
* @return the Element that matches with the given ID or
* <code>null</code> if the ID is not present.
*
* @throws NullPointerException if id is null
*/
Element getElementByIdAll(final String id) {
ElementNode n = (ElementNode) idToElement.get(id);
if (n == null) {
n = (ElementNode) reservedIds.get(id);
}
return n;
}
/**
* Adds the input element to the list of identified nodes.
*
* @param element the new element with a non-null identifier
*
* @throws NullPointerException if the input element is null or if its
* id is null.
*/
void addIdentifiedNode(final ElementNode element) {
final String id = element.getId();
// Add element to id map
idToElement.put(id, element);
// Check if there are any unresolved references for
// the newly identified node.
if (unresolvedIDRefs != null) {
Vector unresIdRefs = (Vector) unresolvedIDRefs.get(id);
if (unresIdRefs != null) {
// Since we will resolve some unresolved references,
// prepare to add the resolved references to the
// resolvedIDRefs Hashtable
Vector resIdRefs = (Vector)resolvedIDRefs.get(id);
if (resIdRefs == null) {
resIdRefs = new Vector(1);
resolvedIDRefs.put(id, resIdRefs);
}
int n = unresIdRefs.size();
for (int i = 0; i < n; i++) {
IDRef idRef = (IDRef) unresIdRefs.elementAt(i);
idRef.resolveTo(element);
// Since we have resolved this reference, add it to the
// resolvedIDRefs Hashtable
resIdRefs.addElement(idRef);
}
// and remove it from the unresolved references Hashtable
unresolvedIDRefs.remove(id);
}
}
// If the id was in the reserved map, remove it.
reservedIds.remove(id);
}
/**
* Reserves the given id. This is used to be able to check for
* duplicate identifiers.
*
* @param element the element reserving the id. Should not be null.
* The element id should not be null.
*/
void reserveId(final ElementNode element) {
reservedIds.put(element.getId(), element);
}
/**
* Remove element from the list of identified nodes
*
* @param element the element to remove from the list of identified nodes
* @throws NullPointerException if element is null or if its id is null.
*/
void removeIdentifiedNode(final ElementNode element) {
String id = element.getId();
if (idToElement.get(id) == element) {
idToElement.remove(id);
}
}
/**
* Schedules the given Runnable object for a later invocation in
* the document's update thread, and returns.
* <br />
* If there is no <code>updateQueue</code> the <code>Runnable</code>
* is run before returning. Otherwise, the <code>Runnable</code>
* is scheduled in the associated <code>updateQueue</code>
*
* @param r the <code>Runnable</code> to put at the end of the
* execution list.
* @throws IllegalStateException if there is an associated
* <code>RunnableQueue</code> but that one has exited
* or was not started.
*/
public void invokeLater(final Runnable r) {
if (updateQueue == null) {
r.run();
} else {
updateQueue.invokeLater(r, runHandler);
}
}
/**
* Waits until the given Runnable's <tt>run()</tt> has returned.
* <em>Note: <tt>invokeAndWait()</tt> must not be called from the
* current thread (for example from the <tt>run()</tt> method of the
* argument)</em>.
*
* @param r the <code>Runnable</code> to put at the end of the
* execution list.
* @throws IllegalStateException if there is an associated RunnableQueue
* which has exited or was not started.
* @throws InterruptedException if the thread is interrupted while
* waiting for the input <code>Runnable</code> to complete
* its execution.
*/
public void invokeAndWait(final Runnable r)
throws InterruptedException {
if (updateQueue == null) {
r.run();
} else {
updateQueue.invokeAndWait(r, runHandler);
}
}
/**
* Waits until the given Runnable's <tt>run()</tt> has returned.
* <em>Note: <tt>safeInvokeAndWait()</tt> may be called from any thread.
* This method checks if this thread is the update thread, in which case
* the Runnable is invoked directly. Otherwise, it delegates to the
* invokeAndWait method.
*
* @param r the <code>Runnable</code> to put at the end of the
* execution list. Should not be null.
* @param runHandler the <code>RunnableHandler</code> to notify
* once the <code>Runnable</code> has finished executing.
* Should not be null.
* @throws IllegalStateException if getThread() is null or if the
* thread returned by getThread() is the current one.
*/
public void safeInvokeAndWait(final Runnable r) {
if (updateQueue == null) {
r.run();
} else {
updateQueue.safeInvokeAndWait(r, runHandler);
}
}
/**
* This is where font matching happens. This method compares the
* font attributes (such as 'font-family' or 'font-weight') with
* the corresponding attributes in the FontFace set. At a minimum,
* this method returns a single font in the list: the defaultFontFace.
*
* This process follows the algorithm described in section
* 15.5 of the CSS2 specification (http://www.w3.org/TR/REC-CSS2/)
*
* In SVG Tiny, the font attributes on Text are:
* - font-family
* - font-size
* - font-style
* - font-weight
*
* Therefore, font matching is limited to these attributes (i.e.,
* font-variant is not used).
*
* IMPORTANT NOTE: The FontFaces data base is built when the
* setFontFaceSet method is invoked, from the input array. The
* input array is expected to be in document order and the
* fontFaceDB hashtable's value Vectors contain values which are
* in the same order as in the array passed to setFontFaceSet.
* Therefore, the resolveFontFaces returns matches in document
* order. This is important for situations where a less specific
* FontFace (font-family:Arial, font-style: any) would appear
* before (in document order) a more specific FontFace (font-family:Arial,
* font-style: italic). In that case, only the less specific FontFace
* will be the match for a text with font-family set to Arial
* and font-style set to italic.
*
* @param tp The <code>TextProperties</code> containing the font selection
* properties.
* @return a chain of <code>FontFace.Match</code>. The first element is
* <b>always</b> the default font face. The next element (if any),
* is the first match, ordered according to the CSS2 font matching
* rules.
*/
protected FontFace.Match resolveFontFaces(final TextProperties tp) {
// If no default font face has been set, set the default one now.
if (defaultFontFace == null) {
setDefaultFontFace(DefaultFontFace.getDefaultFontFace());
}
// If no initial font face has been set, set the default one now.
if (initialFontFaces == null) {
setInitialFontFaces(DefaultFontFace.getInitialFontFaces());
}
// Build the font face data base if it has not been
// built already
if (fontFaceDB == null) {
setFontFaceSetSilent(null);
}
// Iterate over each matching font-family name
Vector fontFamilyMatch = null;
String[] fontFamily = tp.getFontFamily();
int nff = 0;
if (fontFamily != null) {
nff = fontFamily.length;
}
FontFace.Match firstMatch = new FontFace.Match(defaultFontFace);
FontFace.Match lastMatch = firstMatch;
for (int i = 0; i < nff; i++) {
fontFamilyMatch = (Vector) fontFaceDB.get(fontFamily[i]);
lastMatch = matchFontFaces(fontFamilyMatch, lastMatch, tp);
}
return firstMatch;
}
/**
* Matches values in the input fontFamily map against the
* current context values.
*
* @param fontFamily the 'candidate' <tt>FontFace</tt>s
* @param lastMatch the last <code>FontFace.Match</code>. Any new
* match sub-chain (i.e., for the input fontFamily) will be
* chained after <code>lastMatch</code>
* @param tp the <tt>TextProperties</tt> defining the applicable
* font selection properties
*
* @return the first <code>FontFace.Match</code> node, head of
* the chain of matches, linked in precedence order.
*
* @see #resolveFontFaces
*/
protected FontFace.Match matchFontFaces(final Vector fontFamily,
final FontFace.Match lastMatch,
final TextProperties tp) {
if (fontFamily == null) {
return lastMatch;
}
int n = fontFamily.size();
int fontStyle = tp.getFontStyle();
float fontSize = tp.getFontSize();
int fontWeight = tp.getFontWeight();
FontFace.Match match = null, firstMatch = null;
for (int i = 0; i < n; i++) {
FontFace ff = (FontFace) fontFamily.elementAt(i);
// First, match on font-style.
if ((ff.getFontStyles() & fontStyle) != 0
||
(
(fontStyle == TextNode.FONT_STYLE_ITALIC)
&&
((ff.getFontStyles() & TextNode.FONT_STYLE_OBLIQUE) != 0))) {
// Match on font-size
if (ff.getFontSizes() == null) {
match = new FontFace.Match(ff);
match.distance = ff.fontWeightDistance(fontWeight);
firstMatch = addMatch(firstMatch, match);
} else {
float[] fs = ff.getFontSizes();
for (int j = 0; j < fs.length; j++) {
if (fs[j] == fontSize) {
match = new FontFace.Match(ff);
match.distance = ff.fontWeightDistance(fontWeight);
firstMatch = addMatch(firstMatch, match);
break;
}
}
}
}
}
if (match == null) {
return lastMatch;
} else {
if (lastMatch != null) {
lastMatch.next = firstMatch;
}
while (match.next != null) {
match = match.next;
}
return match;
}
}
/**
* Chains <code>newMatch</code> into the chain starting with
* <code>firstMatch. This inserts the new matching font face
* according to the CSS2 font matching rules.
*
* @param firstMatch the first match in the chain of matching
* <code>FontFace.Match</code> instances.
* @param newMatch the new <code>FontFace.Match</code> object to
* insert into the chain.
* @return the head of the <code>FontFace.Match</code> chain after
* insertion of the new match.
*/
protected FontFace.Match addMatch(final FontFace.Match firstMatch,
final FontFace.Match newMatch) {
if (firstMatch == null) {
return newMatch;
}
FontFace.Match curMatch = firstMatch;
FontFace.Match prevMatch = null;
while (curMatch != null && curMatch.distance <= newMatch.distance) {
prevMatch = curMatch;
curMatch = curMatch.next;
}
if (curMatch == null) {
// We reached the end of the list, simply append newMatch
prevMatch.next = newMatch;
return firstMatch;
} else {
// We need to insert newMatch before curMatch
if (prevMatch == null) {
// Inserted at the start of the list
newMatch.next = firstMatch;
return newMatch;
} else {
// Insert somewhere in the middle of the list
prevMatch.next = newMatch;
newMatch.next = curMatch;
return firstMatch;
}
}
}
/**
* Sets the default FontFace, i.e., the FontFace which
* will always be the last element in the
* FontFace array returned from <code>resolveFontFaces</code>
*
* @param newDefaultFontFace the fall back font
* @throws IllegalArgumentException if defaultFontFace is null
*/
public void setDefaultFontFace(final FontFace newDefaultFontFace) {
if (newDefaultFontFace == null) {
throw new IllegalArgumentException();
}
if (defaultFontFace == newDefaultFontFace) {
return;
}
this.defaultFontFace = newDefaultFontFace;
clearLayouts();
}
/**
* Sets the intial set of FontFaces. The intent for this property
* is to provide a set of FontFaces that match the initial
* font-family property for varying values of font-weight and
* font-style. However, this can also be used to provide support
* for the logical font-faces.
*
* The RenderGraphics will keep a reference to the input array which
* should not be modified after it has been passed to this RenderGraphics.
*
* @param newInitialFontFaces should not be null and should not contain
* null values
*
* @throws IllegalArgumentException if initialFontFaces is null or if
* one of the array values is null.
*/
public void setInitialFontFaces(final FontFace[] newInitialFontFaces) {
if (newInitialFontFaces == null) {
throw new IllegalArgumentException();
}
if (initialFontFaces == newInitialFontFaces) {
return;
}
for (int i = 0; i < newInitialFontFaces.length; i++) {
if (newInitialFontFaces[i] == null) {
throw new IllegalArgumentException();
}
}
this.initialFontFaces = newInitialFontFaces;
clearLayouts();
}
/**
* @return The default FontFace used by this RenderGraphics
*/
public FontFace getDefaultFontFace() {
return defaultFontFace;
}
/**
* @return The set of FontFaces used to match the initial font-family
* value
*/
public FontFace[] getInitialFontFaces() {
return initialFontFaces;
}
/**
* Sets the set of FontFaces that this Document uses
* to display text.
*
* @param fontFaceSet the set of font faces to use
*/
public void setFontFaceSet(final FontFace[] fontFaceSet) {
setFontFaceSetSilent(fontFaceSet);
clearLayouts();
}
/**
* Sets the set of FontFaces this document uses but does
* not generate a modifyingNode notification.
*
* @param fontFaceSet the set of font faces to use
*/
protected void setFontFaceSetSilent(final FontFace[] fontFaceSet) {
fontFaceDB = new Hashtable();
growFontFaceDB(fontFaceDB, fontFaceSet);
growFontFaceDB(fontFaceDB, initialFontFaces);
}
/**
* Adds a new <code>FontFace</code> to the data base
* available to the document.
*
* @param fontFace the new <code>FontFace</code> which is
* now available.
*/
public void addFontFace(final FontFace fontFace) {
if (fontFaceDB == null) {
// This initializes the data base
setFontFaceSetSilent(null);
}
growFontFaceDB(fontFaceDB, new FontFace[] {fontFace});
clearLayouts();
}
/**
* Builds a Hashtable of font faces to map font family
* names to FontFace instances.
*
* @param ffDB the font data base to grow
* @param fontFaceSet array of <tt>FontFace</tt> instances to add
* to the font data base.
* @see #setFontFaceSet
*/
protected void growFontFaceDB(final Hashtable ffDB,
final FontFace[] fontFaceSet) {
if (fontFaceSet == null) {
return;
}
// Grow the font data base from the new fontFaceSet
for (int i = 0; i < fontFaceSet.length; i++) {
FontFace ff = fontFaceSet[i];
String[] fontFamilies = ff.getFontFamilies();
if (fontFamilies != null) {
// null is actually a CSS error. But CSS is silent
// about errors, so we should not break or halt on
// such a condition which should be expected
for (int j = 0; j < fontFamilies.length; j++) {
Vector v = (Vector) ffDB.get(fontFamilies[j]);
if (v == null) {
v = new Vector(1);
ffDB.put(fontFamilies[j], v);
v.addElement(ff);
} else {
if (!v.contains(ff)) {
v.addElement(ff);
}
}
}
}
}
}
/**
* Returns the current time for the document, i.e., the time at which the
* document was last sampled.
*
* @return the time at which the container was last sampled.
*/
public Time getCurrentTime() {
return timeContainerRootSupport.lastSampleTime;
}
/**
* Applies all currently active animations.
*/
public void applyAnimations() {
try {
// Set flag used by Discard.nodeUnhookedFromDocumentTree()
applyAnimationsCalled = true;
int n = activeTraitAnims.size();
// System.err.println(">>>>>>>>>>>>>>>>>> There are : " + n
// + " active animations");
for (int i = 0; i < n; i++) {
// System.err.println("Applying TraitAnim [" + i + "]");
((TraitAnim) activeTraitAnims.elementAt(i)).apply();
}
ElementNode targetElement;
n = elementsToDiscard.size();
for (int i=0; i<n; i++) {
// Remove the targetElement
targetElement = (ElementNode)elementsToDiscard.elementAt(i);
// Elements referenced by anything other than a discard element
// can not be removed
if (!(CompositeNode.isIdBranch(targetElement))) {
if (targetElement.getParentNode() != null)
targetElement.getParentNode().removeChild(targetElement);
}
// Since we just discarded an element, remove the element
// from all relevant data structures
String discardedID = targetElement.getId();
if (discardedID != null) {
resolvedIDRefs.remove(discardedID);
// Remove from idToElement
idToElement.remove(discardedID);
// Can't be in unresolvedIDRefs or reservedIds, so nothing to
// update there
}
}
// Remove elements from the elementsToDiscard Vector since we have
// already discarded them
elementsToDiscard.removeAllElements();
} finally {
// Reset flag used by Discard.nodeUnhookedFromDocumentTree()
applyAnimationsCalled = false;
}
}
/**
* Applies all currently active media if this document is playing.
*/
void applyMedia() {
if (playing) {
int n = activeMediaElements.size();
for (int i = n - 1; i >= 0; i--) {
((MediaElement) activeMediaElements.elementAt(i)).playMedia();
}
} else {
int n = activeMediaElements.size();
for (int i = n - 1; i >= 0; i--) {
((MediaElement) activeMediaElements.elementAt(i)).endMedia();
}
}
}
/**
* This method is typically called by this element's time container
* when it samples.
*
* Note that if this element is not in the waiting or playing
* state, this does nothing. This method assumes that successive
* calls are made with increasing time values.
*
* @param currentTime the time at which this element should be
* sampled.
*/
public void sample(final Time currentTime) {
timeContainerRootSupport.sample(currentTime);
// System.err.println(">>>>>>>>>>>>>>>>>> currentTime : "
// + currentTime);
// timeContainerRootSupport.dump();
}
/**
* Increments the animation or media timeline for this SVGImage (in
* seconds). As the name implies, this method is intended to move only
* forward in the timeline and typically should be used to animate SVG
* content in the "one-shot" rendering mode. Setting negative values will
* throw an Exception. It is important to note that setting large increments
* of time would result in dropping or skipping of frames as per the SVG
* animation model.
*
* @throws IllegalArgumentException if the specified time is negative.
*/
public void incrementTime(float seconds) {
if (seconds < 0) {
throw new IllegalArgumentException();
}
long lastSampleTime = timeContainerRootSupport
.lastSampleTime.value;
timeContainerRootSupport.sample
(new Time(lastSampleTime + (long) (seconds*1000)));
}
/**
* Initializes the timing engine.
*/
public void initializeTimingEngine() {
timeContainerRootSupport.initialize();
}
/**
* Debug helper
*
* @return a textual description of this viewport object
*/
/*
public String toString() {
return "[Document(zoomPan=" + zoomAndPan + ", width="
+ width + " height=" + height
+ " txf=" + transform
+ "]";
}
*/
/**
* Traces this viewport tree
*/
public void dump() {
dump(this, "", System.err);
}
/**
* Debug: traces the input ModelNode, using the input prefix
*
* @param n the node to dump
* @param prefix the string used to prefix the node information
* @param out the stream where the node structure is dumped.
*/
static void dump(final ModelNode n, final String prefix,
final PrintStream out) {
out.print(prefix + " " + n);
if (n instanceof ElementNode) {
ElementNode e = (ElementNode) n;
String pfx = n.ownerDocument.toPrefix(e.getNamespaceURI(), e);
if (pfx == null || pfx.length() == 0) {
out.println(" <" + e.getLocalName() + ">");
} else {
out.println(" <" + pfx + ":" + e.getLocalName() + ">");
}
} else {
out.println();
}
ModelNode child = n.getFirstChildNode();
while (child != null) {
dump(child, prefix + "+-->", out);
child = child.nextSibling;
}
child = n.getFirstExpandedChild();
while (child != null) {
dump(child, prefix + "*~~>", out);
child = child.nextSibling;
}
}
/**
* @return null as per the DOM Level 2 specification for Document
* nodes.
*/
public String getNamespaceURI() {
return null;
}
/**
* @return returns the unprefixed node name. For an SVGElement, this returns
* the tag name without a prefix. In case of the Document node, string
* <code>null/code> is returned.
*/
public String getLocalName() {
return null;
}
/**
* The JSR does not need multiple namespaces and may throw a
* DOMException with NOT_SUPPORTED_ERR code if the URI is not the SVG
* namespace URI, or if the provided name is not one of valid SVG Tiny
* element names. The elements must be supported are only:
* <rect>, <circle>, <line>, <ellipse>, <path> <use> <image> <text>,
* <g> and <a>.
*
* @param namespaceURI the namespace URI for the element to be created.
* This should be the SVG namespace URI (http://www.w3.org/2000/svg)
* in any case.
* @param qualifiedName the qualified name for the element to be created
* (For instance, "rect" for a <rect> element).
*
* @return the newly created SVG Element.
*
* @throws DOMException with NOT_SUPPORTED_ERR error code if this type of
* element is not supported by the implementation.
* The JSR only requires support for some of the SVG namespace elements
* and only for a part of local names in that namespace (please see
* documentation above).
* Thus, in some valid JSR 226 implementation, trying to create elements
* with a namespace URIs that differ from the SVG namespace URI
* and with a qualified name not listed as required qualified name may
* lead to this exception being thrown.
* @throws NullPointerException either <code>namespaceURI</code> or
* <code>qualifiedName</code> is null.
*/
public Element createElementNS(String namespaceURI,
final String qualifiedName)
throws DOMException {
if (namespaceURI == null || qualifiedName == null) {
throw new NullPointerException();
}
// Extract qualified name from the local name.
String localName = qualifiedName;
int pi = qualifiedName.indexOf(':');
if (pi != -1) {
if (pi == localName.length() - 1) {
// Namepace prefix separator is at the end of the qualified name
localName = "";
} else {
localName = qualifiedName.substring(pi + 1);
}
}
//
// If the namespaceURI is empty, we have two cases:
// 1. (extremely rare) the content maps some prefix, or the default
// namespace, to the "" namespace URI.
// 2. (extremely common) the content does not have an xmlns declaration
// on its root element and the parser reported the root element
// and children as being in the "" namespace URI.
//
// The following code means that the situation 1. is _not_ handled. If
// the author mapped a prefix or the default prefix to the
// empty string URI, that will be overridden by the default namespace.
if (namespaceURI.length() == 0) {
namespaceURI = defaultNamespaceURI;
}
Hashtable lmap = (Hashtable) namespaceMap.get(namespaceURI);
ElementNode en = null;
if (lmap != null) {
en = (ElementNode) lmap.get(localName);
if (en != null) {
en = en.newInstance(this);
}
}
if (en == null) {
checkNCName(localName);
en = new GenericElementNode(namespaceURI, localName, this);
}
return en;
}
/**
* @param name the trait name
* @return a DOMException describing the unsupported trait error.
*/
protected DOMException unsupportedTrait(final String name) {
return new DOMException(DOMException.NOT_SUPPORTED_ERR,
Messages.formatMessage
(Messages.ERROR_UNSUPPORTED_TRAIT,
new String[] {name,
null,
getLocalName(),
getNamespaceURI()}));
}
/**
* Checks if the input trait name is valid and throws a DOMException
* with error code NOT_SUPPORTED_ERR if not.
*
* @param name the name whose syntax should be checked.
* @throws DOMException with error code NOT_SUPPORTED_ERR if the
* trait name is syntactically incorrect (e.g., null or containing
* characters not conforming to the Namespaces in XML specification.
*
* @see http://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-NCName
*/
final void checkNCName(final String name) throws DOMException {
if (name == null || name.length() == 0) {
throw unsupportedTrait(name);
}
// NCName ::= (Letter | '_') (NCNameChar)*
// NCNameChar ::= Letter | Digit | '.' | '-' | '_' | CombiningChar
// | Extender
char c = name.charAt(0);
if (!isLetter(c) && c != '_') {
throw unsupportedTrait(name);
}
for (int i = 1; i < name.length(); i++) {
c = name.charAt(i);
if (!isNCNameChar(c)) {
throw unsupportedTrait(name);
}
}
}
final static boolean isNCNameChar(final char c) {
return isLetter(c)
|| Character.isDigit(c)
|| c == '.'
|| c == '-'
|| c == '_'
|| isCombiningChar(c)
|| isExtender(c);
}
final static boolean isExtender(final int c) {
return c == 0x00B7
|| c == 0x02D0
|| c == 0x02D1
|| c == 0x0387
|| c == 0x0640
|| c == 0x0E46
|| c == 0x0EC6
|| c == 0x3005
|| (c >= 0x3031 && c <= 0x3035)
|| (c >= 0x309D && c <= 0x309E)
|| (c >= 0x30FC && c <= 0x30FE);
}
final static boolean isCombiningChar(final int c) {
return (c >= 0x0300 && c <= 0x0345)
|| (c >= 0x0360 && c <= 0x0361)
|| (c >= 0x0483 && c <= 0x0486)
|| (c >= 0x0591 && c <= 0x05A1)
|| (c >= 0x05A3 && c <= 0x05B9)
|| (c >= 0x05BB && c <= 0x05BD)
|| c == 0x05BF
|| (c >= 0x05C1 && c <= 0x05C2)
|| c == 0x05C4
|| (c >= 0x064B && c <= 0x0652)
|| c == 0x0670
|| (c >= 0x06D6 && c <= 0x06DC)
|| (c >= 0x06DD && c <= 0x06DF)
|| (c >= 0x06E0 && c <= 0x06E4)
|| (c >= 0x06E7 && c <= 0x06E8)
|| (c >= 0x06EA && c <= 0x06ED)
|| (c >= 0x0901 && c <= 0x0903)
|| c == 0x093C
|| (c >= 0x093E && c <= 0x094C)
|| c == 0x094D
|| (c >= 0x0951 && c <= 0x0954)
|| (c >= 0x0962 && c <= 0x0963)
|| (c >= 0x0981 && c <= 0x0983)
|| c == 0x09BC
|| c == 0x09BE
|| c == 0x09BF
|| (c >= 0x09C0 && c <= 0x09C4)
|| (c >= 0x09C7 && c <= 0x09C8)
|| (c >= 0x09CB && c <= 0x09CD)
|| c == 0x09D7
|| (c >= 0x09E2 && c <= 0x09E3)
|| c == 0x0A02
|| c == 0x0A3C
|| c == 0x0A3E
|| c == 0x0A3F
|| (c >= 0x0A40 && c <= 0x0A42)
|| (c >= 0x0A47 && c <= 0x0A48)
|| (c >= 0x0A4B && c <= 0x0A4D)
|| (c >= 0x0A70 && c <= 0x0A71)
|| (c >= 0x0A81 && c <= 0x0A83)
|| c == 0x0ABC
|| (c >= 0x0ABE && c <= 0x0AC5)
|| (c >= 0x0AC7 && c <= 0x0AC9)
|| (c >= 0x0ACB && c <= 0x0ACD)
|| (c >= 0x0B01 && c <= 0x0B03)
|| c == 0x0B3C
|| (c >= 0x0B3E && c <= 0x0B43)
|| (c >= 0x0B47 && c <= 0x0B48)
|| (c >= 0x0B4B && c <= 0x0B4D)
|| (c >= 0x0B56 && c <= 0x0B57)
|| (c >= 0x0B82 && c <= 0x0B83)
|| (c >= 0x0BBE && c <= 0x0BC2)
|| (c >= 0x0BC6 && c <= 0x0BC8)
|| (c >= 0x0BCA && c <= 0x0BCD)
|| c == 0x0BD7
|| (c >= 0x0C01 && c <= 0x0C03)
|| (c >= 0x0C3E && c <= 0x0C44)
|| (c >= 0x0C46 && c <= 0x0C48)
|| (c >= 0x0C4A && c <= 0x0C4D)
|| (c >= 0x0C55 && c <= 0x0C56)
|| (c >= 0x0C82 && c <= 0x0C83)
|| (c >= 0x0CBE && c <= 0x0CC4)
|| (c >= 0x0CC6 && c <= 0x0CC8)
|| (c >= 0x0CCA && c <= 0x0CCD)
|| (c >= 0x0CD5 && c <= 0x0CD6)
|| (c >= 0x0D02 && c <= 0x0D03)
|| (c >= 0x0D3E && c <= 0x0D43)
|| (c >= 0x0D46 && c <= 0x0D48)
|| (c >= 0x0D4A && c <= 0x0D4D)
|| c == 0x0D57
|| c == 0x0E31
|| (c >= 0x0E34 && c <= 0x0E3A)
|| (c >= 0x0E47 && c <= 0x0E4E)
|| c == 0x0EB1
|| (c >= 0x0EB4 && c <= 0x0EB9)
|| (c >= 0x0EBB && c <= 0x0EBC)
|| (c >= 0x0EC8 && c <= 0x0ECD)
|| (c >= 0x0F18 && c <= 0x0F19)
|| c == 0x0F35
|| c == 0x0F37
|| c == 0x0F39
|| c == 0x0F3E
|| c == 0x0F3F
|| (c >= 0x0F71 && c <= 0x0F84)
|| (c >= 0x0F86 && c <= 0x0F8B)
|| (c >= 0x0F90 && c <= 0x0F95)
|| c == 0x0F97
|| (c >= 0x0F99 && c <= 0x0FAD)
|| (c >= 0x0FB1 && c <= 0x0FB7)
|| c == 0x0FB9
|| (c >= 0x20D0 && c <= 0x20DC)
|| c == 0x20E1
|| (c >= 0x302A && c <= 0x302F)
|| c == 0x3099
|| c == 0x309A;
}
final static boolean isLetter(final int c) {
return isIdeographic(c) || isBaseChar(c);
}
final static boolean isIdeographic(final int c) {
return (c >= 0x4E00 && c <= 0x9FA5)
|| c == 0x3007
|| (c >= 0x3021 && c <= 0x3029);
}
final static boolean isBaseChar(final int c) {
return (c >= 0x0041 && c <= 0x005A)
|| (c >= 0x0061 && c <= 0x007A)
|| (c >= 0x00C0 && c <= 0x00D6)
|| (c >= 0x00D8 && c <= 0x00F6)
|| (c >= 0x00F8 && c <= 0x00FF)
|| (c >= 0x0100 && c <= 0x0131)
|| (c >= 0x0134 && c <= 0x013E)
|| (c >= 0x0141 && c <= 0x0148)
|| (c >= 0x014A && c <= 0x017E)
|| (c >= 0x0180 && c <= 0x01C3)
|| (c >= 0x01CD && c <= 0x01F0)
|| (c >= 0x01F4 && c <= 0x01F5)
|| (c >= 0x01FA && c <= 0x0217)
|| (c >= 0x0250 && c <= 0x02A8)
|| (c >= 0x02BB && c <= 0x02C1)
|| c == 0x0386
|| (c >= 0x0388 && c <= 0x038A)
|| c == 0x038C
|| (c >= 0x038E && c <= 0x03A1)
|| (c >= 0x03A3 && c <= 0x03CE)
|| (c >= 0x03D0 && c <= 0x03D6)
|| c == 0x03DA
|| c == 0x03DC
|| c == 0x03DE
|| c == 0x03E0
|| (c >= 0x03E2 && c <= 0x03F3)
|| (c >= 0x0401 && c <= 0x040C)
|| (c >= 0x040E && c <= 0x044F)
|| (c >= 0x0451 && c <= 0x045C)
|| (c >= 0x045E && c <= 0x0481)
|| (c >= 0x0490 && c <= 0x04C4)
|| (c >= 0x04C7 && c <= 0x04C8)
|| (c >= 0x04CB && c <= 0x04CC)
|| (c >= 0x04D0 && c <= 0x04EB)
|| (c >= 0x04EE && c <= 0x04F5)
|| (c >= 0x04F8 && c <= 0x04F9)
|| (c >= 0x0531 && c <= 0x0556)
|| c == 0x0559
|| (c >= 0x0561 && c <= 0x0586)
|| (c >= 0x05D0 && c <= 0x05EA)
|| (c >= 0x05F0 && c <= 0x05F2)
|| (c >= 0x0621 && c <= 0x063A)
|| (c >= 0x0641 && c <= 0x064A)
|| (c >= 0x0671 && c <= 0x06B7)
|| (c >= 0x06BA && c <= 0x06BE)
|| (c >= 0x06C0 && c <= 0x06CE)
|| (c >= 0x06D0 && c <= 0x06D3)
|| c == 0x06D5
|| (c >= 0x06E5 && c <= 0x06E6)
|| (c >= 0x0905 && c <= 0x0939)
|| c == 0x093D
|| (c >= 0x0958 && c <= 0x0961)
|| (c >= 0x0985 && c <= 0x098C)
|| (c >= 0x098F && c <= 0x0990)
|| (c >= 0x0993 && c <= 0x09A8)
|| (c >= 0x09AA && c <= 0x09B0)
|| c == 0x09B2
|| (c >= 0x09B6 && c <= 0x09B9)
|| (c >= 0x09DC && c <= 0x09DD)
|| (c >= 0x09DF && c <= 0x09E1)
|| (c >= 0x09F0 && c <= 0x09F1)
|| (c >= 0x0A05 && c <= 0x0A0A)
|| (c >= 0x0A0F && c <= 0x0A10)
|| (c >= 0x0A13 && c <= 0x0A28)
|| (c >= 0x0A2A && c <= 0x0A30)
|| (c >= 0x0A32 && c <= 0x0A33)
|| (c >= 0x0A35 && c <= 0x0A36)
|| (c >= 0x0A38 && c <= 0x0A39)
|| (c >= 0x0A59 && c <= 0x0A5C)
|| c == 0x0A5E
|| (c >= 0x0A72 && c <= 0x0A74)
|| (c >= 0x0A85 && c <= 0x0A8B)
|| c == 0x0A8D
|| (c >= 0x0A8F && c <= 0x0A91)
|| (c >= 0x0A93 && c <= 0x0AA8)
|| (c >= 0x0AAA && c <= 0x0AB0)
|| (c >= 0x0AB2 && c <= 0x0AB3)
|| (c >= 0x0AB5 && c <= 0x0AB9)
|| c == 0x0ABD
|| c == 0x0AE0
|| (c >= 0x0B05 && c <= 0x0B0C)
|| (c >= 0x0B0F && c <= 0x0B10)
|| (c >= 0x0B13 && c <= 0x0B28)
|| (c >= 0x0B2A && c <= 0x0B30)
|| (c >= 0x0B32 && c <= 0x0B33)
|| (c >= 0x0B36 && c <= 0x0B39)
|| c == 0x0B3D
|| (c >= 0x0B5C && c <= 0x0B5D)
|| (c >= 0x0B5F && c <= 0x0B61)
|| (c >= 0x0B85 && c <= 0x0B8A)
|| (c >= 0x0B8E && c <= 0x0B90)
|| (c >= 0x0B92 && c <= 0x0B95)
|| (c >= 0x0B99 && c <= 0x0B9A)
|| c == 0x0B9C
|| (c >= 0x0B9E && c <= 0x0B9F)
|| (c >= 0x0BA3 && c <= 0x0BA4)
|| (c >= 0x0BA8 && c <= 0x0BAA)
|| (c >= 0x0BAE && c <= 0x0BB5)
|| (c >= 0x0BB7 && c <= 0x0BB9)
|| (c >= 0x0C05 && c <= 0x0C0C)
|| (c >= 0x0C0E && c <= 0x0C10)
|| (c >= 0x0C12 && c <= 0x0C28)
|| (c >= 0x0C2A && c <= 0x0C33)
|| (c >= 0x0C35 && c <= 0x0C39)
|| (c >= 0x0C60 && c <= 0x0C61)
|| (c >= 0x0C85 && c <= 0x0C8C)
|| (c >= 0x0C8E && c <= 0x0C90)
|| (c >= 0x0C92 && c <= 0x0CA8)
|| (c >= 0x0CAA && c <= 0x0CB3)
|| (c >= 0x0CB5 && c <= 0x0CB9)
|| c == 0x0CDE
|| (c >= 0x0CE0 && c <= 0x0CE1)
|| (c >= 0x0D05 && c <= 0x0D0C)
|| (c >= 0x0D0E && c <= 0x0D10)
|| (c >= 0x0D12 && c <= 0x0D28)
|| (c >= 0x0D2A && c <= 0x0D39)
|| (c >= 0x0D60 && c <= 0x0D61)
|| (c >= 0x0E01 && c <= 0x0E2E)
|| c == 0x0E30
|| (c >= 0x0E32 && c <= 0x0E33)
|| (c >= 0x0E40 && c <= 0x0E45)
|| (c >= 0x0E81 && c <= 0x0E82)
|| c == 0x0E84
|| (c >= 0x0E87 && c <= 0x0E88)
|| c == 0x0E8A
|| c == 0x0E8D
|| (c >= 0x0E94 && c <= 0x0E97)
|| (c >= 0x0E99 && c <= 0x0E9F)
|| (c >= 0x0EA1 && c <= 0x0EA3)
|| c == 0x0EA5
|| c == 0x0EA7
|| (c >= 0x0EAA && c <= 0x0EAB)
|| (c >= 0x0EAD && c <= 0x0EAE)
|| c == 0x0EB0
|| (c >= 0x0EB2 && c <= 0x0EB3)
|| c == 0x0EBD
|| (c >= 0x0EC0 && c <= 0x0EC4)
|| (c >= 0x0F40 && c <= 0x0F47)
|| (c >= 0x0F49 && c <= 0x0F69)
|| (c >= 0x10A0 && c <= 0x10C5)
|| (c >= 0x10D0 && c <= 0x10F6)
|| c == 0x1100
|| (c >= 0x1102 && c <= 0x1103)
|| (c >= 0x1105 && c <= 0x1107)
|| c == 0x1109
|| (c >= 0x110B && c <= 0x110C)
|| (c >= 0x110E && c <= 0x1112)
|| c == 0x113C
|| c == 0x113E
|| c == 0x1140
|| c == 0x114C
|| c == 0x114E
|| c == 0x1150
|| (c >= 0x1154 && c <= 0x1155)
|| c == 0x1159
|| (c >= 0x115F && c <= 0x1161)
|| c == 0x1163
|| c == 0x1165
|| c == 0x1167
|| c == 0x1169
|| (c >= 0x116D && c <= 0x116E)
|| (c >= 0x1172 && c <= 0x1173)
|| c == 0x1175
|| c == 0x119E
|| c == 0x11A8
|| c == 0x11AB
|| (c >= 0x11AE && c <= 0x11AF)
|| (c >= 0x11B7 && c <= 0x11B8)
|| c == 0x11BA
|| (c >= 0x11BC && c <= 0x11C2)
|| c == 0x11EB
|| c == 0x11F0
|| c == 0x11F9
|| (c >= 0x1E00 && c <= 0x1E9B)
|| (c >= 0x1EA0 && c <= 0x1EF9)
|| (c >= 0x1F00 && c <= 0x1F15)
|| (c >= 0x1F18 && c <= 0x1F1D)
|| (c >= 0x1F20 && c <= 0x1F45)
|| (c >= 0x1F48 && c <= 0x1F4D)
|| (c >= 0x1F50 && c <= 0x1F57)
|| c == 0x1F59
|| c == 0x1F5B
|| c == 0x1F5D
|| (c >= 0x1F5F && c <= 0x1F7D)
|| (c >= 0x1F80 && c <= 0x1FB4)
|| (c >= 0x1FB6 && c <= 0x1FBC)
|| c == 0x1FBE
|| (c >= 0x1FC2 && c <= 0x1FC4)
|| (c >= 0x1FC6 && c <= 0x1FCC)
|| (c >= 0x1FD0 && c <= 0x1FD3)
|| (c >= 0x1FD6 && c <= 0x1FDB)
|| (c >= 0x1FE0 && c <= 0x1FEC)
|| (c >= 0x1FF2 && c <= 0x1FF4)
|| (c >= 0x1FF6 && c <= 0x1FFC)
|| c == 0x2126
|| (c >= 0x212A && c <= 0x212B)
|| c == 0x212E
|| (c >= 0x2180 && c <= 0x2182)
|| (c >= 0x3041 && c <= 0x3094)
|| (c >= 0x30A1 && c <= 0x30FA)
|| (c >= 0x3105 && c <= 0x312C)
|| (c >= 0xAC00 && c <= 0xD7A3);
}
/**
* To support the creation of elements in the <code>createElementNS</code>
* method, we supply prototypes to the <code>DocumentNode</code> so that it
* can create nodes of specific data types for the prototype's namespaceURI
* and localName.
*
* @param prototypeElement the <code>ElementNode</code> which will be used
* as a prototype in <code>createElementNS</code>. Should not be null.
* @throws IllegalArgumentException If there is already a prototype node for
* the given namespace and local name.
*/
public void addPrototype(final ElementNode prototypeElement) {
// Get the namespace map
String namespaceURI = prototypeElement.getNamespaceURI();
Hashtable lmap = (Hashtable) namespaceMap.get(namespaceURI);
if (lmap == null) {
lmap = new Hashtable();
namespaceMap.put(namespaceURI, lmap);
}
lmap.put(prototypeElement.getLocalName(), prototypeElement);
}
/**
* For SVG files this must be
* <code>SVGSVGElement</code>, but return type is Element for DOM Core
* compatibility and to allow for future extensions. Return null if
* document does not have an element child.
*
*/
public Element getDocumentElement() {
return firstChild;
}
/**
* Attempts to adopt a node from another document to this document. If
* supported, it changes the ownerDocument of the source node, and its
* children. If the source node has a parent it is first removed from
* the child list of its parent. This effectively allows moving a
* subtree from one document to another. Note that if the adopted node
* is already part of this document (i.e. the source and target document
* are the same), this method still has the effect of removing the
* source node from the child list of its parent, if any.
*
* @param source the Node to move into this document.
* @returns the adopted node or null, if this operation fails.
* @throws DOMException with error code NOT_SUPPORTED_ERR: if the source
* node is of type Document.
* @throws java.lang.NullPointerException - if source is null.
* @throws java.lang.SecurityException - if the application does not
* have the necessary privilege rights to access this (SVG) content.
*/
public Node adoptNode(Node source) throws DOMException {
if (source == null)
throw new NullPointerException();
if (source instanceof Document) {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
Messages.formatMessage
(Messages.ERROR_ADOPTING_DOCUMENT_ELEMENT,
null));
}
// This implementation does not throw a SecurityException since it
// does not deal with DRM issues.
if (!(source instanceof ModelNode))
return null;
ModelNode modelNode = (ModelNode) source;
// Remove modelNode from its parent's children list
// Note that this will throw a DOMException if the node or any
// of its children has an id
Node parent = (Node) modelNode.getParent();
if (parent != null) {
parent.removeChild((Node) modelNode);
}
// Change the ownerDocument on modelNode and all its children
modelNode.ownerDocument = this.ownerDocument;
ModelNode child = modelNode.getFirstChildNode();
while (child != null) {
child.ownerDocument = this.ownerDocument;
child = child.nextSibling;
}
return (Node) modelNode;
}
/**
* Attempts to adopt a node from another document to this document. If
* supported, it changes the ownerDocument of the source node, and its
* children. If the source node has a parent it is first removed from
* the child list of its parent. This effectively allows moving a
* subtree from one document to another. Note that if the adopted node
* is already part of this document (i.e. the source and target document
* are the same), this method still has the effect of removing the
* source node from the child list of its parent, if any.
*
* @param source the Node to move into this document.
* @returns the adopted node or null, if this operation fails.
* @throws DOMException with error code NOT_SUPPORTED_ERR: if the source
* node is of type Document.
* @throws java.lang.NullPointerException - if source is null.
* @throws java.lang.SecurityException - if the application does not
* have the necessary privilege rights to access this (SVG) content.
*/
public Node adoptNode(Node source, boolean strict) throws DOMException {
// If the strict implementation is desired, redirect to the strict
// adoptNode implementation
if (strict)
return adoptNode(source);
// Non strict implementation
if (source == null)
throw new NullPointerException();
if (source instanceof Document) {
throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
Messages.formatMessage
(Messages.ERROR_ADOPTING_DOCUMENT_ELEMENT,
null));
}
// This implementation does not throw a SecurityException since it
// does not deal with DRM issues.
if (!(source instanceof ModelNode))
return null;
ModelNode modelNode = (ModelNode) source;
// Unhook the modelNode from its parent's children list
// Note that this will allow unhooking of even those nodes that have
// an id.
CompositeNode parent = (CompositeNode) modelNode.getParent();
if (parent != null) {
parent.removeChildQuiet((Node) modelNode);
}
// OLD DOCUMENT OPERATIONS
// . Remove from idToElement Hashtable
// . Remove from reservedIDs Hashtable
// . Remove IDRefs from resolvedIDRefs Hashtable for those ids
// that were in the tree rooted at the node being adopted and
// move to unresolvedIDRefs Hashtable (unresolvedIDRefs
// Hashtable may be null if the document is already loaded)
// . Remove unresolved IDRefs from the the unresolvedIDRefs Hashtable
// NEW DOCUMENT OPERATIONS
// . Add ids to reservedIds Hashtable
// . Change ownerDocument on each of the adopted nodes
// . Check new node and all its children whether they are
// instanceof IDRef and if so, call getIdRef() and use the
// return value (if non-null) to call
// ownerDocument.resolveIDRef(IDRef(node), getIdRef()) to get
// each node's id added to either resolvedIDRef or
// unresolvedIDRef
DocumentNode oldDoc = modelNode.getOwnerDocument();
ModelNode currNode = modelNode;
ElementNode currElementNode;
String id = null;
Vector resVector, unresVector = null;
boolean needsChild = true;
while (currNode != null) {
if (currNode instanceof ElementNode) {
currElementNode = (ElementNode) currNode;
id = currElementNode.getId();
if (id != null) {
oldDoc.removeIdentifiedNode(currElementNode);
oldDoc.reservedIds.remove(currElementNode);
if (oldDoc.resolvedIDRefs != null) {
resVector = (Vector) oldDoc.resolvedIDRefs.get(id);
oldDoc.resolvedIDRefs.remove(id);
// XXX What should be done if unresolvedIDRefs is null
if (resVector != null && oldDoc.unresolvedIDRefs != null) {
unresVector = (Vector) oldDoc.unresolvedIDRefs.get(id);
if (unresVector == null) {
unresVector = resVector;
oldDoc.unresolvedIDRefs.put(id, unresVector);
} else {
// Move IDRefs from resVector to unresVector
for (int i=0; i<resVector.size(); i++) {
unresVector.addElement(resVector.elementAt(i));
}
}
}
}
// Update new document's reservedId's Hashtable
reservedIds.put(id, currElementNode);
}
if (currNode instanceof IDRef) {
String idref = ((IDRef) currNode).getIdRef();
// If this reference was unresolved in the old document,
// remove it from the unresolvedIDRefs Hashtable because
// it is no longer part of this tree
if (oldDoc.unresolvedIDRefs != null) {
unresVector = (Vector) oldDoc.unresolvedIDRefs.get(idref);
if (unresVector != null)
unresVector.removeElement(currNode);
}
// Figure out whether the IDRef is resolved or unresolved
// in the new document
resolveIDRef((IDRef) currNode, idref);
}
}
currNode.ownerDocument = this.ownerDocument;
if (needsChild) {
currNode = currNode.getFirstChildNode();
} else {
currNode = currNode.nextSibling;
}
needsChild = false;
}
return (Node) modelNode;
}
/**
* Returns the parent <code>Node</code> of this <code>Node</code>.
*
* @return the parent node or null if there is no parent (i.e. if a node has
* just been created and not yet added to the tree, or if it has been
* removed from the tree, this is null).
*/
public Node getParentNode() {
return null;
}
/**
* @return false, as DocumentNode does not support removing children.
*/
protected boolean isRemoveChildSupported() {
return false;
}
/**
* Only a SVG child is allowed under a DocumentNode.
*
* @param node the candidate child node.
* @return true if the input node can be inserted under this CompositeNode
*/
protected boolean isAllowedChild(final ElementNode node) {
if (node instanceof SVG) {
return true;
}
return false;
}
// =========================================================================
// Namespace prefix management. Note that this is designed to be minimal
// in terms of memory and is not optimized for speed.
// =========================================================================
/**
* Adds a new prefix to namespace mapping. The scope is provided by
* the node parameter.
*
* @param prefix the new namespace prefix.
* @param uri the new namespace URI which maps to the prefix.
* @param node the scope of the namespace prefix mapping. The mapping
* applies to all children, unless overridden.
*/
public void addNamespacePrefix(final String prefix,
final String namespaceURI,
final ModelNode node) {
if (prefix == null) {
throw new NullPointerException();
}
// First, check if there are already mappings for the
// prefix.
Object[][] namespaceEntry = (Object[][]) prefixes.get(prefix);
if (namespaceEntry == null) {
// Simple case: there is not entry yet. Create a new one.
namespaceEntry = new Object[][] {
{namespaceURI, node} };
} else {
// There is an existing entry. Add the new one ahead of the
// other ones.
Object[][] newNamespaceEntry =
new Object[namespaceEntry.length + 1][];
newNamespaceEntry[0] = new Object[] {namespaceURI, node};
System.arraycopy(namespaceEntry, 0, newNamespaceEntry, 1,
namespaceEntry.length);
namespaceEntry = newNamespaceEntry;
}
prefixes.put(prefix, namespaceEntry);
// Now, update the namespaces map.
Object[][] prefixEntry = (Object[][]) namespaces.get(namespaceURI);
if (prefixEntry == null) {
// Simple case: there is no entry yet. Create a new one.
prefixEntry = new Object[][] {
{prefix, node} };
} else {
// There is an existing entry. Add the new one ahead of the other
// ones.
Object[][] newPrefixEntry = new Object[prefixEntry.length + 1][];
newPrefixEntry[0] = new Object[] {prefix, node};
System.arraycopy(prefixEntry, 0, newPrefixEntry, 1,
prefixEntry.length);
prefixEntry = newPrefixEntry;
}
namespaces.put(namespaceURI, prefixEntry);
}
/**
* Maps the input prefix name to a namespace value.
*
* @param prefix the prefix to map.
* @param node the node for which the prefix needs to be mapped.
* @return the namespace the prefix maps to for the node, or null if
* there is no such namespace prefix.
*/
String toNamespace(final String prefix, final ModelNode node) {
Object[][] namespaceEntry = (Object[][]) prefixes.get(prefix);
if (namespaceEntry == null) {
// No namespace entry
return null;
} else {
// Note that namespaceEntry.length == 0 should _never_ happen.
// There are multiple prefix that map to entries.
// Walk up the parent tree and match with the
// first parent that is found in an entry.
ModelNode cur = node;
final int n = namespaceEntry.length;
while (cur != null && cur != this) {
for (int i = 0; i < n; i++) {
if (namespaceEntry[i][1] == cur) {
return (String) namespaceEntry[i][0];
}
}
cur = cur.parent;
}
// If we are here, it means we have not found a
// matching namespace in the document tree. Check
// if there are any default mapping on the document
// node itself.
for (int i = 0; i < n; i++) {
if (namespaceEntry[i][1] == this) {
return (String) namespaceEntry[i][0];
}
}
// Did not find any matching namespace prefix.
return null;
}
}
/**
* Maps the input namespace value to a prefix.
*
* @param namespaceURI the URI to map.
* @param node the node for which the namespace needs to be mapped.
* @return the namespace the prefix maps to for the node, or null if
* there is no such namespace prefix.
*/
public String toPrefix(final String namespaceURI, final Element node) {
Object[][] prefixEntry = (Object[][]) namespaces.get(namespaceURI);
if (prefixEntry == null) {
// No prefix entry
return null;
} else {
// Note that prefixEntry.length == 0 should _never_ happen.
// There are multiple prefixEntries that map to entries.
// Walk up the parent tree and match with the
// first parent that is found in an entry.
ModelNode cur = (ElementNode) node;
final int n = prefixEntry.length;
while (cur != null && cur != this) {
for (int i = 0; i < n; i++) {
if (prefixEntry[i][1] == cur) {
return (String) prefixEntry[i][0];
}
}
cur = cur.parent;
}
// If we are here, it means we have not found a
// matching prefix in the document tree. Check
// if there are any default mapping on the document
// node itself.
for (int i = 0; i < n; i++) {
if (prefixEntry[i][1] == this) {
return (String) prefixEntry[i][0];
}
}
// Did not find any matching prefix.
return null;
}
}
/**
* Invoked at the end of the parsing stage to validate things which cannot
* be validated earlier, such as unresolved use references or invalid
* animation settings.
*
* @throws DOMException if there are validation errors.
*/
public void validate() throws DOMException {
// First, check unresolved ID references.
if (unresolvedIDRefs != null && unresolvedIDRefs.size() > 0) {
// There are unresolved ID references, this is a validation error.
Enumeration iter = unresolvedIDRefs.keys();
StringBuffer buf = new StringBuffer();
while (iter.hasMoreElements()) {
buf.append('[');
buf.append(iter.nextElement());
buf.append(']');
}
String message
= Messages.formatMessage
(Messages.ERROR_UNRESOLVED_REFERENCES,
new Object[] {buf.toString()});
throw new DOMException(DOMException.INVALID_ACCESS_ERR,
message);
}
unresolvedIDRefs = null;
// Now, validate TimedElementNode elements. At this stage, we know
// that the TimedElementNode elements either had no xlink:href or
// had one which has been resolved.
if (timedElementNodes != null) {
int n = timedElementNodes.size();
for (int i = 0; i < n; i++) {
TimedElementNode ten =
(TimedElementNode) timedElementNodes.elementAt(i);
if (ten.parent != null) {
// The prototypes Set may have a null parent, so we account
// for that situation here.
ten.validate();
}
}
}
timedElementNodes = null;
}
/**
* Implementation helper. Checks that the unknownTraitNS map is not null for
* the given ElementNode before using it, for the requested namespaceURI.
*
* @param element the ElementNode for which a table should be created for
* the given namespace URI.
* @param namespaceURI the trait's namespace URI
* @param traitName the trait's local name.
* @param value the trait value.
* @return the namespaceURI's unknown traits table.
*/
void setUnknownTraitsNS(final ElementNode element,
final String namespaceURI,
final String traitName,
final String value) {
// Make sure we do have a table for storing unknown traits.
if (unknownTraitsNS == null) {
unknownTraitsNS = new Hashtable();
}
// Make sure we do have a table for storing unknown traits for
// the requested element.
Hashtable eltMap = (Hashtable) unknownTraitsNS.get(element);
if (eltMap == null) {
eltMap = new Hashtable();
unknownTraitsNS.put(element, eltMap);
}
// If there is already a map for the given namespace, use that.
// Otherwise, create a new map.
Hashtable nsMap = (Hashtable) eltMap.get(namespaceURI);
if (nsMap == null) {
nsMap = new Hashtable();
eltMap.put(namespaceURI, nsMap);
}
nsMap.put(traitName, value);
}
/**
* Implementation helper. Returns the ElementNode's trait value if
* it was ever set.
*
* @param element the ElementNode on which the trait might be set.
* @param namespaceURI the trait's namespace URI
* @param traitName the trait's local name.
* @return the trait value or null if the value was never set.
*/
String getUnknownTraitsNS(final ElementNode element,
final String namespaceURI,
final String traitName) {
if (unknownTraitsNS == null) {
return null;
}
Hashtable eltMap = (Hashtable) unknownTraitsNS.get(element);
if (eltMap == null) {
return null;
}
Hashtable nsMap = (Hashtable) eltMap.get(namespaceURI);
if (nsMap == null) {
return null;
}
return (String) nsMap.get(traitName);
}
}