KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > nu > xom > xinclude > XIncluder


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 JavaDoc;
25 import java.io.BufferedReader JavaDoc;
26 import java.io.IOException JavaDoc;
27 import java.io.InputStream JavaDoc;
28 import java.io.InputStreamReader JavaDoc;
29 import java.io.Reader JavaDoc;
30 import java.io.UnsupportedEncodingException JavaDoc;
31 import java.net.MalformedURLException JavaDoc;
32 import java.net.URL JavaDoc;
33 import java.net.URLConnection JavaDoc;
34 import java.util.Locale JavaDoc;
35 import java.util.Stack JavaDoc;
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.0
66  *
67  */

68 public class XIncluder {
69     
70     private static String JavaDoc 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 JavaDoc 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 JavaDoc, NoIncludeLocationException, ParsingException,
130              UnsupportedEncodingException JavaDoc, 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 JavaDoc, NoIncludeLocationException, ParsingException,
181              UnsupportedEncodingException JavaDoc, 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 JavaDoc, NoIncludeLocationException, ParsingException,
234              UnsupportedEncodingException JavaDoc, 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 JavaDoc, NoIncludeLocationException, ParsingException,
284              UnsupportedEncodingException JavaDoc, XIncludeException {
285         
286         Stack JavaDoc stack = new Stack JavaDoc();
287         resolveInPlace(in, builder, stack);
288         
289     }
290
291     
292     private static void resolveInPlace(
293       Document in, Builder builder, Stack JavaDoc baseURLs)
294       throws IOException JavaDoc, ParsingException, XIncludeException {
295         
296         String JavaDoc 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.push(base);
303         Element root = in.getRootElement();
304         resolve(root, builder, baseURLs);
305         baseURLs.pop();
306         
307     }
308
309     
310     private static void resolve(
311       Element element, Builder builder, Stack JavaDoc baseURLs)
312       throws IOException JavaDoc, ParsingException, XIncludeException {
313         
314         resolve(element, builder, baseURLs, null);
315         
316     }
317     
318     
319     private static void resolve(
320       Element element, Builder builder, Stack JavaDoc baseURLs, Document originalDoc)
321       throws IOException JavaDoc, ParsingException, XIncludeException {
322         
323         if (isIncludeElement(element)) {
324             verifyIncludeElement(element);
325             
326             String JavaDoc parse = element.getAttributeValue("parse");
327             if (parse == null) parse = "xml";
328             String JavaDoc xpointer = element.getAttributeValue("xpointer");
329             String JavaDoc encoding = element.getAttributeValue("encoding");
330             String JavaDoc 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 JavaDoc base = element.getBaseURI();
336             URL JavaDoc baseURL = null;
337             try {
338                 baseURL = new URL JavaDoc(base);
339             }
340             catch (MalformedURLException JavaDoc ex) {
341                // don't use base
342
}
343             URL JavaDoc 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                     testURISyntax(href);
353                     url = new URL JavaDoc(href);
354                 }
355                 
356                 String JavaDoc accept = element.getAttributeValue("accept");
357                 checkHeader(accept);
358                 String JavaDoc acceptLanguage = element.getAttributeValue("accept-language");
359                 checkHeader(acceptLanguage);
360                 
361                 if (parse.equals("xml")) {
362                     
363                     String JavaDoc parentLanguage = "";
364                     if (parent instanceof Element) {
365                         parentLanguage = getXMLLangValue((Element) parent);
366                     }
367                     
368                     Nodes replacements;
369                     if (url != null) {
370                         replacements = downloadXMLDocument(url,
371                           xpointer, builder, baseURLs, accept, acceptLanguage, parentLanguage);
372                         // Add base URIs. Base URIs added by XInclusion require
373
// the element to maintain the same base URI as it had
374
// in the original document. Since its base URI in the
375
// original document does not contain a fragment ID,
376
// therefore its base URI after inclusion shouldn't,
377
// and this special case is unnecessary. Base URI fixup
378
// should not add the fragment ID.
379
for (int i = 0; i < replacements.size(); i++) {
380                             Node child = replacements.get(i);
381                             if (child instanceof Element) {
382                                 String JavaDoc noFragment = child.getBaseURI();
383                                 if (noFragment.indexOf('#') >= 0) {
384                                     noFragment = noFragment.substring(
385                                       0, noFragment.indexOf('#'));
386                                 }
387                                 Element baseless = (Element) child;
388                                 Attribute baseAttribute = new Attribute(
389                                   "xml:base",
390                                   "http://www.w3.org/XML/1998/namespace",
391                                   noFragment
392                                 );
393                                 baseless.addAttribute(baseAttribute);
394                                 
395                             }
396                         }
397                     }
398                     else {
399                         Document parentDoc = element.getDocument();
400                         if (parentDoc == null) {
401                             parentDoc = originalDoc;
402                         }
403                         Nodes originals = XPointer.query(parentDoc, xpointer);
404                         replacements = new Nodes();
405                         for (int i = 0; i < originals.size(); i++) {
406                             Node original = originals.get(i);
407                             // current implementation of XPointer never returns non-elements
408
if (contains((Element) original, element)) {
409                                 throw new InclusionLoopException(
410                                   "Element tried to include itself"
411                                 );
412                             }
413                             Node copy = original.copy();
414                             replacements.append(copy);
415                         }
416                         replacements = resolveXPointerSelection(
417                           replacements, builder, baseURLs, parentDoc);
418                                                  
419                     }
420                       
421                     // Will fail if we're replacing the root element with
422
// a node list containing zero or multiple elements,
423
// but that should fail. However, I may wish to
424
// adjust the type of exception thrown. This is only
425
// relevant if I add support for the xpointer scheme
426
// since otherwise you can only point at one element
427
// or document.
428
if (parent instanceof Element) {
429                         int position = parent.indexOf(element);
430                         for (int i = 0; i < replacements.size(); i++) {
431                             Node child = replacements.get(i);
432                             parent.insertChild(child, position+i);
433                         }
434                         element.detach();
435                     }
436                     else { // root element needs special treatment
437
// I am assuming here that it is not possible
438
// for parent to be null. I think this is true
439
// in the current version, but it could change
440
// if I made it possible to directly resolve an
441
// element or a Nodes.
442
Document doc = (Document) parent;
443                         int i = 0;
444                         // prolog and root
445
while (true) {
446                             Node child = replacements.get(i);
447                             i++;
448                             if (child instanceof Element) {
449                                 doc.setRootElement((Element) child);
450                                 break;
451                             }
452                             else {
453                                 doc.insertChild(
454                                   child, doc.indexOf(element)
455                                 );
456                             }
457
458                         }
459                         // epilog
460
Element root = doc.getRootElement();
461                         int position = doc.indexOf(root);
462                         for (int j=i; j < replacements.size(); j++) {
463                             doc.insertChild(
464                               replacements.get(j), position+1+j-i
465                             );
466                         }
467                     }
468                 }
469                 else if (parse.equals("text")) {
470                     Nodes replacements
471                       = downloadTextDocument(url, encoding, builder, accept, acceptLanguage);
472                     for (int j = 0; j < replacements.size(); j++) {
473                         Node replacement = replacements.get(j);
474                         if (replacement instanceof Attribute) {
475                             ((Element) parent).addAttribute((Attribute) replacement);
476                         }
477                         else {
478                             parent.insertChild(replacement, parent.indexOf(element));
479                         }
480                     }
481                     parent.removeChild(element);
482                 }
483                 else {
484                    throw new BadParseAttributeException(
485                      "Bad value for parse attribute: " + parse,
486                      element.getDocument().getBaseURI());
487                 }
488             
489             }
490             catch (IOException JavaDoc ex) {
491                 processFallback(element, builder, baseURLs, parent, ex);
492             }
493             catch (XPointerSyntaxException ex) {
494                 processFallback(element, builder, baseURLs, parent, ex);
495             }
496             catch (XPointerResourceException ex) {
497                 // Process fallbacks; I'm not sure this is correct
498
// behavior. Possibly this should include nothing. See
499
// http://lists.w3.org/Archives/Public/www-xml-xinclude-comments/2003Aug/0000.html
500
// Daniel Veillard thinks this is correct. See
501
// http://lists.w3.org/Archives/Public/www-xml-xinclude-comments/2003Aug/0001.html
502
processFallback(element, builder, baseURLs, parent, ex);
503             }
504             
505         }
506         else if (isFallbackElement(element)) {
507             throw new MisplacedFallbackException(
508               "Fallback element outside include element",
509               element.getDocument().getBaseURI()
510             );
511         }
512         else {
513             Elements children = element.getChildElements();
514             for (int i = 0; i < children.size(); i++) {
515                 resolve(children.get(i), builder, baseURLs);
516             }
517         }
518         
519     }
520     
521     
522     private static void verifyIncludeElement(Element element)
523       throws XIncludeException {
524
525         testHref(element);
526         testForFragmentIdentifier(element);
527         verifyEncoding(element);
528         testForForbiddenChildElements(element);
529     }
530
531     
532     private static void testHref(Element include) throws NoIncludeLocationException {
533
534         String JavaDoc href = include.getAttributeValue("href");
535         String JavaDoc xpointer = include.getAttributeValue("xpointer");
536         if (href == null && xpointer == null) {
537             throw new NoIncludeLocationException(
538               "Missing href attribute",
539               include.getDocument().getBaseURI()
540             );
541         }
542     }
543
544     
545     private static void testForFragmentIdentifier(Element include)
546       throws BadHrefAttributeException {
547
548         String JavaDoc href = include.getAttributeValue("href");
549         if (href != null) {
550             if (href.indexOf('#') > -1) {
551                 throw new BadHrefAttributeException(
552                   "fragment identifier in URI " + href, include.getBaseURI()
553                 );
554             }
555         }
556         
557     }
558
559     
560     private static void verifyEncoding(Element include)
561       throws BadEncodingAttributeException {
562
563         String JavaDoc encoding = include.getAttributeValue("encoding");
564         if (encoding == null) return;
565         // production 81 of XML spec
566
// EncName :=[A-Za-z] ([A-Za-z0-9._] | '-')*
567
char[] text = encoding.toCharArray();
568         if (text.length == 0) {
569             throw new BadEncodingAttributeException(
570               "Empty encoding attribute", include.getBaseURI());
571         }
572         char c = text[0];
573         if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))) {
574             throw new BadEncodingAttributeException(
575               "Illegal value for encoding attribute: " + encoding, include.getBaseURI()
576             );
577         }
578         for (int i = 1; i < text.length; i++) {
579             c = text[i];
580             if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
581               || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.') {
582                 continue;
583             }
584             throw new BadEncodingAttributeException(
585               "Illegal value for encoding attribute: " + encoding, include.getBaseURI()
586             );
587         }
588         
589     }
590
591     
592     // hack because URIUtil isn't public
593
private static URL JavaDoc absolutize(URL JavaDoc baseURL, String JavaDoc href)
594       throws MalformedURLException JavaDoc, BadHrefAttributeException {
595         
596         Element parent = new Element("c");
597         parent.setBaseURI(baseURL.toExternalForm());
598         Element child = new Element("c");
599         parent.appendChild(child);
600         child.addAttribute(new Attribute(
601           "xml:base", "http://www.w3.org/XML/1998/namespace", href));
602         URL JavaDoc result = new URL JavaDoc(child.getBaseURI());
603         if (!"".equals(href) && result.equals(baseURL)) {
604             if (! baseURL.toExternalForm().endsWith(href)) {
605                 throw new BadHrefAttributeException(href
606                   + " is not a syntactically correct IRI");
607             }
608         }
609         return result;
610         
611     }
612
613     
614     private static void testURISyntax(String JavaDoc href)
615       throws BadHrefAttributeException {
616         
617         try {
618             Element e = new Element("e");
619             e.setNamespaceURI(href);
620         }
621         catch (MalformedURIException ex) {
622             throw new BadHrefAttributeException("Illegal IRI in href attribute", href);
623         }
624         
625     }
626
627     
628     private static String JavaDoc getXMLLangValue(Element element) {
629         
630         while (true) {
631            Attribute lang = element.getAttribute(
632              "lang", "http://www.w3.org/XML/1998/namespace");
633            if (lang != null) return lang.getValue();
634            ParentNode parent = element.getParent();
635            if (parent == null) return "";
636            else if (parent instanceof Document) return "";
637            else element = (Element) parent;
638         }
639         
640     }
641     
642     
643     // This assumes current implementation of XPointer that
644
// always selects exactly one element or throws an exception.
645
private static Nodes resolveXPointerSelection(Nodes in,
646       Builder builder, Stack JavaDoc baseURLs, Document original)
647       throws IOException JavaDoc, ParsingException, XIncludeException {
648
649         Element preinclude = (Element) in.get(0);
650         return resolveSilently(preinclude, builder, baseURLs, original);
651         
652     }
653     
654
655     private static boolean contains(ParentNode ancestor, Node descendant) {
656         
657         for (Node parent = descendant;
658              parent != null;
659              parent=parent.getParent()) {
660             if (parent == ancestor) return true;
661         }
662         
663         return false;
664         
665     }
666
667     
668     private static Nodes resolveSilently(
669       Element element, Builder builder, Stack JavaDoc baseURLs, Document originalDoc)
670       throws IOException JavaDoc, ParsingException, XIncludeException {
671         
672         // There is no possibility the element passed to this method
673
// is an include or a fallback element
674
if (isIncludeElement(element) || isFallbackElement(element) ) {
675             throw new RuntimeException JavaDoc(
676               "XOM BUG: include or fallback element passed to resolveSilently;"
677               + " please report with a test case");
678         }
679         
680         Elements children = element.getChildElements();
681         for (int i = 0; i < children.size(); i++) {
682             resolve(children.get(i), builder, baseURLs, originalDoc);
683         }
684         return new Nodes(element);
685         
686     }
687
688     
689     private static void testForForbiddenChildElements(Element element)
690       throws XIncludeException {
691         
692         int fallbacks = 0;
693         Elements children = element.getChildElements();
694         int size = children.size();
695         for (int i = 0; i < size; i++) {
696             Element child = children.get(i);
697             if (XINCLUDE_NS.equals(child.getNamespaceURI())) {
698                 if ("fallback".equals(child.getLocalName())) {
699                     fallbacks++;
700                     if (fallbacks > 1) {
701                         throw new XIncludeException("Multiple fallback elements",
702                           element.getDocument().getBaseURI());
703                     }
704                 }
705                 else {
706                     throw new XIncludeException(
707                       "Include element contains an include child",
708                       element.getDocument().getBaseURI());
709                 }
710             }
711         }
712         
713     }
714
715     
716     private static void processFallback(Element includeElement,
717       Builder builder, Stack JavaDoc baseURLs, ParentNode parent, Exception JavaDoc ex)
718         throws XIncludeException, IOException JavaDoc, ParsingException {
719         
720            Element fallback
721               = includeElement.getFirstChildElement("fallback", XINCLUDE_NS);
722            if (fallback == null) {
723                 if (ex instanceof IOException JavaDoc) throw (IOException JavaDoc) ex;
724                 XIncludeException ex2 = new XIncludeException(
725                   ex.getMessage(), includeElement.getDocument().getBaseURI());
726                 ex2.initCause(ex);
727                 throw ex2;
728            }
729              
730            while (fallback.getChildCount() > 0) {
731                 Node child = fallback.getChild(0);
732                 if (child instanceof Element) {
733                     resolve((Element) child, builder, baseURLs);
734                 }
735                 child = fallback.getChild(0);
736                 child.detach();
737                 parent.insertChild(child, parent.indexOf(includeElement));
738            }
739            includeElement.detach();
740            
741     }
742
743     
744     // I could probably move the xpointer out of this method
745
private static Nodes downloadXMLDocument(
746       URL JavaDoc source, String JavaDoc xpointer, Builder builder, Stack JavaDoc baseURLs,
747       String JavaDoc accept, String JavaDoc acceptLanguage, String JavaDoc parentLanguage)
748       throws IOException JavaDoc, ParsingException, XIncludeException,
749         XPointerSyntaxException, XPointerResourceException {
750
751         String JavaDoc base = source.toExternalForm();
752         if (xpointer == null && baseURLs.indexOf(base) != -1) {
753             throw new InclusionLoopException(
754               "Tried to include the already included document " + base +
755               " from " + baseURLs.peek(), (String JavaDoc) baseURLs.peek());
756         }
757         
758         URLConnection JavaDoc uc = source.openConnection();
759         setHeaders(uc, accept, acceptLanguage);
760         InputStream JavaDoc in = new BufferedInputStream JavaDoc(uc.getInputStream());
761         Document doc;
762         try {
763             doc = builder.build(in, source.toExternalForm());
764         }
765         finally {
766             in.close();
767         }
768           
769         resolveInPlace(doc, builder, baseURLs);
770         Nodes included;
771         if (xpointer != null && xpointer.length() != 0) {
772             included = XPointer.query(doc, xpointer);
773             // fill in lang attributes here
774
for (int i = 0; i < included.size(); i++) {
775                 Node node = included.get(i);
776                 // Current implementation can only select elements
777
Element top = (Element) node;
778                 Attribute lang = top.getAttribute("lang",
779                   "http://www.w3.org/XML/1998/namespace");
780                 if (lang == null) {
781                     String JavaDoc childLanguage = getXMLLangValue(top);
782                     if (!parentLanguage.equals(childLanguage)) {
783                         top.addAttribute(new Attribute("xml:lang",
784                           "http://www.w3.org/XML/1998/namespace",
785                           childLanguage));
786                     }
787                 }
788             }
789         }
790         else {
791             included = new Nodes();
792             for (int i = 0; i < doc.getChildCount(); i++) {
793                 Node child = doc.getChild(i);
794                 if (!(child instanceof DocType)) {
795                     included.append(child);
796                 }
797             }
798         }
799         // so we can detach the old root if necessary
800
doc.setRootElement(new Element("f"));
801         for (int i = 0; i < included.size(); i++) {
802             Node node = included.get(i);
803             // Take account of xml:base attribute, which we normally
804
// don't do when detaching
805
String JavaDoc noFragment = node.getBaseURI();
806             if (noFragment.indexOf('#') >= 0) {
807                 noFragment = noFragment.substring(0, noFragment.indexOf('#'));
808             }
809             node.detach();
810             if (node instanceof Element) {
811                 ((Element) node).setBaseURI(noFragment);
812             }
813         }
814           
815         return included;
816         
817     }
818
819
820   /**
821     * <p>
822     * This utility method reads a document at a specified URL
823     * and returns the contents of that document as a <code>Text</code>.
824     * It's used to include files with <code>parse="text"</code>.
825     * </p>
826     *
827     * @param source <code>URL</code> of the document to download
828     * @param encoding encoding of the document; e.g. UTF-8,
829     * ISO-8859-1, etc.
830     * @param builder the <code>Builder</code> used to build the
831     * nodes included from other documents
832     *
833     * @return the document retrieved from the source <code>URL</code>
834     *
835     * @throws IOException if the remote document cannot
836     * be read due to an I/O error
837     */

