~ubuntu-branches/ubuntu/precise/xom/precise

« back to all changes in this revision

Viewing changes to src/nu/xom/xinclude/XIncluder.java

  • Committer: Bazaar Package Importer
  • Author(s): Varun Hiremath
  • Date: 2007-11-25 15:50:40 UTC
  • Revision ID: james.westby@ubuntu.com-20071125155040-r75ikcqf1vu0cei7
Tags: upstream-1.1
ImportĀ upstreamĀ versionĀ 1.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* Copyright 2002-2005 Elliotte Rusty Harold
 
2
   
 
3
   This library is free software; you can redistribute it and/or modify
 
4
   it under the terms of version 2.1 of the GNU Lesser General Public 
 
5
   License as published by the Free Software Foundation.
 
6
   
 
7
   This library is distributed in the hope that it will be useful,
 
8
   but WITHOUT ANY WARRANTY; without even the implied warranty of
 
9
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 
10
   GNU Lesser General Public License for more details.
 
11
   
 
12
   You should have received a copy of the GNU Lesser General Public
 
13
   License along with this library; if not, write to the 
 
14
   Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
 
15
   Boston, MA 02111-1307  USA
 
16
   
 
17
   You can contact Elliotte Rusty Harold by sending e-mail to
 
18
   elharo@metalab.unc.edu. Please include the word "XOM" in the
 
19
   subject line. The XOM home page is located at http://www.xom.nu/
 
20
*/
 
21
 
 
22
package nu.xom.xinclude;
 
23
 
 
24
import java.io.BufferedInputStream;
 
25
import java.io.BufferedReader;
 
26
import java.io.IOException;
 
27
import java.io.InputStream;
 
28
import java.io.InputStreamReader;
 
29
import java.io.Reader;
 
30
import java.io.UnsupportedEncodingException;
 
31
import java.net.MalformedURLException;
 
32
import java.net.URL;
 
33
import java.net.URLConnection;
 
34
import java.util.Locale;
 
35
import java.util.ArrayList;
 
36
 
 
37
import nu.xom.Attribute;
 
38
import nu.xom.Builder;
 
39
import nu.xom.DocType;
 
40
import nu.xom.Document;
 
41
import nu.xom.Element;
 
42
import nu.xom.Elements;
 
43
import nu.xom.MalformedURIException;
 
44
import nu.xom.Node;
 
45
import nu.xom.NodeFactory;
 
46
import nu.xom.Nodes;
 
47
import nu.xom.ParentNode;
 
48
import nu.xom.ParsingException;
 
49
import nu.xom.Text;
 
50
 
 
51
/**
 
52
 * <p>
 
53
 * Implements XInclude resolution as specified in 
 
54
 * <a href="http://www.w3.org/TR/2004/REC-xinclude-20041220/"
 
55
 * target="_top"><cite>XML Inclusions (XInclude) Version 
 
56
 * 1.0</cite></a>. Fallbacks are supported. 
 
57
 * The XPointer <code>element()</code> scheme and 
 
58
 * shorthand XPointers are also supported. The XPointer 
 
59
 * <code>xpointer()</code> scheme is not supported. 
 
60
 * The <code>accept</code> and <code>accept-language</code> 
 
61
 * attributes are supported.
 
62
 * </p>
 
63
 * 
 
64
 * @author Elliotte Rusty Harold
 
65
 * @version 1.1b3
 
66
 *
 
67
 */
 
