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