/* * SVG Salamander * Copyright (c) 2004, Mark McKay * All rights reserved. * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the following * disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * * Mark McKay can be contacted at mark@kitfox.com. Salamander and other * projects can be found at http://www.kitfox.com * * Created on February 18, 2004, 1:49 PM */ package com.kitfox.svg.xml; import java.awt.Toolkit; //import java.lang.reflect.Array; import java.net.URL; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; import com.kitfox.svg.SVGConst; /** * @author Mark McKay * @author <a href="mailto:mark@kitfox.com">Mark McKay</a> */ public class XMLParseUtil { static final Matcher fpMatch = Pattern .compile( "([-+]?((\\d*\\.\\d+)|(\\d+))([eE][+-]?\\d+)?)(\\%|in|cm|mm|pt|pc|px|em|ex)?") .matcher(""); static final Matcher intMatch = Pattern.compile("[-+]?\\d+").matcher(""); /** Creates a new instance of XMLParseUtil */ private XMLParseUtil() { } /** * Scans the tag's children and returns the first text element found */ public static String getTagText(Element ele) { NodeList nl = ele.getChildNodes(); int size = nl.getLength(); Node node = null; int i = 0; for (; i < size; i++) { node = nl.item(i); if (node instanceof Text) { break; } } if (i == size || node == null) { return null; } return ((Text) node).getData(); } /** * Returns the first node that is a direct child of root with the * coresponding name. Does not search children of children. */ public static Element getFirstChild(Element root, String name) { NodeList nl = root.getChildNodes(); int size = nl.getLength(); for (int i = 0; i < size; i++) { Node node = nl.item(i); if (!(node instanceof Element)) { continue; } Element ele = (Element) node; if (ele.getTagName().equals(name)) { return ele; } } return null; } public static String[] parseStringList(String list) { // final Pattern patWs = Pattern.compile("\\s+"); final Matcher matchWs = Pattern.compile("[^\\s]+").matcher(""); matchWs.reset(list); LinkedList matchList = new LinkedList(); while (matchWs.find()) { matchList.add(matchWs.group()); } String[] retArr = new String[matchList.size()]; return (String[]) matchList.toArray(retArr); } public static boolean isDouble(String val) { fpMatch.reset(val); return fpMatch.matches(); } public static double parseDouble(String val) { /* * if (val == null) return 0.0; * * double retVal = 0.0; try { retVal = Double.parseDouble(val); } catch * (Exception e) {} return retVal; */ return findDouble(val); } /** * Searches the given string for the first floating point number it * contains, parses and returns it. */ public synchronized static double findDouble(String val) { if (val == null) { return 0; } fpMatch.reset(val); try { if (!fpMatch.find()) { return 0; } } catch (StringIndexOutOfBoundsException e) { Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, "XMLParseUtil: regex parse problem: '" + val + "'", e); } val = fpMatch.group(1); // System.err.println("Parsing " + val); double retVal = 0; try { retVal = Double.parseDouble(val); float pixPerInch; try { pixPerInch = Toolkit.getDefaultToolkit() .getScreenResolution(); } catch (NoClassDefFoundError err) { // Default value for headless X servers pixPerInch = 72; } final float inchesPerCm = .3936f; final String units = fpMatch.group(6); if ("%".equals(units)) { retVal /= 100; } else if ("in".equals(units)) { retVal *= pixPerInch; } else if ("cm".equals(units)) { retVal *= inchesPerCm * pixPerInch; } else if ("mm".equals(units)) { retVal *= inchesPerCm * pixPerInch * .1f; } else if ("pt".equals(units)) { retVal *= (1f / 72f) * pixPerInch; } else if ("pc".equals(units)) { retVal *= (1f / 6f) * pixPerInch; } } catch (Exception e) { } return retVal; } /** * Scans an input string for double values. For each value found, places in * a list. This method regards any characters not part of a floating point * value to be seperators. Thus this will parse whitespace seperated, comma * seperated, and many other separation schemes correctly. */ public synchronized static double[] parseDoubleList(String list) { if (list == null) { return null; } fpMatch.reset(list); LinkedList doubList = new LinkedList(); while (fpMatch.find()) { String val = fpMatch.group(1); doubList.add(Double.valueOf(val)); } double[] retArr = new double[doubList.size()]; Iterator it = doubList.iterator(); int idx = 0; while (it.hasNext()) { retArr[idx++] = ((Double) it.next()).doubleValue(); } return retArr; } public static float parseFloat(String val) { /* * if (val == null) return 0f; * * float retVal = 0f; try { retVal = Float.parseFloat(val); } catch * (Exception e) {} return retVal; */ return findFloat(val); } /** * Searches the given string for the first floating point number it * contains, parses and returns it. */ public synchronized static float findFloat(String val) { if (val == null) { return 0f; } fpMatch.reset(val); if (!fpMatch.find()) { return 0f; } val = fpMatch.group(1); // System.err.println("Parsing " + val); float retVal = 0f; try { retVal = Float.parseFloat(val); String units = fpMatch.group(6); if ("%".equals(units)) { retVal /= 100; } } catch (Exception e) { } return retVal; } public synchronized static float[] parseFloatList(String list) { if (list == null) { return null; } fpMatch.reset(list); LinkedList floatList = new LinkedList(); while (fpMatch.find()) { String val = fpMatch.group(1); floatList.add(Float.valueOf(val)); } float[] retArr = new float[floatList.size()]; Iterator it = floatList.iterator(); int idx = 0; while (it.hasNext()) { retArr[idx++] = ((Float) it.next()).floatValue(); } return retArr; } public static int parseInt(String val) { if (val == null) { return 0; } int retVal = 0; try { retVal = Integer.parseInt(val); } catch (Exception e) { } return retVal; } /** * Searches the given string for the first integer point number it contains, * parses and returns it. */ public static int findInt(String val) { if (val == null) { return 0; } intMatch.reset(val); if (!intMatch.find()) { return 0; } val = intMatch.group(); // System.err.println("Parsing " + val); int retVal = 0; try { retVal = Integer.parseInt(val); } catch (Exception e) { } return retVal; } public static int[] parseIntList(String list) { if (list == null) { return null; } intMatch.reset(list); LinkedList intList = new LinkedList(); while (intMatch.find()) { String val = intMatch.group(); intList.add(Integer.valueOf(val)); } int[] retArr = new int[intList.size()]; Iterator it = intList.iterator(); int idx = 0; while (it.hasNext()) { retArr[idx++] = ((Integer) it.next()).intValue(); } return retArr; } /* * public static int parseHex(String val) { int retVal = 0; * * for (int i = 0; i < val.length(); i++) { retVal <<= 4; * * char ch = val.charAt(i); if (ch >= '0' && ch <= '9') { retVal |= ch - * '0'; } else if (ch >= 'a' && ch <= 'z') { retVal |= ch - 'a' + 10; } else * if (ch >= 'A' && ch <= 'Z') { retVal |= ch - 'A' + 10; } else throw new * RuntimeException(); } * * return retVal; } */ /** * The input string represents a ratio. Can either be specified as a double * number on the range of [0.0 1.0] or as a percentage [0% 100%] */ public static double parseRatio(String val) { if (val == null || val.equals("")) { return 0.0; } if (val.charAt(val.length() - 1) == '%') { parseDouble(val.substring(0, val.length() - 1)); } return parseDouble(val); } public static NumberWithUnits parseNumberWithUnits(String val) { if (val == null) { return null; } return new NumberWithUnits(val); } /* * public static Color parseColor(String val) { Color retVal = null; * * if (val.charAt(0) == '#') { String hexStrn = val.substring(1); * * if (hexStrn.length() == 3) { hexStrn = "" + hexStrn.charAt(0) + * hexStrn.charAt(0) + hexStrn.charAt(1) + hexStrn.charAt(1) + * hexStrn.charAt(2) + hexStrn.charAt(2); } int hexVal = parseHex(hexStrn); * * retVal = new Color(hexVal); } else { final Matcher rgbMatch = * Pattern.compile("rgb\\((\\d+),(\\d+),(\\d+)\\)", * Pattern.CASE_INSENSITIVE).matcher(""); * * rgbMatch.reset(val); if (rgbMatch.matches()) { int r = * Integer.parseInt(rgbMatch.group(1)); int g = * Integer.parseInt(rgbMatch.group(2)); int b = * Integer.parseInt(rgbMatch.group(3)); retVal = new Color(r, g, b); } else * { Color lookupCol = ColorTable.instance().lookupColor(val); if (lookupCol * != null) retVal = lookupCol; } } * * return retVal; } */ /** * Parses the given attribute of this tag and returns it as a String. */ public static String getAttribString(Element ele, String name) { return ele.getAttribute(name); } /** * Parses the given attribute of this tag and returns it as an int. */ public static int getAttribInt(Element ele, String name) { String sval = ele.getAttribute(name); int val = 0; try { val = Integer.parseInt(sval); } catch (Exception e) { } return val; } /** * Parses the given attribute of this tag as a hexadecimal encoded string * and returns it as an int */ public static int getAttribIntHex(Element ele, String name) { String sval = ele.getAttribute(name); int val = 0; try { val = Integer.parseInt(sval, 16); } catch (Exception e) { } return val; } /** * Parses the given attribute of this tag and returns it as a float */ public static float getAttribFloat(Element ele, String name) { String sval = ele.getAttribute(name); float val = 0.0f; try { val = Float.parseFloat(sval); } catch (Exception e) { } return val; } /** * Parses the given attribute of this tag and returns it as a double. */ public static double getAttribDouble(Element ele, String name) { String sval = ele.getAttribute(name); double val = 0.0; try { val = Double.parseDouble(sval); } catch (Exception e) { } return val; } /** * Parses the given attribute of this tag and returns it as a boolean. * Essentially compares the lower case textual value to the string "true" */ public static boolean getAttribBoolean(Element ele, String name) { String sval = ele.getAttribute(name); return sval.toLowerCase().equals("true"); } public static URL getAttribURL(Element ele, String name, URL docRoot) { String sval = ele.getAttribute(name); try { return new URL(docRoot, sval); } catch (Exception e) { return null; } } /** * Returns the first ReadableXMLElement with the given name */ public static ReadableXMLElement getElement(Class classType, Element root, String name, URL docRoot) { if (root == null) { return null; } // Do not process if not a LoadableObject if (!ReadableXMLElement.class.isAssignableFrom(classType)) { return null; } NodeList nl = root.getChildNodes(); int size = nl.getLength(); for (int i = 0; i < size; i++) { Node node = nl.item(i); if (!(node instanceof Element)) { continue; } Element ele = (Element) node; if (!ele.getTagName().equals(name)) { continue; } ReadableXMLElement newObj = null; try { newObj = (ReadableXMLElement) classType.newInstance(); } catch (Exception e) { Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, null, e); continue; } newObj.read(ele, docRoot); if (newObj == null) { continue; } return newObj; } return null; } /** * Returns a HashMap of nodes that are children of root. All nodes will be * of class classType and have a tag name of 'name'. 'key' is an attribute * of tag 'name' who's string value will be used as the key in the HashMap */ public static HashMap getElementHashMap(Class classType, Element root, String name, String key, URL docRoot) { if (root == null) { return null; } // Do not process if not a LoadableObject if (!ReadableXMLElement.class.isAssignableFrom(classType)) { return null; } HashMap retMap = new HashMap(); NodeList nl = root.getChildNodes(); int size = nl.getLength(); for (int i = 0; i < size; i++) { Node node = nl.item(i); if (!(node instanceof Element)) { continue; } Element ele = (Element) node; if (!ele.getTagName().equals(name)) { continue; } ReadableXMLElement newObj = null; try { newObj = (ReadableXMLElement) classType.newInstance(); } catch (Exception e) { Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, null, e); continue; } newObj.read(ele, docRoot); if (newObj == null) { continue; } String keyVal = getAttribString(ele, key); retMap.put(keyVal, newObj); } return retMap; } public static HashSet getElementHashSet(Class classType, Element root, String name, URL docRoot) { if (root == null) { return null; } // Do not process if not a LoadableObject if (!ReadableXMLElement.class.isAssignableFrom(classType)) { return null; } HashSet retSet = new HashSet(); NodeList nl = root.getChildNodes(); int size = nl.getLength(); for (int i = 0; i < size; i++) { Node node = nl.item(i); if (!(node instanceof Element)) { continue; } Element ele = (Element) node; if (!ele.getTagName().equals(name)) { continue; } ReadableXMLElement newObj = null; try { newObj = (ReadableXMLElement) classType.newInstance(); } catch (Exception e) { Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, null, e); continue; } newObj.read(ele, docRoot); if (newObj == null) { continue; } retSet.add(newObj); } return retSet; } public static LinkedList getElementLinkedList(Class classType, Element root, String name, URL docRoot) { if (root == null) { return null; } // Do not process if not a LoadableObject if (!ReadableXMLElement.class.isAssignableFrom(classType)) { return null; } NodeList nl = root.getChildNodes(); LinkedList elementCache = new LinkedList(); int size = nl.getLength(); for (int i = 0; i < size; i++) { Node node = nl.item(i); if (!(node instanceof Element)) { continue; } Element ele = (Element) node; if (!ele.getTagName().equals(name)) { continue; } ReadableXMLElement newObj = null; try { newObj = (ReadableXMLElement) classType.newInstance(); } catch (Exception e) { Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, null, e); continue; } newObj.read(ele, docRoot); elementCache.addLast(newObj); } return elementCache; } // public static Object[] getElementArray(Class classType, Element root, // String name, URL docRoot) // { // if (root == null) return null; // // //Do not process if not a LoadableObject // if (!ReadableXMLElement.class.isAssignableFrom(classType)) // { // return null; // } // // LinkedList elementCache = getElementLinkedList(classType, root, name, // docRoot); // // Object[] retArr = (Object[])Array.newInstance(classType, // elementCache.size()); // return elementCache.toArray(retArr); // } /** * Takes a number of tags of name 'name' that are children of 'root', and * looks for attributes of 'attrib' on them. Converts attributes to an int * and returns in an array. */ public static int[] getElementArrayInt(Element root, String name, String attrib) { if (root == null) { return null; } NodeList nl = root.getChildNodes(); LinkedList elementCache = new LinkedList(); int size = nl.getLength(); for (int i = 0; i < size; i++) { Node node = nl.item(i); if (!(node instanceof Element)) { continue; } Element ele = (Element) node; if (!ele.getTagName().equals(name)) { continue; } String valS = ele.getAttribute(attrib); int eleVal = 0; try { eleVal = Integer.parseInt(valS); } catch (Exception e) { } elementCache.addLast(Integer.valueOf(eleVal)); } int[] retArr = new int[elementCache.size()]; Iterator it = elementCache.iterator(); int idx = 0; while (it.hasNext()) { retArr[idx++] = ((Integer) it.next()).intValue(); } return retArr; } /** * Takes a number of tags of name 'name' that are children of 'root', and * looks for attributes of 'attrib' on them. Converts attributes to an int * and returns in an array. */ public static String[] getElementArrayString(Element root, String name, String attrib) { if (root == null) { return null; } NodeList nl = root.getChildNodes(); LinkedList elementCache = new LinkedList(); int size = nl.getLength(); for (int i = 0; i < size; i++) { Node node = nl.item(i); if (!(node instanceof Element)) { continue; } Element ele = (Element) node; if (!ele.getTagName().equals(name)) { continue; } String valS = ele.getAttribute(attrib); elementCache.addLast(valS); } String[] retArr = new String[elementCache.size()]; Iterator it = elementCache.iterator(); int idx = 0; while (it.hasNext()) { retArr[idx++] = (String) it.next(); } return retArr; } /** * Takes a CSS style string and retursn a hash of them. * * @param styleString * - A CSS formatted string of styles. Eg, * "font-size:12;fill:#d32c27;fill-rule:evenodd;stroke-width:1pt;" */ public static HashMap parseStyle(String styleString) { return parseStyle(styleString, new HashMap()); } /** * Takes a CSS style string and returns a hash of them. * * @param styleString * - A CSS formatted string of styles. Eg, * "font-size:12;fill:#d32c27;fill-rule:evenodd;stroke-width:1pt;" * @param map * - A map to which these styles will be added */ public static HashMap parseStyle(String styleString, HashMap map) { final Pattern patSemi = Pattern.compile(";"); String[] styles = patSemi.split(styleString); for (int i = 0; i < styles.length; i++) { if (styles[i].length() == 0) { continue; } int colon = styles[i].indexOf(':'); if (colon == -1) { continue; } String key = styles[i].substring(0, colon).trim(); String value = styles[i].substring(colon + 1).trim(); map.put(key, new StyleAttribute(key, value)); } return map; } }