summaryrefslogtreecommitdiffstats
path: root/src/main/java/com/kitfox/svg/xml
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/kitfox/svg/xml')
-rw-r--r--src/main/java/com/kitfox/svg/xml/ColorTable.java273
-rw-r--r--src/main/java/com/kitfox/svg/xml/NumberWithUnits.java89
-rw-r--r--src/main/java/com/kitfox/svg/xml/ReadableXMLElement.java48
-rw-r--r--src/main/java/com/kitfox/svg/xml/StyleAttribute.java290
-rw-r--r--src/main/java/com/kitfox/svg/xml/WritableXMLElement.java48
-rw-r--r--src/main/java/com/kitfox/svg/xml/XMLParseUtil.java806
-rw-r--r--src/main/java/com/kitfox/svg/xml/cpx/CPXConsts.java40
-rw-r--r--src/main/java/com/kitfox/svg/xml/cpx/CPXInputStream.java293
-rw-r--r--src/main/java/com/kitfox/svg/xml/cpx/CPXOutputStream.java174
-rw-r--r--src/main/java/com/kitfox/svg/xml/cpx/CPXTest.java100
10 files changed, 2161 insertions, 0 deletions
diff --git a/src/main/java/com/kitfox/svg/xml/ColorTable.java b/src/main/java/com/kitfox/svg/xml/ColorTable.java
new file mode 100644
index 0000000..f3db3b2
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/xml/ColorTable.java
@@ -0,0 +1,273 @@
+/*
+ * ColorTable.java
+ *
+ *
+ * The Salamander Project - 2D and 3D graphics libraries in Java
+ * Copyright (C) 2004 Mark McKay
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Mark McKay can be contacted at mark@kitfox.com. Salamander and other
+ * projects can be found at http://www.kitfox.com
+ *
+ * Created on January 26, 2004, 4:34 AM
+ */
+
+package com.kitfox.svg.xml;
+
+import java.awt.*;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class ColorTable
+{
+
+ static final Map colorTable;
+ static {
+ HashMap table = new HashMap();
+ table.put("aliceblue", new Color(0xf0f8ff));
+ table.put("antiquewhite", new Color(0xfaebd7));
+ table.put("aqua", new Color(0x00ffff));
+ table.put("aquamarine", new Color(0x7fffd4));
+ table.put("azure", new Color(0xf0ffff));
+ table.put("beige", new Color(0xf5f5dc));
+ table.put("bisque", new Color(0xffe4c4));
+ table.put("black", new Color(0x000000));
+ table.put("blanchedalmond", new Color(0xffebcd));
+ table.put("blue", new Color(0x0000ff));
+ table.put("blueviolet", new Color(0x8a2be2));
+ table.put("brown", new Color(0xa52a2a));
+ table.put("burlywood", new Color(0xdeb887));
+ table.put("cadetblue", new Color(0x5f9ea0));
+ table.put("chartreuse", new Color(0x7fff00));
+ table.put("chocolate", new Color(0xd2691e));
+ table.put("coral", new Color(0xff7f50));
+ table.put("cornflowerblue", new Color(0x6495ed));
+ table.put("cornsilk", new Color(0xfff8dc));
+ table.put("crimson", new Color(0xdc143c));
+ table.put("cyan", new Color(0x00ffff));
+ table.put("darkblue", new Color(0x00008b));
+ table.put("darkcyan", new Color(0x008b8b));
+ table.put("darkgoldenrod", new Color(0xb8860b));
+ table.put("darkgray", new Color(0xa9a9a9));
+ table.put("darkgreen", new Color(0x006400));
+ table.put("darkkhaki", new Color(0xbdb76b));
+ table.put("darkmagenta", new Color(0x8b008b));
+ table.put("darkolivegreen", new Color(0x556b2f));
+ table.put("darkorange", new Color(0xff8c00));
+ table.put("darkorchid", new Color(0x9932cc));
+ table.put("darkred", new Color(0x8b0000));
+ table.put("darksalmon", new Color(0xe9967a));
+ table.put("darkseagreen", new Color(0x8fbc8f));
+ table.put("darkslateblue", new Color(0x483d8b));
+ table.put("darkslategray", new Color(0x2f4f4f));
+ table.put("darkturquoise", new Color(0x00ced1));
+ table.put("darkviolet", new Color(0x9400d3));
+ table.put("deeppink", new Color(0xff1493));
+ table.put("deepskyblue", new Color(0x00bfff));
+ table.put("dimgray", new Color(0x696969));
+ table.put("dodgerblue", new Color(0x1e90ff));
+ table.put("feldspar", new Color(0xd19275));
+ table.put("firebrick", new Color(0xb22222));
+ table.put("floralwhite", new Color(0xfffaf0));
+ table.put("forestgreen", new Color(0x228b22));
+ table.put("fuchsia", new Color(0xff00ff));
+ table.put("gainsboro", new Color(0xdcdcdc));
+ table.put("ghostwhite", new Color(0xf8f8ff));
+ table.put("gold", new Color(0xffd700));
+ table.put("goldenrod", new Color(0xdaa520));
+ table.put("gray", new Color(0x808080));
+ table.put("green", new Color(0x008000));
+ table.put("greenyellow", new Color(0xadff2f));
+ table.put("honeydew", new Color(0xf0fff0));
+ table.put("hotpink", new Color(0xff69b4));
+ table.put("indianred", new Color(0xcd5c5c));
+ table.put("indigo", new Color(0x4b0082));
+ table.put("ivory", new Color(0xfffff0));
+ table.put("khaki", new Color(0xf0e68c));
+ table.put("lavender", new Color(0xe6e6fa));
+ table.put("lavenderblush", new Color(0xfff0f5));
+ table.put("lawngreen", new Color(0x7cfc00));
+ table.put("lemonchiffon", new Color(0xfffacd));
+ table.put("lightblue", new Color(0xadd8e6));
+ table.put("lightcoral", new Color(0xf08080));
+ table.put("lightcyan", new Color(0xe0ffff));
+ table.put("lightgoldenrodyellow", new Color(0xfafad2));
+ table.put("lightgrey", new Color(0xd3d3d3));
+ table.put("lightgreen", new Color(0x90ee90));
+ table.put("lightpink", new Color(0xffb6c1));
+ table.put("lightsalmon", new Color(0xffa07a));
+ table.put("lightseagreen", new Color(0x20b2aa));
+ table.put("lightskyblue", new Color(0x87cefa));
+ table.put("lightslateblue", new Color(0x8470ff));
+ table.put("lightslategray", new Color(0x778899));
+ table.put("lightsteelblue", new Color(0xb0c4de));
+ table.put("lightyellow", new Color(0xffffe0));
+ table.put("lime", new Color(0x00ff00));
+ table.put("limegreen", new Color(0x32cd32));
+ table.put("linen", new Color(0xfaf0e6));
+ table.put("magenta", new Color(0xff00ff));
+ table.put("maroon", new Color(0x800000));
+ table.put("mediumaquamarine", new Color(0x66cdaa));
+ table.put("mediumblue", new Color(0x0000cd));
+ table.put("mediumorchid", new Color(0xba55d3));
+ table.put("mediumpurple", new Color(0x9370d8));
+ table.put("mediumseagreen", new Color(0x3cb371));
+ table.put("mediumslateblue", new Color(0x7b68ee));
+ table.put("mediumspringgreen", new Color(0x00fa9a));
+ table.put("mediumturquoise", new Color(0x48d1cc));
+ table.put("mediumvioletred", new Color(0xc71585));
+ table.put("midnightblue", new Color(0x191970));
+ table.put("mintcream", new Color(0xf5fffa));
+ table.put("mistyrose", new Color(0xffe4e1));
+ table.put("moccasin", new Color(0xffe4b5));
+ table.put("navajowhite", new Color(0xffdead));
+ table.put("navy", new Color(0x000080));
+ table.put("oldlace", new Color(0xfdf5e6));
+ table.put("olive", new Color(0x808000));
+ table.put("olivedrab", new Color(0x6b8e23));
+ table.put("orange", new Color(0xffa500));
+ table.put("orangered", new Color(0xff4500));
+ table.put("orchid", new Color(0xda70d6));
+ table.put("palegoldenrod", new Color(0xeee8aa));
+ table.put("palegreen", new Color(0x98fb98));
+ table.put("paleturquoise", new Color(0xafeeee));
+ table.put("palevioletred", new Color(0xd87093));
+ table.put("papayawhip", new Color(0xffefd5));
+ table.put("peachpuff", new Color(0xffdab9));
+ table.put("peru", new Color(0xcd853f));
+ table.put("pink", new Color(0xffc0cb));
+ table.put("plum", new Color(0xdda0dd));
+ table.put("powderblue", new Color(0xb0e0e6));
+ table.put("purple", new Color(0x800080));
+ table.put("red", new Color(0xff0000));
+ table.put("rosybrown", new Color(0xbc8f8f));
+ table.put("royalblue", new Color(0x4169e1));
+ table.put("saddlebrown", new Color(0x8b4513));
+ table.put("salmon", new Color(0xfa8072));
+ table.put("sandybrown", new Color(0xf4a460));
+ table.put("seagreen", new Color(0x2e8b57));
+ table.put("seashell", new Color(0xfff5ee));
+ table.put("sienna", new Color(0xa0522d));
+ table.put("silver", new Color(0xc0c0c0));
+ table.put("skyblue", new Color(0x87ceeb));
+ table.put("slateblue", new Color(0x6a5acd));
+ table.put("slategray", new Color(0x708090));
+ table.put("snow", new Color(0xfffafa));
+ table.put("springgreen", new Color(0x00ff7f));
+ table.put("steelblue", new Color(0x4682b4));
+ table.put("tan", new Color(0xd2b48c));
+ table.put("teal", new Color(0x008080));
+ table.put("thistle", new Color(0xd8bfd8));
+ table.put("tomato", new Color(0xff6347));
+ table.put("turquoise", new Color(0x40e0d0));
+ table.put("violet", new Color(0xee82ee));
+ table.put("violetred", new Color(0xd02090));
+ table.put("wheat", new Color(0xf5deb3));
+ table.put("white", new Color(0xffffff));
+ table.put("whitesmoke", new Color(0xf5f5f5));
+ table.put("yellow", new Color(0xffff00));
+ table.put("yellowgreen", new Color(0x9acd32));
+
+ colorTable = Collections.unmodifiableMap(table);
+ }
+
+ static ColorTable singleton = new ColorTable();
+
+ /** Creates a new instance of ColorTable */
+ protected ColorTable() {
+// buildColorList();
+ }
+
+ static public ColorTable instance() { return singleton; }
+
+ public Color lookupColor(String name) {
+ Object obj = colorTable.get(name.toLowerCase());
+ if (obj == null) return null;
+
+ return (Color)obj;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+}
diff --git a/src/main/java/com/kitfox/svg/xml/NumberWithUnits.java b/src/main/java/com/kitfox/svg/xml/NumberWithUnits.java
new file mode 100644
index 0000000..77bb895
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/xml/NumberWithUnits.java
@@ -0,0 +1,89 @@
+/*
+ * NumberWithUnits.java
+ *
+ *
+ * The Salamander Project - 2D and 3D graphics libraries in Java
+ * Copyright (C) 2004 Mark McKay
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * 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, 2:43 PM
+ */
+
+package com.kitfox.svg.xml;
+
+import java.io.Serializable;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class NumberWithUnits implements Serializable
+{
+ public static final long serialVersionUID = 0;
+
+ public static final int UT_UNITLESS = 0;
+ public static final int UT_PX = 1; //Pixels
+ public static final int UT_CM = 2; //Centimeters
+ public static final int UT_MM = 3; //Millimeters
+ public static final int UT_IN = 4; //Inches
+ public static final int UT_EM = 5; //Default font height
+ public static final int UT_EX = 6; //Height of character 'x' in default font
+ public static final int UT_PT = 7; //Points - 1/72 of an inch
+ public static final int UT_PC = 8; //Picas - 1/6 of an inch
+ public static final int UT_PERCENT = 9; //Percent - relative width
+
+ float value = 0f;
+ int unitType = UT_UNITLESS;
+
+ /** Creates a new instance of NumberWithUnits */
+ public NumberWithUnits()
+ {
+ }
+
+ public NumberWithUnits(String value)
+ {
+ set(value);
+ }
+
+ public NumberWithUnits(float value, int unitType)
+ {
+ this.value = value;
+ this.unitType = unitType;
+ }
+
+ public float getValue() { return value; }
+ public int getUnits() { return unitType; }
+
+ public void set(String value)
+ {
+ this.value = XMLParseUtil.findFloat(value);
+ unitType = UT_UNITLESS;
+
+ if (value.indexOf("px") != -1) { unitType = UT_PX; return; }
+ if (value.indexOf("cm") != -1) { unitType = UT_CM; return; }
+ if (value.indexOf("mm") != -1) { unitType = UT_MM; return; }
+ if (value.indexOf("in") != -1) { unitType = UT_IN; return; }
+ if (value.indexOf("em") != -1) { unitType = UT_EM; return; }
+ if (value.indexOf("ex") != -1) { unitType = UT_EX; return; }
+ if (value.indexOf("pt") != -1) { unitType = UT_PT; return; }
+ if (value.indexOf("pc") != -1) { unitType = UT_PC; return; }
+ if (value.indexOf("%") != -1) { unitType = UT_PERCENT; return; }
+ }
+
+}
diff --git a/src/main/java/com/kitfox/svg/xml/ReadableXMLElement.java b/src/main/java/com/kitfox/svg/xml/ReadableXMLElement.java
new file mode 100644
index 0000000..c7416ba
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/xml/ReadableXMLElement.java
@@ -0,0 +1,48 @@
+/*
+ * LoadableObject.java
+ *
+ *
+ * The Salamander Project - 2D and 3D graphics libraries in Java
+ * Copyright (C) 2004 Mark McKay
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Mark McKay can be contacted at mark@kitfox.com. Salamander and other
+ * projects can be found at http://www.kitfox.com
+ *
+ * Created on September 1, 2003, 1:46 AM
+ */
+
+package com.kitfox.svg.xml;
+
+import org.w3c.dom.*;
+import java.net.*;
+import java.util.*;
+import java.lang.reflect.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public interface ReadableXMLElement {
+
+ /**
+ * Initializes this element from the passed DOM tree.
+ * @param root - DOM tree to build from
+ * @param docRoot - URL of the document this DOM tree was created from
+ */
+ public void read(Element root, URL docRoot);
+
+}
diff --git a/src/main/java/com/kitfox/svg/xml/StyleAttribute.java b/src/main/java/com/kitfox/svg/xml/StyleAttribute.java
new file mode 100644
index 0000000..70cf71a
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/xml/StyleAttribute.java
@@ -0,0 +1,290 @@
+/*
+ * StyleAttribute.java
+ *
+ *
+ * The Salamander Project - 2D and 3D graphics libraries in Java
+ * Copyright (C) 2004 Mark McKay
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Mark McKay can be contacted at mark@kitfox.com. Salamander and other
+ * projects can be found at http://www.kitfox.com
+ *
+ * Created on January 27, 2004, 2:53 PM
+ */
+
+package com.kitfox.svg.xml;
+
+import java.awt.*;
+import java.net.*;
+import java.io.*;
+import java.util.regex.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class StyleAttribute implements Serializable
+{
+ public static final long serialVersionUID = 0;
+
+ static final Matcher matchUrl = Pattern.compile("\\s*url\\((.*)\\)\\s*").matcher("");
+ static final Matcher matchFpNumUnits = Pattern.compile("\\s*([-+]?((\\d*\\.\\d+)|(\\d+))([-+]?[eE]\\d+)?)\\s*(px|cm|mm|in|pc|pt|em|ex)\\s*").matcher("");
+
+ String name;
+ String stringValue;
+
+ boolean colorCompatable = false;
+ boolean urlCompatable = false;
+
+ /** Creates a new instance of StyleAttribute */
+ public StyleAttribute()
+ {
+ this(null, null);
+ }
+
+ public StyleAttribute(String name)
+ {
+ this.name = name;
+ stringValue = null;
+ }
+
+ public StyleAttribute(String name, String stringValue)
+ {
+ this.name = name;
+ this.stringValue = stringValue;
+ }
+
+ public String getName() { return name; }
+ public StyleAttribute setName(String name) { this.name = name; return this; }
+
+ public String getStringValue() { return stringValue; }
+
+ public String[] getStringList()
+ {
+ return XMLParseUtil.parseStringList(stringValue);
+ }
+
+ public void setStringValue(String value)
+ {
+ stringValue = value;
+ }
+
+ public boolean getBooleanValue() {
+ return stringValue.toLowerCase().equals("true");
+ }
+
+ public int getIntValue() {
+ return XMLParseUtil.findInt(stringValue);
+ }
+
+ public int[] getIntList() {
+ return XMLParseUtil.parseIntList(stringValue);
+ }
+
+ public double getDoubleValue() {
+ return XMLParseUtil.findDouble(stringValue);
+ }
+
+ public double[] getDoubleList() {
+ return XMLParseUtil.parseDoubleList(stringValue);
+ }
+
+ public float getFloatValue() {
+ return XMLParseUtil.findFloat(stringValue);
+ }
+
+ public float[] getFloatList() {
+ return XMLParseUtil.parseFloatList(stringValue);
+ }
+
+ public float getRatioValue() {
+ return (float)XMLParseUtil.parseRatio(stringValue);
+// try { return Float.parseFloat(stringValue); }
+// catch (Exception e) {}
+// return 0f;
+ }
+
+ public String getUnits() {
+ matchFpNumUnits.reset(stringValue);
+ if (!matchFpNumUnits.matches()) return null;
+ return matchFpNumUnits.group(6);
+ }
+
+ public NumberWithUnits getNumberWithUnits() {
+ return XMLParseUtil.parseNumberWithUnits(stringValue);
+ }
+
+ public float getFloatValueWithUnits()
+ {
+ NumberWithUnits number = getNumberWithUnits();
+ return convertUnitsToPixels(number.getUnits(), number.getValue());
+ }
+
+ static public float convertUnitsToPixels(int unitType, float value)
+ {
+ if (unitType == NumberWithUnits.UT_UNITLESS || unitType == NumberWithUnits.UT_PERCENT)
+ {
+ return value;
+ }
+
+ float pixPerInch;
+ try
+ {
+ pixPerInch = (float)Toolkit.getDefaultToolkit().getScreenResolution();
+ }
+ catch (HeadlessException ex)
+ {
+ //default to 72 dpi
+ pixPerInch = 72;
+ }
+ final float inchesPerCm = .3936f;
+
+ switch (unitType)
+ {
+ case NumberWithUnits.UT_IN:
+ return value * pixPerInch;
+ case NumberWithUnits.UT_CM:
+ return value * inchesPerCm * pixPerInch;
+ case NumberWithUnits.UT_MM:
+ return value * .1f * inchesPerCm * pixPerInch;
+ case NumberWithUnits.UT_PT:
+ return value * (1f / 72f) * pixPerInch;
+ case NumberWithUnits.UT_PC:
+ return value * (1f / 6f) * pixPerInch;
+ }
+
+ return value;
+ }
+
+ public Color getColorValue()
+ {
+ return ColorTable.parseColor(stringValue);
+ }
+
+ public String parseURLFn()
+ {
+ matchUrl.reset(stringValue);
+ if (!matchUrl.matches()) return null;
+ return matchUrl.group(1);
+ }
+
+ public URL getURLValue(URL docRoot)
+ {
+ String fragment = parseURLFn();
+ if (fragment == null) return null;
+ try {
+ return new URL(docRoot, fragment);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public URL getURLValue(URI docRoot)
+ {
+ String fragment = parseURLFn();
+ if (fragment == null) return null;
+ try {
+ URI ref = docRoot.resolve(fragment);
+ return ref.toURL();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public URI getURIValue()
+ {
+ return getURIValue(null);
+ }
+
+ /**
+ * Parse this sytle attribute as a URL and return it in URI form resolved
+ * against the passed base.
+ *
+ * @param base - URI to resolve against. If null, will return value without
+ * attempting to resolve it.
+ */
+ public URI getURIValue(URI base)
+ {
+ try {
+ String fragment = parseURLFn();
+ if (fragment == null) fragment = stringValue;
+ if (fragment == null) return null;
+
+ //======================
+ //This gets around a bug in the 1.5.0 JDK
+ if (Pattern.matches("[a-zA-Z]:!\\\\.*", fragment))
+ {
+ File file = new File(fragment);
+ return file.toURI();
+ }
+ //======================
+
+ //[scheme:]scheme-specific-part[#fragment]
+
+ URI uriFrag = new URI(fragment);
+ if (uriFrag.isAbsolute())
+ {
+ //Has scheme
+ return uriFrag;
+ }
+
+ if (base == null) return uriFrag;
+
+ URI relBase = new URI(null, base.getSchemeSpecificPart(), null);
+ URI relUri;
+ if (relBase.isOpaque())
+ {
+ relUri = new URI(null, base.getSchemeSpecificPart(), uriFrag.getFragment());
+ }
+ else
+ {
+ relUri = relBase.resolve(uriFrag);
+ }
+ return new URI(base.getScheme() + ":" + relUri);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public static void main(String[] args)
+ {
+ try
+ {
+ URI uri = new URI("jar:http://www.kitfox.com/jackal/jackal.jar!/res/doc/about.svg");
+ uri = uri.resolve("#myFragment");
+
+ System.err.println(uri.toString());
+
+ uri = new URI("http://www.kitfox.com/jackal/jackal.html");
+ uri = uri.resolve("#myFragment");
+
+ System.err.println(uri.toString());
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/xml/WritableXMLElement.java b/src/main/java/com/kitfox/svg/xml/WritableXMLElement.java
new file mode 100644
index 0000000..09bf00e
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/xml/WritableXMLElement.java
@@ -0,0 +1,48 @@
+/*
+ * LoadableObject.java
+ *
+ *
+ * The Salamander Project - 2D and 3D graphics libraries in Java
+ * Copyright (C) 2004 Mark McKay
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Mark McKay can be contacted at mark@kitfox.com. Salamander and other
+ * projects can be found at http://www.kitfox.com
+ *
+ * Created on September 1, 2003, 1:46 AM
+ */
+
+package com.kitfox.svg.xml;
+
+import org.w3c.dom.*;
+import java.net.*;
+import java.util.*;
+import java.lang.reflect.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public interface WritableXMLElement {
+
+ /**
+ * Initializes this element from the passed DOM tree.
+ * @param root - DOM tree to build from
+ * @param docRoot - URL of the document this DOM tree was created from
+ */
+// public void write(Element root, URL docRoot);
+
+}
diff --git a/src/main/java/com/kitfox/svg/xml/XMLParseUtil.java b/src/main/java/com/kitfox/svg/xml/XMLParseUtil.java
new file mode 100644
index 0000000..b9cbec0
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/xml/XMLParseUtil.java
@@ -0,0 +1,806 @@
+/*
+ * XMLParseUtil.java
+ *
+ *
+ * The Salamander Project - 2D and 3D graphics libraries in Java
+ * Copyright (C) 2004 Mark McKay
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * 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 org.w3c.dom.*;
+import java.awt.*;
+import java.net.*;
+import java.util.*;
+import java.util.regex.*;
+import java.lang.reflect.*;
+
+/**
+ * @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)?").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)
+ {
+ System.err.println("XMLParseUtil: regex parse problem: '" + val + "'");
+ e.printStackTrace();
+ }
+
+ val = fpMatch.group(1);
+ //System.err.println("Parsing " + val);
+
+ double retVal = 0;
+ try
+ {
+ retVal = Double.parseDouble(val);
+
+ float pixPerInch = (float)Toolkit.getDefaultToolkit().getScreenResolution();
+ 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);
+ if (fpMatch.group(6).equals("%")) 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);
+
+ URL url;
+ 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) { e.printStackTrace(); 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();
+
+/*
+ Class[] params = {Element.class, URL.class};
+ Method loadMethod = null;
+ try { loadMethod = classType.getMethod("load", params); }
+ catch (Exception e) { e.printStackTrace(); 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) { e.printStackTrace(); continue; }
+ newObj.read(ele, docRoot);
+/*
+ Object[] args = {ele, source};
+ Object obj = null;
+ try { obj = loadMethod.invoke(null, args); }
+ catch (Exception e) { e.printStackTrace(); }
+
+ */
+ 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();
+
+ /*
+ Class[] params = {Element.class, URL.class};
+ Method loadMethod = null;
+ try { loadMethod = classType.getMethod("load", params); }
+ catch (Exception e) { e.printStackTrace(); 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) { e.printStackTrace(); continue; }
+ newObj.read(ele, docRoot);
+ /*
+ Object[] args = {ele, source};
+ Object obj = null;
+ try { obj = loadMethod.invoke(null, args); }
+ catch (Exception e) { e.printStackTrace(); }
+ */
+
+ 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) { e.printStackTrace(); 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(new Integer(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 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;"
+ * @param map - A map to which these styles will be added
+ */
+ public static HashMap parseStyle(String styleString, HashMap map) {
+ final Pattern patSemi = Pattern.compile(";");
+ final Pattern patColonSpace = Pattern.compile(":");
+
+ //Strips left and right whitespace
+ final Matcher matcherContent = Pattern.compile("\\s*([^\\s](.*[^\\s])?)\\s*").matcher("");
+
+ String[] styles = patSemi.split(styleString);
+
+ for (int i = 0; i < styles.length; i++) {
+ String[] vals = patColonSpace.split(styles[i]);
+
+ matcherContent.reset(vals[0]);
+ matcherContent.matches();
+ vals[0] = matcherContent.group(1);
+
+ matcherContent.reset(vals[1]);
+ matcherContent.matches();
+ vals[1] = matcherContent.group(1);
+
+ map.put(vals[0], new StyleAttribute(vals[0], vals[1]));
+ }
+
+ return map;
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/xml/cpx/CPXConsts.java b/src/main/java/com/kitfox/svg/xml/cpx/CPXConsts.java
new file mode 100644
index 0000000..6cbcaba
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/xml/cpx/CPXConsts.java
@@ -0,0 +1,40 @@
+/*
+ * CPXConst.java
+ *
+ *
+ * The Salamander Project - 2D and 3D graphics libraries in Java
+ * Copyright (C) 2004 Mark McKay
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Mark McKay can be contacted at mark@kitfox.com. Salamander and other
+ * projects can be found at http://www.kitfox.com
+ *
+ * Created on February 12, 2004, 12:51 PM
+ */
+
+package com.kitfox.svg.xml.cpx;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public interface CPXConsts {
+
+ static final byte[] MAGIC_NUMBER = {'C', 'P', 'X', 0};
+
+ static final int XL_PLAIN = 0;
+ static final int XL_ZIP_CRYPT = 1;
+}
diff --git a/src/main/java/com/kitfox/svg/xml/cpx/CPXInputStream.java b/src/main/java/com/kitfox/svg/xml/cpx/CPXInputStream.java
new file mode 100644
index 0000000..1e6ee36
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/xml/cpx/CPXInputStream.java
@@ -0,0 +1,293 @@
+/*
+ * CPXInputStream.java
+ *
+ *
+ * The Salamander Project - 2D and 3D graphics libraries in Java
+ * Copyright (C) 2004 Mark McKay
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Mark McKay can be contacted at mark@kitfox.com. Salamander and other
+ * projects can be found at http://www.kitfox.com
+ *
+ * Created on February 12, 2004, 10:34 AM
+ */
+
+package com.kitfox.svg.xml.cpx;
+
+import java.io.*;
+import java.util.*;
+import java.util.zip.*;
+import java.security.*;
+import javax.crypto.*;
+
+/**
+ * This class reads/decodes the CPX file format. This format is a simple
+ * compression/encryption transformer for XML data. This stream takes in
+ * encrypted XML and outputs decrypted. It does this by checking for a magic
+ * number at the start of the stream. If absent, it treats the stream as
+ * raw XML data and passes it through unaltered. This is to aid development
+ * in debugging versions, where the XML files will not be in CPX format.
+ *
+ * See http://java.sun.com/developer/technicalArticles/Security/Crypto/
+ *
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class CPXInputStream extends FilterInputStream implements CPXConsts {
+
+
+ SecureRandom sec = new SecureRandom();
+
+ Inflater inflater = new Inflater();
+
+ int xlateMode;
+
+ //Keep header bytes in case this stream turns out to be plain text
+ byte[] head = new byte[4];
+ int headSize = 0;
+ int headPtr = 0;
+
+ boolean reachedEOF = false;
+ byte[] inBuffer = new byte[2048];
+ byte[] decryptBuffer = new byte[2048];
+
+ /** Creates a new instance of CPXInputStream */
+ public CPXInputStream(InputStream in) throws IOException {
+ super(in);
+
+ //Determine processing type
+ for (int i = 0; i < 4; i++)
+ {
+ int val = in.read();
+ head[i] = (byte)val;
+ if (val == -1 || head[i] != MAGIC_NUMBER[i])
+ {
+ headSize = i + 1;
+ xlateMode = XL_PLAIN;
+ return;
+ }
+ }
+
+ xlateMode = XL_ZIP_CRYPT;
+ }
+
+ /**
+ * We do not allow marking
+ */
+ public boolean markSupported() { return false; }
+
+ /**
+ * Closes this input stream and releases any system resources
+ * associated with the stream.
+ * This
+ * method simply performs <code>in.close()</code>.
+ *
+ * @exception IOException if an I/O error occurs.
+ * @see java.io.FilterInputStream#in
+ */
+ public void close() throws IOException {
+ reachedEOF = true;
+ in.close();
+ }
+
+ /**
+ * Reads the next byte of data from this input stream. The value
+ * byte is returned as an <code>int</code> in the range
+ * <code>0</code> to <code>255</code>. If no byte is available
+ * because the end of the stream has been reached, the value
+ * <code>-1</code> is returned. This method blocks until input data
+ * is available, the end of the stream is detected, or an exception
+ * is thrown.
+ * <p>
+ * This method
+ * simply performs <code>in.read()</code> and returns the result.
+ *
+ * @return the next byte of data, or <code>-1</code> if the end of the
+ * stream is reached.
+ * @exception IOException if an I/O error occurs.
+ * @see java.io.FilterInputStream#in
+ */
+ public int read() throws IOException
+ {
+ final byte[] b = new byte[1];
+ int retVal = read(b, 0, 1);
+ if (retVal == -1) return -1;
+ return b[0];
+ }
+
+ /**
+ * Reads up to <code>byte.length</code> bytes of data from this
+ * input stream into an array of bytes. This method blocks until some
+ * input is available.
+ * <p>
+ * This method simply performs the call
+ * <code>read(b, 0, b.length)</code> and returns
+ * the result. It is important that it does
+ * <i>not</i> do <code>in.read(b)</code> instead;
+ * certain subclasses of <code>FilterInputStream</code>
+ * depend on the implementation strategy actually
+ * used.
+ *
+ * @param b the buffer into which the data is read.
+ * @return the total number of bytes read into the buffer, or
+ * <code>-1</code> if there is no more data because the end of
+ * the stream has been reached.
+ * @exception IOException if an I/O error occurs.
+ * @see java.io.FilterInputStream#read(byte[], int, int)
+ */
+ public int read(byte[] b) throws IOException
+ {
+ return read(b, 0, b.length);
+ }
+
+ /**
+ * Reads up to <code>len</code> bytes of data from this input stream
+ * into an array of bytes. This method blocks until some input is
+ * available.
+ * <p>
+ * This method simply performs <code>in.read(b, off, len)</code>
+ * and returns the result.
+ *
+ * @param b the buffer into which the data is read.
+ * @param off the start offset of the data.
+ * @param len the maximum number of bytes read.
+ * @return the total number of bytes read into the buffer, or
+ * <code>-1</code> if there is no more data because the end of
+ * the stream has been reached.
+ * @exception IOException if an I/O error occurs.
+ * @see java.io.FilterInputStream#in
+ */
+ public int read(byte[] b, int off, int len) throws IOException
+ {
+ if (reachedEOF) return -1;
+
+ if (xlateMode == XL_PLAIN)
+ {
+ int count = 0;
+ //Write header if appropriate
+ while (headPtr < headSize && len > 0)
+ {
+ b[off++] = head[headPtr++];
+ count++;
+ len--;
+ }
+
+ return (len == 0) ? count : count + in.read(b, off, len);
+ }
+
+ //Decrypt and inflate
+ if (inflater.needsInput() && !decryptChunk())
+ {
+ reachedEOF = true;
+
+ //Read remaining bytes
+ int numRead;
+ try {
+ numRead = inflater.inflate(b, off, len);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ return -1;
+ }
+
+ if (!inflater.finished())
+ {
+ new Exception("Inflation incomplete").printStackTrace();
+ }
+
+ return numRead == 0 ? -1 : numRead;
+ }
+
+ try {
+ return inflater.inflate(b, off, len);
+ }
+ catch (DataFormatException e)
+ {
+ e.printStackTrace();
+ return -1;
+ }
+ }
+
+
+ /**
+ * Call when inflater indicates that it needs more bytes.
+ * @return - true if we decrypted more bytes to deflate, false if we
+ * encountered the end of stream
+ */
+ protected boolean decryptChunk() throws IOException
+ {
+ while (inflater.needsInput())
+ {
+ int numInBytes = in.read(inBuffer);
+ if (numInBytes == -1) return false;
+// int numDecryptBytes = cipher.update(inBuffer, 0, numInBytes, decryptBuffer);
+// inflater.setInput(decryptBuffer, 0, numDecryptBytes);
+inflater.setInput(inBuffer, 0, numInBytes);
+ }
+
+ return true;
+ }
+
+ /**
+ * This method returns 1 if we've not reached EOF, 0 if we have. Programs
+ * should not rely on this to determine the number of bytes that can be
+ * read without blocking.
+ */
+ public int available() { return reachedEOF ? 0 : 1; }
+
+ /**
+ * Skips bytes by reading them into a cached buffer
+ */
+ public long skip(long n) throws IOException
+ {
+ int skipSize = (int)n;
+ if (skipSize > inBuffer.length) skipSize = inBuffer.length;
+ return read(inBuffer, 0, skipSize);
+ }
+
+}
+
+/*
+ import java.security.KeyPairGenerator;
+ import java.security.KeyPair;
+ import java.security.KeyPairGenerator;
+ import java.security.PrivateKey;
+ import java.security.PublicKey;
+ import java.security.SecureRandom;
+ import java.security.Cipher;
+
+ ....
+
+ java.security.Security.addProvider(new cryptix.provider.Cryptix());
+
+ SecureRandom random = new SecureRandom(SecureRandom.getSeed(30));
+ KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA");
+ keygen.initialize(1024, random);
+ keypair = keygen.generateKeyPair();
+
+ PublicKey pubkey = keypair.getPublic();
+ PrivateKey privkey = keypair.getPrivate();
+ */
+
+/*
+ *
+ *Generate key pairs
+KeyPairGenerator keyGen =
+ KeyPairGenerator.getInstance("DSA");
+KeyGen.initialize(1024, new SecureRandom(userSeed));
+KeyPair pair = KeyGen.generateKeyPair();
+ */ \ No newline at end of file
diff --git a/src/main/java/com/kitfox/svg/xml/cpx/CPXOutputStream.java b/src/main/java/com/kitfox/svg/xml/cpx/CPXOutputStream.java
new file mode 100644
index 0000000..f129cfb
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/xml/cpx/CPXOutputStream.java
@@ -0,0 +1,174 @@
+/*
+ * CPXOutputStream.java
+ *
+ *
+ * The Salamander Project - 2D and 3D graphics libraries in Java
+ * Copyright (C) 2004 Mark McKay
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Mark McKay can be contacted at mark@kitfox.com. Salamander and other
+ * projects can be found at http://www.kitfox.com
+ *
+ * Created on February 12, 2004, 12:50 PM
+ */
+
+package com.kitfox.svg.xml.cpx;
+
+import java.io.*;
+import java.util.zip.*;
+import java.security.*;
+import javax.crypto.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class CPXOutputStream extends FilterOutputStream implements CPXConsts {
+
+ Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION);
+
+ /** Creates a new instance of CPXOutputStream */
+ public CPXOutputStream(OutputStream os) throws IOException {
+ super(os);
+
+ //Write magic number
+ os.write(MAGIC_NUMBER);
+ }
+
+ /**
+ * Writes the specified <code>byte</code> to this output stream.
+ * <p>
+ * The <code>write</code> method of <code>FilterOutputStream</code>
+ * calls the <code>write</code> method of its underlying output stream,
+ * that is, it performs <tt>out.write(b)</tt>.
+ * <p>
+ * Implements the abstract <tt>write</tt> method of <tt>OutputStream</tt>.
+ *
+ * @param b the <code>byte</code>.
+ * @exception IOException if an I/O error occurs.
+ */
+ public void write(int b) throws IOException {
+ final byte[] buf = new byte[1];
+ buf[0] = (byte)b;
+ write(buf, 0, 1);
+ }
+
+ /**
+ * Writes <code>b.length</code> bytes to this output stream.
+ * <p>
+ * The <code>write</code> method of <code>FilterOutputStream</code>
+ * calls its <code>write</code> method of three arguments with the
+ * arguments <code>b</code>, <code>0</code>, and
+ * <code>b.length</code>.
+ * <p>
+ * Note that this method does not call the one-argument
+ * <code>write</code> method of its underlying stream with the single
+ * argument <code>b</code>.
+ *
+ * @param b the data to be written.
+ * @exception IOException if an I/O error occurs.
+ * @see java.io.FilterOutputStream#write(byte[], int, int)
+ */
+ public void write(byte b[]) throws IOException {
+ write(b, 0, b.length);
+ }
+
+ byte[] deflateBuffer = new byte[2048];
+
+ /**
+ * Writes <code>len</code> bytes from the specified
+ * <code>byte</code> array starting at offset <code>off</code> to
+ * this output stream.
+ * <p>
+ * The <code>write</code> method of <code>FilterOutputStream</code>
+ * calls the <code>write</code> method of one argument on each
+ * <code>byte</code> to output.
+ * <p>
+ * Note that this method does not call the <code>write</code> method
+ * of its underlying input stream with the same arguments. Subclasses
+ * of <code>FilterOutputStream</code> should provide a more efficient
+ * implementation of this method.
+ *
+ * @param b the data.
+ * @param off the start offset in the data.
+ * @param len the number of bytes to write.
+ * @exception IOException if an I/O error occurs.
+ * @see java.io.FilterOutputStream#write(int)
+ */
+ public void write(byte b[], int off, int len) throws IOException
+ {
+ deflater.setInput(b, off, len);
+
+ processAllData();
+ /*
+ int numDeflatedBytes;
+ while ((numDeflatedBytes = deflater.deflate(deflateBuffer)) != 0)
+ {
+// byte[] cipherBuf = cipher.update(deflateBuffer, 0, numDeflatedBytes);
+// out.write(cipherBytes);
+out.write(deflateBuffer, 0, numDeflatedBytes);
+ }
+ */
+ }
+
+ protected void processAllData() throws IOException
+ {
+ int numDeflatedBytes;
+ while ((numDeflatedBytes = deflater.deflate(deflateBuffer)) != 0)
+ {
+// byte[] cipherBuf = cipher.update(deflateBuffer, 0, numDeflatedBytes);
+// out.write(cipherBytes);
+out.write(deflateBuffer, 0, numDeflatedBytes);
+ }
+ }
+
+ /**
+ * Flushes this output stream and forces any buffered output bytes
+ * to be written out to the stream.
+ * <p>
+ * The <code>flush</code> method of <code>FilterOutputStream</code>
+ * calls the <code>flush</code> method of its underlying output stream.
+ *
+ * @exception IOException if an I/O error occurs.
+ * @see java.io.FilterOutputStream#out
+ */
+ public void flush() throws IOException {
+ out.flush();
+ }
+
+ /**
+ * Closes this output stream and releases any system resources
+ * associated with the stream.
+ * <p>
+ * The <code>close</code> method of <code>FilterOutputStream</code>
+ * calls its <code>flush</code> method, and then calls the
+ * <code>close</code> method of its underlying output stream.
+ *
+ * @exception IOException if an I/O error occurs.
+ * @see java.io.FilterOutputStream#flush()
+ * @see java.io.FilterOutputStream#out
+ */
+ public void close() throws IOException {
+ deflater.finish();
+ processAllData();
+
+ try {
+ flush();
+ } catch (IOException ignored) {
+ }
+ out.close();
+ }
+}
diff --git a/src/main/java/com/kitfox/svg/xml/cpx/CPXTest.java b/src/main/java/com/kitfox/svg/xml/cpx/CPXTest.java
new file mode 100644
index 0000000..1b6954a
--- /dev/null
+++ b/src/main/java/com/kitfox/svg/xml/cpx/CPXTest.java
@@ -0,0 +1,100 @@
+/*
+ * CPXTest.java
+ *
+ *
+ * The Salamander Project - 2D and 3D graphics libraries in Java
+ * Copyright (C) 2004 Mark McKay
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Mark McKay can be contacted at mark@kitfox.com. Salamander and other
+ * projects can be found at http://www.kitfox.com
+ *
+ * Created on February 12, 2004, 2:45 PM
+ */
+
+package com.kitfox.svg.xml.cpx;
+
+import java.io.*;
+import java.net.*;
+
+/**
+ * @author Mark McKay
+ * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
+ */
+public class CPXTest {
+
+ /** Creates a new instance of CPXTest */
+ public CPXTest() {
+
+// FileInputStream fin = new FileInputStream();
+ writeTest();
+ readTest();
+ }
+
+ public void writeTest()
+ {
+ try {
+
+ InputStream is = CPXTest.class.getResourceAsStream("/data/readme.txt");
+//System.err.println("Is " + is);
+
+ FileOutputStream fout = new FileOutputStream("C:\\tmp\\cpxFile.cpx");
+ CPXOutputStream cout = new CPXOutputStream(fout);
+
+ byte[] buffer = new byte[1024];
+ int numBytes;
+ while ((numBytes = is.read(buffer)) != -1)
+ {
+ cout.write(buffer, 0, numBytes);
+ }
+ cout.close();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public void readTest()
+ {
+ try {
+
+// InputStream is = CPXTest.class.getResourceAsStream("/rawdata/test/cpx/text.txt");
+// InputStream is = CPXTest.class.getResourceAsStream("/rawdata/test/cpx/cpxFile.cpx");
+ FileInputStream is = new FileInputStream("C:\\tmp\\cpxFile.cpx");
+ CPXInputStream cin = new CPXInputStream(is);
+
+ BufferedReader br = new BufferedReader(new InputStreamReader(cin));
+ String line;
+ while ((line = br.readLine()) != null)
+ {
+ System.err.println(line);
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * @param args the command line arguments
+ */
+ public static void main(String[] args) {
+ new CPXTest();
+ }
+
+}