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, 1:49 PM 035 */ 036 037package com.kitfox.svg.xml; 038 039import com.kitfox.svg.SVGConst; 040import org.w3c.dom.*; 041import java.awt.*; 042import java.net.*; 043import java.util.*; 044import java.util.regex.*; 045import java.lang.reflect.*; 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 XMLParseUtil 054{ 055 static final Matcher fpMatch = Pattern.compile("([-+]?((\\d*\\.\\d+)|(\\d+))([eE][+-]?\\d+)?)(\\%|in|cm|mm|pt|pc|px|em|ex)?").matcher(""); 056 static final Matcher intMatch = Pattern.compile("[-+]?\\d+").matcher(""); 057 static final Matcher quoteMatch = Pattern.compile("^'|'$").matcher(""); 058 059 /** Creates a new instance of XMLParseUtil */ 060 private XMLParseUtil() 061 { 062 } 063 064 /** 065 * Scans the tag's children and returns the first text element found 066 */ 067 public static String getTagText(Element ele) 068 { 069 NodeList nl = ele.getChildNodes(); 070 int size = nl.getLength(); 071 072 Node node = null; 073 int i = 0; 074 for (; i < size; i++) 075 { 076 node = nl.item(i); 077 if (node instanceof Text) break; 078 } 079 if (i == size || node == null) return null; 080 081 return ((Text)node).getData(); 082 } 083 084 /** 085 * Returns the first node that is a direct child of root with the coresponding 086 * name. Does not search children of children. 087 */ 088 public static Element getFirstChild(Element root, String name) 089 { 090 NodeList nl = root.getChildNodes(); 091 int size = nl.getLength(); 092 for (int i = 0; i < size; i++) 093 { 094 Node node = nl.item(i); 095 if (!(node instanceof Element)) continue; 096 Element ele = (Element)node; 097 if (ele.getTagName().equals(name)) return ele; 098 } 099 100 return null; 101 } 102 103 public static String[] parseStringList(String list) 104 { 105// final Pattern patWs = Pattern.compile("\\s+"); 106 final Matcher matchWs = Pattern.compile("[^\\s]+").matcher(""); 107 matchWs.reset(list); 108 109 LinkedList<String> matchList = new LinkedList<String>(); 110 while (matchWs.find()) 111 { 112 matchList.add(matchWs.group()); 113 } 114 115 String[] retArr = new String[matchList.size()]; 116 return (String[])matchList.toArray(retArr); 117 } 118 119 public static boolean isDouble(String val) 120 { 121 fpMatch.reset(val); 122 return fpMatch.matches(); 123 } 124 125 public static double parseDouble(String val) 126 { 127 /* 128 if (val == null) return 0.0; 129 130 double retVal = 0.0; 131 try 132 { retVal = Double.parseDouble(val); } 133 catch (Exception e) 134 {} 135 return retVal; 136 */ 137 return findDouble(val); 138 } 139 140 /** 141 * Searches the given string for the first floating point number it contains, 142 * parses and returns it. 143 */ 144 public synchronized static double findDouble(String val) 145 { 146 if (val == null) return 0; 147 148 fpMatch.reset(val); 149 try 150 { 151 if (!fpMatch.find()) return 0; 152 } 153 catch (StringIndexOutOfBoundsException e) 154 { 155 Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, 156 "XMLParseUtil: regex parse problem: '" + val + "'", e); 157 } 158 159 val = fpMatch.group(1); 160 //System.err.println("Parsing " + val); 161 162 double retVal = 0; 163 try 164 { 165 retVal = Double.parseDouble(val); 166 167 float pixPerInch; 168 try { 169 pixPerInch = (float)Toolkit.getDefaultToolkit().getScreenResolution(); 170 } 171 catch (NoClassDefFoundError err) 172 { 173 //Default value for headless X servers 174 pixPerInch = 72; 175 } 176 final float inchesPerCm = .3936f; 177 final String units = fpMatch.group(6); 178 179 if ("%".equals(units)) retVal /= 100; 180 else if ("in".equals(units)) 181 { 182 retVal *= pixPerInch; 183 } 184 else if ("cm".equals(units)) 185 { 186 retVal *= inchesPerCm * pixPerInch; 187 } 188 else if ("mm".equals(units)) 189 { 190 retVal *= inchesPerCm * pixPerInch * .1f; 191 } 192 else if ("pt".equals(units)) 193 { 194 retVal *= (1f / 72f) * pixPerInch; 195 } 196 else if ("pc".equals(units)) 197 { 198 retVal *= (1f / 6f) * pixPerInch; 199 } 200 } 201 catch (Exception e) 202 {} 203 return retVal; 204 } 205 206 /** 207 * Scans an input string for double values. For each value found, places 208 * in a list. This method regards any characters not part of a floating 209 * point value to be seperators. Thus this will parse whitespace seperated, 210 * comma seperated, and many other separation schemes correctly. 211 */ 212 public synchronized static double[] parseDoubleList(String list) 213 { 214 if (list == null) return null; 215 216 fpMatch.reset(list); 217 218 LinkedList<Double> doubList = new LinkedList<Double>(); 219 while (fpMatch.find()) 220 { 221 String val = fpMatch.group(1); 222 doubList.add(Double.valueOf(val)); 223 } 224 225 double[] retArr = new double[doubList.size()]; 226 Iterator<Double> it = doubList.iterator(); 227 int idx = 0; 228 while (it.hasNext()) 229 { 230 retArr[idx++] = ((Double)it.next()).doubleValue(); 231 } 232 233 return retArr; 234 } 235 236 public static float parseFloat(String val) 237 { 238 /* 239 if (val == null) return 0f; 240 241 float retVal = 0f; 242 try 243 { retVal = Float.parseFloat(val); } 244 catch (Exception e) 245 {} 246 return retVal; 247 */ 248 return findFloat(val); 249 } 250 251 /** 252 * Searches the given string for the first floating point number it contains, 253 * parses and returns it. 254 */ 255 public synchronized static float findFloat(String val) 256 { 257 if (val == null) return 0f; 258 259 fpMatch.reset(val); 260 if (!fpMatch.find()) return 0f; 261 262 val = fpMatch.group(1); 263 //System.err.println("Parsing " + val); 264 265 float retVal = 0f; 266 try 267 { 268 retVal = Float.parseFloat(val); 269 String units = fpMatch.group(6); 270 if ("%".equals(units)) retVal /= 100; 271 } 272 catch (Exception e) 273 {} 274 return retVal; 275 } 276 277 public synchronized static float[] parseFloatList(String list) 278 { 279 if (list == null) return null; 280 281 fpMatch.reset(list); 282 283 LinkedList<Float> floatList = new LinkedList<Float>(); 284 while (fpMatch.find()) 285 { 286 String val = fpMatch.group(1); 287 floatList.add(Float.valueOf(val)); 288 } 289 290 float[] retArr = new float[floatList.size()]; 291 Iterator<Float> it = floatList.iterator(); 292 int idx = 0; 293 while (it.hasNext()) 294 { 295 retArr[idx++] = ((Float)it.next()).floatValue(); 296 } 297 298 return retArr; 299 } 300 301 public static int parseInt(String val) 302 { 303 if (val == null) return 0; 304 305 int retVal = 0; 306 try 307 { retVal = Integer.parseInt(val); } 308 catch (Exception e) 309 {} 310 return retVal; 311 } 312 313 /** 314 * Searches the given string for the first integer point number it contains, 315 * parses and returns it. 316 */ 317 public static int findInt(String val) 318 { 319 if (val == null) return 0; 320 321 intMatch.reset(val); 322 if (!intMatch.find()) return 0; 323 324 val = intMatch.group(); 325 //System.err.println("Parsing " + val); 326 327 int retVal = 0; 328 try 329 { retVal = Integer.parseInt(val); } 330 catch (Exception e) 331 {} 332 return retVal; 333 } 334 335 public static int[] parseIntList(String list) 336 { 337 if (list == null) return null; 338 339 intMatch.reset(list); 340 341 LinkedList<Integer> intList = new LinkedList<Integer>(); 342 while (intMatch.find()) 343 { 344 String val = intMatch.group(); 345 intList.add(Integer.valueOf(val)); 346 } 347 348 int[] retArr = new int[intList.size()]; 349 Iterator<Integer> it = intList.iterator(); 350 int idx = 0; 351 while (it.hasNext()) 352 { 353 retArr[idx++] = ((Integer)it.next()).intValue(); 354 } 355 356 return retArr; 357 } 358/* 359 public static int parseHex(String val) 360 { 361 int retVal = 0; 362 363 for (int i = 0; i < val.length(); i++) 364 { 365 retVal <<= 4; 366 367 char ch = val.charAt(i); 368 if (ch >= '0' && ch <= '9') 369 { 370 retVal |= ch - '0'; 371 } 372 else if (ch >= 'a' && ch <= 'z') 373 { 374 retVal |= ch - 'a' + 10; 375 } 376 else if (ch >= 'A' && ch <= 'Z') 377 { 378 retVal |= ch - 'A' + 10; 379 } 380 else throw new RuntimeException(); 381 } 382 383 return retVal; 384 } 385*/ 386 /** 387 * The input string represents a ratio. Can either be specified as a 388 * double number on the range of [0.0 1.0] or as a percentage [0% 100%] 389 */ 390 public static double parseRatio(String val) 391 { 392 if (val == null || val.equals("")) return 0.0; 393 394 if (val.charAt(val.length() - 1) == '%') 395 { 396 parseDouble(val.substring(0, val.length() - 1)); 397 } 398 return parseDouble(val); 399 } 400 401 public static NumberWithUnits parseNumberWithUnits(String val) 402 { 403 if (val == null) return null; 404 405 return new NumberWithUnits(val); 406 } 407/* 408 public static Color parseColor(String val) 409 { 410 Color retVal = null; 411 412 if (val.charAt(0) == '#') 413 { 414 String hexStrn = val.substring(1); 415 416 if (hexStrn.length() == 3) 417 { 418 hexStrn = "" + hexStrn.charAt(0) + hexStrn.charAt(0) + hexStrn.charAt(1) + hexStrn.charAt(1) + hexStrn.charAt(2) + hexStrn.charAt(2); 419 } 420 int hexVal = parseHex(hexStrn); 421 422 retVal = new Color(hexVal); 423 } 424 else 425 { 426 final Matcher rgbMatch = Pattern.compile("rgb\\((\\d+),(\\d+),(\\d+)\\)", Pattern.CASE_INSENSITIVE).matcher(""); 427 428 rgbMatch.reset(val); 429 if (rgbMatch.matches()) 430 { 431 int r = Integer.parseInt(rgbMatch.group(1)); 432 int g = Integer.parseInt(rgbMatch.group(2)); 433 int b = Integer.parseInt(rgbMatch.group(3)); 434 retVal = new Color(r, g, b); 435 } 436 else 437 { 438 Color lookupCol = ColorTable.instance().lookupColor(val); 439 if (lookupCol != null) retVal = lookupCol; 440 } 441 } 442 443 return retVal; 444 } 445*/ 446 /** 447 * Parses the given attribute of this tag and returns it as a String. 448 */ 449 public static String getAttribString(Element ele, String name) 450 { 451 return ele.getAttribute(name); 452 } 453 454 /** 455 * Parses the given attribute of this tag and returns it as an int. 456 */ 457 public static int getAttribInt(Element ele, String name) 458 { 459 String sval = ele.getAttribute(name); 460 int val = 0; 461 try { val = Integer.parseInt(sval); } catch (Exception e) {} 462 463 return val; 464 } 465 466 /** 467 * Parses the given attribute of this tag as a hexadecimal encoded string and 468 * returns it as an int 469 */ 470 public static int getAttribIntHex(Element ele, String name) 471 { 472 String sval = ele.getAttribute(name); 473 int val = 0; 474 try { val = Integer.parseInt(sval, 16); } catch (Exception e) {} 475 476 return val; 477 } 478 479 /** 480 * Parses the given attribute of this tag and returns it as a float 481 */ 482 public static float getAttribFloat(Element ele, String name) 483 { 484 String sval = ele.getAttribute(name); 485 float val = 0.0f; 486 try { val = Float.parseFloat(sval); } catch (Exception e) {} 487 488 return val; 489 } 490 491 /** 492 * Parses the given attribute of this tag and returns it as a double. 493 */ 494 public static double getAttribDouble(Element ele, String name) 495 { 496 String sval = ele.getAttribute(name); 497 double val = 0.0; 498 try { val = Double.parseDouble(sval); } catch (Exception e) {} 499 500 return val; 501 } 502 503 /** 504 * Parses the given attribute of this tag and returns it as a boolean. 505 * Essentially compares the lower case textual value to the string "true" 506 */ 507 public static boolean getAttribBoolean(Element ele, String name) 508 { 509 String sval = ele.getAttribute(name); 510 511 return sval.toLowerCase().equals("true"); 512 } 513 514 public static URL getAttribURL(Element ele, String name, URL docRoot) 515 { 516 String sval = ele.getAttribute(name); 517 518 try 519 { 520 return new URL(docRoot, sval); 521 } 522 catch (Exception e) 523 { 524 return null; 525 } 526 } 527 528 /** 529 * Returns the first ReadableXMLElement with the given name 530 */ 531 public static ReadableXMLElement getElement(Class<?> classType, Element root, String name, URL docRoot) 532 { 533 if (root == null) return null; 534 535 //Do not process if not a LoadableObject 536 if (!ReadableXMLElement.class.isAssignableFrom(classType)) 537 { 538 return null; 539 } 540 541 NodeList nl = root.getChildNodes(); 542 int size = nl.getLength(); 543 for (int i = 0; i < size; i++) 544 { 545 Node node = nl.item(i); 546 if (!(node instanceof Element)) continue; 547 Element ele = (Element)node; 548 if (!ele.getTagName().equals(name)) continue; 549 550 ReadableXMLElement newObj = null; 551 try 552 { 553 newObj = (ReadableXMLElement)classType.newInstance(); 554 } 555 catch (Exception e) 556 { 557 Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, null, e); 558 continue; 559 } 560 newObj.read(ele, docRoot); 561 562 if (newObj == null) continue; 563 564 return newObj; 565 } 566 567 return null; 568 } 569 570 /** 571 * Returns a HashMap of nodes that are children of root. All nodes will 572 * be of class classType and have a tag name of 'name'. 'key' is 573 * an attribute of tag 'name' who's string value will be used as the key 574 * in the HashMap 575 */ 576 public static HashMap<String, ReadableXMLElement> getElementHashMap(Class<?> classType, Element root, String name, String key, URL docRoot) 577 { 578 if (root == null) return null; 579 580 //Do not process if not a LoadableObject 581 if (!ReadableXMLElement.class.isAssignableFrom(classType)) 582 { 583 return null; 584 } 585 586 HashMap<String, ReadableXMLElement> retMap = new HashMap<String, ReadableXMLElement>(); 587 588 NodeList nl = root.getChildNodes(); 589 int size = nl.getLength(); 590 for (int i = 0; i < size; i++) 591 { 592 Node node = nl.item(i); 593 if (!(node instanceof Element)) continue; 594 Element ele = (Element)node; 595 if (!ele.getTagName().equals(name)) continue; 596 597 ReadableXMLElement newObj = null; 598 try 599 { 600 newObj = (ReadableXMLElement)classType.newInstance(); 601 } 602 catch (Exception e) 603 { 604 Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, null, e); 605 continue; 606 } 607 newObj.read(ele, docRoot); 608 609 if (newObj == null) continue; 610 611 String keyVal = getAttribString(ele, key); 612 retMap.put(keyVal, newObj); 613 } 614 615 return retMap; 616 } 617 618 public static HashSet<ReadableXMLElement> getElementHashSet(Class<?> classType, Element root, String name, URL docRoot) 619 { 620 if (root == null) return null; 621 622 //Do not process if not a LoadableObject 623 if (!ReadableXMLElement.class.isAssignableFrom(classType)) 624 { 625 return null; 626 } 627 628 HashSet<ReadableXMLElement> retSet = new HashSet<ReadableXMLElement>(); 629 630 NodeList nl = root.getChildNodes(); 631 int size = nl.getLength(); 632 for (int i = 0; i < size; i++) 633 { 634 Node node = nl.item(i); 635 if (!(node instanceof Element)) continue; 636 Element ele = (Element)node; 637 if (!ele.getTagName().equals(name)) continue; 638 639 ReadableXMLElement newObj = null; 640 try 641 { 642 newObj = (ReadableXMLElement)classType.newInstance(); 643 } 644 catch (Exception e) 645 { 646 Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, null, e); 647 continue; 648 } 649 newObj.read(ele, docRoot); 650 651 if (newObj == null) 652 { 653 continue; 654 } 655 656 retSet.add(newObj); 657 } 658 659 return retSet; 660 } 661 662 663 public static LinkedList<ReadableXMLElement> getElementLinkedList(Class<?> classType, Element root, String name, URL docRoot) 664 { 665 if (root == null) return null; 666 667 //Do not process if not a LoadableObject 668 if (!ReadableXMLElement.class.isAssignableFrom(classType)) 669 { 670 return null; 671 } 672 673 NodeList nl = root.getChildNodes(); 674 LinkedList<ReadableXMLElement> elementCache = new LinkedList<ReadableXMLElement>(); 675 int size = nl.getLength(); 676 for (int i = 0; i < size; i++) 677 { 678 Node node = nl.item(i); 679 if (!(node instanceof Element)) continue; 680 Element ele = (Element)node; 681 if (!ele.getTagName().equals(name)) continue; 682 683 ReadableXMLElement newObj = null; 684 try 685 { 686 newObj = (ReadableXMLElement)classType.newInstance(); 687 } 688 catch (Exception e) 689 { 690 Logger.getLogger(SVGConst.SVG_LOGGER).log(Level.WARNING, null, e); 691 continue; 692 } 693 newObj.read(ele, docRoot); 694 695 elementCache.addLast(newObj); 696 } 697 698 return elementCache; 699 } 700 701 public static Object[] getElementArray(Class<?> classType, Element root, String name, URL docRoot) 702 { 703 if (root == null) return null; 704 705 //Do not process if not a LoadableObject 706 if (!ReadableXMLElement.class.isAssignableFrom(classType)) 707 { 708 return null; 709 } 710 711 LinkedList<ReadableXMLElement> elementCache = getElementLinkedList(classType, root, name, docRoot); 712 713 Object[] retArr = (Object[])Array.newInstance(classType, elementCache.size()); 714 return elementCache.toArray(retArr); 715 } 716 717 /** 718 * Takes a number of tags of name 'name' that are children of 'root', and 719 * looks for attributes of 'attrib' on them. Converts attributes to an 720 * int and returns in an array. 721 */ 722 public static int[] getElementArrayInt(Element root, String name, String attrib) 723 { 724 if (root == null) return null; 725 726 NodeList nl = root.getChildNodes(); 727 LinkedList<Integer> elementCache = new LinkedList<Integer>(); 728 int size = nl.getLength(); 729 730 for (int i = 0; i < size; i++) 731 { 732 Node node = nl.item(i); 733 if (!(node instanceof Element)) continue; 734 Element ele = (Element)node; 735 if (!ele.getTagName().equals(name)) continue; 736 737 String valS = ele.getAttribute(attrib); 738 int eleVal = 0; 739 try { eleVal = Integer.parseInt(valS); } 740 catch (Exception e) {} 741 742 elementCache.addLast(new Integer(eleVal)); 743 } 744 745 int[] retArr = new int[elementCache.size()]; 746 Iterator<Integer> it = elementCache.iterator(); 747 int idx = 0; 748 while (it.hasNext()) 749 { 750 retArr[idx++] = it.next().intValue(); 751 } 752 753 return retArr; 754 } 755 756 /** 757 * Takes a number of tags of name 'name' that are children of 'root', and 758 * looks for attributes of 'attrib' on them. Converts attributes to an 759 * int and returns in an array. 760 */ 761 public static String[] getElementArrayString(Element root, String name, String attrib) 762 { 763 if (root == null) return null; 764 765 NodeList nl = root.getChildNodes(); 766 LinkedList<String> elementCache = new LinkedList<String>(); 767 int size = nl.getLength(); 768 769 for (int i = 0; i < size; i++) 770 { 771 Node node = nl.item(i); 772 if (!(node instanceof Element)) continue; 773 Element ele = (Element)node; 774 if (!ele.getTagName().equals(name)) continue; 775 776 String valS = ele.getAttribute(attrib); 777 778 elementCache.addLast(valS); 779 } 780 781 String[] retArr = new String[elementCache.size()]; 782 Iterator<String> it = elementCache.iterator(); 783 int idx = 0; 784 while (it.hasNext()) 785 { 786 retArr[idx++] = it.next(); 787 } 788 789 return retArr; 790 } 791 792 /** 793 * Takes a CSS style string and retursn a hash of them. 794 * @param styleString - A CSS formatted string of styles. Eg, 795 * "font-size:12;fill:#d32c27;fill-rule:evenodd;stroke-width:1pt;" 796 */ 797 public static HashMap<String, StyleAttribute> parseStyle(String styleString) { 798 return parseStyle(styleString, new HashMap<String, StyleAttribute>()); 799 } 800 801 /** 802 * Takes a CSS style string and returns a hash of them. 803 * @param styleString - A CSS formatted string of styles. Eg, 804 * "font-size:12;fill:#d32c27;fill-rule:evenodd;stroke-width:1pt;" 805 * @param map - A map to which these styles will be added 806 */ 807 public static HashMap<String, StyleAttribute> parseStyle(String styleString, HashMap<String, StyleAttribute> map) { 808 final Pattern patSemi = Pattern.compile(";"); 809 810 String[] styles = patSemi.split(styleString); 811 812 for (int i = 0; i < styles.length; i++) 813 { 814 if (styles[i].length() == 0) 815 { 816 continue; 817 } 818 819 int colon = styles[i].indexOf(':'); 820 if (colon == -1) 821 { 822 continue; 823 } 824 825 String key = styles[i].substring(0, colon).trim(); 826 String value = quoteMatch.reset(styles[i].substring(colon + 1).trim()).replaceAll(""); 827 828 map.put(key, new StyleAttribute(key, value)); 829 } 830 831 return map; 832 } 833}