68
public class XIncluder {
 
69
    
 
70
    private static String version = System.getProperty("java.version");   
 
71
 
 
72
    // could rewrite this to handle only elements in documents 
 
73
    // (no parentless elements) and then add code to handle Nodes 
 
74
    // and parentless elements by sticking each one in a Document
 
75
 
 
76
    // prevent instantiation
 
77
    private XIncluder() {}
 
78
 
 
79
    /**
 
80
     * <p>
 
81
     *   The namespace name of all XInclude elements.
 
82
     * </p>
 
83
     */
 
84
    public final static String XINCLUDE_NS 
 
85
      = "http://www.w3.org/2001/XInclude";
 
86
 
 
87
    /**
 
88
     * <p>
 
89
     * Returns a copy of the document in which all 
 
90
     * <code>xinclude:include</code> elements have been  
 
91
     * replaced by their referenced content. The original 
 
92
     * <code>Document</code> object is not modified.
 
93
     * Resolution is recursive; that is, include elements
 
94
     * in the included documents are themselves resolved.
 
95
     * The <code>Document</code> returned contains no
 
96
     * include elements.
 
97
     * </p>
 
98
     * 
 
99
     * @param in the document in which include elements
 
100
     *     should be resolved
 
101
     * 
 
102
     * @return copy of the document in which
 
103
     *     all <code>xinclude:include</code> elements
 
104
     *     have been replaced by their referenced content
 
105
     * 
 
106
     * @throws BadParseAttributeException if an <code>include</code>  
 
107
     *     element has a <code>parse</code> attribute with any value 
 
108
     *     other than <code>text</code> or <code>parse</code>
 
109
     * @throws InclusionLoopException if the document  
 
110
     *     contains an XInclude element that attempts to include 
 
111
     *     a document in which this element is directly or indirectly 
 
112
     *     included.
 
113
     * @throws IOException if an included document could not be loaded,
 
114
     *     and no fallback was available
 
115
     * @throws NoIncludeLocationException if an <code>xinclude:include</code> 
 
116
     *      element does not have an <code>href</code> attribute
 
117
     * @throws ParsingException if an included XML document 
 
118
     *     was malformed
 
119
     * @throws UnsupportedEncodingException if an included document 
 
120
     *     used an encoding this parser does not support, and no
 
121
     *     fallback was available
 
122
     * @throws XIncludeException if the document violates the
 
123
     *     syntax rules of XInclude
 
124
     * @throws XMLException if resolving an include element would 
 
125
     *      result in a malformed document
 
126
     */
 
127
     public static Document resolve(Document in)  
 
128
       throws BadParseAttributeException, InclusionLoopException, 
 
129
             IOException, NoIncludeLocationException, ParsingException, 
 
130
             UnsupportedEncodingException, XIncludeException {  
 
131
         
 
132
        Builder builder = new Builder();
 
133
        return resolve(in, builder);
 
134
        
 
135
    }
 
136
 
 
137
    /**
 
138
     * <p>
 
139
     * Returns a copy of the document in which all 
 
140
     * <code>xinclude:include</code> elements have been  
 
141
     * replaced by their referenced content as loaded by the builder.
 
142
     * The original <code>Document</code> object is not modified.
 
143
     * Resolution is recursive; that is, include elements
 
144
     * in the included documents are themselves resolved.
 
145
     * The document returned contains no <code>include</code> elements.
 
146
     * </p>
 
147
     * 
 
148
     * @param in the document in which include elements
 
149
     *     should be resolved
 
150
     * @param builder the builder used to build the
 
151
     *     nodes included from other documents
 
152
     * 
 
153
     * @return copy of the document in which
 
154
     *     all <code>xinclude:include</code> elements
 
155
     *     have been replaced by their referenced content
 
156
     * 
 
157
     * @throws BadParseAttributeException if an <code>include</code>  
 
158
     *     element has a <code>parse</code> attribute with any value 
 
159
     *     other than <code>text</code> or <code>parse</code>
 
160
     * @throws InclusionLoopException if the document 
 
161
     *     contains an XInclude element that attempts to include 
 
162
     *     a document in which this element is directly or indirectly 
 
163
     *     included.
 
164
     * @throws IOException if an included document could not be loaded,
 
165
     *     and no fallback was available
 
166
     * @throws NoIncludeLocationException if an <code>xinclude:include</code> 
 
167
     *      element does not have an href attribute.
 
168
     * @throws ParsingException if an included XML document 
 
169
     *     was malformed
 
170
     * @throws UnsupportedEncodingException if an included document 
 
171
     *     used an encoding this parser does not support, and no
 
172
     *     fallback was available
 
173
     * @throws XIncludeException if the document violates the
 
174
     *     syntax rules of XInclude
 
175
     * @throws XMLException if resolving an include element would 
 
176
     *      result in a malformed document
 
177
     */
 
178
     public static Document resolve(Document in, Builder builder)  
 
179
       throws BadParseAttributeException, InclusionLoopException, 
 
180
             IOException, NoIncludeLocationException, ParsingException, 
 
181
             UnsupportedEncodingException, XIncludeException {        
 
182
         
 
183
        Document copy = new Document(in);
 
184
        resolveInPlace(copy, builder);
 
185
        return copy;   
 
186
        
 
187
    }
 
188
 
 
189
    /**
 
190
     * <p>
 
191
     * Modifies a document by replacing all  
 
192
     * <code>xinclude:include</code> elements 
 
193
     * by their referenced content. 
 
194
     * Resolution is recursive; that is, include elements
 
195
     * in the included documents are themselves resolved.
 
196
     * The resolved document contains no
 
197
     * <code>xinclude:include</code> elements.
 
198
     * </p>
 
199
     * 
 
200
     * <p>
 
201
     * If the inclusion fails for any reason&mdash;XInclude syntax
 
202
     * error, missing resource with no fallback, etc.&mdash;the document
 
203
     * may be left in a partially resolved state.
 
204
     * </p>
 
205
     * 
 
206
     * @param in the document in which include elements
 
207
     *     should be resolved
 
208
     *
 
209
     * @throws BadParseAttributeException if an <code>include</code>  
 
210
     *     element has a <code>parse</code> attribute
 
211
     *     with any value other than <code>text</code> 
 
212
     *     or <code>parse</code>
 
213
     * @throws InclusionLoopException if the document 
 
214
     *     contains an XInclude element that attempts to include a  
 
215
     *     document in which this element is directly or indirectly 
 
216
     *     included
 
217
     * @throws IOException if an included document could not be loaded,
 
218
     *     and no fallback was available
 
219
     * @throws NoIncludeLocationException if an <code>xinclude:include</code>
 
220
     *     element does not have an <code>href</code> attribute
 
221
     * @throws ParsingException if an included XML document
 
222
     *    was malformed
 
223
     * @throws UnsupportedEncodingException if an included document 
 
224
     *     used an encoding this parser does not support, and no 
 
225
     *     fallback was available
 
226
     * @throws XIncludeException if the document violates the
 
227
     *     syntax rules of XInclude
 
228
     * @throws XMLException if resolving an include element would 
 
229
     *     result in a malformed document
 
230
     */
 
231
    public static void resolveInPlace(Document in) 
 
232
      throws BadParseAttributeException, InclusionLoopException,  
 
233
             IOException, NoIncludeLocationException, ParsingException, 
 
234
             UnsupportedEncodingException, XIncludeException {        
 
235
        resolveInPlace(in, new Builder());
 
236
    }
 
237
 
 
238
    /**
 
239
     * <p>
 
240
     * Modifies a document by replacing all 
 
241
     * <code>xinclude:include</code> elements with their referenced 
 
242
     * content as loaded by the builder. Resolution is recursive; 
 
243
     * that is, <code>include</code> elements in the included documents 
 
244
     * are themselves resolved. The resolved document contains no 
 
245
     * <code>xinclude:include</code> elements.
 
246
     * </p>
 
247
     * 
 
248
     * <p>
 
249
     * If the inclusion fails for any reason &mdash; XInclude syntax
 
250
     * error, missing resource with no fallback, etc. &mdash; the 
 
251
     * document may be left in a partially resolved state.
 
252
     * </p>
 
253
     * 
 
254
     * @param in the document in which include elements
 
255
     *     should be resolved
 
256
     * @param builder the builder used to build the
 
257
     *     nodes included from other documents
 
258
     * 
 
259
     * @throws BadParseAttributeException if an <code>include</code>  
 
260
     *     element has a <code>parse</code> attribute
 
261
     *     with any value other than <code>text</code> 
 
262
     *     or <code>parse</code>
 
263
     * @throws InclusionLoopException if this element 
 
264
     *     contains an XInclude element that attempts to include a  
 
265
     *     document in which this element is directly or indirectly 
 
266
     *     included
 
267
     * @throws IOException if an included document could not be loaded,
 
268
     *     and no fallback was available
 
269
     * @throws NoIncludeLocationException if an <code>xinclude:include</code>
 
270
     *     element does not have an <code>href</code> attribute.
 
271
     * @throws ParsingException if an included XML document
 
272
     *    was malformed
 
273
     * @throws UnsupportedEncodingException if an included document 
 
274
     *     used an encoding this parser does not support, and no 
 
275
     *     fallback was available
 
276
     * @throws XIncludeException if the document violates the
 
277
     *     syntax rules of XInclude
 
278
     * @throws XMLException if resolving an include element would 
 
279
     *     result in a malformed document
 
280
     */
 
281
    public static void resolveInPlace(Document in, Builder builder) 
 
282
      throws BadParseAttributeException, InclusionLoopException,  
 
283
             IOException, NoIncludeLocationException, ParsingException, 
 
284
             UnsupportedEncodingException, XIncludeException {
 
285
        
 
286
        ArrayList stack = new ArrayList();
 
287
        resolveInPlace(in, builder, stack);
 
288
        
 
289
    }
 
290
 
 
291
    
 
292
    private static void resolveInPlace(
 
293
      Document in, Builder builder, ArrayList baseURLs) 
 
294
      throws IOException, ParsingException, XIncludeException {
 
295
        
 
296
        String base = in.getBaseURI();
 
297
        // workaround a bug in Sun VMs
 
298
        if (base != null && base.startsWith("file:///")) {
 
299
            base = "file:/" + base.substring(8);
 
300
        }
 
301
        
 
302
        baseURLs.add(base);   
 
303
        Element root = in.getRootElement();
 
304
        resolve(root, builder, baseURLs);
 
305
        baseURLs.remove(baseURLs.size()-1);
 
306
        
 
307
    }
 
308
 
 
309
    
 
310
    private static void resolve(
 
311
      Element element, Builder builder, ArrayList baseURLs)
 
312
      throws IOException, ParsingException, XIncludeException {
 
313
        
 
314
        resolve(element, builder, baseURLs, null);
 
315
        
 
316
    }
 
317
    
 
318
    
 
319
    private static void resolve(
 
320
      Element element, Builder builder, ArrayList baseURLs, Document originalDoc)
 
321
      throws IOException, ParsingException, XIncludeException {
 
322
        
 
323
        if (isIncludeElement(element)) {
 
324
            verifyIncludeElement(element);
 
325
            
 
326
            String parse = element.getAttributeValue("parse");
 
327
            if (parse == null) parse = "xml";
 
328
            String xpointer = element.getAttributeValue("xpointer");
 
329
            String encoding = element.getAttributeValue("encoding");
 
330
            String href = element.getAttributeValue("href");
 
331
            // empty string href is same as no href attribute
 
332
            if ("".equals(href)) href = null;
 
333
            
 
334
            ParentNode parent = element.getParent();
 
335
            String base = element.getBaseURI();
 
336
            URL baseURL = null;
 
337
            try {
 
338
                baseURL = new URL(base);     
 
339
            }
 
340
            catch (MalformedURLException ex) {
 
341
               // don't use base   
 
342
            }
 
343
            URL url = null;
 
344
            try {
 
345
                // xml:base attributes added to maintain the 
 
346
                // base URI should not have fragment IDs
 
347
 
 
348
                if (baseURL != null && href != null) {
 
349
                    url = absolutize(baseURL, href);
 
350
                }
 
351
                else if (href != null) {
 
352
                    try {
 
353
                        testURISyntax(href);
 
354
                        url = new URL(href); 
 
355
                    }
 
356
                    catch (MalformedURIException ex) {
 
357
                        if (baseURL == null) {
 
358
                            throw new BadHrefAttributeException(
 
359
                              "Could not resolve relative URI " + href
 
360
                              + " because the xi:include element does" 
 
361
                              + " not have a base URI.", href);    
 
362
                        }
 
363
                        throw new BadHrefAttributeException("Illegal IRI in href attribute", href);
 
364
                    }
 
365
                }
 
366
                
 
367
                String accept = element.getAttributeValue("accept");
 
368
                checkHeader(accept);
 
369
                String acceptLanguage = element.getAttributeValue("accept-language"); 
 
370
                checkHeader(acceptLanguage);
 
371
                
 
372
                if (parse.equals("xml")) {
 
373
                    
 
374
                    String parentLanguage = "";
 
375
                    if (parent instanceof Element) {
 
376
                        parentLanguage = getXMLLangValue((Element) parent);
 
377
                    }
 
378
                    
 
379
                    Nodes replacements;
 
380
                    if (url != null) { 
 
381
                        replacements = downloadXMLDocument(url, 
 
382
                          xpointer, builder, baseURLs, accept, acceptLanguage, parentLanguage);
 
383
                        // Add base URIs. Base URIs added by XInclusion require
 
384
                        // the element to maintain the same base URI as it had  
 
385
                        // in the original document. Since its base URI in the 
 
386
                        // original document does not contain a fragment ID,
 
387
                        // therefore its base URI after inclusion shouldn't, 
 
388
                        // and this special case is unnecessary. Base URI fixup
 
389
                        // should not add the fragment ID. 
 
390
                        for (int i = 0; i < replacements.size(); i++) {
 
391
                            Node child = replacements.get(i);
 
392
                            if (child instanceof Element) {
 
393
                                String noFragment = child.getBaseURI();
 
394
                                if (noFragment.indexOf('#') >= 0) {
 
395
                                    noFragment = noFragment.substring(
 
396
                                      0, noFragment.indexOf('#'));
 
397
                                }
 
398
                                Element baseless = (Element) child;
 
399
                                
 
400
                                // parent is null here; need to get real parent
 
401
                                String parentBase = parent.getBaseURI();
 
402
                                if (parentBase != null && ! "".equals(parentBase)) {
 
403
                                    parentBase = getDirectoryBase(parentBase);
 
404
                                }
 
405
                                
 
406
                                if (noFragment.startsWith(parentBase)) {
 
407
                                    noFragment = noFragment.substring(parentBase.length());
 
408
                                }
 
409
                                Attribute baseAttribute = new Attribute(
 
410
                                  "xml:base", 
 
411
                                  "http://www.w3.org/XML/1998/namespace", 
 
412
                                  noFragment 
 
413
                                );
 
414
                                baseless.addAttribute(baseAttribute);
 
415
                                
 
416
                            }
 
417
                        }  
 
418
                    }
 
419
                    else {
 
420
                        Document parentDoc = element.getDocument();
 
421
                        if (parentDoc == null) {
 
422
                            parentDoc = originalDoc;
 
423
                        }
 
424
                        Nodes originals = XPointer.query(parentDoc, xpointer);
 
425
                        replacements = new Nodes(); 
 
426
                        for (int i = 0; i < originals.size(); i++) {
 
427
                            Node original = originals.get(i);
 
428
                            // current implementation of XPointer never returns non-elements
 
429
                            if (contains((Element) original, element)) {
 
430
                                throw new InclusionLoopException(
 
431
                                  "Element tried to include itself"
 
432
                                ); 
 
433
                            }  
 
434
                            Node copy = original.copy();
 
435
                            replacements.append(copy);        
 
436
                        }  
 
437
                        replacements = resolveXPointerSelection(
 
438
                          replacements, builder, baseURLs, parentDoc);  
 
439
                                                 
 
440
                    }
 
441
                      
 
442
                    // Will fail if we're replacing the root element with 
 
443
                    // a node list containing zero or multiple elements,
 
444
                    // but that should fail. However, I may wish to 
 
445
                    // adjust the type of exception thrown. This is only
 
446
                    // relevant if I add support for the xpointer scheme
 
447
                    // since otherwise you can only point at one element
 
448
                    // or document.
 
449
                    if (parent instanceof Element) {
 
450
                        int position = parent.indexOf(element);
 
451
                        for (int i = 0; i < replacements.size(); i++) {
 
452
                            Node child = replacements.get(i);
 
453
                            parent.insertChild(child, position+i); 
 
454
                        }
 
455
                        element.detach();
 
456
                    }
 
457
                    else {  // root element needs special treatment
 
458
                        // I am assuming here that it is not possible 
 
459
                        // for parent to be null. I think this is true 
 
460
                        // in the current version, but it could change 
 
461
                        // if I made it possible to directly resolve an
 
462
                        // element or a Nodes.
 
463
                        Document doc = (Document) parent;
 
464
                        int i = 0;
 
465
                        // prolog and root
 
466
                        while (true) {
 
467
                            Node child = replacements.get(i);
 
468
                            i++;
 
469
                            if (child instanceof Element) {
 
470
                                doc.setRootElement((Element) child);
 
471
                                break;   
 
472
                            }
 
473
                            else {
 
474
                                doc.insertChild(
 
475
                                  child, doc.indexOf(element)
 
476
                                ); 
 
477
                            }
 
478
 
 
479
                        }
 
480
                        // epilog
 
481
                        Element root = doc.getRootElement();
 
482
                        int position = doc.indexOf(root);
 
483
                        for (int j=i; j < replacements.size(); j++) {
 
484
                            doc.insertChild(
 
485
                              replacements.get(j), position+1+j-i
 
486
                            );                             
 
487
                        }
 
488
                    }
 
489
                }
 
490
                else if (parse.equals("text")) {                   
 
491
                    Nodes replacements 
 
492
                      = downloadTextDocument(url, encoding, builder, accept, acceptLanguage);
 
493
                    for (int j = 0; j < replacements.size(); j++) {
 
494
                        Node replacement = replacements.get(j);
 
495
                        if (replacement instanceof Attribute) {
 
496
                            ((Element) parent).addAttribute((Attribute) replacement);
 
497
                        }
 
498
                        else {
 
499
                            parent.insertChild(replacement, parent.indexOf(element));
 
500
                        }   
 
501
                    }                    
 
502
                    parent.removeChild(element);
 
503
                }
 
504
                else {
 
505
                   throw new BadParseAttributeException(
 
506
                     "Bad value for parse attribute: " + parse, 
 
507
                     element.getDocument().getBaseURI());   
 
508
                }
 
509
            
 
510
            }
 
511
            catch (IOException ex) {
 
512
                processFallback(element, builder, baseURLs, parent, ex);
 
513
            }
 
514
            catch (XPointerSyntaxException ex) {
 
515
                processFallback(element, builder, baseURLs, parent, ex);
 
516
            }
 
517
            catch (XPointerResourceException ex) {
 
518
                // Process fallbacks;  I'm not sure this is correct 
 
519
                // behavior. Possibly this should include nothing. See
 
520
                // http://lists.w3.org/Archives/Public/www-xml-xinclude-comments/2003Aug/0000.html
 
521
                // Daniel Veillard thinks this is correct. See
 
522
                // http://lists.w3.org/Archives/Public/www-xml-xinclude-comments/2003Aug/0001.html
 
523
                processFallback(element, builder, baseURLs, parent, ex);
 
524
            }
 
525
            
 
526
        }
 
527
        else if (isFallbackElement(element)) {
 
528
            throw new MisplacedFallbackException(
 
529
              "Fallback element outside include element", 
 
530
              element.getDocument().getBaseURI()
 
531
            );
 
532
        }
 
533
        else {
 
534
            Elements children = element.getChildElements();
 
535
            for (int i = 0; i < children.size(); i++) {
 
536
                resolve(children.get(i), builder, baseURLs);   
 
537
            } 
 
538
        }
 
539
        
 
540
    }
 
541
    
 
542
    
 
543
    // ???? Move this into URIUtil when it goes public
 
544
    private static String getDirectoryBase(String parentBase) {
 
545
        if (parentBase.endsWith("/")) return parentBase;
 
546
        int lastSlash = parentBase.lastIndexOf('/');
 
547
        return parentBase.substring(0, lastSlash+1);
 
548
    }
 
549
    
 
550
    
 
551
    
 
552
    private static void verifyIncludeElement(Element element) 
 
553
      throws XIncludeException {
 
554
 
 
555
        testHref(element);
 
556
        testForFragmentIdentifier(element);
 
557
        verifyEncoding(element);
 
558
        testForForbiddenChildElements(element);
 
559
    }
 
560
 
 
561
    
 
562
    private static void testHref(Element include) throws NoIncludeLocationException {
 
563
 
 
564
        String href = include.getAttributeValue("href");
 
565
        String xpointer = include.getAttributeValue("xpointer");
 
566
        if (href == null && xpointer == null) {
 
567
            throw new NoIncludeLocationException(
 
568
              "Missing href attribute", 
 
569
              include.getDocument().getBaseURI()
 
570
            );   
 
571
        }
 
572
    }
 
573
 
 
574
    
 
575
    private static void testForFragmentIdentifier(Element include) 
 
576
      throws BadHrefAttributeException {
 
577
 
 
578
        String href = include.getAttributeValue("href");
 
579
        if (href != null) {
 
580
            if (href.indexOf('#') > -1) {
 
581
                throw new BadHrefAttributeException(
 
582
                  "fragment identifier in URI " + href, include.getBaseURI()
 
583
                );
 
584
            }
 
585
        }
 
586
        
 
587
    }
 
588
 
 
589
    
 
590
    private static void verifyEncoding(Element include) 
 
591
      throws BadEncodingAttributeException {
 
592
 
 
593
        String encoding = include.getAttributeValue("encoding");
 
594
        if (encoding == null) return;
 
595
        // production 81 of XML spec
 
596
        // EncName :=[A-Za-z] ([A-Za-z0-9._] | '-')*
 
597
        char[] text = encoding.toCharArray();
 
598
        if (text.length == 0) {
 
599
            throw new BadEncodingAttributeException(
 
600
              "Empty encoding attribute", include.getBaseURI());
 
601
        }
 
602
        char c = text[0];
 
603
        if (!((c >= 'A' &&  c <= 'Z') || (c >= 'a' &&  c <= 'z'))) {
 
604
            throw new BadEncodingAttributeException(
 
605
              "Illegal value for encoding attribute: " + encoding, include.getBaseURI()
 
606
            );
 
607
        }
 
608
        for (int i = 1; i < text.length; i++) {
 
609
            c = text[i];
 
610
            if ((c >= 'A' &&  c <= 'Z') || (c >= 'a' &&  c <= 'z')
 
611
              || (c >= '0' &&  c <= '9') || c == '-' || c == '_' || c == '.') {
 
612
                continue;
 
613
            }
 
614
            throw new BadEncodingAttributeException(
 
615
              "Illegal value for encoding attribute: " + encoding, include.getBaseURI()
 
616
            );
 
617
        }
 
618
        
 
619
    }
 
620
 
 
621
    
 
622
    // hack because URIUtil isn't public
 
623
    private static URL absolutize(URL baseURL, String href) 
 
624
      throws MalformedURLException, BadHrefAttributeException {
 
625
        
 
626
        Element parent = new Element("c");
 
627
        parent.setBaseURI(baseURL.toExternalForm());
 
628
        Element child = new Element("c");
 
629
        parent.appendChild(child);
 
630
        child.addAttribute(new Attribute(
 
631
          "xml:base", "http://www.w3.org/XML/1998/namespace", href));
 
632
        URL result = new URL(child.getBaseURI());
 
633
        if (!"".equals(href) && result.equals(baseURL)) {
 
634
            if (! baseURL.toExternalForm().endsWith(href)) {
 
635
                throw new BadHrefAttributeException(href 
 
636
                  + " is not a syntactically correct IRI");
 
637
            }
 
638
        }
 
639
        return result;
 
640
        
 
641
    }
 
642
 
 
643
    
 
644
    private static void testURISyntax(String href) {       
 
645
        Element e = new Element("e");
 
646
        e.setNamespaceURI(href);
 
647
    }
 
648
 
 
649
    
 
650
    private static String getXMLLangValue(Element element) {
 
651
        
 
652
        while (true) {
 
653
           Attribute lang = element.getAttribute(
 
654
             "lang", "http://www.w3.org/XML/1998/namespace");
 
655
           if (lang != null) return lang.getValue();
 
656
           ParentNode parent = element.getParent();
 
657
           if (parent == null) return "";
 
658
           else if (parent instanceof Document) return "";
 
659
           else element = (Element) parent;
 
660
        }
 
661
        
 
662
    }
 
663
    
 
664
    
 
665
    // This assumes current implementation of XPointer that
 
666
    // always selects exactly one element or throws an exception.
 
667
    private static Nodes resolveXPointerSelection(Nodes in, 
 
668
      Builder builder, ArrayList baseURLs, Document original) 
 
669
      throws IOException, ParsingException, XIncludeException {
 
670
 
 
671
        Element preinclude = (Element) in.get(0);
 
672
        return resolveSilently(preinclude, builder, baseURLs, original);
 
673
        
 
674
    }
 
675
    
 
676
 
 
677
    private static boolean contains(ParentNode ancestor, Node descendant) {
 
678
        
 
679
        for (Node parent = descendant; 
 
680
             parent != null; 
 
681
             parent=parent.getParent()) {
 
682
            if (parent == ancestor) return true;  
 
683
        }    
 
684
        
 
685
        return false;   
 
686
        
 
687
    }
 
688
 
 
689
    
 
690
    private static Nodes resolveSilently(
 
691
      Element element, Builder builder, ArrayList baseURLs, Document originalDoc)
 
692
      throws IOException, ParsingException, XIncludeException {
 
693
        
 
694
        // There is no possibility the element passed to this method 
 
695
        // is an include or a fallback element 
 
696
        if (isIncludeElement(element) || isFallbackElement(element) ) {
 
697
            throw new RuntimeException(
 
698
              "XOM BUG: include or fallback element passed to resolveSilently;"
 
699
              + " please report with a test case");
 
700
        }
 
701
        
 
702
        Elements children = element.getChildElements();
 
703
        for (int i = 0; i < children.size(); i++) {
 
704
            resolve(children.get(i), builder, baseURLs, originalDoc);   
 
705
        } 
 
706
        return new Nodes(element);
 
707
        
 
708
    }
 
709
 
 
710
    
 
711
    private static void testForForbiddenChildElements(Element element) 
 
712
      throws XIncludeException {
 
713
        
 
714
        int fallbacks = 0;
 
715
        Elements children = element.getChildElements();
 
716
        int size = children.size();
 
717
        for (int i = 0; i < size; i++) {
 
718
            Element child = children.get(i);
 
719
            if (XINCLUDE_NS.equals(child.getNamespaceURI())) {
 
720
                if ("fallback".equals(child.getLocalName())) {
 
721
                    fallbacks++;
 
722
                    if (fallbacks > 1) {
 
723
                        throw new XIncludeException("Multiple fallback elements", 
 
724
                          element.getDocument().getBaseURI()); 
 
725
                    }
 
726
                }
 
727
                else {
 
728
                    throw new XIncludeException(
 
729
                      "Include element contains an include child",
 
730
                      element.getDocument().getBaseURI());     
 
731
                }
 
732
            }
 
733
        }
 
734
        
 
735
    }
 
736
 
 
737
    
 
738
    private static void processFallback(Element includeElement, 
 
739
      Builder builder, ArrayList baseURLs, ParentNode parent, Exception ex)
 
740
        throws XIncludeException, IOException, ParsingException {
 
741
        
 
742
           Element fallback 
 
743
              = includeElement.getFirstChildElement("fallback", XINCLUDE_NS);
 
744
           if (fallback == null) {
 
745
                if (ex instanceof IOException) throw (IOException) ex;
 
746
                XIncludeException ex2 = new XIncludeException(
 
747
                  ex.getMessage(), includeElement.getDocument().getBaseURI());
 
748
                ex2.initCause(ex);
 
749
                throw ex2;
 
750
           }
 
751
             
 
752
           while (fallback.getChildCount() > 0) {
 
753
                Node child = fallback.getChild(0);
 
754
                if (child instanceof Element) {
 
755
                    resolve((Element) child, builder, baseURLs);
 
756
                }
 
757
                child = fallback.getChild(0);
 
758
                child.detach();
 
759
                parent.insertChild(child, parent.indexOf(includeElement)); 
 
760
           }
 
761
           includeElement.detach();
 
762
           
 
763
    }
 
764
 
 
765
    
 
766
    // I could probably move the xpointer out of this method
 
767
    private static Nodes downloadXMLDocument(
 
768
      URL source, String xpointer, Builder builder, ArrayList baseURLs,
 
769
      String accept, String acceptLanguage, String parentLanguage) 
 
770
      throws IOException, ParsingException, XIncludeException, 
 
771
        XPointerSyntaxException, XPointerResourceException {
 
772
 
 
773
        String base = source.toExternalForm();
 
774
        if (xpointer == null && baseURLs.indexOf(base) != -1) {
 
775
            throw new InclusionLoopException(
 
776
              "Tried to include the already included document " + base +
 
777
              " from " + baseURLs.get(baseURLs.size()-1), (String) baseURLs.get(baseURLs.size()-1));
 
778
        }      
 
779
        
 
780
        URLConnection uc = source.openConnection();
 
781
        setHeaders(uc, accept, acceptLanguage);
 
782
        InputStream in = new BufferedInputStream(uc.getInputStream());
 
783
        Document doc;
 
784
        try {
 
785
            doc = builder.build(in, source.toExternalForm());
 
786
        }
 
787
        finally {
 
788
            in.close();
 
789
        }
 
790
          
 
791
        resolveInPlace(doc, builder, baseURLs); 
 
792
        Nodes included;
 
793
        if (xpointer != null && xpointer.length() != 0) {
 
794
            included = XPointer.query(doc, xpointer); 
 
795
            // fill in lang attributes here
 
796
            for (int i = 0; i < included.size(); i++) {
 
797
                Node node = included.get(i);
 
798
                // Current implementation can only select elements
 
799
                Element top = (Element) node;
 
800
                Attribute lang = top.getAttribute("lang", 
 
801
                  "http://www.w3.org/XML/1998/namespace");
 
802
                if (lang == null) {
 
803
                    String childLanguage = getXMLLangValue(top);
 
804
                    if (!parentLanguage.equals(childLanguage)) {
 
805
                        top.addAttribute(new Attribute("xml:lang", 
 
806
                          "http://www.w3.org/XML/1998/namespace", 
 
807
                          childLanguage));
 
808
                    }
 
809
                }
 
810
            }
 
811
        }
 
812
        else {
 
813
            included = new Nodes();
 
814
            for (int i = 0; i < doc.getChildCount(); i++) {
 
815
                Node child = doc.getChild(i);
 
816
                if (!(child instanceof DocType)) {
 
817
                    included.append(child);
 
818
                }            
 
819
            }
 
820
        }
 
821
        // so we can detach the old root if necessary
 
822
        doc.setRootElement(new Element("f")); 
 
823
        for (int i = 0; i < included.size(); i++) {
 
824
            Node node = included.get(i);
 
825
            // Take account of xml:base attribute, which we normally 
 
826
            // don't do when detaching
 
827
            String noFragment = node.getBaseURI();
 
828
            if (noFragment.indexOf('#') >= 0) {
 
829
                noFragment = noFragment.substring(0, noFragment.indexOf('#'));
 
830
            }
 
831
            node.detach();
 
832
            if (node instanceof Element) {
 
833
                ((Element) node).setBaseURI(noFragment);
 
834
            }
 
835
        }  
 
836
          
 
837
        return included;
 
838
        
 
839
    }
 
840
 
 
841
 
 
842
  /**
 
843
    * <p>
 
844
    * This utility method reads a document at a specified URL
 
845
    * and returns the contents of that document as a <code>Text</code>.
 
846
    * It's used to include files with <code>parse="text"</code>.
 
847
    * </p>
 
848
    *
 
849
    * @param source   <code>URL</code> of the document to download 
 
850
    * @param encoding encoding of the document; e.g. UTF-8,
 
851
    *                  ISO-8859-1, etc.
 
852
    * @param builder the <code>Builder</code> used to build the
 
853
    *     nodes included from other documents
 
854
    * 
 
855
    * @return the document retrieved from the source <code>URL</code>
 
856
    * 
 
857
    * @throws IOException if the remote document cannot
 
858
    *     be read due to an I/O error
 
859
    */    
 
860
    private static Nodes downloadTextDocument(
 
861
      URL source, String encoding, Builder builder,
 
862
      String accept, String language) 
 
863
      throws IOException, XIncludeException {
 
864
         
 
865
        if (encoding == null || encoding.length() == 0) {
 
866
            encoding = "UTF-8"; 
 
867
        }
 
868
 
 
869
        URLConnection uc = source.openConnection();
 
870
        setHeaders(uc, accept, language);
 
871
        
 
872
        String encodingFromHeader = uc.getContentEncoding();
 
873
        String contentType = uc.getContentType();
 
874
        int contentLength = uc.getContentLength();
 
875
        if (contentLength < 0) contentLength = 1024;
 
876
        InputStream in = new BufferedInputStream(uc.getInputStream());
 
877
        try {
 
878
            if (encodingFromHeader != null) encoding = encodingFromHeader;
 
879
            else {
 
880
                if (contentType != null) {
 
881
                    contentType = contentType.toLowerCase(Locale.ENGLISH);
 
882
                    if (contentType.equals("text/xml") 
 
883
                      || contentType.equals("application/xml")   
 
884
                      || (contentType.startsWith("text/") 
 
885
                            && contentType.endsWith("+xml") ) 
 
886
                      || (contentType.startsWith("application/") 
 
887
                            && contentType.endsWith("+xml"))) {
 
888
                         encoding 
 
889
                           = EncodingHeuristics.readEncodingFromStream(in);
 
890
                    }
 
891
                }
 
892
            }
 
893
            // workaround for pre-1.3 VMs that don't recognize UTF-16
 
894
            if (version.startsWith("1.2")  || version.startsWith("1.1")) {
 
895
                if (encoding.equalsIgnoreCase("UTF-16")) {
 
896
                    // is it  big-endian or little-endian?
 
897
                    in.mark(2);
 
898
                    int first = in.read();
 
899
                    if (first == 0xFF) encoding = "UnicodeLittle";
 
900
                    else encoding="UnicodeBig";
 
901
                    in.reset();  
 
902
                }
 
903
                else if (encoding.equalsIgnoreCase("UnicodeBigUnmarked")) {
 
904
                    encoding = "UnicodeBig";
 
905
                }
 
906
                else if (encoding.equalsIgnoreCase("UnicodeLittleUnmarked")) {
 
907
                    encoding = "UnicodeLittle";
 
908
                }
 
909
            }
 
910
            Reader reader = new BufferedReader(
 
911
              new InputStreamReader(in, encoding)
 
912
            );
 
913
            StringBuffer sb = new StringBuffer(contentLength);
 
914
            for (int c = reader.read(); c != -1; c = reader.read()) {
 
915
              sb.append((char) c);
 
916
            }
 
917
            
 
918
            NodeFactory factory = builder.getNodeFactory();
 
919
            if (factory != null) {
 
920
                return factory.makeText(sb.toString());
 
921
            }
 
922
            else return new Nodes(new Text(sb.toString()));
 
923
        }
 
924
        finally {
 
925
            in.close();   
 
926
        }
 
927
      
 
928
    }
 
929
    
 
930
    
 
931
    private static void setHeaders(URLConnection uc, String accept, 
 
932
      String language) throws BadHTTPHeaderException {
 
933
      
 
934
        if (accept != null) {
 
935
            checkHeader(accept);
 
936
            uc.setRequestProperty("accept", accept);
 
937
        }
 
938
        if (language != null) {
 
939
            checkHeader(language);
 
940
            uc.setRequestProperty("accept-language", language);
 
941
        }
 
942
        
 
943
    }
 
944
    
 
945
    
 
946
    private static void checkHeader(String header) 
 
947
      throws BadHTTPHeaderException {
 
948
     
 
949
        if (header == null) return;
 
950
        int length = header.length();
 
951
        for (int i = 0; i < length; i++) {
 
952
            char c = header.charAt(i);
 
953
            if (c < 0x20 || c > 0x7E) {
 
954
                throw new BadHTTPHeaderException(
 
955
                  "Header contains illegal character 0x" 
 
956
                  + Integer.toHexString(c).toUpperCase());
 
957
            }
 
958
        }
 
959
        
 
960
    }
 
961
    
 
962
    
 
963
    private static boolean isIncludeElement(Element element) {
 
964
     
 
965
        return element.getLocalName().equals("include")
 
966
          && element.getNamespaceURI().equals(XINCLUDE_NS);
 
967
        
 
968
    }
 
969
 
 
970
    
 
971
    private static boolean isFallbackElement(Element element) {
 
972
     
 
973
        return element.getLocalName().equals("fallback")
 
974
          && element.getNamespaceURI().equals(XINCLUDE_NS);
 
975
        
 
976
    }
 
977
 
 
978
 
 
979
}
 
 
b'\\ No newline at end of file'