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