838     private static Nodes downloadTextDocument(
839       URL JavaDoc source, String JavaDoc encoding, Builder builder,
840       String JavaDoc accept, String JavaDoc language)
841       throws IOException JavaDoc, XIncludeException {
842          
843         if (encoding == null || encoding.length() == 0) {
844             encoding = "UTF-8";
845         }
846
847         URLConnection JavaDoc uc = source.openConnection();
848         setHeaders(uc, accept, language);
849         
850         String JavaDoc encodingFromHeader = uc.getContentEncoding();
851         String JavaDoc contentType = uc.getContentType();
852         int contentLength = uc.getContentLength();
853         if (contentLength < 0) contentLength = 1024;
854         InputStream JavaDoc in = new BufferedInputStream JavaDoc(uc.getInputStream());
855         try {
856             if (encodingFromHeader != null) encoding = encodingFromHeader;
857             else {
858                 if (contentType != null) {
859                     contentType = contentType.toLowerCase(Locale.ENGLISH);
860                     if (contentType.equals("text/xml")
861                       || contentType.equals("application/xml")
862                       || (contentType.startsWith("text/")
863                             && contentType.endsWith("+xml") )
864                       || (contentType.startsWith("application/")
865                             && contentType.endsWith("+xml"))) {
866                          encoding
867                            = EncodingHeuristics.readEncodingFromStream(in);
868                     }
869                 }
870             }
871             // workaround for pre-1.3 VMs that don't recognize UTF-16
872
if (version.startsWith("1.2") || version.startsWith("1.1")) {
873                 if (encoding.equalsIgnoreCase("UTF-16")) {
874                     // is it big-endian or little-endian?
875
in.mark(2);
876                     int first = in.read();
877                     if (first == 0xFF) encoding = "UnicodeLittle";
878                     else encoding="UnicodeBig";
879                     in.reset();
880                 }
881                 else if (encoding.equalsIgnoreCase("UnicodeBigUnmarked")) {
882                     encoding = "UnicodeBig";
883                 }
884                 else if (encoding.equalsIgnoreCase("UnicodeLittleUnmarked")) {
885                     encoding = "UnicodeLittle";
886                 }
887             }
888             Reader JavaDoc reader = new BufferedReader JavaDoc(
889               new InputStreamReader JavaDoc(in, encoding)
890             );
891             StringBuffer JavaDoc sb = new StringBuffer JavaDoc(contentLength);
892             for (int c = reader.read(); c != -1; c = reader.read()) {
893               sb.append((char) c);
894             }
895             
896             NodeFactory factory = builder.getNodeFactory();
897             if (factory != null) {
898                 return factory.makeText(sb.toString());
899             }
900             else return new Nodes(new Text(sb.toString()));
901         }
902         finally {
903             in.close();
904         }
905       
906     }
907     
908     
909     private static void setHeaders(URLConnection JavaDoc uc, String JavaDoc accept,
910       String JavaDoc language) throws BadHTTPHeaderException {
911       
912         if (accept != null) {
913             checkHeader(accept);
914             uc.setRequestProperty("accept", accept);
915         }
916         if (language != null) {
917             checkHeader(language);
918             uc.setRequestProperty("accept-language", language);
919         }
920         
921     }
922     
923     
924     private static void checkHeader(String JavaDoc header)
925       throws BadHTTPHeaderException {
926      
927         if (header == null) return;
928         int length = header.length();
929         for (int i = 0; i < length; i++) {
930             char c = header.charAt(i);
931             if (c < 0x20 || c > 0x7E) {
932                 throw new BadHTTPHeaderException(
933                   "Header contains illegal character 0x"
934                   + Integer.toHexString(c).toUpperCase());
935             }
936         }
937         
938     }
939     
940     
941     private static boolean isIncludeElement(Element element) {
942      
943         return element.getLocalName().equals("include")
944           && element.getNamespaceURI().equals(XINCLUDE_NS);
945         
946     }
947
948     
949     private static boolean isFallbackElement(Element element) {
950      
951         return element.getLocalName().equals("fallback")
952           && element.getNamespaceURI().equals(XINCLUDE_NS);
953         
954     }
955
956
957 }
Popular Tags