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 matchList = new LinkedList(); 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 doubList = new LinkedList(); 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 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 floatList = new LinkedList(); 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 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 intList = new LinkedList(); 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 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 URL url; 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 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 retMap = new HashMap(); 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 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 retSet = new HashSet(); 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 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 elementCache = new LinkedList(); 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 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 elementCache = new LinkedList(); 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 it = elementCache.iterator(); 747 int idx = 0; 748 while (it.hasNext()) 749 { 750 retArr[idx++] = ((Integer)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 elementCache = new LinkedList(); 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 it = elementCache.iterator(); 783 int idx = 0; 784 while (it.hasNext()) 785 { 786 retArr[idx++] = (String)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 parseStyle(String styleString) { 798 return parseStyle(styleString, new HashMap()); 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 parseStyle(String styleString, HashMap 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 = styles[i].substring(colon + 1).trim(); 827 828 map.put(key, new StyleAttribute(key, value)); 829 } 830 831 return map; 832 } 833}