001: /*
002:
003: Licensed to the Apache Software Foundation (ASF) under one or more
004: contributor license agreements. See the NOTICE file distributed with
005: this work for additional information regarding copyright ownership.
006: The ASF licenses this file to You under the Apache License, Version 2.0
007: (the "License"); you may not use this file except in compliance with
008: the License. You may obtain a copy of the License at
009:
010: http://www.apache.org/licenses/LICENSE-2.0
011:
012: Unless required by applicable law or agreed to in writing, software
013: distributed under the License is distributed on an "AS IS" BASIS,
014: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: See the License for the specific language governing permissions and
016: limitations under the License.
017:
018: */
019: package org.apache.batik.dom.svg;
020:
021: import java.io.IOException;
022: import java.io.ObjectInputStream;
023: import java.net.MalformedURLException;
024: import java.net.URL;
025: import java.util.HashMap;
026: import java.util.Iterator;
027: import java.util.LinkedList;
028: import java.util.Locale;
029: import java.util.MissingResourceException;
030:
031: import org.apache.batik.css.engine.CSSNavigableDocument;
032: import org.apache.batik.css.engine.CSSNavigableDocumentListener;
033: import org.apache.batik.css.engine.CSSStylableElement;
034: import org.apache.batik.dom.AbstractStylableDocument;
035: import org.apache.batik.dom.GenericAttr;
036: import org.apache.batik.dom.GenericAttrNS;
037: import org.apache.batik.dom.GenericCDATASection;
038: import org.apache.batik.dom.GenericComment;
039: import org.apache.batik.dom.GenericDocumentFragment;
040: import org.apache.batik.dom.GenericElement;
041: import org.apache.batik.dom.GenericEntityReference;
042: import org.apache.batik.dom.GenericProcessingInstruction;
043: import org.apache.batik.dom.GenericText;
044: import org.apache.batik.dom.StyleSheetFactory;
045: import org.apache.batik.dom.events.EventSupport;
046: import org.apache.batik.dom.util.XMLSupport;
047: import org.apache.batik.i18n.Localizable;
048: import org.apache.batik.i18n.LocalizableSupport;
049: import org.apache.batik.util.SVGConstants;
050: import org.apache.batik.util.XMLConstants;
051:
052: import org.w3c.dom.Attr;
053: import org.w3c.dom.CDATASection;
054: import org.w3c.dom.Comment;
055: import org.w3c.dom.DOMException;
056: import org.w3c.dom.DOMImplementation;
057: import org.w3c.dom.Document;
058: import org.w3c.dom.DocumentFragment;
059: import org.w3c.dom.DocumentType;
060: import org.w3c.dom.Element;
061: import org.w3c.dom.EntityReference;
062: import org.w3c.dom.Node;
063: import org.w3c.dom.ProcessingInstruction;
064: import org.w3c.dom.Text;
065: import org.w3c.dom.css.CSSStyleDeclaration;
066: import org.w3c.dom.css.DocumentCSS;
067: import org.w3c.dom.events.Event;
068: import org.w3c.dom.events.EventListener;
069: import org.w3c.dom.events.MutationEvent;
070: import org.w3c.dom.svg.SVGDocument;
071: import org.w3c.dom.svg.SVGLangSpace;
072: import org.w3c.dom.svg.SVGSVGElement;
073:
074: /**
075: * This class implements {@link SVGDocument}.
076: *
077: * @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
078: * @version $Id: SVGOMDocument.java 479349 2006-11-26 11:54:23Z cam $
079: */
080: public class SVGOMDocument extends AbstractStylableDocument implements
081: SVGDocument, SVGConstants, CSSNavigableDocument, IdContainer {
082:
083: /**
084: * The error messages bundle class name.
085: */
086: protected static final String RESOURCES = "org.apache.batik.dom.svg.resources.Messages";
087:
088: /**
089: * The localizable support for the error messages.
090: */
091: protected transient LocalizableSupport localizableSupport = new LocalizableSupport(
092: RESOURCES, getClass().getClassLoader());
093:
094: /**
095: * The string representing the referrer.
096: */
097: protected String referrer = "";
098:
099: /**
100: * The url of the document.
101: */
102: protected URL url;
103:
104: /**
105: * Is this document immutable?
106: */
107: protected transient boolean readonly;
108:
109: /**
110: * Whether the document supports SVG 1.2.
111: */
112: protected boolean isSVG12;
113:
114: /**
115: * Map of CSSNavigableDocumentListeners to an array of wrapper
116: * DOM listeners.
117: */
118: protected HashMap cssNavigableDocumentListeners = new HashMap();
119:
120: /**
121: * The main {@link AnimatedAttributeListener} that redispatches to all
122: * listeners in {@link #animatedAttributeListeners}.
123: */
124: protected AnimatedAttributeListener mainAnimatedAttributeListener = new AnimAttrListener();
125:
126: /**
127: * List of {@link AnimatedAttributeListener}s attached to this document.
128: */
129: protected LinkedList animatedAttributeListeners = new LinkedList();
130:
131: /**
132: * Creates a new uninitialized document.
133: */
134: protected SVGOMDocument() {
135: }
136:
137: /**
138: * Creates a new document.
139: */
140: public SVGOMDocument(DocumentType dt, DOMImplementation impl) {
141: super (dt, impl);
142: }
143:
144: /**
145: * Implements {@link Localizable#setLocale(Locale)}.
146: */
147: public void setLocale(Locale l) {
148: super .setLocale(l);
149: localizableSupport.setLocale(l);
150: }
151:
152: /**
153: * Implements {@link Localizable#formatMessage(String,Object[])}.
154: */
155: public String formatMessage(String key, Object[] args)
156: throws MissingResourceException {
157: try {
158: return super .formatMessage(key, args);
159: } catch (MissingResourceException e) {
160: return localizableSupport.formatMessage(key, args);
161: }
162: }
163:
164: /**
165: * <b>DOM</b>: Implements {@link SVGDocument#getTitle()}.
166: */
167: public String getTitle() {
168: StringBuffer sb = new StringBuffer();
169: boolean preserve = false;
170:
171: for (Node n = getDocumentElement().getFirstChild(); n != null; n = n
172: .getNextSibling()) {
173: String ns = n.getNamespaceURI();
174: if (ns != null && ns.equals(SVG_NAMESPACE_URI)) {
175: if (n.getLocalName().equals(SVG_TITLE_TAG)) {
176: preserve = ((SVGLangSpace) n).getXMLspace().equals(
177: "preserve");
178: for (n = n.getFirstChild(); n != null; n = n
179: .getNextSibling()) {
180: if (n.getNodeType() == Node.TEXT_NODE) {
181: sb.append(n.getNodeValue());
182: }
183: }
184: break;
185: }
186: }
187: }
188:
189: String s = sb.toString();
190: return (preserve) ? XMLSupport.preserveXMLSpace(s) : XMLSupport
191: .defaultXMLSpace(s);
192: }
193:
194: /**
195: * <b>DOM</b>: Implements {@link SVGDocument#getReferrer()}.
196: */
197: public String getReferrer() {
198: return referrer;
199: }
200:
201: /**
202: * Sets the referrer string.
203: */
204: public void setReferrer(String s) {
205: referrer = s;
206: }
207:
208: /**
209: * <b>DOM</b>: Implements {@link SVGDocument#getDomain()}.
210: */
211: public String getDomain() {
212: return (url == null) ? null : url.getHost();
213: }
214:
215: /**
216: * <b>DOM</b>: Implements {@link SVGDocument#getRootElement()}.
217: */
218: public SVGSVGElement getRootElement() {
219: return (SVGSVGElement) getDocumentElement();
220: }
221:
222: /**
223: * <b>DOM</b>: Implements {@link SVGDocument#getURL()}
224: */
225: public String getURL() {
226: return documentURI;
227: }
228:
229: /**
230: * Returns the URI of the document.
231: */
232: public URL getURLObject() {
233: return url;
234: }
235:
236: /**
237: * Sets the URI of the document.
238: */
239: public void setURLObject(URL url) {
240: this .url = url;
241: documentURI = url == null ? null : url.toString();
242: }
243:
244: /**
245: * <b>DOM</b>: Implements {@link org.w3c.dom.Document#setDocumentURI(String)}.
246: */
247: public void setDocumentURI(String uri) {
248: documentURI = uri;
249: try {
250: url = uri == null ? null : new URL(uri);
251: } catch (MalformedURLException ex) {
252: url = null;
253: }
254: }
255:
256: /**
257: * <b>DOM</b>: Implements {@link Document#createElement(String)}.
258: */
259: public Element createElement(String tagName) throws DOMException {
260: return new GenericElement(tagName.intern(), this );
261: }
262:
263: /**
264: * <b>DOM</b>: Implements {@link Document#createDocumentFragment()}.
265: */
266: public DocumentFragment createDocumentFragment() {
267: return new GenericDocumentFragment(this );
268: }
269:
270: /**
271: * <b>DOM</b>: Implements {@link Document#createTextNode(String)}.
272: */
273: public Text createTextNode(String data) {
274: return new GenericText(data, this );
275: }
276:
277: /**
278: * <b>DOM</b>: Implements {@link Document#createComment(String)}.
279: */
280: public Comment createComment(String data) {
281: return new GenericComment(data, this );
282: }
283:
284: /**
285: * <b>DOM</b>: Implements {@link Document#createCDATASection(String)}
286: */
287: public CDATASection createCDATASection(String data)
288: throws DOMException {
289: return new GenericCDATASection(data, this );
290: }
291:
292: /**
293: * <b>DOM</b>: Implements {@link
294: * Document#createProcessingInstruction(String,String)}.
295: * @return a SVGStyleSheetProcessingInstruction if target is
296: * "xml-stylesheet" or a GenericProcessingInstruction otherwise.
297: */
298: public ProcessingInstruction createProcessingInstruction(
299: String target, String data) throws DOMException {
300: if ("xml-stylesheet".equals(target)) {
301: return new SVGStyleSheetProcessingInstruction(data, this ,
302: (StyleSheetFactory) getImplementation());
303: }
304: return new GenericProcessingInstruction(target, data, this );
305: }
306:
307: /**
308: * <b>DOM</b>: Implements {@link Document#createAttribute(String)}.
309: */
310: public Attr createAttribute(String name) throws DOMException {
311: return new GenericAttr(name.intern(), this );
312: }
313:
314: /**
315: * <b>DOM</b>: Implements {@link Document#createEntityReference(String)}.
316: */
317: public EntityReference createEntityReference(String name)
318: throws DOMException {
319: return new GenericEntityReference(name, this );
320: }
321:
322: /**
323: * <b>DOM</b>: Implements {@link Document#createAttributeNS(String,String)}.
324: */
325: public Attr createAttributeNS(String namespaceURI,
326: String qualifiedName) throws DOMException {
327: if (namespaceURI == null) {
328: return new GenericAttr(qualifiedName.intern(), this );
329: } else {
330: return new GenericAttrNS(namespaceURI.intern(),
331: qualifiedName.intern(), this );
332: }
333: }
334:
335: /**
336: * <b>DOM</b>: Implements {@link Document#createElementNS(String,String)}.
337: */
338: public Element createElementNS(String namespaceURI,
339: String qualifiedName) throws DOMException {
340: SVGDOMImplementation impl = (SVGDOMImplementation) implementation;
341: return impl.createElementNS(this , namespaceURI, qualifiedName);
342: }
343:
344: /**
345: * Returns whether the document supports SVG 1.2.
346: */
347: public boolean isSVG12() {
348: return isSVG12;
349: }
350:
351: /**
352: * Sets whether the document supports SVG 1.2.
353: */
354: public void setIsSVG12(boolean b) {
355: isSVG12 = b;
356: }
357:
358: /**
359: * Returns true if the given Attr node represents an 'id'
360: * for this document.
361: */
362: public boolean isId(Attr node) {
363: if (node.getNamespaceURI() != null)
364: return false;
365: return SVG_ID_ATTRIBUTE.equals(node.getNodeName());
366: }
367:
368: // CSSNavigableDocument ///////////////////////////////////////////
369:
370: /**
371: * Adds an event listener for mutations on the
372: * CSSNavigableDocument tree.
373: */
374: public void addCSSNavigableDocumentListener(
375: CSSNavigableDocumentListener l) {
376: if (cssNavigableDocumentListeners.containsKey(l)) {
377: return;
378: }
379:
380: DOMNodeInsertedListenerWrapper nodeInserted = new DOMNodeInsertedListenerWrapper(
381: l);
382: DOMNodeRemovedListenerWrapper nodeRemoved = new DOMNodeRemovedListenerWrapper(
383: l);
384: DOMSubtreeModifiedListenerWrapper subtreeModified = new DOMSubtreeModifiedListenerWrapper(
385: l);
386: DOMCharacterDataModifiedListenerWrapper cdataModified = new DOMCharacterDataModifiedListenerWrapper(
387: l);
388: DOMAttrModifiedListenerWrapper attrModified = new DOMAttrModifiedListenerWrapper(
389: l);
390:
391: cssNavigableDocumentListeners.put(l, new EventListener[] {
392: nodeInserted, nodeRemoved, subtreeModified,
393: cdataModified, attrModified });
394:
395: addEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
396: "DOMNodeInserted", nodeInserted, false, null);
397: addEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
398: "DOMNodeRemoved", nodeRemoved, false, null);
399: addEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
400: "DOMSubtreeModified", subtreeModified, false, null);
401: addEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
402: "DOMCharacterDataModified", cdataModified, false, null);
403: addEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
404: "DOMAttrModified", attrModified, false, null);
405: }
406:
407: /**
408: * Removes an event listener for mutations on the
409: * CSSNavigableDocument tree.
410: */
411: public void removeCSSNavigableDocumentListener(
412: CSSNavigableDocumentListener l) {
413: EventListener[] listeners = (EventListener[]) cssNavigableDocumentListeners
414: .get(l);
415: if (listeners == null) {
416: return;
417: }
418:
419: removeEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
420: "DOMNodeInserted", listeners[0], false);
421: removeEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
422: "DOMNodeRemoved", listeners[1], false);
423: removeEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
424: "DOMSubtreeModified", listeners[2], false);
425: removeEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
426: "DOMCharacterDataModified", listeners[3], false);
427: removeEventListenerNS(XMLConstants.XML_EVENTS_NAMESPACE_URI,
428: "DOMAttrModified", listeners[4], false);
429:
430: cssNavigableDocumentListeners.remove(l);
431: }
432:
433: /**
434: * Returns the {@link AnimatedAttributeListener} for the document.
435: */
436: protected AnimatedAttributeListener getAnimatedAttributeListener() {
437: return mainAnimatedAttributeListener;
438: }
439:
440: /**
441: * The text of the override style declaration for this element has been
442: * modified.
443: */
444: protected void overrideStyleTextChanged(CSSStylableElement e,
445: String text) {
446: Iterator i = cssNavigableDocumentListeners.keySet().iterator();
447: while (i.hasNext()) {
448: CSSNavigableDocumentListener l = (CSSNavigableDocumentListener) i
449: .next();
450: l.overrideStyleTextChanged(e, text);
451: }
452: }
453:
454: /**
455: * A property in the override style declaration has been removed.
456: */
457: protected void overrideStylePropertyRemoved(CSSStylableElement e,
458: String name) {
459: Iterator i = cssNavigableDocumentListeners.keySet().iterator();
460: while (i.hasNext()) {
461: CSSNavigableDocumentListener l = (CSSNavigableDocumentListener) i
462: .next();
463: l.overrideStylePropertyRemoved(e, name);
464: }
465: }
466:
467: /**
468: * A property in the override style declaration has been changed.
469: */
470: protected void overrideStylePropertyChanged(CSSStylableElement e,
471: String name, String value, String prio) {
472: Iterator i = cssNavigableDocumentListeners.keySet().iterator();
473: while (i.hasNext()) {
474: CSSNavigableDocumentListener l = (CSSNavigableDocumentListener) i
475: .next();
476: l.overrideStylePropertyChanged(e, name, value, prio);
477: }
478: }
479:
480: /**
481: * Adds an {@link AnimatedAttributeListener} to this document, to be
482: * notified of animated XML attribute changes.
483: */
484: public void addAnimatedAttributeListener(
485: AnimatedAttributeListener aal) {
486: if (animatedAttributeListeners.contains(aal)) {
487: return;
488: }
489: animatedAttributeListeners.add(aal);
490: }
491:
492: /**
493: * Removes an {@link AnimatedAttributeListener} from this document.
494: */
495: public void removeAnimatedAttributeListener(
496: AnimatedAttributeListener aal) {
497: animatedAttributeListeners.remove(aal);
498: }
499:
500: /**
501: * DOM node inserted listener wrapper.
502: */
503: protected class DOMNodeInsertedListenerWrapper implements
504: EventListener {
505:
506: /**
507: * The CSSNavigableDocumentListener.
508: */
509: protected CSSNavigableDocumentListener listener;
510:
511: /**
512: * Creates a new DOMNodeInsertedListenerWrapper.
513: */
514: public DOMNodeInsertedListenerWrapper(
515: CSSNavigableDocumentListener l) {
516: listener = l;
517: }
518:
519: /**
520: * Handles the event.
521: */
522: public void handleEvent(Event evt) {
523: evt = EventSupport.getUltimateOriginalEvent(evt);
524: listener.nodeInserted((Node) evt.getTarget());
525: }
526: }
527:
528: /**
529: * DOM node removed listener wrapper.
530: */
531: protected class DOMNodeRemovedListenerWrapper implements
532: EventListener {
533:
534: /**
535: * The CSSNavigableDocumentListener.
536: */
537: protected CSSNavigableDocumentListener listener;
538:
539: /**
540: * Creates a new DOMNodeRemovedListenerWrapper.
541: */
542: public DOMNodeRemovedListenerWrapper(
543: CSSNavigableDocumentListener l) {
544: listener = l;
545: }
546:
547: /**
548: * Handles the event.
549: */
550: public void handleEvent(Event evt) {
551: evt = EventSupport.getUltimateOriginalEvent(evt);
552: listener.nodeToBeRemoved((Node) evt.getTarget());
553: }
554: }
555:
556: /**
557: * DOM subtree modified listener wrapper.
558: */
559: protected class DOMSubtreeModifiedListenerWrapper implements
560: EventListener {
561:
562: /**
563: * The CSSNavigableDocumentListener.
564: */
565: protected CSSNavigableDocumentListener listener;
566:
567: /**
568: * Creates a new DOMSubtreeModifiedListenerWrapper.
569: */
570: public DOMSubtreeModifiedListenerWrapper(
571: CSSNavigableDocumentListener l) {
572: listener = l;
573: }
574:
575: /**
576: * Handles the event.
577: */
578: public void handleEvent(Event evt) {
579: evt = EventSupport.getUltimateOriginalEvent(evt);
580: listener.subtreeModified((Node) evt.getTarget());
581: }
582: }
583:
584: /**
585: * DOM character data modified listener wrapper.
586: */
587: protected class DOMCharacterDataModifiedListenerWrapper implements
588: EventListener {
589:
590: /**
591: * The CSSNavigableDocumentListener.
592: */
593: protected CSSNavigableDocumentListener listener;
594:
595: /**
596: * Creates a new DOMCharacterDataModifiedListenerWrapper.
597: */
598: public DOMCharacterDataModifiedListenerWrapper(
599: CSSNavigableDocumentListener l) {
600: listener = l;
601: }
602:
603: /**
604: * Handles the event.
605: */
606: public void handleEvent(Event evt) {
607: evt = EventSupport.getUltimateOriginalEvent(evt);
608: listener.subtreeModified((Node) evt.getTarget());
609: }
610: }
611:
612: /**
613: * DOM attribute modified listener wrapper.
614: */
615: protected class DOMAttrModifiedListenerWrapper implements
616: EventListener {
617:
618: /**
619: * The CSSNavigableDocumentListener.
620: */
621: protected CSSNavigableDocumentListener listener;
622:
623: /**
624: * Creates a new DOMAttrModifiedListenerWrapper.
625: */
626: public DOMAttrModifiedListenerWrapper(
627: CSSNavigableDocumentListener l) {
628: listener = l;
629: }
630:
631: /**
632: * Handles the event.
633: */
634: public void handleEvent(Event evt) {
635: evt = EventSupport.getUltimateOriginalEvent(evt);
636: MutationEvent mevt = (MutationEvent) evt;
637: listener.attrModified((Element) evt.getTarget(),
638: (Attr) mevt.getRelatedNode(), mevt.getAttrChange(),
639: mevt.getPrevValue(), mevt.getNewValue());
640: }
641: }
642:
643: /**
644: * Listener class for animated attribute changes.
645: */
646: protected class AnimAttrListener implements
647: AnimatedAttributeListener {
648:
649: /**
650: * Called to notify an object of a change to the animated value of
651: * an animatable XML attribute.
652: * @param e the owner element of the changed animatable attribute
653: * @param alav the AnimatedLiveAttributeValue that changed
654: */
655: public void animatedAttributeChanged(Element e,
656: AnimatedLiveAttributeValue alav) {
657: Iterator i = animatedAttributeListeners.iterator();
658: while (i.hasNext()) {
659: AnimatedAttributeListener aal = (AnimatedAttributeListener) i
660: .next();
661: aal.animatedAttributeChanged(e, alav);
662: }
663: }
664:
665: /**
666: * Called to notify an object of a change to the value of an 'other'
667: * animation.
668: * @param e the element being animated
669: * @param type the type of animation whose value changed
670: */
671: public void otherAnimationChanged(Element e, String type) {
672: Iterator i = animatedAttributeListeners.iterator();
673: while (i.hasNext()) {
674: AnimatedAttributeListener aal = (AnimatedAttributeListener) i
675: .next();
676: aal.otherAnimationChanged(e, type);
677: }
678: }
679: }
680:
681: // DocumentCSS ////////////////////////////////////////////////////////////
682:
683: /**
684: * <b>DOM</b>: Implements
685: * {@link DocumentCSS#getOverrideStyle(Element,String)}.
686: */
687: public CSSStyleDeclaration getOverrideStyle(Element elt,
688: String pseudoElt) {
689: if (elt instanceof SVGStylableElement && pseudoElt == null) {
690: return ((SVGStylableElement) elt).getOverrideStyle();
691: }
692: return null;
693: }
694:
695: // AbstractDocument ///////////////////////////////////////////////
696:
697: /**
698: * Tests whether this node is readonly.
699: */
700: public boolean isReadonly() {
701: return readonly;
702: }
703:
704: /**
705: * Sets this node readonly attribute.
706: */
707: public void setReadonly(boolean v) {
708: readonly = v;
709: }
710:
711: /**
712: * Returns a new uninitialized instance of this object's class.
713: */
714: protected Node newNode() {
715: return new SVGOMDocument();
716: }
717:
718: /**
719: * Copy the fields of the current node into the given node.
720: * @param n a node of the type of this.
721: */
722: protected Node copyInto(Node n) {
723: super .copyInto(n);
724: SVGOMDocument sd = (SVGOMDocument) n;
725: sd.localizableSupport = new LocalizableSupport(RESOURCES,
726: getClass().getClassLoader());
727: sd.referrer = referrer;
728: sd.url = url;
729: return n;
730: }
731:
732: /**
733: * Deeply copy the fields of the current node into the given node.
734: * @param n a node of the type of this.
735: */
736: protected Node deepCopyInto(Node n) {
737: super .deepCopyInto(n);
738: SVGOMDocument sd = (SVGOMDocument) n;
739: sd.localizableSupport = new LocalizableSupport(RESOURCES,
740: getClass().getClassLoader());
741: sd.referrer = referrer;
742: sd.url = url;
743: return n;
744: }
745:
746: // Serialization //////////////////////////////////////////////////////
747:
748: /**
749: * Reads the object from the given stream.
750: */
751: private void readObject(ObjectInputStream s) throws IOException,
752: ClassNotFoundException {
753: s.defaultReadObject();
754:
755: localizableSupport = new LocalizableSupport(RESOURCES,
756: getClass().getClassLoader());
757: }
758: }
|