Imported GNU Classpath 0.90
[official-gcc.git] / libjava / classpath / gnu / xml / stream / XIncludeFilter.java
blob7e707820d52c7cde637dada3e95261b53cad08e5
1 /* XIncludeFilter.java --
2 Copyright (C) 2005 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
38 package gnu.xml.stream;
40 import java.io.InputStream;
41 import java.io.InputStreamReader;
42 import java.io.IOException;
43 import java.io.Reader;
44 import java.net.HttpURLConnection;
45 import java.net.MalformedURLException;
46 import java.net.URL;
47 import java.net.URLConnection;
48 import java.util.HashSet;
49 import java.util.NoSuchElementException;
50 import java.util.StringTokenizer;
51 import javax.xml.namespace.QName;
52 import javax.xml.parsers.DocumentBuilder;
53 import javax.xml.parsers.DocumentBuilderFactory;
54 import javax.xml.parsers.ParserConfigurationException;
55 import javax.xml.stream.XMLInputFactory;
56 import javax.xml.stream.XMLStreamConstants;
57 import javax.xml.stream.XMLStreamException;
58 import javax.xml.stream.XMLStreamReader;
59 import javax.xml.stream.util.ReaderDelegate;
61 import org.w3c.dom.Attr;
62 import org.w3c.dom.Document;
63 import org.w3c.dom.DOMImplementation;
64 import org.w3c.dom.NamedNodeMap;
65 import org.w3c.dom.Node;
66 import org.w3c.dom.ProcessingInstruction;
67 import org.w3c.dom.TypeInfo;
68 import org.w3c.dom.traversal.DocumentTraversal;
69 import org.w3c.dom.traversal.NodeFilter;
70 import org.w3c.dom.traversal.TreeWalker;
71 import org.w3c.dom.xpath.XPathEvaluator;
72 import org.w3c.dom.xpath.XPathNSResolver;
73 import org.w3c.dom.xpath.XPathResult;
74 import org.xml.sax.SAXException;
76 /**
77 * StAX filter for performing XInclude processing.
79 * @see http://www.w3.org/TR/xinclude/
80 * @see http://www.w3.org/TR/xptr-framework/
81 * @see http://www.w3.org/TR/xptr-element/
83 * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
85 class XIncludeFilter
86 extends ReaderDelegate
89 static final String XINCLUDE_NS_URI = "http://www.w3.org/2001/XInclude";
90 static final int SHOW_FLAGS =
91 NodeFilter.SHOW_CDATA_SECTION |
92 NodeFilter.SHOW_COMMENT |
93 NodeFilter.SHOW_ELEMENT |
94 NodeFilter.SHOW_ENTITY_REFERENCE |
95 NodeFilter.SHOW_PROCESSING_INSTRUCTION |
96 NodeFilter.SHOW_TEXT;
98 final String systemId;
99 final boolean namespaceAware;
100 final boolean validating;
101 final boolean expandERefs;
102 String href;
103 int event;
104 boolean included;
105 XPathResult result;
106 int snapshotIndex;
107 Node current;
108 TreeWalker walker;
109 HashSet seen = new HashSet();
110 boolean backtracking;
111 boolean lookahead;
113 Reader includedText;
114 char[] buf;
115 int len = -1;
116 boolean inInclude, inFallback, seenFallback;
118 DocumentBuilder builder;
120 XIncludeFilter(XMLStreamReader reader, String systemId,
121 boolean namespaceAware, boolean validating,
122 boolean expandERefs)
124 super(reader);
127 this.systemId = XMLParser.absolutize(null, systemId);
129 catch (MalformedURLException e)
131 RuntimeException e2 = new RuntimeException("unsupported URL: " +
132 systemId);
133 e2.initCause(e);
134 throw e2;
136 this.namespaceAware = namespaceAware;
137 this.validating = validating;
138 this.expandERefs = expandERefs;
141 public int getAttributeCount()
143 if (current != null)
145 NamedNodeMap attrs = current.getAttributes();
146 return (attrs == null) ? 0 : attrs.getLength();
148 return super.getAttributeCount();
151 public String getAttributeLocalName(int index)
153 if (current != null)
155 NamedNodeMap attrs = current.getAttributes();
156 if (attrs == null)
157 return null;
158 Node attr = attrs.item(index);
159 return attr.getLocalName();
161 return super.getAttributeLocalName(index);
164 public String getAttributeNamespace(int index)
166 if (current != null)
168 NamedNodeMap attrs = current.getAttributes();
169 if (attrs == null)
170 return null;
171 Node attr = attrs.item(index);
172 return attr.getNamespaceURI();
174 return super.getAttributeNamespace(index);
177 public String getAttributePrefix(int index)
179 if (current != null)
181 NamedNodeMap attrs = current.getAttributes();
182 if (attrs == null)
183 return null;
184 Node attr = attrs.item(index);
185 return attr.getPrefix();
187 return super.getAttributePrefix(index);
190 public QName getAttributeName(int index)
192 if (current != null)
194 NamedNodeMap attrs = current.getAttributes();
195 if (attrs == null)
196 return null;
197 Node attr = attrs.item(index);
198 String localName = attr.getLocalName();
199 String uri = attr.getNamespaceURI();
200 String prefix = attr.getPrefix();
201 return new QName(uri, localName, prefix);
203 return super.getAttributeName(index);
206 public String getAttributeType(int index)
208 if (current != null)
210 NamedNodeMap attrs = current.getAttributes();
211 if (attrs == null)
212 return null;
213 Attr attr = (Attr) attrs.item(index);
214 TypeInfo ti = attr.getSchemaTypeInfo();
215 return (ti == null) ? "CDATA" : ti.getTypeName();
217 return super.getAttributeType(index);
220 public boolean isAttributeSpecified(int index)
222 if (current != null)
224 NamedNodeMap attrs = current.getAttributes();
225 if (attrs == null)
226 return false;
227 Attr attr = (Attr) attrs.item(index);
228 return attr.getSpecified();
230 return super.isAttributeSpecified(index);
233 public String getAttributeValue(int index)
235 if (current != null)
237 NamedNodeMap attrs = current.getAttributes();
238 if (attrs == null)
239 return null;
240 Node attr = attrs.item(index);
241 return attr.getNodeValue();
243 return super.getAttributeValue(index);
246 public String getAttributeValue(String uri, String localName)
248 if (current != null)
250 NamedNodeMap attrs = current.getAttributes();
251 if (attrs == null)
252 return null;
253 Node attr = attrs.getNamedItemNS(uri, localName);
254 return (attr == null) ? null : attr.getNodeValue();
256 return super.getAttributeValue(uri, localName);
259 public String getElementText()
260 throws XMLStreamException
262 if (current != null)
263 return current.getTextContent();
264 return super.getElementText();
267 public int getEventType()
269 return event;
272 public String getLocalName()
274 if (current != null)
275 return current.getLocalName();
276 return super.getLocalName();
279 public QName getName()
281 if (current != null)
283 String localName = current.getLocalName();
284 String uri = current.getNamespaceURI();
285 String prefix = current.getPrefix();
286 return new QName(uri, localName, prefix);
288 return super.getName();
291 public String getNamespaceURI()
293 if (current != null)
294 return current.getNamespaceURI();
295 return super.getNamespaceURI();
298 // TODO namespaces
300 public String getPIData()
302 if (current != null)
303 return ((ProcessingInstruction) current).getData();
304 return super.getPIData();
307 public String getPITarget()
309 if (current != null)
310 return ((ProcessingInstruction) current).getTarget();
311 return super.getPITarget();
314 public String getPrefix()
316 if (current != null)
317 return current.getPrefix();
318 return super.getPrefix();
321 public String getText()
323 if (current != null)
324 return current.getNodeValue();
325 if (walker != null)
327 Node n = walker.getCurrentNode();
328 if (n != null)
329 return n.getTextContent();
331 if (buf != null)
332 return new String(buf, 0, len);
333 return super.getText();
336 public char[] getTextCharacters()
338 if (current != null)
340 buf = current.getNodeValue().toCharArray();
341 len = buf.length;
343 if (buf != null)
344 return buf;
345 return super.getTextCharacters();
348 public int getTextCharacters(int sourceStart, char[] target,
349 int targetStart, int length)
350 throws XMLStreamException
352 if (current != null)
354 buf = current.getNodeValue().toCharArray();
355 len = buf.length;
357 if (buf != null)
359 int max = Math.min(len - sourceStart, length);
360 if (max > 0)
361 System.arraycopy(buf, sourceStart, target, targetStart, max);
362 return max;
364 return super.getTextCharacters(sourceStart, target, targetStart, length);
367 public int getTextLength()
369 if (current != null)
371 buf = current.getNodeValue().toCharArray();
372 len = buf.length;
374 if (buf != null)
375 return len;
376 return super.getTextLength();
379 public int getTextStart()
381 if (current != null)
383 buf = current.getNodeValue().toCharArray();
384 len = buf.length;
386 if (buf != null)
387 return 0;
388 return super.getTextStart();
391 public boolean hasNext()
392 throws XMLStreamException
394 if (!lookahead)
398 next();
400 catch (NoSuchElementException e)
402 event = -1;
404 lookahead = true;
406 return (event != -1);
409 public int next()
410 throws XMLStreamException
412 if (lookahead)
414 lookahead = false;
415 return event;
417 buf = null;
418 len = 0;
419 if (walker != null)
421 Node c = walker.getCurrentNode();
422 Node n = null;
423 if (c.getNodeType() == Node.ELEMENT_NODE)
425 boolean isStartElement = !seen.contains(c);
426 if (isStartElement)
428 seen.add(c);
429 current = c;
430 event = XMLStreamConstants.START_ELEMENT;
431 return event;
433 else if (backtracking)
435 n = walker.nextSibling();
436 if (n != null)
437 backtracking = false;
439 else
441 n = walker.firstChild();
442 if (n == null)
443 n = walker.nextSibling();
446 else
448 n = walker.firstChild();
449 if (n == null)
450 n = walker.nextSibling();
452 if (n == null)
454 current = walker.parentNode();
455 if (current != null && current.getNodeType() == Node.ELEMENT_NODE)
457 // end-element
458 backtracking = true;
459 event = XMLStreamConstants.END_ELEMENT;
460 return event;
462 else
464 walker = null;
465 current = null;
468 else
470 current = n;
471 switch (n.getNodeType())
473 case Node.ELEMENT_NODE:
474 return next();
475 case Node.TEXT_NODE:
476 String text = n.getNodeValue();
477 buf = text.toCharArray();
478 len = buf.length;
479 event = isSpace(buf, len) ?
480 XMLStreamConstants.SPACE :
481 XMLStreamConstants.CHARACTERS;
482 return event;
483 case Node.CDATA_SECTION_NODE:
484 event = XMLStreamConstants.CDATA;
485 return event;
486 case Node.COMMENT_NODE:
487 event = XMLStreamConstants.COMMENT;
488 return event;
489 case Node.PROCESSING_INSTRUCTION_NODE:
490 event = XMLStreamConstants.PROCESSING_INSTRUCTION;
491 return event;
492 case Node.ENTITY_REFERENCE_NODE:
493 event = XMLStreamConstants.ENTITY_REFERENCE;
494 return event;
495 default:
496 throw new IllegalStateException();
500 if (result != null)
502 switch (result.getResultType())
504 case XPathResult.BOOLEAN_TYPE:
505 boolean bval = result.getBooleanValue();
506 String btext = bval ? "true" : "false";
507 buf = btext.toCharArray();
508 len = buf.length;
509 result = null;
510 event = XMLStreamConstants.CHARACTERS;
511 return event;
512 case XPathResult.NUMBER_TYPE:
513 double nval = result.getNumberValue();
514 String ntext = new Double(nval).toString();
515 buf = ntext.toCharArray();
516 len = buf.length;
517 result = null;
518 event = XMLStreamConstants.CHARACTERS;
519 return event;
520 case XPathResult.STRING_TYPE:
521 String stext = result.getStringValue();
522 buf = stext.toCharArray();
523 len = buf.length;
524 result = null;
525 event = isSpace(buf, len) ?
526 XMLStreamConstants.SPACE :
527 XMLStreamConstants.CHARACTERS;
528 return event;
529 case XPathResult.ANY_UNORDERED_NODE_TYPE:
530 case XPathResult.FIRST_ORDERED_NODE_TYPE:
531 Node n1 = result.getSingleNodeValue();
532 Document d1 = getDocument(n1);
533 walker = getDocumentTraversal(d1)
534 .createTreeWalker(n1, SHOW_FLAGS, null, expandERefs);
535 result = null;
536 return next();
537 case XPathResult.ORDERED_NODE_ITERATOR_TYPE:
538 case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
539 Node n2 = result.iterateNext();
540 if (n2 == null)
542 result = null;
543 return next();
545 Document d2 = getDocument(n2);
546 walker = getDocumentTraversal(d2)
547 .createTreeWalker(n2, SHOW_FLAGS, null, expandERefs);
548 return next();
549 case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE:
550 case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE:
551 Node n3 = result.snapshotItem(snapshotIndex++);
552 if (n3 == null)
554 result = null;
555 return next();
557 Document d3 = getDocument(n3);
558 walker = getDocumentTraversal(d3)
559 .createTreeWalker(n3, SHOW_FLAGS, null, expandERefs);
560 return next();
561 default:
562 throw new IllegalStateException();
565 if (includedText != null)
567 // fill buffer
568 if (buf == null)
569 buf = new char[2048];
572 len = includedText.read(buf, 0, buf.length);
573 if (len == -1)
575 includedText = null;
576 buf = null;
577 return next();
579 // chars or space?
580 return (event = isSpace(buf, len) ?
581 XMLStreamConstants.SPACE :
582 XMLStreamConstants.CHARACTERS);
584 catch (IOException e)
586 XMLStreamException e2 = new XMLStreamException(e.getMessage());
587 e2.initCause(e);
588 throw e2;
591 event = super.next();
592 switch (event)
594 case XMLStreamConstants.START_ELEMENT:
595 String uri = getNamespaceURI();
596 if (XINCLUDE_NS_URI.equals(uri))
598 String localName = getLocalName();
599 if ("include".equals(localName))
601 href = getAttributeValue(null, "href");
602 String parse = getAttributeValue(null, "parse");
603 String xpointer = getAttributeValue(null, "xpointer");
604 String encoding = getAttributeValue(null, "encoding");
605 String accept = getAttributeValue(null, "accept");
606 String acceptLanguage = getAttributeValue(null,
607 "accept-language");
608 if (includeResource(href, parse, xpointer, encoding,
609 accept, acceptLanguage))
611 // Skip to xi:include end-element event
612 int depth = 0;
613 while (depth >= 0)
615 event = super.next();
616 switch (event)
618 case XMLStreamConstants.START_ELEMENT:
619 depth++;
620 break;
621 case XMLStreamConstants.END_ELEMENT:
622 depth--;
626 else
627 inInclude = true;
629 else if (inInclude && "fallback".equals(localName))
631 if (!seenFallback)
632 inFallback = seenFallback = true;
633 else
634 throw new XMLStreamException("duplicate xi:fallback element");
636 else if (inInclude)
638 throw new XMLStreamException("illegal xi element '" +
639 localName + "'");
641 return next();
643 break;
644 case XMLStreamConstants.END_ELEMENT:
645 String uri2 = getNamespaceURI();
646 if (XINCLUDE_NS_URI.equals(uri2))
648 String localName = getLocalName();
649 if ("include".equals(localName))
651 if (!seenFallback && included)
653 String msg = "Unable to read " + href +
654 " and no xi:fallback element present";
655 throw new XMLStreamException(msg);
657 included = false;
658 href = null;
659 inInclude = inFallback = seenFallback = false;
661 else if ("fallback".equals(localName))
662 inFallback = false;
663 return next();
665 break;
667 if (inInclude && !inFallback)
668 return next();
669 return event;
672 boolean isSpace(char[] text, int len)
674 boolean space = true;
675 for (int i = 0; i < len; i++)
677 char c = text[i];
678 if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
680 space = false;
681 break;
684 return space;
687 String getBaseURI()
689 String base = (String) getParent().getProperty("gnu.xml.stream.baseURI");
690 return (base == null) ? systemId : base;
693 boolean includeResource(String href, String parse, String xpointer,
694 String encoding, String accept,
695 String acceptLanguage)
697 included = false;
700 if (xpointer != null)
701 throw new XMLStreamException("xpointer attribute not yet supported");
702 String base = getBaseURI();
703 if (href == null || "".equals(href))
704 href = base;
705 else
706 href = XMLParser.absolutize(base, href);
707 if (parse == null || "xml".equals(parse))
709 seen.clear();
710 result = null;
711 snapshotIndex = 0;
712 walker = null;
713 current = null;
714 backtracking = false;
716 URLConnection connection = getURLConnection(href, accept,
717 acceptLanguage);
718 InputStream in = connection.getInputStream();
719 Document doc = getDocumentBuilder().parse(in, href);
720 DocumentTraversal dt = getDocumentTraversal(doc);
721 if (xpointer == null)
723 result = null;
724 Node item = doc.getDocumentElement();
725 walker = dt.createTreeWalker(item, SHOW_FLAGS, null,
726 expandERefs);
728 else
730 result = null;
731 snapshotIndex = 0;
732 walker = null;
733 // shorthand or scheme-based?
734 int lpi = xpointer.indexOf('(');
735 int rpi = xpointer.indexOf(')', lpi);
736 if (lpi != -1 && rpi != -1)
738 String scheme = xpointer.substring(0, lpi);
739 if ("element".equals(scheme))
741 // element() scheme
742 String elementSchemeData =
743 xpointer.substring(lpi + 1, rpi);
744 Node item = doc;
745 int si = elementSchemeData.indexOf('/');
746 if (si == -1)
748 if (elementSchemeData.length() > 0)
749 item = doc.getElementById(elementSchemeData);
751 else
753 if (si > 0)
755 String context =
756 elementSchemeData.substring(0, si);
757 item = doc.getElementById(context);
758 elementSchemeData =
759 elementSchemeData.substring(si + 1);
761 StringTokenizer st =
762 new StringTokenizer(elementSchemeData, "/");
763 while (st.hasMoreTokens() && item != null)
765 int n = Integer.parseInt(st.nextToken());
766 Node ctx = item.getFirstChild();
767 int count = 1;
768 while (ctx != null && count++ < n)
769 ctx = ctx.getNextSibling();
770 item = ctx;
773 walker = dt.createTreeWalker(item, SHOW_FLAGS, null,
774 expandERefs);
775 included = true;
777 else if ("xpointer".equals(scheme))
779 xpointer = xpointer.substring(lpi + 1, rpi);
780 XPathEvaluator eval = getXPathEvaluator(doc);
781 XPathNSResolver resolver = eval.createNSResolver(doc);
782 result =
783 (XPathResult) eval.evaluate(xpointer, doc,
784 resolver,
785 XPathResult.ANY_TYPE,
786 null);
787 // TODO xpointer() scheme functions
788 included = true;
790 else
792 String msg = "Unknown XPointer scheme: " + scheme;
793 throw new XMLStreamException(msg);
796 else
798 Node item = doc.getElementById(xpointer);
799 walker = dt.createTreeWalker(item, SHOW_FLAGS, null,
800 expandERefs);
801 included = true;
805 else if ("text".equals(parse))
807 URLConnection connection = getURLConnection(href, accept,
808 acceptLanguage);
809 InputStream in = connection.getInputStream();
810 if (encoding == null)
812 encoding = connection.getContentEncoding();
813 if (encoding == null)
815 String contentType = connection.getContentType();
816 if (contentType != null)
817 encoding = getParameter(contentType, "charset");
820 if (encoding == null)
821 includedText = new InputStreamReader(in, "UTF-8");
822 else
823 includedText = new InputStreamReader(in, encoding);
824 included = true;
826 else
827 throw new XMLStreamException("value of 'parse' attribute must be "+
828 "'xml' or 'text'");
829 return true;
831 catch (IOException e)
833 return false;
835 catch (XMLStreamException e)
837 return false;
839 catch (SAXException e)
841 return false;
845 URLConnection getURLConnection(String href, String accept,
846 String acceptLanguage)
847 throws IOException
849 URL url = new URL(href);
850 URLConnection connection = url.openConnection();
851 if (connection instanceof HttpURLConnection)
853 HttpURLConnection http = (HttpURLConnection) connection;
854 http.setInstanceFollowRedirects(true);
855 if (accept != null)
856 http.setRequestProperty("Accept", accept);
857 if (acceptLanguage != null)
858 http.setRequestProperty("Accept-Language", acceptLanguage);
860 return connection;
863 Document getDocument(Node node)
865 if (node.getNodeType() == Node.DOCUMENT_NODE)
866 return (Document) node;
867 return node.getOwnerDocument();
870 DocumentBuilder getDocumentBuilder()
871 throws XMLStreamException
873 if (builder == null)
877 DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
878 f.setXIncludeAware(true);
879 f.setNamespaceAware(namespaceAware);
880 f.setValidating(validating);
881 builder = f.newDocumentBuilder();
883 catch (ParserConfigurationException e)
885 XMLStreamException e2 = new XMLStreamException(e.getMessage());
886 e2.initCause(e);
887 throw e2;
890 builder.reset();
891 return builder;
894 DocumentTraversal getDocumentTraversal(Document doc)
895 throws XMLStreamException
897 DOMImplementation dom = doc.getImplementation();
898 if (!dom.hasFeature("Traversal", "2.0"))
899 throw new XMLStreamException("Traversal not supported");
900 return (DocumentTraversal) doc;
903 XPathEvaluator getXPathEvaluator(Document doc)
904 throws XMLStreamException
906 DOMImplementation dom = doc.getImplementation();
907 if (!dom.hasFeature("XPath", "3.0"))
908 throw new XMLStreamException("XPath not supported");
909 return (XPathEvaluator) doc;
912 static String getParameter(String contentType, String name)
914 StringTokenizer st = new StringTokenizer(contentType, " ;");
915 if (st.hasMoreTokens())
916 st.nextToken();
917 while (st.hasMoreTokens())
919 String token = st.nextToken();
920 int ei = token.indexOf('=');
921 if (ei != -1)
923 String key = token.substring(0, ei);
924 if (key.equals(name))
926 String value = token.substring(ei + 1);
927 int len = value.length();
928 if (len > 1 &&
929 value.charAt(0) == '"' &&
930 value.charAt(len - 1) == '"')
931 value = value.substring(1, len - 1);
932 else if (len > 1 &&
933 value.charAt(0) == '\'' &&
934 value.charAt(len - 1) == '\'')
935 value = value.substring(1, len - 1);
936 return value;
940 return null;