001/*
002Copyright (c) 2011+, HL7, Inc
003All rights reserved.
004
005Redistribution and use in source and binary forms, with or without modification, 
006are permitted provided that the following conditions are met:
007
008 * Redistributions of source code must retain the above copyright notice, this 
009   list of conditions and the following disclaimer.
010 * Redistributions in binary form must reproduce the above copyright notice, 
011   this list of conditions and the following disclaimer in the documentation 
012   and/or other materials provided with the distribution.
013 * Neither the name of HL7 nor the names of its contributors may be used to 
014   endorse or promote products derived from this software without specific 
015   prior written permission.
016
017THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
018ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
019WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
020IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
021INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
022NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
023PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
024WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
025ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
026POSSIBILITY OF SUCH DAMAGE.
027
028*/
029package org.hl7.fhir.instance.utilities.xml;
030
031import java.io.ByteArrayInputStream;
032import java.io.IOException;
033import java.io.InputStream;
034import java.io.InputStreamReader;
035import java.util.List;
036
037import javax.xml.parsers.DocumentBuilder;
038import javax.xml.parsers.DocumentBuilderFactory;
039
040import org.hl7.fhir.instance.utilities.Utilities;
041import org.w3c.dom.Document;
042import org.w3c.dom.Element;
043import org.w3c.dom.Node;
044import org.w3c.dom.ls.DOMImplementationLS;
045import org.w3c.dom.ls.LSSerializer;
046
047public class XMLUtil {
048
049        public static final String SPACE_CHAR = "\u00A0";
050
051  public static boolean isNMToken(String name) {
052                if (name == null)
053                        return false;
054                for (int i = 0; i < name.length(); i++) 
055                        if (!isNMTokenChar(name.charAt(i)))
056                                return false;   
057                return name.length() > 0;
058        }
059
060        public static boolean isNMTokenChar(char c) {
061                return isLetter(c) || isDigit(c) || c == '.' || c == '-' || c == '_' || c == ':' || isCombiningChar(c) || isExtender(c);
062        }
063
064        private static boolean isDigit(char c) {
065                return (c >= '\u0030' && c <= '\u0039') || (c >= '\u0660' && c <= '\u0669') || (c >= '\u06F0' && c <= '\u06F9') || 
066                        (c >= '\u0966' && c <= '\u096F') || (c >= '\u09E6' && c <= '\u09EF') || (c >= '\u0A66' && c <= '\u0A6F') || 
067                        (c >= '\u0AE6' && c <= '\u0AEF') || (c >= '\u0B66' && c <= '\u0B6F') || (c >= '\u0BE7' && c <= '\u0BEF') || 
068                        (c >= '\u0C66' && c <= '\u0C6F') || (c >= '\u0CE6' && c <= '\u0CEF') || (c >= '\u0D66' && c <= '\u0D6F') || 
069                        (c >= '\u0E50' && c <= '\u0E59') || (c >= '\u0ED0' && c <= '\u0ED9') || (c >= '\u0F20' && c <= '\u0F29');
070        }
071
072        private static boolean isCombiningChar(char c) {
073                return (c >= '\u0300' && c <= '\u0345') || (c >= '\u0360' && c <= '\u0361') || (c >= '\u0483' && c <= '\u0486') || 
074                        (c >= '\u0591' && c <= '\u05A1') || (c >= '\u05A3' && c <= '\u05B9') || (c >= '\u05BB' && c <= '\u05BD') || 
075                        c == '\u05BF' || (c >= '\u05C1' && c <= '\u05C2') || c == '\u05C4' || (c >= '\u064B' && c <= '\u0652') || 
076                        c == '\u0670' || (c >= '\u06D6' && c <= '\u06DC') || (c >= '\u06DD' && c <= '\u06DF') || (c >= '\u06E0' && c <= '\u06E4') || 
077                        (c >= '\u06E7' && c <= '\u06E8') || (c >= '\u06EA' && c <= '\u06ED') || (c >= '\u0901' && c <= '\u0903') || c == '\u093C' || 
078                        (c >= '\u093E' && c <= '\u094C') || c == '\u094D' || (c >= '\u0951' && c <= '\u0954') || (c >= '\u0962' && c <= '\u0963') || 
079                        (c >= '\u0981' && c <= '\u0983') || c == '\u09BC' || c == '\u09BE' || c == '\u09BF' || (c >= '\u09C0' && c <= '\u09C4') || 
080                        (c >= '\u09C7' && c <= '\u09C8') || (c >= '\u09CB' && c <= '\u09CD') || c == '\u09D7' || (c >= '\u09E2' && c <= '\u09E3') || 
081                        c == '\u0A02' || c == '\u0A3C' || c == '\u0A3E' || c == '\u0A3F' || (c >= '\u0A40' && c <= '\u0A42') || 
082                        (c >= '\u0A47' && c <= '\u0A48') || (c >= '\u0A4B' && c <= '\u0A4D') || (c >= '\u0A70' && c <= '\u0A71') || 
083                        (c >= '\u0A81' && c <= '\u0A83') || c == '\u0ABC' || (c >= '\u0ABE' && c <= '\u0AC5') || (c >= '\u0AC7' && c <= '\u0AC9') || 
084                        (c >= '\u0ACB' && c <= '\u0ACD') || (c >= '\u0B01' && c <= '\u0B03') || c == '\u0B3C' || (c >= '\u0B3E' && c <= '\u0B43') || 
085                        (c >= '\u0B47' && c <= '\u0B48') || (c >= '\u0B4B' && c <= '\u0B4D') || (c >= '\u0B56' && c <= '\u0B57') || 
086                        (c >= '\u0B82' && c <= '\u0B83') || (c >= '\u0BBE' && c <= '\u0BC2') || (c >= '\u0BC6' && c <= '\u0BC8') || 
087                        (c >= '\u0BCA' && c <= '\u0BCD') || c == '\u0BD7' || (c >= '\u0C01' && c <= '\u0C03') || (c >= '\u0C3E' && c <= '\u0C44') || 
088                        (c >= '\u0C46' && c <= '\u0C48') || (c >= '\u0C4A' && c <= '\u0C4D') || (c >= '\u0C55' && c <= '\u0C56') || 
089                        (c >= '\u0C82' && c <= '\u0C83') || (c >= '\u0CBE' && c <= '\u0CC4') || (c >= '\u0CC6' && c <= '\u0CC8') || 
090                        (c >= '\u0CCA' && c <= '\u0CCD') || (c >= '\u0CD5' && c <= '\u0CD6') || (c >= '\u0D02' && c <= '\u0D03') || 
091                        (c >= '\u0D3E' && c <= '\u0D43') || (c >= '\u0D46' && c <= '\u0D48') || (c >= '\u0D4A' && c <= '\u0D4D') || c == '\u0D57' || 
092                        c == '\u0E31' || (c >= '\u0E34' && c <= '\u0E3A') || (c >= '\u0E47' && c <= '\u0E4E') || c == '\u0EB1' || 
093                        (c >= '\u0EB4' && c <= '\u0EB9') || (c >= '\u0EBB' && c <= '\u0EBC') || (c >= '\u0EC8' && c <= '\u0ECD') || 
094                        (c >= '\u0F18' && c <= '\u0F19') || c == '\u0F35' || c == '\u0F37' || c == '\u0F39' || c == '\u0F3E' || c == '\u0F3F' || 
095                        (c >= '\u0F71' && c <= '\u0F84') || (c >= '\u0F86' && c <= '\u0F8B') || (c >= '\u0F90' && c <= '\u0F95') || c == '\u0F97' || 
096                        (c >= '\u0F99' && c <= '\u0FAD') || (c >= '\u0FB1' && c <= '\u0FB7') || c == '\u0FB9' || (c >= '\u20D0' && c <= '\u20DC') ||
097                        c == '\u20E1' || (c >= '\u302A' && c <= '\u302F') || c == '\u3099' || c == '\u309A';
098        }
099
100        private static boolean isExtender(char c) {
101                return c == '\u00B7' || c == '\u02D0' || c == '\u02D1' || c == '\u0387' || c == '\u0640' || c == '\u0E46' || 
102                        c == '\u0EC6' || c == '\u3005' || (c >= '\u3031' && c <= '\u3035') || (c >= '\u309D' && c <= '\u309E') || 
103                        (c >= '\u30FC' && c <= '\u30FE');
104        }
105
106        private static boolean isLetter(char c) {
107                return isBaseChar(c) || isIdeographic(c);
108        }
109
110        private static boolean isBaseChar(char c) {
111                return (c >= '\u0041' && c <= '\u005A') || (c >= '\u0061' && c <= '\u007A') || (c >= '\u00C0' && c <= '\u00D6') || 
112                        (c >= '\u00D8' && c <= '\u00F6') || (c >= '\u00F8' && c <= '\u00FF') || (c >= '\u0100' && c <= '\u0131') || 
113                        (c >= '\u0134' && c <= '\u013E') || (c >= '\u0141' && c <= '\u0148') || (c >= '\u014A' && c <= '\u017E') || 
114                        (c >= '\u0180' && c <= '\u01C3') || (c >= '\u01CD' && c <= '\u01F0') || (c >= '\u01F4' && c <= '\u01F5') || 
115                        (c >= '\u01FA' && c <= '\u0217') || (c >= '\u0250' && c <= '\u02A8') || (c >= '\u02BB' && c <= '\u02C1') || 
116                        c == '\u0386' || (c >= '\u0388' && c <= '\u038A') || c == '\u038C' || (c >= '\u038E' && c <= '\u03A1') || 
117                        (c >= '\u03A3' && c <= '\u03CE') || (c >= '\u03D0' && c <= '\u03D6') || c == '\u03DA' || c == '\u03DC' || c == '\u03DE' || 
118                        c == '\u03E0' || (c >= '\u03E2' && c <= '\u03F3') || (c >= '\u0401' && c <= '\u040C') || (c >= '\u040E' && c <= '\u044F') || 
119                        (c >= '\u0451' && c <= '\u045C') || (c >= '\u045E' && c <= '\u0481') || (c >= '\u0490' && c <= '\u04C4') || 
120                        (c >= '\u04C7' && c <= '\u04C8') || (c >= '\u04CB' && c <= '\u04CC') || (c >= '\u04D0' && c <= '\u04EB') || 
121                        (c >= '\u04EE' && c <= '\u04F5') || (c >= '\u04F8' && c <= '\u04F9') || (c >= '\u0531' && c <= '\u0556') || 
122                        c == '\u0559' || (c >= '\u0561' && c <= '\u0586') || (c >= '\u05D0' && c <= '\u05EA') || (c >= '\u05F0' && c <= '\u05F2') || 
123                        (c >= '\u0621' && c <= '\u063A') || (c >= '\u0641' && c <= '\u064A') || (c >= '\u0671' && c <= '\u06B7') || 
124                        (c >= '\u06BA' && c <= '\u06BE') || (c >= '\u06C0' && c <= '\u06CE') || (c >= '\u06D0' && c <= '\u06D3') || 
125                        c == '\u06D5' || (c >= '\u06E5' && c <= '\u06E6') || (c >= '\u0905' && c <= '\u0939') || c == '\u093D' || 
126                        (c >= '\u0958' && c <= '\u0961') || (c >= '\u0985' && c <= '\u098C') || (c >= '\u098F' && c <= '\u0990') || 
127                        (c >= '\u0993' && c <= '\u09A8') || (c >= '\u09AA' && c <= '\u09B0') || c == '\u09B2' || 
128                        (c >= '\u09B6' && c <= '\u09B9') || (c >= '\u09DC' && c <= '\u09DD') || (c >= '\u09DF' && c <= '\u09E1') || 
129                        (c >= '\u09F0' && c <= '\u09F1') || (c >= '\u0A05' && c <= '\u0A0A') || (c >= '\u0A0F' && c <= '\u0A10') || 
130                        (c >= '\u0A13' && c <= '\u0A28') || (c >= '\u0A2A' && c <= '\u0A30') || (c >= '\u0A32' && c <= '\u0A33') || 
131                        (c >= '\u0A35' && c <= '\u0A36') || (c >= '\u0A38' && c <= '\u0A39') || (c >= '\u0A59' && c <= '\u0A5C') || 
132                        c == '\u0A5E' || (c >= '\u0A72' && c <= '\u0A74') || (c >= '\u0A85' && c <= '\u0A8B') || c == '\u0A8D' || 
133                        (c >= '\u0A8F' && c <= '\u0A91') || (c >= '\u0A93' && c <= '\u0AA8') || (c >= '\u0AAA' && c <= '\u0AB0') || 
134                        (c >= '\u0AB2' && c <= '\u0AB3') || (c >= '\u0AB5' && c <= '\u0AB9') || c == '\u0ABD' || c == '\u0AE0' || 
135                        (c >= '\u0B05' && c <= '\u0B0C') || (c >= '\u0B0F' && c <= '\u0B10') || (c >= '\u0B13' && c <= '\u0B28') || 
136                        (c >= '\u0B2A' && c <= '\u0B30') || (c >= '\u0B32' && c <= '\u0B33') || (c >= '\u0B36' && c <= '\u0B39') || 
137                        c == '\u0B3D' || (c >= '\u0B5C' && c <= '\u0B5D') || (c >= '\u0B5F' && c <= '\u0B61') || 
138                        (c >= '\u0B85' && c <= '\u0B8A') || (c >= '\u0B8E' && c <= '\u0B90') || (c >= '\u0B92' && c <= '\u0B95') || 
139                        (c >= '\u0B99' && c <= '\u0B9A') || c == '\u0B9C' || (c >= '\u0B9E' && c <= '\u0B9F') || 
140                        (c >= '\u0BA3' && c <= '\u0BA4') || (c >= '\u0BA8' && c <= '\u0BAA') || (c >= '\u0BAE' && c <= '\u0BB5') || 
141                        (c >= '\u0BB7' && c <= '\u0BB9') || (c >= '\u0C05' && c <= '\u0C0C') || (c >= '\u0C0E' && c <= '\u0C10') || 
142                        (c >= '\u0C12' && c <= '\u0C28') || (c >= '\u0C2A' && c <= '\u0C33') || (c >= '\u0C35' && c <= '\u0C39') || 
143                        (c >= '\u0C60' && c <= '\u0C61') || (c >= '\u0C85' && c <= '\u0C8C') || (c >= '\u0C8E' && c <= '\u0C90') || 
144                        (c >= '\u0C92' && c <= '\u0CA8') || (c >= '\u0CAA' && c <= '\u0CB3') || (c >= '\u0CB5' && c <= '\u0CB9') || 
145                        c == '\u0CDE' || (c >= '\u0CE0' && c <= '\u0CE1') || (c >= '\u0D05' && c <= '\u0D0C') || 
146                        (c >= '\u0D0E' && c <= '\u0D10') || (c >= '\u0D12' && c <= '\u0D28') || (c >= '\u0D2A' && c <= '\u0D39') || 
147                        (c >= '\u0D60' && c <= '\u0D61') || (c >= '\u0E01' && c <= '\u0E2E') || c == '\u0E30' || 
148                        (c >= '\u0E32' && c <= '\u0E33') || (c >= '\u0E40' && c <= '\u0E45') || (c >= '\u0E81' && c <= '\u0E82') || 
149                        c == '\u0E84' || (c >= '\u0E87' && c <= '\u0E88') || c == '\u0E8A' || c == '\u0E8D' || (c >= '\u0E94' && c <= '\u0E97') || 
150                        (c >= '\u0E99' && c <= '\u0E9F') || (c >= '\u0EA1' && c <= '\u0EA3') || c == '\u0EA5' || c == '\u0EA7' || 
151                        (c >= '\u0EAA' && c <= '\u0EAB') || (c >= '\u0EAD' && c <= '\u0EAE') || c == '\u0EB0' || 
152                        (c >= '\u0EB2' && c <= '\u0EB3') || c == '\u0EBD' || (c >= '\u0EC0' && c <= '\u0EC4') || 
153                        (c >= '\u0F40' && c <= '\u0F47') || (c >= '\u0F49' && c <= '\u0F69') || (c >= '\u10A0' && c <= '\u10C5') || 
154                        (c >= '\u10D0' && c <= '\u10F6') || c == '\u1100' || (c >= '\u1102' && c <= '\u1103') || 
155                        (c >= '\u1105' && c <= '\u1107') || c == '\u1109' || (c >= '\u110B' && c <= '\u110C') || 
156                        (c >= '\u110E' && c <= '\u1112') || c == '\u113C' || c == '\u113E' || c == '\u1140' || c == '\u114C' || 
157                        c == '\u114E' || c == '\u1150' || (c >= '\u1154' && c <= '\u1155') || c == '\u1159' || 
158                        (c >= '\u115F' && c <= '\u1161') || c == '\u1163' || c == '\u1165' || c == '\u1167' || c == '\u1169' || 
159                        (c >= '\u116D' && c <= '\u116E') || (c >= '\u1172' && c <= '\u1173') || c == '\u1175' || 
160                        c == '\u119E' || c == '\u11A8' || c == '\u11AB' || (c >= '\u11AE' && c <= '\u11AF') || 
161                        (c >= '\u11B7' && c <= '\u11B8') || c == '\u11BA' || (c >= '\u11BC' && c <= '\u11C2') || 
162                        c == '\u11EB' || c == '\u11F0' || c == '\u11F9' || (c >= '\u1E00' && c <= '\u1E9B') || (c >= '\u1EA0' && c <= '\u1EF9') || 
163                        (c >= '\u1F00' && c <= '\u1F15') || (c >= '\u1F18' && c <= '\u1F1D') || (c >= '\u1F20' && c <= '\u1F45') || 
164                        (c >= '\u1F48' && c <= '\u1F4D') || (c >= '\u1F50' && c <= '\u1F57') || c == '\u1F59' || c == '\u1F5B' || c == '\u1F5D' || 
165                        (c >= '\u1F5F' && c <= '\u1F7D') || (c >= '\u1F80' && c <= '\u1FB4') || (c >= '\u1FB6' && c <= '\u1FBC') || 
166                        c == '\u1FBE' || (c >= '\u1FC2' && c <= '\u1FC4') || (c >= '\u1FC6' && c <= '\u1FCC') || 
167                        (c >= '\u1FD0' && c <= '\u1FD3') || (c >= '\u1FD6' && c <= '\u1FDB') || (c >= '\u1FE0' && c <= '\u1FEC') || 
168                        (c >= '\u1FF2' && c <= '\u1FF4') || (c >= '\u1FF6' && c <= '\u1FFC') || c == '\u2126' || 
169                        (c >= '\u212A' && c <= '\u212B') || c == '\u212E' || (c >= '\u2180' && c <= '\u2182') || 
170                        (c >= '\u3041' && c <= '\u3094') || (c >= '\u30A1' && c <= '\u30FA') || (c >= '\u3105' && c <= '\u312C') || 
171                        (c >= '\uAC00' && c <= '\uD7A3');
172        }
173
174        private static boolean isIdeographic(char c) {
175                return (c >= '\u4E00' && c <= '\u9FA5') || c == '\u3007' || (c >= '\u3021' && c <= '\u3029');
176        }
177
178        public static String determineEncoding(InputStream stream) throws IOException {
179                stream.mark(20000);
180                try {
181                        int b0 = stream.read();
182                        int b1 = stream.read();
183                        int b2 = stream.read();
184                        int b3 = stream.read();
185
186                        if (b0 == 0xFE && b1 == 0xFF)
187                                return "UTF-16BE";
188                        else if (b0 == 0xFF && b1 == 0xFE)
189                                return "UTF-16LE";
190                        else if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF )
191                                return "UTF-8";
192                        else if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x3F)
193                                return "UTF-16BE";
194                        else if (b0 == 0x3C && b1 == 0x00 && b2 == 0x3F && b3 == 0x00)
195                                return "UTF-16LE";
196                        else if (b0 == 0x3C && b1 == 0x3F && b2 == 0x78 && b3 == 0x6D) {
197//                              UTF-8, ISO 646, ASCII, some part of ISO 8859, Shift-JIS, EUC, or any other 7-bit, 8-bit, or mixed-width encoding 
198//                              which ensures that the characters of ASCII have their normal positions, width, and values; the actual encoding 
199//                              declaration must be read to detect which of these applies, but since all of these encodings use the same bit patterns 
200//                              for the relevant ASCII characters, the encoding declaration itself may be read reliably
201                                InputStreamReader rdr = new InputStreamReader(stream, "US-ASCII");
202                                String hdr = readFirstLine(rdr);
203                                return extractEncoding(hdr); 
204                        } else
205                                return null;
206                } finally {
207                        stream.reset();
208                }
209        }
210
211        private static String extractEncoding(String hdr) {
212                int i = hdr.indexOf("encoding=");
213                if (i == -1)
214                        return null;
215                hdr = hdr.substring(i+9);
216                char sep = hdr.charAt(0);
217                hdr = hdr.substring(1);
218                i = hdr.indexOf(sep);
219                if (i == -1)
220                        return null;
221                return hdr.substring(0, i);
222        }
223
224        private static String readFirstLine(InputStreamReader rdr) throws IOException {
225                char[] buf = new char[1];
226                StringBuffer bldr = new StringBuffer();
227                rdr.read(buf);
228                while (buf[0] != '>') {
229                        bldr.append(buf[0]);
230                        rdr.read(buf);
231                }
232                return bldr.toString();
233        }
234
235        
236    public static boolean charSetImpliesAscii(String charset) {
237                return charset.equals("ISO-8859-1") || charset.equals("US-ASCII");
238        }
239
240        
241        /**
242         * Converts the raw characters to XML escapeUrlParam characters.
243         * 
244         * @param rawContent
245         * @param charset Null when charset is not known, so we assume it's unicode
246         * @param isNoLines
247         * @return escapeUrlParam string
248         */
249        public static String escapeXML(String rawContent, String charset, boolean isNoLines) {
250                if (rawContent == null)
251                        return "";
252                else {
253                        StringBuffer sb = new StringBuffer();
254
255                        for (int i = 0; i < rawContent.length(); i++) {
256                                char ch = rawContent.charAt(i);
257                                if (ch == '\'')
258                                        sb.append("&#39;");
259                                else if (ch == '&')
260                                        sb.append("&amp;");
261                                else if (ch == '"')
262                                        sb.append("&quot;");
263                                else if (ch == '<')
264                                        sb.append("&lt;");
265                                else if (ch == '>')
266                                        sb.append("&gt;");
267                                else if (ch > '~' && charset != null && charSetImpliesAscii(charset)) 
268                                        // TODO - why is hashcode the only way to get the unicode number for the character
269                                        // in jre 5.0?
270                                        sb.append("&#x"+Integer.toHexString(new Character(ch).hashCode()).toUpperCase()+";");
271                                else if (isNoLines) {
272                                        if (ch == '\r')
273                                                sb.append("&#xA;");
274                                        else if (ch != '\n')
275                                                sb.append(ch);
276                                }
277                                else
278                                        sb.append(ch);
279                        }
280                        return sb.toString();
281                }
282        }
283
284  public static Element getFirstChild(Element e) {
285    if (e == null)
286      return null;
287    Node n = e.getFirstChild();
288    while (n != null && n.getNodeType() != Node.ELEMENT_NODE)
289      n = n.getNextSibling();
290    return (Element) n;
291  }
292
293  public static Element getNamedChild(Element e, String name) {
294    Element c = getFirstChild(e);
295    while (c != null && !name.equals(c.getLocalName()) && !name.equals(c.getNodeName()))
296      c = getNextSibling(c);
297    return c;
298  }
299
300  public static Element getNextSibling(Element e) {
301    Node n = e.getNextSibling();
302    while (n != null && n.getNodeType() != Node.ELEMENT_NODE)
303      n = n.getNextSibling();
304    return (Element) n;
305  }
306
307  public static void getNamedChildren(Element e, String name, List<Element> set) {
308    Element c = getFirstChild(e);
309    while (c != null) {
310      if (name.equals(c.getLocalName()) || name.equals(c.getNodeName()) )
311        set.add(c);
312      c = getNextSibling(c);
313    }
314  }
315
316  public static String htmlToXmlEscapedPlainText(Element r) {
317    StringBuilder s = new StringBuilder();
318    Node n = r.getFirstChild();
319    boolean ws = false;
320    while (n != null) {
321      if (n.getNodeType() == Node.TEXT_NODE) {
322        String t = n.getTextContent().trim();
323        if (Utilities.noString(t))
324          ws = true;
325        else {
326          if (ws)
327            s.append(" ");
328          ws = false;
329          s.append(t);
330        }
331      }
332      if (n.getNodeType() == Node.ELEMENT_NODE) {
333        if (ws)
334          s.append(" ");
335        ws = false;
336        s.append(htmlToXmlEscapedPlainText((Element) n));
337        if (r.getNodeName().equals("br") || r.getNodeName().equals("p"))
338          s.append("\r\n");
339      }
340      n = n.getNextSibling();      
341    }
342    return s.toString();
343  }
344
345  public static String htmlToXmlEscapedPlainText(String definition) throws Exception {
346    return htmlToXmlEscapedPlainText(parseToDom("<div>"+definition+"</div>").getDocumentElement());
347  }
348
349  public static String elementToString(Element el) {
350    if (el == null)
351      return "";
352    Document document = el.getOwnerDocument();
353    DOMImplementationLS domImplLS = (DOMImplementationLS) document
354        .getImplementation();
355    LSSerializer serializer = domImplLS.createLSSerializer();
356    return serializer.writeToString(el);
357  }
358
359  public static String getNamedChildValue(Element element, String name) {
360    Element e = getNamedChild(element, name);
361    return e == null ? null : e.getAttribute("value");
362  }
363
364        public static void getNamedChildrenWithWildcard(Element focus, String name, List<Element> children) {
365    Element c = getFirstChild(focus);
366    while (c != null) {
367        String n = c.getLocalName() != null ? c.getLocalName() : c.getNodeName(); 
368      if (name.equals(n) || (name.endsWith("[x]") && n.startsWith(name.substring(0, name.length()-3))))
369        children.add(c);
370      c = getNextSibling(c);
371    }
372          
373  }
374
375  public static boolean hasNamedChild(Element e, String name) {
376    Element c = getFirstChild(e);
377    while (c != null && !name.equals(c.getLocalName()) && !name.equals(c.getNodeName()))
378      c = getNextSibling(c);
379    return c != null;
380  }
381
382  public static Document parseToDom(String content) throws Exception {
383    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
384    factory.setNamespaceAware(false);
385    DocumentBuilder builder = factory.newDocumentBuilder();
386    return builder.parse(new ByteArrayInputStream(content.getBytes()));
387  }
388
389  public static Element getLastChild(Element e) {
390    if (e == null)
391      return null;
392    Node n = e.getLastChild();
393    while (n != null && n.getNodeType() != Node.ELEMENT_NODE)
394      n = n.getPreviousSibling();
395    return (Element) n;
396  }
397
398  public static Element getPrevSibling(Element e) {
399    Node n = e.getPreviousSibling();
400    while (n != null && n.getNodeType() != Node.ELEMENT_NODE)
401      n = n.getPreviousSibling();
402    return (Element) n;
403  }
404
405        
406}