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.utilities.xml;
030
031/*-
032 * #%L
033 * org.hl7.fhir.utilities
034 * %%
035 * Copyright (C) 2014 - 2019 Health Level 7
036 * %%
037 * Licensed under the Apache License, Version 2.0 (the "License");
038 * you may not use this file except in compliance with the License.
039 * You may obtain a copy of the License at
040 * 
041 *      http://www.apache.org/licenses/LICENSE-2.0
042 * 
043 * Unless required by applicable law or agreed to in writing, software
044 * distributed under the License is distributed on an "AS IS" BASIS,
045 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
046 * See the License for the specific language governing permissions and
047 * limitations under the License.
048 * #L%
049 */
050
051
052import java.io.ByteArrayInputStream;
053import java.io.File;
054import java.io.FileInputStream;
055import java.io.IOException;
056import java.io.InputStream;
057import java.io.InputStreamReader;
058import java.io.OutputStream;
059import java.util.ArrayList;
060import java.util.List;
061import java.util.Set;
062
063import javax.xml.parsers.DocumentBuilder;
064import javax.xml.parsers.DocumentBuilderFactory;
065import javax.xml.parsers.ParserConfigurationException;
066import javax.xml.transform.Result;
067import javax.xml.transform.Source;
068import javax.xml.transform.Transformer;
069import javax.xml.transform.TransformerException;
070import javax.xml.transform.TransformerFactory;
071import javax.xml.transform.dom.DOMSource;
072import javax.xml.transform.stream.StreamResult;
073
074import org.hl7.fhir.exceptions.FHIRException;
075import org.hl7.fhir.utilities.Utilities;
076import org.w3c.dom.Attr;
077import org.w3c.dom.Document;
078import org.w3c.dom.Element;
079import org.w3c.dom.Node;
080import org.w3c.dom.ls.DOMImplementationLS;
081import org.w3c.dom.ls.LSSerializer;
082import org.xml.sax.SAXException;
083
084public class XMLUtil {
085
086        public static final String SPACE_CHAR = "\u00A0";
087
088  public static boolean isNMToken(String name) {
089                if (name == null)
090                        return false;
091                for (int i = 0; i < name.length(); i++) 
092                        if (!isNMTokenChar(name.charAt(i)))
093                                return false;   
094                return name.length() > 0;
095        }
096
097        public static boolean isNMTokenChar(char c) {
098                return isLetter(c) || isDigit(c) || c == '.' || c == '-' || c == '_' || c == ':' || isCombiningChar(c) || isExtender(c);
099        }
100
101        private static boolean isDigit(char c) {
102                return (c >= '\u0030' && c <= '\u0039') || (c >= '\u0660' && c <= '\u0669') || (c >= '\u06F0' && c <= '\u06F9') || 
103                        (c >= '\u0966' && c <= '\u096F') || (c >= '\u09E6' && c <= '\u09EF') || (c >= '\u0A66' && c <= '\u0A6F') || 
104                        (c >= '\u0AE6' && c <= '\u0AEF') || (c >= '\u0B66' && c <= '\u0B6F') || (c >= '\u0BE7' && c <= '\u0BEF') || 
105                        (c >= '\u0C66' && c <= '\u0C6F') || (c >= '\u0CE6' && c <= '\u0CEF') || (c >= '\u0D66' && c <= '\u0D6F') || 
106                        (c >= '\u0E50' && c <= '\u0E59') || (c >= '\u0ED0' && c <= '\u0ED9') || (c >= '\u0F20' && c <= '\u0F29');
107        }
108
109        private static boolean isCombiningChar(char c) {
110                return (c >= '\u0300' && c <= '\u0345') || (c >= '\u0360' && c <= '\u0361') || (c >= '\u0483' && c <= '\u0486') || 
111                        (c >= '\u0591' && c <= '\u05A1') || (c >= '\u05A3' && c <= '\u05B9') || (c >= '\u05BB' && c <= '\u05BD') || 
112                        c == '\u05BF' || (c >= '\u05C1' && c <= '\u05C2') || c == '\u05C4' || (c >= '\u064B' && c <= '\u0652') || 
113                        c == '\u0670' || (c >= '\u06D6' && c <= '\u06DC') || (c >= '\u06DD' && c <= '\u06DF') || (c >= '\u06E0' && c <= '\u06E4') || 
114                        (c >= '\u06E7' && c <= '\u06E8') || (c >= '\u06EA' && c <= '\u06ED') || (c >= '\u0901' && c <= '\u0903') || c == '\u093C' || 
115                        (c >= '\u093E' && c <= '\u094C') || c == '\u094D' || (c >= '\u0951' && c <= '\u0954') || (c >= '\u0962' && c <= '\u0963') || 
116                        (c >= '\u0981' && c <= '\u0983') || c == '\u09BC' || c == '\u09BE' || c == '\u09BF' || (c >= '\u09C0' && c <= '\u09C4') || 
117                        (c >= '\u09C7' && c <= '\u09C8') || (c >= '\u09CB' && c <= '\u09CD') || c == '\u09D7' || (c >= '\u09E2' && c <= '\u09E3') || 
118                        c == '\u0A02' || c == '\u0A3C' || c == '\u0A3E' || c == '\u0A3F' || (c >= '\u0A40' && c <= '\u0A42') || 
119                        (c >= '\u0A47' && c <= '\u0A48') || (c >= '\u0A4B' && c <= '\u0A4D') || (c >= '\u0A70' && c <= '\u0A71') || 
120                        (c >= '\u0A81' && c <= '\u0A83') || c == '\u0ABC' || (c >= '\u0ABE' && c <= '\u0AC5') || (c >= '\u0AC7' && c <= '\u0AC9') || 
121                        (c >= '\u0ACB' && c <= '\u0ACD') || (c >= '\u0B01' && c <= '\u0B03') || c == '\u0B3C' || (c >= '\u0B3E' && c <= '\u0B43') || 
122                        (c >= '\u0B47' && c <= '\u0B48') || (c >= '\u0B4B' && c <= '\u0B4D') || (c >= '\u0B56' && c <= '\u0B57') || 
123                        (c >= '\u0B82' && c <= '\u0B83') || (c >= '\u0BBE' && c <= '\u0BC2') || (c >= '\u0BC6' && c <= '\u0BC8') || 
124                        (c >= '\u0BCA' && c <= '\u0BCD') || c == '\u0BD7' || (c >= '\u0C01' && c <= '\u0C03') || (c >= '\u0C3E' && c <= '\u0C44') || 
125                        (c >= '\u0C46' && c <= '\u0C48') || (c >= '\u0C4A' && c <= '\u0C4D') || (c >= '\u0C55' && c <= '\u0C56') || 
126                        (c >= '\u0C82' && c <= '\u0C83') || (c >= '\u0CBE' && c <= '\u0CC4') || (c >= '\u0CC6' && c <= '\u0CC8') || 
127                        (c >= '\u0CCA' && c <= '\u0CCD') || (c >= '\u0CD5' && c <= '\u0CD6') || (c >= '\u0D02' && c <= '\u0D03') || 
128                        (c >= '\u0D3E' && c <= '\u0D43') || (c >= '\u0D46' && c <= '\u0D48') || (c >= '\u0D4A' && c <= '\u0D4D') || c == '\u0D57' || 
129                        c == '\u0E31' || (c >= '\u0E34' && c <= '\u0E3A') || (c >= '\u0E47' && c <= '\u0E4E') || c == '\u0EB1' || 
130                        (c >= '\u0EB4' && c <= '\u0EB9') || (c >= '\u0EBB' && c <= '\u0EBC') || (c >= '\u0EC8' && c <= '\u0ECD') || 
131                        (c >= '\u0F18' && c <= '\u0F19') || c == '\u0F35' || c == '\u0F37' || c == '\u0F39' || c == '\u0F3E' || c == '\u0F3F' || 
132                        (c >= '\u0F71' && c <= '\u0F84') || (c >= '\u0F86' && c <= '\u0F8B') || (c >= '\u0F90' && c <= '\u0F95') || c == '\u0F97' || 
133                        (c >= '\u0F99' && c <= '\u0FAD') || (c >= '\u0FB1' && c <= '\u0FB7') || c == '\u0FB9' || (c >= '\u20D0' && c <= '\u20DC') ||
134                        c == '\u20E1' || (c >= '\u302A' && c <= '\u302F') || c == '\u3099' || c == '\u309A';
135        }
136
137        private static boolean isExtender(char c) {
138                return c == '\u00B7' || c == '\u02D0' || c == '\u02D1' || c == '\u0387' || c == '\u0640' || c == '\u0E46' || 
139                        c == '\u0EC6' || c == '\u3005' || (c >= '\u3031' && c <= '\u3035') || (c >= '\u309D' && c <= '\u309E') || 
140                        (c >= '\u30FC' && c <= '\u30FE');
141        }
142
143        private static boolean isLetter(char c) {
144                return isBaseChar(c) || isIdeographic(c);
145        }
146
147        private static boolean isBaseChar(char c) {
148                return (c >= '\u0041' && c <= '\u005A') || (c >= '\u0061' && c <= '\u007A') || (c >= '\u00C0' && c <= '\u00D6') || 
149                        (c >= '\u00D8' && c <= '\u00F6') || (c >= '\u00F8' && c <= '\u00FF') || (c >= '\u0100' && c <= '\u0131') || 
150                        (c >= '\u0134' && c <= '\u013E') || (c >= '\u0141' && c <= '\u0148') || (c >= '\u014A' && c <= '\u017E') || 
151                        (c >= '\u0180' && c <= '\u01C3') || (c >= '\u01CD' && c <= '\u01F0') || (c >= '\u01F4' && c <= '\u01F5') || 
152                        (c >= '\u01FA' && c <= '\u0217') || (c >= '\u0250' && c <= '\u02A8') || (c >= '\u02BB' && c <= '\u02C1') || 
153                        c == '\u0386' || (c >= '\u0388' && c <= '\u038A') || c == '\u038C' || (c >= '\u038E' && c <= '\u03A1') || 
154                        (c >= '\u03A3' && c <= '\u03CE') || (c >= '\u03D0' && c <= '\u03D6') || c == '\u03DA' || c == '\u03DC' || c == '\u03DE' || 
155                        c == '\u03E0' || (c >= '\u03E2' && c <= '\u03F3') || (c >= '\u0401' && c <= '\u040C') || (c >= '\u040E' && c <= '\u044F') || 
156                        (c >= '\u0451' && c <= '\u045C') || (c >= '\u045E' && c <= '\u0481') || (c >= '\u0490' && c <= '\u04C4') || 
157                        (c >= '\u04C7' && c <= '\u04C8') || (c >= '\u04CB' && c <= '\u04CC') || (c >= '\u04D0' && c <= '\u04EB') || 
158                        (c >= '\u04EE' && c <= '\u04F5') || (c >= '\u04F8' && c <= '\u04F9') || (c >= '\u0531' && c <= '\u0556') || 
159                        c == '\u0559' || (c >= '\u0561' && c <= '\u0586') || (c >= '\u05D0' && c <= '\u05EA') || (c >= '\u05F0' && c <= '\u05F2') || 
160                        (c >= '\u0621' && c <= '\u063A') || (c >= '\u0641' && c <= '\u064A') || (c >= '\u0671' && c <= '\u06B7') || 
161                        (c >= '\u06BA' && c <= '\u06BE') || (c >= '\u06C0' && c <= '\u06CE') || (c >= '\u06D0' && c <= '\u06D3') || 
162                        c == '\u06D5' || (c >= '\u06E5' && c <= '\u06E6') || (c >= '\u0905' && c <= '\u0939') || c == '\u093D' || 
163                        (c >= '\u0958' && c <= '\u0961') || (c >= '\u0985' && c <= '\u098C') || (c >= '\u098F' && c <= '\u0990') || 
164                        (c >= '\u0993' && c <= '\u09A8') || (c >= '\u09AA' && c <= '\u09B0') || c == '\u09B2' || 
165                        (c >= '\u09B6' && c <= '\u09B9') || (c >= '\u09DC' && c <= '\u09DD') || (c >= '\u09DF' && c <= '\u09E1') || 
166                        (c >= '\u09F0' && c <= '\u09F1') || (c >= '\u0A05' && c <= '\u0A0A') || (c >= '\u0A0F' && c <= '\u0A10') || 
167                        (c >= '\u0A13' && c <= '\u0A28') || (c >= '\u0A2A' && c <= '\u0A30') || (c >= '\u0A32' && c <= '\u0A33') || 
168                        (c >= '\u0A35' && c <= '\u0A36') || (c >= '\u0A38' && c <= '\u0A39') || (c >= '\u0A59' && c <= '\u0A5C') || 
169                        c == '\u0A5E' || (c >= '\u0A72' && c <= '\u0A74') || (c >= '\u0A85' && c <= '\u0A8B') || c == '\u0A8D' || 
170                        (c >= '\u0A8F' && c <= '\u0A91') || (c >= '\u0A93' && c <= '\u0AA8') || (c >= '\u0AAA' && c <= '\u0AB0') || 
171                        (c >= '\u0AB2' && c <= '\u0AB3') || (c >= '\u0AB5' && c <= '\u0AB9') || c == '\u0ABD' || c == '\u0AE0' || 
172                        (c >= '\u0B05' && c <= '\u0B0C') || (c >= '\u0B0F' && c <= '\u0B10') || (c >= '\u0B13' && c <= '\u0B28') || 
173                        (c >= '\u0B2A' && c <= '\u0B30') || (c >= '\u0B32' && c <= '\u0B33') || (c >= '\u0B36' && c <= '\u0B39') || 
174                        c == '\u0B3D' || (c >= '\u0B5C' && c <= '\u0B5D') || (c >= '\u0B5F' && c <= '\u0B61') || 
175                        (c >= '\u0B85' && c <= '\u0B8A') || (c >= '\u0B8E' && c <= '\u0B90') || (c >= '\u0B92' && c <= '\u0B95') || 
176                        (c >= '\u0B99' && c <= '\u0B9A') || c == '\u0B9C' || (c >= '\u0B9E' && c <= '\u0B9F') || 
177                        (c >= '\u0BA3' && c <= '\u0BA4') || (c >= '\u0BA8' && c <= '\u0BAA') || (c >= '\u0BAE' && c <= '\u0BB5') || 
178                        (c >= '\u0BB7' && c <= '\u0BB9') || (c >= '\u0C05' && c <= '\u0C0C') || (c >= '\u0C0E' && c <= '\u0C10') || 
179                        (c >= '\u0C12' && c <= '\u0C28') || (c >= '\u0C2A' && c <= '\u0C33') || (c >= '\u0C35' && c <= '\u0C39') || 
180                        (c >= '\u0C60' && c <= '\u0C61') || (c >= '\u0C85' && c <= '\u0C8C') || (c >= '\u0C8E' && c <= '\u0C90') || 
181                        (c >= '\u0C92' && c <= '\u0CA8') || (c >= '\u0CAA' && c <= '\u0CB3') || (c >= '\u0CB5' && c <= '\u0CB9') || 
182                        c == '\u0CDE' || (c >= '\u0CE0' && c <= '\u0CE1') || (c >= '\u0D05' && c <= '\u0D0C') || 
183                        (c >= '\u0D0E' && c <= '\u0D10') || (c >= '\u0D12' && c <= '\u0D28') || (c >= '\u0D2A' && c <= '\u0D39') || 
184                        (c >= '\u0D60' && c <= '\u0D61') || (c >= '\u0E01' && c <= '\u0E2E') || c == '\u0E30' || 
185                        (c >= '\u0E32' && c <= '\u0E33') || (c >= '\u0E40' && c <= '\u0E45') || (c >= '\u0E81' && c <= '\u0E82') || 
186                        c == '\u0E84' || (c >= '\u0E87' && c <= '\u0E88') || c == '\u0E8A' || c == '\u0E8D' || (c >= '\u0E94' && c <= '\u0E97') || 
187                        (c >= '\u0E99' && c <= '\u0E9F') || (c >= '\u0EA1' && c <= '\u0EA3') || c == '\u0EA5' || c == '\u0EA7' || 
188                        (c >= '\u0EAA' && c <= '\u0EAB') || (c >= '\u0EAD' && c <= '\u0EAE') || c == '\u0EB0' || 
189                        (c >= '\u0EB2' && c <= '\u0EB3') || c == '\u0EBD' || (c >= '\u0EC0' && c <= '\u0EC4') || 
190                        (c >= '\u0F40' && c <= '\u0F47') || (c >= '\u0F49' && c <= '\u0F69') || (c >= '\u10A0' && c <= '\u10C5') || 
191                        (c >= '\u10D0' && c <= '\u10F6') || c == '\u1100' || (c >= '\u1102' && c <= '\u1103') || 
192                        (c >= '\u1105' && c <= '\u1107') || c == '\u1109' || (c >= '\u110B' && c <= '\u110C') || 
193                        (c >= '\u110E' && c <= '\u1112') || c == '\u113C' || c == '\u113E' || c == '\u1140' || c == '\u114C' || 
194                        c == '\u114E' || c == '\u1150' || (c >= '\u1154' && c <= '\u1155') || c == '\u1159' || 
195                        (c >= '\u115F' && c <= '\u1161') || c == '\u1163' || c == '\u1165' || c == '\u1167' || c == '\u1169' || 
196                        (c >= '\u116D' && c <= '\u116E') || (c >= '\u1172' && c <= '\u1173') || c == '\u1175' || 
197                        c == '\u119E' || c == '\u11A8' || c == '\u11AB' || (c >= '\u11AE' && c <= '\u11AF') || 
198                        (c >= '\u11B7' && c <= '\u11B8') || c == '\u11BA' || (c >= '\u11BC' && c <= '\u11C2') || 
199                        c == '\u11EB' || c == '\u11F0' || c == '\u11F9' || (c >= '\u1E00' && c <= '\u1E9B') || (c >= '\u1EA0' && c <= '\u1EF9') || 
200                        (c >= '\u1F00' && c <= '\u1F15') || (c >= '\u1F18' && c <= '\u1F1D') || (c >= '\u1F20' && c <= '\u1F45') || 
201                        (c >= '\u1F48' && c <= '\u1F4D') || (c >= '\u1F50' && c <= '\u1F57') || c == '\u1F59' || c == '\u1F5B' || c == '\u1F5D' || 
202                        (c >= '\u1F5F' && c <= '\u1F7D') || (c >= '\u1F80' && c <= '\u1FB4') || (c >= '\u1FB6' && c <= '\u1FBC') || 
203                        c == '\u1FBE' || (c >= '\u1FC2' && c <= '\u1FC4') || (c >= '\u1FC6' && c <= '\u1FCC') || 
204                        (c >= '\u1FD0' && c <= '\u1FD3') || (c >= '\u1FD6' && c <= '\u1FDB') || (c >= '\u1FE0' && c <= '\u1FEC') || 
205                        (c >= '\u1FF2' && c <= '\u1FF4') || (c >= '\u1FF6' && c <= '\u1FFC') || c == '\u2126' || 
206                        (c >= '\u212A' && c <= '\u212B') || c == '\u212E' || (c >= '\u2180' && c <= '\u2182') || 
207                        (c >= '\u3041' && c <= '\u3094') || (c >= '\u30A1' && c <= '\u30FA') || (c >= '\u3105' && c <= '\u312C') || 
208                        (c >= '\uAC00' && c <= '\uD7A3');
209        }
210
211        private static boolean isIdeographic(char c) {
212                return (c >= '\u4E00' && c <= '\u9FA5') || c == '\u3007' || (c >= '\u3021' && c <= '\u3029');
213        }
214
215        public static String determineEncoding(InputStream stream) throws IOException {
216                stream.mark(20000);
217                try {
218                        int b0 = stream.read();
219                        int b1 = stream.read();
220                        int b2 = stream.read();
221                        int b3 = stream.read();
222
223                        if (b0 == 0xFE && b1 == 0xFF)
224                                return "UTF-16BE";
225                        else if (b0 == 0xFF && b1 == 0xFE)
226                                return "UTF-16LE";
227                        else if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF )
228                                return "UTF-8";
229                        else if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x3F)
230                                return "UTF-16BE";
231                        else if (b0 == 0x3C && b1 == 0x00 && b2 == 0x3F && b3 == 0x00)
232                                return "UTF-16LE";
233                        else if (b0 == 0x3C && b1 == 0x3F && b2 == 0x78 && b3 == 0x6D) {
234//                              UTF-8, ISO 646, ASCII, some part of ISO 8859, Shift-JIS, EUC, or any other 7-bit, 8-bit, or mixed-width encoding 
235//                              which ensures that the characters of ASCII have their normal positions, width, and values; the actual encoding 
236//                              declaration must be read to detect which of these applies, but since all of these encodings use the same bit patterns 
237//                              for the relevant ASCII characters, the encoding declaration itself may be read reliably
238                                InputStreamReader rdr = new InputStreamReader(stream, "US-ASCII");
239                                String hdr = readFirstLine(rdr);
240                                return extractEncoding(hdr); 
241                        } else
242                                return null;
243                } finally {
244                        stream.reset();
245                }
246        }
247
248        private static String extractEncoding(String hdr) {
249                int i = hdr.indexOf("encoding=");
250                if (i == -1)
251                        return null;
252                hdr = hdr.substring(i+9);
253                char sep = hdr.charAt(0);
254                hdr = hdr.substring(1);
255                i = hdr.indexOf(sep);
256                if (i == -1)
257                        return null;
258                return hdr.substring(0, i);
259        }
260
261        private static String readFirstLine(InputStreamReader rdr) throws IOException {
262                char[] buf = new char[1];
263                StringBuffer bldr = new StringBuffer();
264                rdr.read(buf);
265                while (buf[0] != '>') {
266                        bldr.append(buf[0]);
267                        rdr.read(buf);
268                }
269                return bldr.toString();
270        }
271
272        
273    public static boolean charSetImpliesAscii(String charset) {
274                return charset.equals("ISO-8859-1") || charset.equals("US-ASCII");
275        }
276
277        
278        /**
279         * Converts the raw characters to XML escape characters.
280         * 
281         * @param rawContent
282         * @param charset Null when charset is not known, so we assume it's unicode
283         * @param isNoLines
284         * @return escape string
285         */
286        public static String escapeXML(String rawContent, String charset, boolean isNoLines) {
287                if (rawContent == null)
288                        return "";
289                else {
290                        StringBuffer sb = new StringBuffer();
291
292                        for (int i = 0; i < rawContent.length(); i++) {
293                                char ch = rawContent.charAt(i);
294                                if (ch == '\'')
295                                        sb.append("&#39;");
296                                else if (ch == '&')
297                                        sb.append("&amp;");
298                                else if (ch == '"')
299                                        sb.append("&quot;");
300                                else if (ch == '<')
301                                        sb.append("&lt;");
302                                else if (ch == '>')
303                                        sb.append("&gt;");
304                                else if (ch > '~' && charset != null && charSetImpliesAscii(charset)) 
305                                        // TODO - why is hashcode the only way to get the unicode number for the character
306                                        // in jre 5.0?
307                                        sb.append("&#x"+Integer.toHexString(ch).toUpperCase()+";");
308                                else if (isNoLines) {
309                                        if (ch == '\r')
310                                                sb.append("&#xA;");
311                                        else if (ch != '\n')
312                                                sb.append(ch);
313                                }
314                                else
315                                        sb.append(ch);
316                        }
317                        return sb.toString();
318                }
319        }
320
321  public static Element getFirstChild(Element e) {
322    if (e == null)
323      return null;
324    Node n = e.getFirstChild();
325    while (n != null && n.getNodeType() != Node.ELEMENT_NODE)
326      n = n.getNextSibling();
327    return (Element) n;
328  }
329
330  public static Element getNamedChild(Element e, String name) {
331    Element c = getFirstChild(e);
332    while (c != null && !name.equals(c.getLocalName()) && !name.equals(c.getNodeName()))
333      c = getNextSibling(c);
334    return c;
335  }
336
337  public static Element getNamedChildByAttribute(Element e, String name, String nname, String nvalue) {
338    Element c = getFirstChild(e);
339    while (c != null && !((name.equals(c.getLocalName()) || name.equals(c.getNodeName())) && nvalue.equals(c.getAttribute(nname))))
340      c = getNextSibling(c);
341    return c;
342  }
343
344  public static Element getNextSibling(Element e) {
345    Node n = e.getNextSibling();
346    while (n != null && n.getNodeType() != Node.ELEMENT_NODE)
347      n = n.getNextSibling();
348    return (Element) n;
349  }
350
351  public static void getNamedChildren(Element e, String name, List<Element> set) {
352    Element c = getFirstChild(e);
353    while (c != null) {
354      if (name.equals(c.getLocalName()) || name.equals(c.getNodeName()) )
355        set.add(c);
356      c = getNextSibling(c);
357    }
358  }
359
360  public static List<Element> getNamedChildren(Element e, String name) {
361    List<Element> res = new ArrayList<Element>();
362    Element c = getFirstChild(e);
363    while (c != null) {
364      if (name.equals(c.getLocalName()) || name.equals(c.getNodeName()) )
365        res.add(c);
366      c = getNextSibling(c);
367    }
368    return res;
369  }
370
371  public static String htmlToXmlEscapedPlainText(Element r) {
372    StringBuilder s = new StringBuilder();
373    Node n = r.getFirstChild();
374    boolean ws = false;
375    while (n != null) {
376      if (n.getNodeType() == Node.TEXT_NODE) {
377        String t = n.getTextContent().trim();
378        if (Utilities.noString(t))
379          ws = true;
380        else {
381          if (ws)
382            s.append(" ");
383          ws = false;
384          s.append(t);
385        }
386      }
387      if (n.getNodeType() == Node.ELEMENT_NODE) {
388        if (ws)
389          s.append(" ");
390        ws = false;
391        s.append(htmlToXmlEscapedPlainText((Element) n));
392        if (r.getNodeName().equals("br") || r.getNodeName().equals("p"))
393          s.append("\r\n");
394      }
395      n = n.getNextSibling();      
396    }
397    return s.toString();
398  }
399
400  public static String htmlToXmlEscapedPlainText(String definition) throws ParserConfigurationException, SAXException, IOException  {
401    return htmlToXmlEscapedPlainText(parseToDom("<div>"+definition+"</div>").getDocumentElement());
402  }
403
404  public static String elementToString(Element el) {
405    if (el == null)
406      return "";
407    Document document = el.getOwnerDocument();
408    DOMImplementationLS domImplLS = (DOMImplementationLS) document
409        .getImplementation();
410    LSSerializer serializer = domImplLS.createLSSerializer();
411    return serializer.writeToString(el);
412  }
413
414  public static String getNamedChildValue(Element element, String name) {
415    Element e = getNamedChild(element, name);
416    return e == null ? null : e.getAttribute("value");
417  }
418
419  public static void setNamedChildValue(Element element, String name, String value) throws FHIRException  {
420    Element e = getNamedChild(element, name);
421    if (e == null)
422      throw new FHIRException("unable to find element "+name);
423    e.setAttribute("value", value);
424  }
425
426
427        public static void getNamedChildrenWithWildcard(Element focus, String name, List<Element> children) {
428    Element c = getFirstChild(focus);
429    while (c != null) {
430        String n = c.getLocalName() != null ? c.getLocalName() : c.getNodeName(); 
431      if (name.equals(n) || (name.endsWith("[x]") && n.startsWith(name.substring(0, name.length()-3))))
432        children.add(c);
433      c = getNextSibling(c);
434    }
435  }
436
437        public static void getNamedChildrenWithTails(Element focus, String name, List<Element> children, Set<String> typeTails) {
438    Element c = getFirstChild(focus);
439    while (c != null) {
440      String n = c.getLocalName() != null ? c.getLocalName() : c.getNodeName(); 
441      if (n.equals(name) || (!n.equals("responseCode") && (n.startsWith(name) && typeTails.contains(n.substring(name.length())))))
442        children.add(c);
443      c = getNextSibling(c);
444    }
445  }
446        
447  public static boolean hasNamedChild(Element e, String name) {
448    Element c = getFirstChild(e);
449    while (c != null && !name.equals(c.getLocalName()) && !name.equals(c.getNodeName()))
450      c = getNextSibling(c);
451    return c != null;
452  }
453
454  public static Document parseToDom(String content) throws ParserConfigurationException, SAXException, IOException  {
455    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
456    factory.setNamespaceAware(false);
457    DocumentBuilder builder = factory.newDocumentBuilder();
458    return builder.parse(new ByteArrayInputStream(content.getBytes()));
459  }
460
461  public static Document parseFileToDom(String filename) throws ParserConfigurationException, SAXException, IOException  {
462    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
463    factory.setNamespaceAware(false);
464    DocumentBuilder builder = factory.newDocumentBuilder();
465    return builder.parse(new FileInputStream(filename));
466  }
467
468  public static Element getLastChild(Element e) {
469    if (e == null)
470      return null;
471    Node n = e.getLastChild();
472    while (n != null && n.getNodeType() != Node.ELEMENT_NODE)
473      n = n.getPreviousSibling();
474    return (Element) n;
475  }
476
477  public static Element getPrevSibling(Element e) {
478    Node n = e.getPreviousSibling();
479    while (n != null && n.getNodeType() != Node.ELEMENT_NODE)
480      n = n.getPreviousSibling();
481    return (Element) n;
482  }
483
484  public static String getNamedChildAttribute(Element element, String name, String aname) {
485    Element e = getNamedChild(element, name);
486    return e == null ? null : e.getAttribute(aname);
487  }
488
489  public static void writeDomToFile(Document doc, String filename) throws TransformerException {
490    TransformerFactory transformerFactory = TransformerFactory.newInstance();
491    Transformer transformer = transformerFactory.newTransformer();
492    DOMSource source = new DOMSource(doc);
493    StreamResult streamResult =  new StreamResult(new File(filename));
494    transformer.transform(source, streamResult);    
495  }
496
497  public static String getXsiType(org.w3c.dom.Element element) {
498    Attr a = element.getAttributeNodeNS("http://www.w3.org/2001/XMLSchema-instance", "type");
499    return (a == null ? null : a.getTextContent());
500    
501  }
502
503        public static String getDirectText(org.w3c.dom.Element node) {
504    Node n = node.getFirstChild();
505    StringBuilder b = new StringBuilder();
506    while (n != null) {
507        if (n.getNodeType() == Node.TEXT_NODE) 
508                b.append(n.getTextContent());
509        n = n.getNextSibling();
510    }
511          return b.toString().trim();
512        }
513
514  public static void deleteByName(Element e, String name) {
515    List<Element> matches = getNamedChildren(e, name);
516    for (Element m : matches)
517      e.removeChild(m);    
518  }
519
520  public static void deleteAttr(Element e, String namespaceURI, String localName) {
521    if (e.hasAttributeNS(namespaceURI, localName))
522      e.removeAttributeNS(namespaceURI, localName);
523    
524  }
525
526  public static Node[] children(Element ed) {
527    Node[] res = new Node[ed.getChildNodes().getLength()];
528    for (int i = 0; i < ed.getChildNodes().getLength(); i++)
529      res[i] = ed.getChildNodes().item(i);
530    return res;
531  }
532
533  public static Element insertChild(Document doc, Element element, String name, String namespace, int indent) {
534    Node node = doc.createTextNode("\n"+Utilities.padLeft("", ' ', indent));
535    Element child = doc.createElementNS(namespace, name);
536    element.insertBefore(child, element.getFirstChild());
537    element.insertBefore(node, element.getFirstChild());
538    return child;
539  }
540
541  public static Element insertChild(Document doc, Element element, String name, String namespace, Node before, int indent) {
542    if (before == null) {
543      Node node = doc.createTextNode("\n"+Utilities.padLeft("", ' ', indent));
544      element.insertBefore(node, before);
545    }
546    Element child = doc.createElementNS(namespace, name);
547    element.insertBefore(child, before);
548    if (before != null) {
549      Node node = doc.createTextNode("\n"+Utilities.padLeft("", ' ', indent));
550      element.insertBefore(node, before);
551    }
552    return child;
553  }
554
555  public static void addTextTag(Document doc, Element element, String name, String namespace, String text, int indent) {
556    Node node = doc.createTextNode("\n"+Utilities.padLeft("", ' ', indent));
557    element.appendChild(node);
558    Element child = doc.createElementNS(namespace, name);
559    element.appendChild(child);
560    child.setAttribute("value", text);    
561  }
562
563  public static void saveToFile(Element root, OutputStream stream) throws TransformerException {
564    Transformer transformer = TransformerFactory.newInstance().newTransformer();
565    Result output = new StreamResult(stream);
566    Source input = new DOMSource(root);
567
568    transformer.transform(input, output);
569  }
570
571  public static void spacer(Document doc, Element element, int indent) {
572    Node node = doc.createTextNode("\n"+Utilities.padLeft("", ' ', indent));
573    element.appendChild(node);
574   
575  }
576
577  public static String getNamedChildText(Element element, String name) {
578    Element e = getNamedChild(element, name);
579    return e == null ? null : e.getTextContent();
580  }
581
582        
583}