001/*
002 * SVG Salamander
003 * Copyright (c) 2004, Mark McKay
004 * All rights reserved.
005 *
006 * Redistribution and use in source and binary forms, with or 
007 * without modification, are permitted provided that the following
008 * conditions are met:
009 *
010 *   - Redistributions of source code must retain the above 
011 *     copyright notice, this list of conditions and the following
012 *     disclaimer.
013 *   - Redistributions in binary form must reproduce the above
014 *     copyright notice, this list of conditions and the following
015 *     disclaimer in the documentation and/or other materials 
016 *     provided with the distribution.
017 *
018 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
019 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
020 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
021 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
022 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
023 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
025 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
026 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
027 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
028 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
029 * OF THE POSSIBILITY OF SUCH DAMAGE. 
030 * 
031 * Mark McKay can be contacted at mark@kitfox.com.  Salamander and other
032 * projects can be found at http://www.kitfox.com
033 *
034 * Created on February 18, 2004, 5:09 PM
035 */
036
037package com.kitfox.svg;
038
039
040import java.util.*;
041import java.net.*;
042import org.xml.sax.*;
043import org.xml.sax.helpers.DefaultHandler;
044
045import com.kitfox.svg.animation.*;
046import java.util.logging.Level;
047import java.util.logging.Logger;
048
049/**
050 * @author Mark McKay
051 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
052 */
053public class SVGLoader extends DefaultHandler
054{
055    final HashMap<String, Class<?>> nodeClasses = new HashMap<String, Class<?>>();
056    //final HashMap attribClasses = new HashMap();
057    final LinkedList<SVGElement> buildStack = new LinkedList<SVGElement>();
058
059    final HashSet<String> ignoreClasses = new HashSet<String>();
060
061    final SVGLoaderHelper helper;
062
063    /**
064     * The diagram that represents the base of this SVG document we're loading.
065     * Will be augmented to include node indexing info and other useful stuff.
066     */
067    final SVGDiagram diagram;
068
069//    SVGElement loadRoot;
070
071    //Used to keep track of document elements that are not part of the SVG namespace
072    int skipNonSVGTagDepth = 0;
073    int indent = 0;
074
075    final boolean verbose;
076    
077    /**
078     * Creates a new instance of SVGLoader
079     * @param xmlBase
080     * @param universe
081     */
082    public SVGLoader(URI xmlBase, SVGUniverse universe)
083    {
084        this(xmlBase, universe, false);
085    }
086    
087    public SVGLoader(URI xmlBase, SVGUniverse universe, boolean verbose)
088    {
089        this.verbose = verbose;
090        
091        diagram = new SVGDiagram(xmlBase, universe);
092
093        //Compile a list of important builder classes
094        nodeClasses.put("a", A.class);
095        nodeClasses.put("animate", Animate.class);
096        nodeClasses.put("animatecolor", AnimateColor.class);
097        nodeClasses.put("animatemotion", AnimateMotion.class);
098        nodeClasses.put("animatetransform", AnimateTransform.class);
099        nodeClasses.put("circle", Circle.class);
100        nodeClasses.put("clippath", ClipPath.class);
101        nodeClasses.put("defs", Defs.class);
102        nodeClasses.put("desc", Desc.class);
103        nodeClasses.put("ellipse", Ellipse.class);
104        nodeClasses.put("filter", Filter.class);
105        nodeClasses.put("font", Font.class);
106        nodeClasses.put("font-face", FontFace.class);
107        nodeClasses.put("g", Group.class);
108        nodeClasses.put("glyph", Glyph.class);
109        nodeClasses.put("hkern", Hkern.class);
110        nodeClasses.put("image", ImageSVG.class);
111        nodeClasses.put("line", Line.class);
112        nodeClasses.put("lineargradient", LinearGradient.class);
113        nodeClasses.put("marker", Marker.class);
114        nodeClasses.put("metadata", Metadata.class);
115        nodeClasses.put("missing-glyph", MissingGlyph.class);
116        nodeClasses.put("path", Path.class);
117        nodeClasses.put("pattern", PatternSVG.class);
118        nodeClasses.put("polygon", Polygon.class);
119        nodeClasses.put("polyline", Polyline.class);
120        nodeClasses.put("radialgradient", RadialGradient.class);
121        nodeClasses.put("rect", Rect.class);
122        nodeClasses.put("set", SetSmil.class);
123        nodeClasses.put("shape", ShapeElement.class);
124        nodeClasses.put("stop", Stop.class);
125        nodeClasses.put("style", Style.class);
126        nodeClasses.put("svg", SVGRoot.class);
127        nodeClasses.put("symbol", Symbol.class);
128        nodeClasses.put("text", Text.class);
129        nodeClasses.put("title", Title.class);
130        nodeClasses.put("tspan", Tspan.class);
131        nodeClasses.put("use", Use.class);
132
133        ignoreClasses.add("midpointstop");
134
135        //attribClasses.put("clip-path", StyleUrl.class);
136        //attribClasses.put("color", StyleColor.class);
137
138        helper = new SVGLoaderHelper(xmlBase, universe, diagram);
139    }
140
141    private String printIndent(int indent, String indentStrn)
142    {
143        StringBuffer sb = new StringBuffer();
144        for (int i = 0; i < indent; i++)
145        {
146            sb.append(indentStrn);
147        }
148        return sb.toString();
149    }
150    
151    @Override
152    public void startDocument() throws SAXException
153    {
154//        System.err.println("Start doc");
155
156//        buildStack.clear();
157    }
158
159    @Override
160    public void endDocument() throws SAXException
161    {
162//        System.err.println("End doc");
163    }
164
165    @Override
166    public void startElement(String namespaceURI, String sName, String qName, Attributes attrs) throws SAXException
167    {
168        if (verbose)
169        {
170            System.err.println(printIndent(indent, " ") + "Starting parse of tag " + sName+ ": " + namespaceURI);
171        }
172        indent++;
173        
174        if (skipNonSVGTagDepth != 0 || (!namespaceURI.equals("") && !namespaceURI.equals(SVGElement.SVG_NS)))
175        {
176            skipNonSVGTagDepth++;
177            return;
178        }
179        
180        sName = sName.toLowerCase();
181
182//javax.swing.JOptionPane.showMessageDialog(null, sName);
183
184        Object obj = nodeClasses.get(sName);
185        if (obj == null)
186        {
187            if (!ignoreClasses.contains(sName))
188            {
189                if (verbose)
190                {
191                    System.err.println("SVGLoader: Could not identify tag '" + sName + "'");
192                }
193            }
194            return;
195        }
196
197//Debug info tag depth
198//for (int i = 0; i < buildStack.size(); i++) System.err.print(" ");
199//System.err.println("+" + sName);
200
201        try {
202            Class<?> cls = (Class<?>)obj;
203            SVGElement svgEle = (SVGElement)cls.newInstance();
204
205            SVGElement parent = null;
206            if (buildStack.size() != 0) parent = (SVGElement)buildStack.getLast();
207            svgEle.loaderStartElement(helper, attrs, parent);
208
209            buildStack.addLast(svgEle);
210        }
211        catch (Exception e)
212        {
213            Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, 
214                "Could not load", e);
215            throw new SAXException(e);
216        }
217
218    }
219
220    @Override
221    public void endElement(String namespaceURI, String sName, String qName)
222        throws SAXException
223    {
224        indent--;
225        if (verbose)
226        {
227            System.err.println(printIndent(indent, " ") + "Ending parse of tag " + sName+ ": " + namespaceURI);
228        }
229        
230        if (skipNonSVGTagDepth != 0)
231        {
232            skipNonSVGTagDepth--;
233            return;
234        }
235        
236        sName = sName.toLowerCase();
237
238        Object obj = nodeClasses.get(sName);
239        if (obj == null) return;
240
241//Debug info tag depth
242//for (int i = 0; i < buildStack.size(); i++) System.err.print(" ");
243//System.err.println("-" + sName);
244
245        try {
246            SVGElement svgEle = (SVGElement)buildStack.removeLast();
247
248            svgEle.loaderEndElement(helper);
249
250            SVGElement parent = null;
251            if (buildStack.size() != 0)
252            {
253                parent = (SVGElement)buildStack.getLast();
254            }
255            //else loadRoot = (SVGElement)svgEle;
256
257            if (parent != null)
258            {
259                parent.loaderAddChild(helper, svgEle);
260            }
261            else
262            {
263                diagram.setRoot((SVGRoot)svgEle);
264            }
265
266        }
267        catch (Exception e)
268        {
269            Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, 
270                "Could not parse", e);
271            throw new SAXException(e);
272        }
273    }
274
275    @Override
276    public void characters(char buf[], int offset, int len)
277        throws SAXException
278    {
279        if (skipNonSVGTagDepth != 0)
280        {
281            return;
282        }
283
284        if (buildStack.size() != 0)
285        {
286            SVGElement parent = (SVGElement)buildStack.getLast();
287            String s = new String(buf, offset, len);
288            parent.loaderAddText(helper, s);
289        }
290    }
291
292    @Override
293    public void processingInstruction(String target, String data)
294        throws SAXException
295    {
296        //Check for external style sheet
297    }
298    
299//    public SVGElement getLoadRoot() { return loadRoot; }
300    public SVGDiagram getLoadedDiagram() { return diagram; }
301}