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)
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
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
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
;
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
;
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>
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
|
98 final String systemId
;
99 final boolean namespaceAware
;
100 final boolean validating
;
101 final boolean expandERefs
;
109 HashSet seen
= new HashSet();
110 boolean backtracking
;
116 boolean inInclude
, inFallback
, seenFallback
;
118 DocumentBuilder builder
;
120 XIncludeFilter(XMLStreamReader reader
, String systemId
,
121 boolean namespaceAware
, boolean validating
,
127 this.systemId
= XMLParser
.absolutize(null, systemId
);
129 catch (MalformedURLException e
)
131 RuntimeException e2
= new RuntimeException("unsupported URL: " +
136 this.namespaceAware
= namespaceAware
;
137 this.validating
= validating
;
138 this.expandERefs
= expandERefs
;
141 public int getAttributeCount()
145 NamedNodeMap attrs
= current
.getAttributes();
146 return (attrs
== null) ?
0 : attrs
.getLength();
148 return super.getAttributeCount();
151 public String
getAttributeLocalName(int index
)
155 NamedNodeMap attrs
= current
.getAttributes();
158 Node attr
= attrs
.item(index
);
159 return attr
.getLocalName();
161 return super.getAttributeLocalName(index
);
164 public String
getAttributeNamespace(int index
)
168 NamedNodeMap attrs
= current
.getAttributes();
171 Node attr
= attrs
.item(index
);
172 return attr
.getNamespaceURI();
174 return super.getAttributeNamespace(index
);
177 public String
getAttributePrefix(int index
)
181 NamedNodeMap attrs
= current
.getAttributes();
184 Node attr
= attrs
.item(index
);
185 return attr
.getPrefix();
187 return super.getAttributePrefix(index
);
190 public QName
getAttributeName(int index
)
194 NamedNodeMap attrs
= current
.getAttributes();
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
)
210 NamedNodeMap attrs
= current
.getAttributes();
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
)
224 NamedNodeMap attrs
= current
.getAttributes();
227 Attr attr
= (Attr
) attrs
.item(index
);
228 return attr
.getSpecified();
230 return super.isAttributeSpecified(index
);
233 public String
getAttributeValue(int index
)
237 NamedNodeMap attrs
= current
.getAttributes();
240 Node attr
= attrs
.item(index
);
241 return attr
.getNodeValue();
243 return super.getAttributeValue(index
);
246 public String
getAttributeValue(String uri
, String localName
)
250 NamedNodeMap attrs
= current
.getAttributes();
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
263 return current
.getTextContent();
264 return super.getElementText();
267 public int getEventType()
272 public String
getLocalName()
275 return current
.getLocalName();
276 return super.getLocalName();
279 public QName
getName()
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()
294 return current
.getNamespaceURI();
295 return super.getNamespaceURI();
300 public String
getPIData()
303 return ((ProcessingInstruction
) current
).getData();
304 return super.getPIData();
307 public String
getPITarget()
310 return ((ProcessingInstruction
) current
).getTarget();
311 return super.getPITarget();
314 public String
getPrefix()
317 return current
.getPrefix();
318 return super.getPrefix();
321 public String
getText()
324 return current
.getNodeValue();
327 Node n
= walker
.getCurrentNode();
329 return n
.getTextContent();
332 return new String(buf
, 0, len
);
333 return super.getText();
336 public char[] getTextCharacters()
340 buf
= current
.getNodeValue().toCharArray();
345 return super.getTextCharacters();
348 public int getTextCharacters(int sourceStart
, char[] target
,
349 int targetStart
, int length
)
350 throws XMLStreamException
354 buf
= current
.getNodeValue().toCharArray();
359 int max
= Math
.min(len
- sourceStart
, length
);
361 System
.arraycopy(buf
, sourceStart
, target
, targetStart
, max
);
364 return super.getTextCharacters(sourceStart
, target
, targetStart
, length
);
367 public int getTextLength()
371 buf
= current
.getNodeValue().toCharArray();
376 return super.getTextLength();
379 public int getTextStart()
383 buf
= current
.getNodeValue().toCharArray();
388 return super.getTextStart();
391 public boolean hasNext()
392 throws XMLStreamException
400 catch (NoSuchElementException e
)
406 return (event
!= -1);
410 throws XMLStreamException
421 Node c
= walker
.getCurrentNode();
423 if (c
.getNodeType() == Node
.ELEMENT_NODE
)
425 boolean isStartElement
= !seen
.contains(c
);
430 event
= XMLStreamConstants
.START_ELEMENT
;
433 else if (backtracking
)
435 n
= walker
.nextSibling();
437 backtracking
= false;
441 n
= walker
.firstChild();
443 n
= walker
.nextSibling();
448 n
= walker
.firstChild();
450 n
= walker
.nextSibling();
454 current
= walker
.parentNode();
455 if (current
!= null && current
.getNodeType() == Node
.ELEMENT_NODE
)
459 event
= XMLStreamConstants
.END_ELEMENT
;
471 switch (n
.getNodeType())
473 case Node
.ELEMENT_NODE
:
476 String text
= n
.getNodeValue();
477 buf
= text
.toCharArray();
479 event
= isSpace(buf
, len
) ?
480 XMLStreamConstants
.SPACE
:
481 XMLStreamConstants
.CHARACTERS
;
483 case Node
.CDATA_SECTION_NODE
:
484 event
= XMLStreamConstants
.CDATA
;
486 case Node
.COMMENT_NODE
:
487 event
= XMLStreamConstants
.COMMENT
;
489 case Node
.PROCESSING_INSTRUCTION_NODE
:
490 event
= XMLStreamConstants
.PROCESSING_INSTRUCTION
;
492 case Node
.ENTITY_REFERENCE_NODE
:
493 event
= XMLStreamConstants
.ENTITY_REFERENCE
;
496 throw new IllegalStateException();
502 switch (result
.getResultType())
504 case XPathResult
.BOOLEAN_TYPE
:
505 boolean bval
= result
.getBooleanValue();
506 String btext
= bval ?
"true" : "false";
507 buf
= btext
.toCharArray();
510 event
= XMLStreamConstants
.CHARACTERS
;
512 case XPathResult
.NUMBER_TYPE
:
513 double nval
= result
.getNumberValue();
514 String ntext
= new Double(nval
).toString();
515 buf
= ntext
.toCharArray();
518 event
= XMLStreamConstants
.CHARACTERS
;
520 case XPathResult
.STRING_TYPE
:
521 String stext
= result
.getStringValue();
522 buf
= stext
.toCharArray();
525 event
= isSpace(buf
, len
) ?
526 XMLStreamConstants
.SPACE
:
527 XMLStreamConstants
.CHARACTERS
;
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
);
537 case XPathResult
.ORDERED_NODE_ITERATOR_TYPE
:
538 case XPathResult
.UNORDERED_NODE_ITERATOR_TYPE
:
539 Node n2
= result
.iterateNext();
545 Document d2
= getDocument(n2
);
546 walker
= getDocumentTraversal(d2
)
547 .createTreeWalker(n2
, SHOW_FLAGS
, null, expandERefs
);
549 case XPathResult
.ORDERED_NODE_SNAPSHOT_TYPE
:
550 case XPathResult
.UNORDERED_NODE_SNAPSHOT_TYPE
:
551 Node n3
= result
.snapshotItem(snapshotIndex
++);
557 Document d3
= getDocument(n3
);
558 walker
= getDocumentTraversal(d3
)
559 .createTreeWalker(n3
, SHOW_FLAGS
, null, expandERefs
);
562 throw new IllegalStateException();
565 if (includedText
!= null)
569 buf
= new char[2048];
572 len
= includedText
.read(buf
, 0, buf
.length
);
580 return (event
= isSpace(buf
, len
) ?
581 XMLStreamConstants
.SPACE
:
582 XMLStreamConstants
.CHARACTERS
);
584 catch (IOException e
)
586 XMLStreamException e2
= new XMLStreamException(e
.getMessage());
591 event
= super.next();
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,
608 if (includeResource(href
, parse
, xpointer
, encoding
,
609 accept
, acceptLanguage
))
611 // Skip to xi:include end-element event
615 event
= super.next();
618 case XMLStreamConstants
.START_ELEMENT
:
621 case XMLStreamConstants
.END_ELEMENT
:
629 else if (inInclude
&& "fallback".equals(localName
))
632 inFallback
= seenFallback
= true;
634 throw new XMLStreamException("duplicate xi:fallback element");
638 throw new XMLStreamException("illegal xi element '" +
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
);
659 inInclude
= inFallback
= seenFallback
= false;
661 else if ("fallback".equals(localName
))
667 if (inInclude
&& !inFallback
)
672 boolean isSpace(char[] text
, int len
)
674 boolean space
= true;
675 for (int i
= 0; i
< len
; i
++)
678 if (c
!= ' ' && c
!= '\t' && c
!= '\n' && c
!= '\r')
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
)
700 if (xpointer
!= null)
701 throw new XMLStreamException("xpointer attribute not yet supported");
702 String base
= getBaseURI();
703 if (href
== null || "".equals(href
))
706 href
= XMLParser
.absolutize(base
, href
);
707 if (parse
== null || "xml".equals(parse
))
714 backtracking
= false;
716 URLConnection connection
= getURLConnection(href
, accept
,
718 InputStream in
= connection
.getInputStream();
719 Document doc
= getDocumentBuilder().parse(in
, href
);
720 DocumentTraversal dt
= getDocumentTraversal(doc
);
721 if (xpointer
== null)
724 Node item
= doc
.getDocumentElement();
725 walker
= dt
.createTreeWalker(item
, SHOW_FLAGS
, 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
))
742 String elementSchemeData
=
743 xpointer
.substring(lpi
+ 1, rpi
);
745 int si
= elementSchemeData
.indexOf('/');
748 if (elementSchemeData
.length() > 0)
749 item
= doc
.getElementById(elementSchemeData
);
756 elementSchemeData
.substring(0, si
);
757 item
= doc
.getElementById(context
);
759 elementSchemeData
.substring(si
+ 1);
762 new StringTokenizer(elementSchemeData
, "/");
763 while (st
.hasMoreTokens() && item
!= null)
765 int n
= Integer
.parseInt(st
.nextToken());
766 Node ctx
= item
.getFirstChild();
768 while (ctx
!= null && count
++ < n
)
769 ctx
= ctx
.getNextSibling();
773 walker
= dt
.createTreeWalker(item
, SHOW_FLAGS
, null,
777 else if ("xpointer".equals(scheme
))
779 xpointer
= xpointer
.substring(lpi
+ 1, rpi
);
780 XPathEvaluator eval
= getXPathEvaluator(doc
);
781 XPathNSResolver resolver
= eval
.createNSResolver(doc
);
783 (XPathResult
) eval
.evaluate(xpointer
, doc
,
785 XPathResult
.ANY_TYPE
,
787 // TODO xpointer() scheme functions
792 String msg
= "Unknown XPointer scheme: " + scheme
;
793 throw new XMLStreamException(msg
);
798 Node item
= doc
.getElementById(xpointer
);
799 walker
= dt
.createTreeWalker(item
, SHOW_FLAGS
, null,
805 else if ("text".equals(parse
))
807 URLConnection connection
= getURLConnection(href
, accept
,
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");
823 includedText
= new InputStreamReader(in
, encoding
);
827 throw new XMLStreamException("value of 'parse' attribute must be "+
831 catch (IOException e
)
835 catch (XMLStreamException e
)
839 catch (SAXException e
)
845 URLConnection
getURLConnection(String href
, String accept
,
846 String acceptLanguage
)
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);
856 http
.setRequestProperty("Accept", accept
);
857 if (acceptLanguage
!= null)
858 http
.setRequestProperty("Accept-Language", acceptLanguage
);
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
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());
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())
917 while (st
.hasMoreTokens())
919 String token
= st
.nextToken();
920 int ei
= token
.indexOf('=');
923 String key
= token
.substring(0, ei
);
924 if (key
.equals(name
))
926 String value
= token
.substring(ei
+ 1);
927 int len
= value
.length();
929 value
.charAt(0) == '"' &&
930 value
.charAt(len
- 1) == '"')
931 value
= value
.substring(1, len
- 1);
933 value
.charAt(0) == '\'' &&
934 value
.charAt(len
- 1) == '\'')
935 value
= value
.substring(1, len
- 1);