diff options
author | Tom Tromey <tromey@redhat.com> | 2005-02-02 00:42:32 +0000 |
---|---|---|
committer | Tom Tromey <tromey@gcc.gnu.org> | 2005-02-02 00:42:32 +0000 |
commit | 9ecd03471f54de5411794e6fc58c48420564c192 (patch) | |
tree | d6f5216c5e8b86e821ab50a8cb46c7eec16e0a52 /libjava/gnu/xml | |
parent | c1c5b5e383b7e566b49b4c160a1af16eb9222f52 (diff) | |
download | gcc-9ecd03471f54de5411794e6fc58c48420564c192.zip gcc-9ecd03471f54de5411794e6fc58c48420564c192.tar.gz gcc-9ecd03471f54de5411794e6fc58c48420564c192.tar.bz2 |
Makefile.am: New file.
* external/w3c_dom/Makefile.am: New file.
* external/w3c_dom/Makefile.in: New file.
* external/sax/Makefile.am: New file.
* external/sax/Makefile.in: New file.
* external/Makefile.in: New file.
* external/Makefile.am: New file.
* external/README: New file from Classpath.
* configure, aclocal.m4, Makefile.in: Rebuilt.
* configure.ac (AC_CONFIG_FILES): Create new Makefiles.
* Makefile.am (SUBDIRS): Added external.
(toolexeclib_LTLIBRARIES): Removed lib-org-xml-sax.la,
lib-org-w3c-dom.la, lib-org-ietf-gss.la.
(BOOTCLASSPATH): New variable.
(AM_GCJFLAGS): Use it.
(gnu_xml_source_files): New variable.
(libgcj_la_SOURCES): Use it.
(libgcj_la_LDADD): Include new convenience libraries.
(lib_org_ietf_jgss_la_SOURCES): Removed.
(lib_org_ietf_jgss_la_DEPENDENCIES): Likewise.
(lib_org_ietf_jgss_la_LIBADD): Likewise.
(lib_org_w3c_dom_la_SOURCES): Removed.
(lib_org_w3c_dom_la_DEPENDENCIES): Likewise.
(lib_org_w3c_dom_la_LIBADD): Likewise.
(lib_org_xml_sax_la_SOURCES): Removed.
(lib_org_xml_sax_la_DEPENDENCIES): Likewise.
(lib_org_xml_sax_la_LIBADD): Likewise.
(all_java_source_files): Updated.
(property_files): Likewise.
(libgcj-@gcc_version@.jar): Include classes from external/.
(javax_source_files): Updated.
(ordinary_java_source_files): Updated.
* javax/xml/XMLConstants.java,
javax/xml/datatype/DatatypeConfigurationException.java,
javax/xml/datatype/DatatypeConstants.java,
javax/xml/datatype/DatatypeFactory.java,
javax/xml/datatype/Duration.java,
javax/xml/datatype/XMLGregorianCalendar.java,
javax/xml/datatype/package.html,
javax/xml/namespace/NamespaceContext.java,
javax/xml/namespace/QName.java, javax/xml/namespace/package.html,
javax/xml/parsers/DocumentBuilder.java,
javax/xml/parsers/DocumentBuilderFactory.java,
javax/xml/parsers/FactoryConfigurationError.java,
javax/xml/parsers/ParserConfigurationException.java,
javax/xml/parsers/SAXParser.java,
javax/xml/parsers/SAXParserFactory.java,
javax/xml/parsers/package.html,
javax/xml/transform/ErrorListener.java,
javax/xml/transform/OutputKeys.java,
javax/xml/transform/Result.java, javax/xml/transform/Source.java,
javax/xml/transform/SourceLocator.java,
javax/xml/transform/Templates.java,
javax/xml/transform/Transformer.java,
javax/xml/transform/TransformerConfigurationException.java,
javax/xml/transform/TransformerException.java,
javax/xml/transform/TransformerFactory.java,
javax/xml/transform/TransformerFactoryConfigurationError.java,
javax/xml/transform/URIResolver.java,
javax/xml/transform/package.html,
javax/xml/transform/dom/DOMLocator.java,
javax/xml/transform/dom/DOMResult.java,
javax/xml/transform/dom/DOMSource.java,
javax/xml/transform/dom/package.html,
javax/xml/transform/sax/SAXResult.java,
javax/xml/transform/sax/SAXSource.java,
javax/xml/transform/sax/SAXTransformerFactory.java,
javax/xml/transform/sax/TemplatesHandler.java,
javax/xml/transform/sax/TransformerHandler.java,
javax/xml/transform/sax/package.html,
javax/xml/transform/stream/StreamResult.java,
javax/xml/transform/stream/StreamSource.java,
javax/xml/transform/stream/package.html,
javax/xml/validation/Schema.java,
javax/xml/validation/SchemaFactory.java,
javax/xml/validation/TypeInfoProvider.java,
javax/xml/validation/Validator.java,
javax/xml/validation/ValidatorHandler.java,
javax/xml/validation/package.html, javax/xml/xpath/XPath.java,
javax/xml/xpath/XPathConstants.java,
javax/xml/xpath/XPathException.java,
javax/xml/xpath/XPathExpression.java,
javax/xml/xpath/XPathExpressionException.java,
javax/xml/xpath/XPathFactory.java,
javax/xml/xpath/XPathFactoryConfigurationException.java,
javax/xml/xpath/XPathFunction.java,
javax/xml/xpath/XPathFunctionException.java,
javax/xml/xpath/XPathFunctionResolver.java,
javax/xml/xpath/XPathVariableResolver.java,
javax/xml/xpath/package.html: New files, from GNU JAXP.
* org/w3c/dom/Attr.java, org/w3c/dom/CDATASection.java,
org/w3c/dom/CharacterData.java, org/w3c/dom/Comment.java,
org/w3c/dom/DOMException.java, org/w3c/dom/DOMImplementation.java,
org/w3c/dom/Document.java, org/w3c/dom/DocumentFragment.java,
org/w3c/dom/DocumentType.java, org/w3c/dom/Element.java,
org/w3c/dom/Entity.java, org/w3c/dom/EntityReference.java,
org/w3c/dom/NamedNodeMap.java, org/w3c/dom/Node.java,
org/w3c/dom/NodeList.java, org/w3c/dom/Notation.java,
org/w3c/dom/ProcessingInstruction.java, org/w3c/dom/Text.java,
org/w3c/dom/ranges/DocumentRange.java,
org/w3c/dom/ranges/Range.java,
org/w3c/dom/ranges/RangeException.java,
org/w3c/dom/traversal/DocumentTraversal.java,
org/w3c/dom/traversal/NodeFilter.java,
org/w3c/dom/traversal/NodeIterator.java,
org/w3c/dom/traversal/TreeWalker.java,
org/xml/sax/AttributeList.java, org/xml/sax/Attributes.java,
org/xml/sax/ContentHandler.java, org/xml/sax/DTDHandler.java,
org/xml/sax/DocumentHandler.java, org/xml/sax/EntityResolver.java,
org/xml/sax/ErrorHandler.java, org/xml/sax/HandlerBase.java,
org/xml/sax/InputSource.java, org/xml/sax/Locator.java,
org/xml/sax/Parser.java, org/xml/sax/SAXException.java,
org/xml/sax/SAXNotRecognizedException.java,
org/xml/sax/SAXNotSupportedException.java,
org/xml/sax/SAXParseException.java, org/xml/sax/XMLFilter.java,
org/xml/sax/XMLReader.java, org/xml/sax/package.html,
org/xml/sax/ext/DeclHandler.java,
org/xml/sax/ext/LexicalHandler.java, org/xml/sax/ext/package.html,
org/xml/sax/helpers/AttributeListImpl.java,
org/xml/sax/helpers/AttributesImpl.java,
org/xml/sax/helpers/DefaultHandler.java,
org/xml/sax/helpers/LocatorImpl.java,
org/xml/sax/helpers/NamespaceSupport.java,
org/xml/sax/helpers/NewInstance.java,
org/xml/sax/helpers/ParserAdapter.java,
org/xml/sax/helpers/ParserFactory.java,
org/xml/sax/helpers/XMLFilterImpl.java,
org/xml/sax/helpers/XMLReaderAdapter.java,
org/xml/sax/helpers/XMLReaderFactory.java,
org/xml/sax/helpers/package.html: Moved to external/.
* external/sax/README,
external/sax/org/xml/sax/AttributeList.java,
external/sax/org/xml/sax/Attributes.java,
external/sax/org/xml/sax/ContentHandler.java,
external/sax/org/xml/sax/DTDHandler.java,
external/sax/org/xml/sax/DocumentHandler.java,
external/sax/org/xml/sax/EntityResolver.java,
external/sax/org/xml/sax/ErrorHandler.java,
external/sax/org/xml/sax/HandlerBase.java,
external/sax/org/xml/sax/InputSource.java,
external/sax/org/xml/sax/Locator.java,
external/sax/org/xml/sax/Parser.java,
external/sax/org/xml/sax/SAXException.java,
external/sax/org/xml/sax/SAXNotRecognizedException.java,
external/sax/org/xml/sax/SAXNotSupportedException.java,
external/sax/org/xml/sax/SAXParseException.java,
external/sax/org/xml/sax/XMLFilter.java,
external/sax/org/xml/sax/XMLReader.java,
external/sax/org/xml/sax/package.html,
external/sax/org/xml/sax/ext/Attributes2.java,
external/sax/org/xml/sax/ext/Attributes2Impl.java,
external/sax/org/xml/sax/ext/DeclHandler.java,
external/sax/org/xml/sax/ext/DefaultHandler2.java,
external/sax/org/xml/sax/ext/EntityResolver2.java,
external/sax/org/xml/sax/ext/LexicalHandler.java,
external/sax/org/xml/sax/ext/Locator2.java,
external/sax/org/xml/sax/ext/Locator2Impl.java,
external/sax/org/xml/sax/ext/package.html,
external/sax/org/xml/sax/helpers/AttributeListImpl.java,
external/sax/org/xml/sax/helpers/AttributesImpl.java,
external/sax/org/xml/sax/helpers/DefaultHandler.java,
external/sax/org/xml/sax/helpers/LocatorImpl.java,
external/sax/org/xml/sax/helpers/NamespaceSupport.java,
external/sax/org/xml/sax/helpers/NewInstance.java,
external/sax/org/xml/sax/helpers/ParserAdapter.java,
external/sax/org/xml/sax/helpers/ParserFactory.java,
external/sax/org/xml/sax/helpers/XMLFilterImpl.java,
external/sax/org/xml/sax/helpers/XMLReaderAdapter.java,
external/sax/org/xml/sax/helpers/XMLReaderFactory.java,
external/sax/org/xml/sax/helpers/package.html,
external/w3c_dom/COPYRIGHT.html, external/w3c_dom/README,
external/w3c_dom/org/w3c/dom/Attr.java,
external/w3c_dom/org/w3c/dom/CDATASection.java,
external/w3c_dom/org/w3c/dom/CharacterData.java,
external/w3c_dom/org/w3c/dom/Comment.java,
external/w3c_dom/org/w3c/dom/DOMConfiguration.java,
external/w3c_dom/org/w3c/dom/DOMError.java,
external/w3c_dom/org/w3c/dom/DOMErrorHandler.java,
external/w3c_dom/org/w3c/dom/DOMException.java,
external/w3c_dom/org/w3c/dom/DOMImplementation.java,
external/w3c_dom/org/w3c/dom/DOMImplementationList.java,
external/w3c_dom/org/w3c/dom/DOMImplementationSource.java,
external/w3c_dom/org/w3c/dom/DOMLocator.java,
external/w3c_dom/org/w3c/dom/DOMStringList.java,
external/w3c_dom/org/w3c/dom/Document.java,
external/w3c_dom/org/w3c/dom/DocumentFragment.java,
external/w3c_dom/org/w3c/dom/DocumentType.java,
external/w3c_dom/org/w3c/dom/Element.java,
external/w3c_dom/org/w3c/dom/Entity.java,
external/w3c_dom/org/w3c/dom/EntityReference.java,
external/w3c_dom/org/w3c/dom/NameList.java,
external/w3c_dom/org/w3c/dom/NamedNodeMap.java,
external/w3c_dom/org/w3c/dom/Node.java,
external/w3c_dom/org/w3c/dom/NodeList.java,
external/w3c_dom/org/w3c/dom/Notation.java,
external/w3c_dom/org/w3c/dom/ProcessingInstruction.java,
external/w3c_dom/org/w3c/dom/Text.java,
external/w3c_dom/org/w3c/dom/TypeInfo.java,
external/w3c_dom/org/w3c/dom/UserDataHandler.java,
external/w3c_dom/org/w3c/dom/bootstrap/DOMImplementationRegistry.java,
external/w3c_dom/org/w3c/dom/css/CSS2Properties.java,
external/w3c_dom/org/w3c/dom/css/CSSCharsetRule.java,
external/w3c_dom/org/w3c/dom/css/CSSFontFaceRule.java,
external/w3c_dom/org/w3c/dom/css/CSSImportRule.java,
external/w3c_dom/org/w3c/dom/css/CSSMediaRule.java,
external/w3c_dom/org/w3c/dom/css/CSSPageRule.java,
external/w3c_dom/org/w3c/dom/css/CSSPrimitiveValue.java,
external/w3c_dom/org/w3c/dom/css/CSSRule.java,
external/w3c_dom/org/w3c/dom/css/CSSRuleList.java,
external/w3c_dom/org/w3c/dom/css/CSSStyleDeclaration.java,
external/w3c_dom/org/w3c/dom/css/CSSStyleRule.java,
external/w3c_dom/org/w3c/dom/css/CSSStyleSheet.java,
external/w3c_dom/org/w3c/dom/css/CSSUnknownRule.java,
external/w3c_dom/org/w3c/dom/css/CSSValue.java,
external/w3c_dom/org/w3c/dom/css/CSSValueList.java,
external/w3c_dom/org/w3c/dom/css/Counter.java,
external/w3c_dom/org/w3c/dom/css/DOMImplementationCSS.java,
external/w3c_dom/org/w3c/dom/css/DocumentCSS.java,
external/w3c_dom/org/w3c/dom/css/ElementCSSInlineStyle.java,
external/w3c_dom/org/w3c/dom/css/RGBColor.java,
external/w3c_dom/org/w3c/dom/css/Rect.java,
external/w3c_dom/org/w3c/dom/css/ViewCSS.java,
external/w3c_dom/org/w3c/dom/events/DocumentEvent.java,
external/w3c_dom/org/w3c/dom/events/Event.java,
external/w3c_dom/org/w3c/dom/events/EventException.java,
external/w3c_dom/org/w3c/dom/events/EventListener.java,
external/w3c_dom/org/w3c/dom/events/EventTarget.java,
external/w3c_dom/org/w3c/dom/events/MouseEvent.java,
external/w3c_dom/org/w3c/dom/events/MutationEvent.java,
external/w3c_dom/org/w3c/dom/events/UIEvent.java,
external/w3c_dom/org/w3c/dom/html2/HTMLAnchorElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLAppletElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLAreaElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLBRElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLBaseElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLBaseFontElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLBodyElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLButtonElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLCollection.java,
external/w3c_dom/org/w3c/dom/html2/HTMLDListElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLDirectoryElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLDivElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLDocument.java,
external/w3c_dom/org/w3c/dom/html2/HTMLElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLFieldSetElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLFontElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLFormElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLFrameElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLFrameSetElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLHRElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLHeadElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLHeadingElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLHtmlElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLIFrameElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLImageElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLInputElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLIsIndexElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLLIElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLLabelElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLLegendElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLLinkElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLMapElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLMenuElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLMetaElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLModElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLOListElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLObjectElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLOptGroupElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLOptionElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLOptionsCollection.java,
external/w3c_dom/org/w3c/dom/html2/HTMLParagraphElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLParamElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLPreElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLQuoteElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLScriptElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLSelectElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLStyleElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLTableCaptionElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLTableCellElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLTableColElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLTableElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLTableRowElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLTableSectionElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLTextAreaElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLTitleElement.java,
external/w3c_dom/org/w3c/dom/html2/HTMLUListElement.java,
external/w3c_dom/org/w3c/dom/ls/DOMImplementationLS.java,
external/w3c_dom/org/w3c/dom/ls/LSException.java,
external/w3c_dom/org/w3c/dom/ls/LSInput.java,
external/w3c_dom/org/w3c/dom/ls/LSLoadEvent.java,
external/w3c_dom/org/w3c/dom/ls/LSOutput.java,
external/w3c_dom/org/w3c/dom/ls/LSParser.java,
external/w3c_dom/org/w3c/dom/ls/LSParserFilter.java,
external/w3c_dom/org/w3c/dom/ls/LSProgressEvent.java,
external/w3c_dom/org/w3c/dom/ls/LSResourceResolver.java,
external/w3c_dom/org/w3c/dom/ls/LSSerializer.java,
external/w3c_dom/org/w3c/dom/ls/LSSerializerFilter.java,
external/w3c_dom/org/w3c/dom/ranges/DocumentRange.java,
external/w3c_dom/org/w3c/dom/ranges/Range.java,
external/w3c_dom/org/w3c/dom/ranges/RangeException.java,
external/w3c_dom/org/w3c/dom/stylesheets/DocumentStyle.java,
external/w3c_dom/org/w3c/dom/stylesheets/LinkStyle.java,
external/w3c_dom/org/w3c/dom/stylesheets/MediaList.java,
external/w3c_dom/org/w3c/dom/stylesheets/StyleSheet.java,
external/w3c_dom/org/w3c/dom/stylesheets/StyleSheetList.java,
external/w3c_dom/org/w3c/dom/traversal/DocumentTraversal.java,
external/w3c_dom/org/w3c/dom/traversal/NodeFilter.java,
external/w3c_dom/org/w3c/dom/traversal/NodeIterator.java,
external/w3c_dom/org/w3c/dom/traversal/TreeWalker.java,
external/w3c_dom/org/w3c/dom/views/AbstractView.java,
external/w3c_dom/org/w3c/dom/views/DocumentView.java,
external/w3c_dom/org/w3c/dom/xpath/XPathEvaluator.java,
external/w3c_dom/org/w3c/dom/xpath/XPathException.java,
external/w3c_dom/org/w3c/dom/xpath/XPathExpression.java,
external/w3c_dom/org/w3c/dom/xpath/XPathNSResolver.java,
external/w3c_dom/org/w3c/dom/xpath/XPathNamespace.java,
external/w3c_dom/org/w3c/dom/xpath/XPathResult.java: New files
from Classpath.
From-SVN: r94577
Diffstat (limited to 'libjava/gnu/xml')
180 files changed, 53204 insertions, 0 deletions
diff --git a/libjava/gnu/xml/aelfred2/ContentHandler2.java b/libjava/gnu/xml/aelfred2/ContentHandler2.java new file mode 100644 index 0000000..7bb1e7d --- /dev/null +++ b/libjava/gnu/xml/aelfred2/ContentHandler2.java @@ -0,0 +1,65 @@ +/* ContentHandler2.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.aelfred2; + +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; + +/** + * Extension to the SAX ContentHandler interface to report parsing events + * and parameters required by DOM Level 3 but not supported by SAX. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public interface ContentHandler2 + extends ContentHandler +{ + + /** + * Reports the XML declaration. + * @param version the value of the version attribute in the XML + * declaration + * @param encoding the encoding specified in the XML declaration, if any + * @param standalone the standalone attribute from the XML declaration + * @param inputEncoding the encoding of the XML input + */ + void xmlDecl(String version, String encoding, boolean standalone, + String inputEncoding) + throws SAXException; + +} diff --git a/libjava/gnu/xml/aelfred2/JAXPFactory.java b/libjava/gnu/xml/aelfred2/JAXPFactory.java new file mode 100644 index 0000000..006dc13 --- /dev/null +++ b/libjava/gnu/xml/aelfred2/JAXPFactory.java @@ -0,0 +1,196 @@ +/* JAXPFactory.java -- + Copyright (C) 2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.aelfred2; + +import java.util.Enumeration; +import java.util.Hashtable; + +import org.xml.sax.Parser; +import org.xml.sax.XMLReader; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.helpers.XMLReaderAdapter; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + + +/** + * Configurable factory to create an Ælfred2 JAXP parser; required + * to bootstrap using JAXP. You should use SAX2 directly where possible, + * rather than through JAXP, since that gives you better control. + * This class would normally be configured as a platform default factory. + * + * @author David Brownell + */ +public final class JAXPFactory extends SAXParserFactory +{ + private Hashtable flags = new Hashtable (); + + /** + * Constructs a factory which normally returns a non-validating + * parser. + */ + public JAXPFactory () { } + + public SAXParser newSAXParser () + throws ParserConfigurationException, SAXException + { + JaxpParser jaxp = new JaxpParser (); + Enumeration e = flags.keys (); + XMLReader parser = jaxp.getXMLReader (); + + parser.setFeature ( + SAXDriver.FEATURE + "namespaces", + isNamespaceAware ()); + parser.setFeature ( + SAXDriver.FEATURE + "validation", + isValidating ()); + // that makes SAX2 feature flags trump JAXP + + while (e.hasMoreElements ()) { + String uri = (String) e.nextElement (); + Boolean value = (Boolean) flags.get (uri); + parser.setFeature (uri, value.booleanValue ()); + } + + return jaxp; + } + + // yes, this "feature transfer" mechanism doesn't play well + + public void setFeature (String name, boolean value) + throws + ParserConfigurationException, + SAXNotRecognizedException, + SAXNotSupportedException + { + try { + // force "early" detection of errors where possible + // (flags can't necessarily be set before parsing) + new JaxpParser ().getXMLReader ().setFeature (name, value); + + flags.put (name, new Boolean (value)); + } catch (SAXNotRecognizedException e) { + throw new SAXNotRecognizedException (name); + } catch (SAXNotSupportedException e) { + throw new SAXNotSupportedException (name); + } catch (Exception e) { + throw new ParserConfigurationException ( + e.getClass ().getName () + + ": " + + e.getMessage ()); + } + } + + public boolean getFeature (String name) + throws + ParserConfigurationException, + SAXNotRecognizedException, + SAXNotSupportedException + { + Boolean value = (Boolean) flags.get (name); + + if (value != null) + return value.booleanValue (); + else + try { + return new JaxpParser ().getXMLReader ().getFeature (name); + } catch (SAXNotRecognizedException e) { + throw new SAXNotRecognizedException (name); + } catch (SAXNotSupportedException e) { + throw new SAXNotSupportedException (name); + } catch (SAXException e) { + throw new ParserConfigurationException ( + e.getClass ().getName () + + ": " + + e.getMessage ()); + } + } + + private static class JaxpParser extends SAXParser + { + private XmlReader ae2 = new XmlReader (); + private XMLReaderAdapter parser = null; + + JaxpParser () { } + + public void setProperty (String id, Object value) + throws SAXNotRecognizedException, SAXNotSupportedException + { ae2.setProperty (id, value); } + + public Object getProperty (String id) + throws SAXNotRecognizedException, SAXNotSupportedException + { return ae2.getProperty (id); } + + public Parser getParser () + throws SAXException + { + if (parser == null) + parser = new XMLReaderAdapter (ae2); + return parser; + } + + public XMLReader getXMLReader () + throws SAXException + { return ae2; } + + public boolean isNamespaceAware () + { + try { + return ae2.getFeature (SAXDriver.FEATURE + "namespaces"); + } catch (Exception e) { + throw new Error (); + } + } + + public boolean isValidating () + { + try { + return ae2.getFeature (SAXDriver.FEATURE + "validation"); + } catch (Exception e) { + throw new Error (); + } + } + + // TODO isXIncludeAware() + + } +} diff --git a/libjava/gnu/xml/aelfred2/SAXDriver.java b/libjava/gnu/xml/aelfred2/SAXDriver.java new file mode 100644 index 0000000..4912f27 --- /dev/null +++ b/libjava/gnu/xml/aelfred2/SAXDriver.java @@ -0,0 +1,1385 @@ +/* SAXDriver.java -- + Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. + +Portions derived from code which carried the following notice: + + Copyright (c) 1997, 1998 by Microstar Software Ltd. + + AElfred is free for both commercial and non-commercial use and + redistribution, provided that Microstar's copyright and disclaimer are + retained intact. You are free to modify AElfred for your own use and + to redistribute AElfred with your modifications, provided that the + modifications are clearly documented. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + merchantability or fitness for a particular purpose. Please use it AT + YOUR OWN RISK. +*/ + +package gnu.xml.aelfred2; + +import java.io.*; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Locale; +import java.util.Stack; + +// maintaining 1.1 compatibility for now ... more portable, PJava, etc +// Iterator, Hashmap and ArrayList ought to be faster +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Vector; + +import org.xml.sax.*; +import org.xml.sax.ext.*; +import org.xml.sax.helpers.NamespaceSupport; + + +/** + * An enhanced SAX2 version of Microstar's Ælfred XML parser. + * The enhancements primarily relate to significant improvements in + * conformance to the XML specification, and SAX2 support. Performance + * has been improved. See the package level documentation for more + * information. + * + * <table border="1" width='100%' cellpadding='3' cellspacing='0'> + * <tr bgcolor='#ccccff'> + * <th><font size='+1'>Name</font></th> + * <th><font size='+1'>Notes</font></th></tr> + * + * <tr><td colspan=2><center><em>Features ... URL prefix is + * <b>http://xml.org/sax/features/</b></em></center></td></tr> + * + * <tr><td>(URL)/external-general-entities</td> + * <td>Value defaults to <em>true</em></td></tr> + * <tr><td>(URL)/external-parameter-entities</td> + * <td>Value defaults to <em>true</em></td></tr> + * <tr><td>(URL)/is-standalone</td> + * <td>(PRELIMINARY) Returns true iff the document's parsing + * has started (some non-error event after <em>startDocument()</em> + * was reported) and the document's standalone flag is set.</td></tr> + * <tr><td>(URL)/namespace-prefixes</td> + * <td>Value defaults to <em>false</em> (but XML 1.0 names are + * always reported)</td></tr> + * <tr><td>(URL)/lexical-handler/parameter-entities</td> + * <td>Value is fixed at <em>true</em></td></tr> + * <tr><td>(URL)/namespaces</td> + * <td>Value defaults to <em>true</em></td></tr> + * <tr><td>(URL)/resolve-dtd-uris</td> + * <td>(PRELIMINARY) Value defaults to <em>true</em></td></tr> + * <tr><td>(URL)/string-interning</td> + * <td>Value is fixed at <em>true</em></td></tr> + * <tr><td>(URL)/use-attributes2</td> + * <td>(PRELIMINARY) Value is fixed at <em>true</em></td></tr> + * <tr><td>(URL)/use-entity-resolver2</td> + * <td>(PRELIMINARY) Value defaults to <em>true</em></td></tr> + * <tr><td>(URL)/validation</td> + * <td>Value is fixed at <em>false</em></td></tr> + * + * <tr><td colspan=2><center><em>Handler Properties ... URL prefix is + * <b>http://xml.org/sax/properties/</b></em></center></td></tr> + * + * <tr><td>(URL)/declaration-handler</td> + * <td>A declaration handler may be provided. </td></tr> + * <tr><td>(URL)/lexical-handler</td> + * <td>A lexical handler may be provided. </td></tr> + * </table> + * + * <p>This parser currently implements the SAX1 Parser API, but + * it may not continue to do so in the future. + * + * @author Written by David Megginson (version 1.2a from Microstar) + * @author Updated by David Brownell <dbrownell@users.sourceforge.net> + * @see org.xml.sax.Parser + */ +final public class SAXDriver + implements Locator, Attributes2, XMLReader, Parser, AttributeList +{ + private final DefaultHandler2 base = new DefaultHandler2 (); + private XmlParser parser; + + private EntityResolver entityResolver = base; + private EntityResolver2 resolver2 = null; + private ContentHandler contentHandler = base; + private DTDHandler dtdHandler = base; + private ErrorHandler errorHandler = base; + private DeclHandler declHandler = base; + private LexicalHandler lexicalHandler = base; + + private String elementName; + private Stack entityStack; + + // one vector (of object/struct): faster, smaller + private List attributesList = Collections.synchronizedList(new ArrayList()); + + private boolean attributeSpecified [] = new boolean[10]; + private boolean attributeDeclared [] = new boolean[10]; + + private boolean namespaces = true; + private boolean xmlNames = false; + private boolean extGE = true; + private boolean extPE = true; + private boolean resolveAll = true; + private boolean useResolver2 = true; + private boolean stringInterning = true; + + private int attributeCount; + private boolean attributes; + private String nsTemp []; + private NamespaceSupport prefixStack; + + // + // Constructor. + // + + /** Constructs a SAX Parser. */ + public SAXDriver () + { + reset (); + } + + private void reset () + { + elementName = null; + entityStack = new Stack (); + attributesList = Collections.synchronizedList(new ArrayList()); + attributeSpecified = new boolean[10]; + attributeDeclared = new boolean[10]; + attributeCount = 0; + attributes = false; + nsTemp = new String[3]; + prefixStack = null; + } + + + // + // Implementation of org.xml.sax.Parser. + // + + /** + * <b>SAX1</b>: Sets the locale used for diagnostics; currently, + * only locales using the English language are supported. + * @param locale The locale for which diagnostics will be generated + */ + public void setLocale (Locale locale) + throws SAXException + { + if ("en".equals (locale.getLanguage ())) + return ; + + throw new SAXException ("AElfred2 only supports English locales."); + } + + + /** + * <b>SAX2</b>: Returns the object used when resolving external + * entities during parsing (both general and parameter entities). + */ + public EntityResolver getEntityResolver () + { + return (entityResolver == base) ? null : entityResolver; + } + + /** + * <b>SAX1, SAX2</b>: Set the entity resolver for this parser. + * @param handler The object to receive entity events. + */ + public void setEntityResolver (EntityResolver resolver) + { + if (resolver instanceof EntityResolver2) + resolver2 = (EntityResolver2) resolver; + else + resolver2 = null; + if (resolver == null) + resolver = base; + entityResolver = resolver; + } + + + /** + * <b>SAX2</b>: Returns the object used to process declarations related + * to notations and unparsed entities. + */ + public DTDHandler getDTDHandler () + { + return (dtdHandler == base) ? null : dtdHandler; + } + + /** + * <b>SAX1, SAX2</b>: Set the DTD handler for this parser. + * @param handler The object to receive DTD events. + */ + public void setDTDHandler (DTDHandler handler) + { + if (handler == null) + handler = base; + this.dtdHandler = handler; + } + + + /** + * <b>SAX1</b>: Set the document handler for this parser. If a + * content handler was set, this document handler will supplant it. + * The parser is set to report all XML 1.0 names rather than to + * filter out "xmlns" attributes (the "namespace-prefixes" feature + * is set to true). + * + * @deprecated SAX2 programs should use the XMLReader interface + * and a ContentHandler. + * + * @param handler The object to receive document events. + */ + public void setDocumentHandler (DocumentHandler handler) + { + contentHandler = new Adapter (handler); + xmlNames = true; + } + + /** + * <b>SAX2</b>: Returns the object used to report the logical + * content of an XML document. + */ + public ContentHandler getContentHandler () + { + return contentHandler == base ? null : contentHandler; + } + + /** + * <b>SAX2</b>: Assigns the object used to report the logical + * content of an XML document. If a document handler was set, + * this content handler will supplant it (but XML 1.0 style name + * reporting may remain enabled). + */ + public void setContentHandler (ContentHandler handler) + { + if (handler == null) + handler = base; + contentHandler = handler; + } + + /** + * <b>SAX1, SAX2</b>: Set the error handler for this parser. + * @param handler The object to receive error events. + */ + public void setErrorHandler (ErrorHandler handler) + { + if (handler == null) + handler = base; + this.errorHandler = handler; + } + + /** + * <b>SAX2</b>: Returns the object used to receive callbacks for XML + * errors of all levels (fatal, nonfatal, warning); this is never null; + */ + public ErrorHandler getErrorHandler () + { return errorHandler == base ? null : errorHandler; } + + + /** + * <b>SAX1, SAX2</b>: Auxiliary API to parse an XML document, used mostly + * when no URI is available. + * If you want anything useful to happen, you should set + * at least one type of handler. + * @param source The XML input source. Don't set 'encoding' unless + * you know for a fact that it's correct. + * @see #setEntityResolver + * @see #setDTDHandler + * @see #setContentHandler + * @see #setErrorHandler + * @exception SAXException The handlers may throw any SAXException, + * and the parser normally throws SAXParseException objects. + * @exception IOException IOExceptions are normally through through + * the parser if there are problems reading the source document. + */ + public void parse (InputSource source) + throws SAXException, IOException + { + synchronized (base) { + parser = new XmlParser (); + if (namespaces) + prefixStack = new NamespaceSupport (); + else if (!xmlNames) + throw new IllegalStateException (); + parser.setHandler (this); + + try { + + Reader r = source.getCharacterStream(); + InputStream in = source.getByteStream(); + + + parser.doParse (source.getSystemId (), + source.getPublicId (), + r, + in, + source.getEncoding ()); + } catch (SAXException e) { + throw e; + } catch (IOException e) { + throw e; + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new SAXParseException (e.getMessage (), this, e); + } finally { + contentHandler.endDocument (); + reset(); + } + } + } + + + /** + * <b>SAX1, SAX2</b>: Preferred API to parse an XML document, using a + * system identifier (URI). + */ + public void parse (String systemId) + throws SAXException, IOException + { + parse (new InputSource (systemId)); + } + + // + // Implementation of SAX2 "XMLReader" interface + // + static final String FEATURE = "http://xml.org/sax/features/"; + static final String PROPERTY = "http://xml.org/sax/properties/"; + + /** + * <b>SAX2</b>: Tells the value of the specified feature flag. + * + * @exception SAXNotRecognizedException thrown if the feature flag + * is neither built in, nor yet assigned. + */ + public boolean getFeature (String featureId) + throws SAXNotRecognizedException, SAXNotSupportedException + { + if ((FEATURE + "validation").equals (featureId)) + return false; + + // external entities (both types) are optionally included + if ((FEATURE + "external-general-entities").equals (featureId)) + return extGE; + if ((FEATURE + "external-parameter-entities") .equals (featureId)) + return extPE; + + // element/attribute names are as written in document; no mangling + if ((FEATURE + "namespace-prefixes").equals (featureId)) + return xmlNames; + + // report element/attribute namespaces? + if ((FEATURE + "namespaces").equals (featureId)) + return namespaces; + + // all PEs and GEs are reported + if ((FEATURE + "lexical-handler/parameter-entities").equals (featureId)) + return true; + + // default is true + if ((FEATURE + "string-interning").equals (featureId)) + return stringInterning; + + // EXTENSIONS 1.1 + + // always returns isSpecified info + if ((FEATURE + "use-attributes2").equals (featureId)) + return true; + + // meaningful between startDocument/endDocument + if ((FEATURE + "is-standalone").equals (featureId)) { + if (parser == null) + throw new SAXNotSupportedException (featureId); + return parser.isStandalone (); + } + + // optionally don't absolutize URIs in declarations + if ((FEATURE + "resolve-dtd-uris").equals (featureId)) + return resolveAll; + + // optionally use resolver2 interface methods, if possible + if ((FEATURE + "use-entity-resolver2").equals (featureId)) + return useResolver2; + + throw new SAXNotRecognizedException (featureId); + } + + // package private + DeclHandler getDeclHandler () { return declHandler; } + + // package private + boolean resolveURIs () { return resolveAll; } + + /** + * <b>SAX2</b>: Returns the specified property. + * + * @exception SAXNotRecognizedException thrown if the property value + * is neither built in, nor yet stored. + */ + public Object getProperty (String propertyId) + throws SAXNotRecognizedException + { + if ((PROPERTY + "declaration-handler").equals (propertyId)) + return declHandler == base ? null : declHandler; + + if ((PROPERTY + "lexical-handler").equals (propertyId)) + return lexicalHandler == base ? null : lexicalHandler; + + // unknown properties + throw new SAXNotRecognizedException (propertyId); + } + + /** + * <b>SAX2</b>: Sets the state of feature flags in this parser. Some + * built-in feature flags are mutable. + */ + public void setFeature (String featureId, boolean value) + throws SAXNotRecognizedException, SAXNotSupportedException + { + boolean state; + + // Features with a defined value, we just change it if we can. + state = getFeature (featureId); + + if (state == value) + return; + if (parser != null) + throw new SAXNotSupportedException ("not while parsing"); + + if ((FEATURE + "namespace-prefixes").equals (featureId)) { + // in this implementation, this only affects xmlns reporting + xmlNames = value; + // forcibly prevent illegal parser state + if (!xmlNames) + namespaces = true; + return; + } + + if ((FEATURE + "namespaces").equals (featureId)) { + namespaces = value; + // forcibly prevent illegal parser state + if (!namespaces) + xmlNames = true; + return; + } + + if ((FEATURE + "external-general-entities").equals (featureId)) { + extGE = value; + return; + } + if ((FEATURE + "external-parameter-entities") .equals (featureId)) { + extPE = value; + return; + } + if ((FEATURE + "resolve-dtd-uris").equals (featureId)) { + resolveAll = value; + return; + } + + if ((FEATURE + "use-entity-resolver2").equals (featureId)) { + useResolver2 = value; + return; + } + + throw new SAXNotRecognizedException (featureId); + } + + /** + * <b>SAX2</b>: Assigns the specified property. Like SAX1 handlers, + * these may be changed at any time. + */ + public void setProperty (String propertyId, Object value) + throws SAXNotRecognizedException, SAXNotSupportedException + { + // see if the property is recognized + getProperty (propertyId); + + // Properties with a defined value, we just change it if we can. + + if ((PROPERTY + "declaration-handler").equals (propertyId)) { + if (value == null) + declHandler = base; + else if (! (value instanceof DeclHandler)) + throw new SAXNotSupportedException (propertyId); + else + declHandler = (DeclHandler) value; + return ; + } + + if ((PROPERTY + "lexical-handler").equals (propertyId)) { + if (value == null) + lexicalHandler = base; + else if (! (value instanceof LexicalHandler)) + throw new SAXNotSupportedException (propertyId); + else + lexicalHandler = (LexicalHandler) value; + return ; + } + + throw new SAXNotSupportedException (propertyId); + } + + + // + // This is where the driver receives XmlParser callbacks and translates + // them into SAX callbacks. Some more callbacks have been added for + // SAX2 support. + // + + void startDocument () + throws SAXException + { + contentHandler.setDocumentLocator (this); + contentHandler.startDocument (); + attributesList.clear (); + } + + void xmlDecl(String version, + String encoding, + boolean standalone, + String inputEncoding) + throws SAXException + { + if (contentHandler instanceof ContentHandler2) + { + ((ContentHandler2) contentHandler).xmlDecl(version, + encoding, + standalone, + inputEncoding); + } + } + + void skippedEntity (String name) + throws SAXException + { contentHandler.skippedEntity (name); } + + InputSource getExternalSubset (String name, String baseURI) + throws SAXException, IOException + { + if (resolver2 == null || !useResolver2 || !extPE) + return null; + return resolver2.getExternalSubset (name, baseURI); + } + + InputSource resolveEntity (boolean isPE, String name, + InputSource in, String baseURI) + throws SAXException, IOException + { + InputSource source; + + // external entities might be skipped + if (isPE && !extPE) + return null; + if (!isPE && !extGE) + return null; + + // ... or not + lexicalHandler.startEntity (name); + if (resolver2 != null && useResolver2) { + source = resolver2.resolveEntity (name, in.getPublicId (), + baseURI, in.getSystemId ()); + if (source == null) { + in.setSystemId (absolutize (baseURI, + in.getSystemId (), false)); + source = in; + } + } else { + in.setSystemId (absolutize (baseURI, in.getSystemId (), false)); + source = entityResolver.resolveEntity (in.getPublicId (), + in.getSystemId ()); + if (source == null) + source = in; + } + startExternalEntity (name, source.getSystemId (), true); + return source; + } + + // absolutize a system ID relative to the specified base URI + // (temporarily) package-visible for external entity decls + String absolutize (String baseURI, String systemId, boolean nice) + throws MalformedURLException, SAXException + { + // FIXME normalize system IDs -- when? + // - Convert to UTF-8 + // - Map reserved and non-ASCII characters to %HH + + try { + if (baseURI == null) { + warn ("No base URI; hope this SYSTEM id is absolute: " + + systemId); + return new URL (systemId).toString (); + } else + return new URL (new URL (baseURI), systemId).toString (); + + } catch (MalformedURLException e) { + + // Let unknown URI schemes pass through unless we need + // the JVM to map them to i/o streams for us... + if (!nice) + throw e; + + // sometimes sysids for notations or unparsed entities + // aren't really URIs... + warn ("Can't absolutize SYSTEM id: " + e.getMessage ()); + return systemId; + } + } + + void startExternalEntity (String name, String systemId, + boolean stackOnly) + throws SAXException + { + // The following warning was deleted because the application has the + // option of not setting systemId. Sun's JAXP or Xerces seems to + // ignore this case. + /* + if (systemId == null) + warn ("URI was not reported to parser for entity " + name); + */ + if (!stackOnly) // spliced [dtd] needs startEntity + lexicalHandler.startEntity (name); + entityStack.push (systemId); + } + + void endExternalEntity (String name) + throws SAXException + { + if (!"[document]".equals (name)) + lexicalHandler.endEntity (name); + entityStack.pop (); + } + + void startInternalEntity (String name) + throws SAXException + { + lexicalHandler.startEntity (name); + } + + void endInternalEntity (String name) + throws SAXException + { + lexicalHandler.endEntity (name); + } + + void doctypeDecl (String name, String publicId, String systemId) + throws SAXException + { + lexicalHandler.startDTD (name, publicId, systemId); + + // ... the "name" is a declaration and should be given + // to the DeclHandler (but sax2 doesn't). + + // the IDs for the external subset are lexical details, + // as are the contents of the internal subset; but sax2 + // doesn't provide the internal subset "pre-parse" + } + + void notationDecl (String name, String ids []) + throws SAXException + { + try { + dtdHandler.notationDecl (name, ids [0], + (resolveAll && ids [1] != null) + ? absolutize (ids [2], ids [1], true) + : ids [1]); + } catch (IOException e) { + // "can't happen" + throw new SAXParseException (e.getMessage (), this, e); + } + } + + void unparsedEntityDecl (String name, String ids [], String notation) + throws SAXException + { + try { + dtdHandler.unparsedEntityDecl (name, ids [0], + resolveAll + ? absolutize (ids [2], ids [1], true) + : ids [1], + notation); + } catch (IOException e) { + // "can't happen" + throw new SAXParseException (e.getMessage (), this, e); + } + } + + void endDoctype () + throws SAXException + { + lexicalHandler.endDTD (); + } + + private void declarePrefix (String prefix, String uri) + throws SAXException + { + int index = uri.indexOf (':'); + + // many versions of nwalsh docbook stylesheets + // have bogus URLs; so this can't be an error... + if (index < 1 && uri.length () != 0) + warn ("relative URI for namespace: " + uri); + + // FIXME: char [0] must be ascii alpha; chars [1..index] + // must be ascii alphanumeric or in "+-." [RFC 2396] + + //Namespace Constraints + //name for xml prefix must be http://www.w3.org/XML/1998/namespace + boolean prefixEquality = prefix.equals("xml"); + boolean uriEquality = uri.equals("http://www.w3.org/XML/1998/namespace"); + if ((prefixEquality || uriEquality) && !(prefixEquality && uriEquality)) + fatal ("xml is by definition bound to the namespace name " + + "http://www.w3.org/XML/1998/namespace"); + + //xmlns prefix declaration is illegal but xml prefix declaration is llegal... + if (prefixEquality && uriEquality) + return; + + //name for xmlns prefix must be http://www.w3.org/2000/xmlns/ + prefixEquality = prefix.equals("xmlns"); + uriEquality = uri.equals("http://www.w3.org/2000/xmlns/"); + if ((prefixEquality || uriEquality) && !(prefixEquality && uriEquality)) + fatal("http://www.w3.org/2000/xmlns/ is by definition bound" + + " to prefix xmlns"); + + //even if the uri is http://www.w3.org/2000/xmlns/ it is illegal to declare it + if (prefixEquality && uriEquality) + fatal ("declaring the xmlns prefix is illegal"); + + uri = uri.intern (); + prefixStack.declarePrefix (prefix, uri); + contentHandler.startPrefixMapping (prefix, uri); + } + + void attribute (String qname, String value, boolean isSpecified) + throws SAXException + { + if (!attributes) { + attributes = true; + if (namespaces) + prefixStack.pushContext (); + } + + // process namespace decls immediately; + // then maybe forget this as an attribute + if (namespaces) { + int index; + + // default NS declaration? + if (getFeature (FEATURE + "string-interning")) { + if ("xmlns" == qname) { + declarePrefix ("", value); + if (!xmlNames) + return; + } + // NS prefix declaration? + else if ((index = qname.indexOf (':')) == 5 + && qname.startsWith ("xmlns")) { + String prefix = qname.substring (6); + + if (prefix.equals("")) + fatal ("missing prefix in namespace declaration attribute"); + if (value.length () == 0) { + verror ("missing URI in namespace declaration attribute: " + + qname); + } else + declarePrefix (prefix, value); + if (!xmlNames) + return; + } + } else { + if ("xmlns".equals(qname)) { + declarePrefix ("", value); + if (!xmlNames) + return; + } + // NS prefix declaration? + else if ((index = qname.indexOf (':')) == 5 + && qname.startsWith ("xmlns")) { + String prefix = qname.substring (6); + + if (value.length () == 0) { + verror ("missing URI in namespace decl attribute: " + + qname); + } else + declarePrefix (prefix, value); + if (!xmlNames) + return; + } + } + } + // remember this attribute ... + + if (attributeCount == attributeSpecified.length) { // grow array? + boolean temp [] = new boolean [attributeSpecified.length + 5]; + System.arraycopy (attributeSpecified, 0, temp, 0, attributeCount); + attributeSpecified = temp; + } + attributeSpecified [attributeCount] = isSpecified; + + attributeCount++; + + // attribute type comes from querying parser's DTD records + attributesList.add(new Attribute(qname, value)); + + } + + void startElement (String elname) + throws SAXException + { + ContentHandler handler = contentHandler; + + // + // NOTE: this implementation of namespace support adds something + // like six percent to parsing CPU time, in a large (~50 MB) + // document that doesn't use namespaces at all. (Measured by PC + // sampling, with a bug where endElement processing was omitted.) + // [Measurement referred to older implementation, older JVM ...] + // + // It ought to become notably faster in such cases. Most + // costs are the prefix stack calling Hashtable.get() (2%), + // String.hashCode() (1.5%) and about 1.3% each for pushing + // the context, and two chunks of name processing. + // + + if (!attributes) { + if (namespaces) + prefixStack.pushContext (); + } else if (namespaces) { + + // now we can patch up namespace refs; we saw all the + // declarations, so now we'll do the Right Thing + Iterator itt = attributesList.iterator (); + while(itt.hasNext()) + { + Attribute attribute = (Attribute) itt.next(); + String qname = attribute.name; + int index; + + // default NS declaration? + if (getFeature (FEATURE + "string-interning")) { + if ("xmlns" == qname) + continue; + } else { + if ("xmlns".equals(qname)) + continue; + } + //Illegal in the new Namespaces Draft + //should it be only in 1.1 docs?? + if (qname.equals (":")) + fatal ("namespace names consisting of a single colon " + + "character are invalid"); + index = qname.indexOf (':'); + + // NS prefix declaration? + if (index == 5 && qname.startsWith ("xmlns")) + continue; + + // it's not a NS decl; patch namespace info items + if (prefixStack.processName (qname, nsTemp, true) == null) + fatal ("undeclared attribute prefix in: " + qname); + else { + attribute.nameSpace = nsTemp[0]; + attribute.localName = nsTemp[1]; + } + } + } + + // save element name so attribute callbacks work + elementName = elname; + if (namespaces) { + if (prefixStack.processName (elname, nsTemp, false) == null) { + fatal ("undeclared element prefix in: " + elname); + nsTemp [0] = nsTemp [1] = ""; + } + handler.startElement (nsTemp [0], nsTemp [1], elname, this); + } else + handler.startElement ("", "", elname, this); + // elementName = null; + + // elements with no attributes are pretty common! + if (attributes) { + attributesList.clear(); + attributeCount = 0; + attributes = false; + } + } + + void endElement (String elname) + throws SAXException + { + ContentHandler handler = contentHandler; + + if (!namespaces) { + handler.endElement ("", "", elname); + return; + } + prefixStack.processName (elname, nsTemp, false); + handler.endElement (nsTemp [0], nsTemp [1], elname); + + Enumeration prefixes = prefixStack.getDeclaredPrefixes (); + + while (prefixes.hasMoreElements ()) + handler.endPrefixMapping ((String) prefixes.nextElement ()); + prefixStack.popContext (); + } + + void startCDATA () + throws SAXException + { + lexicalHandler.startCDATA (); + } + + void charData (char ch[], int start, int length) + throws SAXException + { + contentHandler.characters (ch, start, length); + } + + void endCDATA () + throws SAXException + { + lexicalHandler.endCDATA (); + } + + void ignorableWhitespace (char ch[], int start, int length) + throws SAXException + { + contentHandler.ignorableWhitespace (ch, start, length); + } + + void processingInstruction (String target, String data) + throws SAXException + { + contentHandler.processingInstruction (target, data); + } + + void comment (char ch[], int start, int length) + throws SAXException + { + if (lexicalHandler != base) + lexicalHandler.comment (ch, start, length); + } + + void fatal (String message) + throws SAXException + { + SAXParseException fatal; + + fatal = new SAXParseException (message, this); + errorHandler.fatalError (fatal); + + // Even if the application can continue ... we can't! + throw fatal; + } + + // We can safely report a few validity errors that + // make layered SAX2 DTD validation more conformant + void verror (String message) + throws SAXException + { + SAXParseException err; + + err = new SAXParseException (message, this); + errorHandler.error (err); + } + + void warn (String message) + throws SAXException + { + SAXParseException err; + + err = new SAXParseException (message, this); + errorHandler.warning (err); + } + + + // + // Implementation of org.xml.sax.Attributes. + // + + /** + * <b>SAX1 AttributeList, SAX2 Attributes</b> method + * (don't invoke on parser); + */ + public int getLength () + { + return attributesList.size (); + } + + /** + * <b>SAX2 Attributes</b> method (don't invoke on parser); + */ + public String getURI (int index) + { + return ((Attribute) attributesList.get (index)).nameSpace; + } + + /** + * <b>SAX2 Attributes</b> method (don't invoke on parser); + */ + public String getLocalName (int index) + { + Attribute attr = (Attribute) attributesList.get (index); + // FIXME attr.localName is sometimes null, why? + if (namespaces && attr.localName == null) + { + // XXX fix this here for now + int ci = attr.name.indexOf(':'); + attr.localName = (ci == -1) ? attr.name : + attr.name.substring(ci + 1); + } + return attr.localName; + } + + /** + * <b>SAX2 Attributes</b> method (don't invoke on parser); + */ + public String getQName (int i) + { + return ((Attribute) attributesList.get (i)).name; + } + + /** + * <b>SAX1 AttributeList</b> method (don't invoke on parser); + */ + public String getName (int i) + { + return ((Attribute) attributesList.get (i)).name; + } + + /** + * <b>SAX1 AttributeList, SAX2 Attributes</b> method + * (don't invoke on parser); + */ + public String getType (int i) + { + String type = parser.getAttributeType (elementName, getQName (i)); + if (type == null) + return "CDATA"; + // ... use DeclHandler.attributeDecl to see enumerations + if (type == "ENUMERATION") + return "NMTOKEN"; + return type; + } + + + /** + * <b>SAX1 AttributeList, SAX2 Attributes</b> method + * (don't invoke on parser); + */ + public String getValue (int i) + { + return ((Attribute) attributesList.get (i)).value; + } + + + /** + * <b>SAX2 Attributes</b> method (don't invoke on parser); + */ + public int getIndex (String uri, String local) + { + int length = getLength (); + + for (int i = 0; i < length; i++) { + if (!getURI (i).equals (uri)) + continue; + if (getLocalName (i).equals (local)) + return i; + } + return -1; + } + + + /** + * <b>SAX2 Attributes</b> method (don't invoke on parser); + */ + public int getIndex (String xmlName) + { + int length = getLength (); + + for (int i = 0; i < length; i++) { + if (getQName (i).equals (xmlName)) + return i; + } + return -1; + } + + + /** + * <b>SAX2 Attributes</b> method (don't invoke on parser); + */ + public String getType (String uri, String local) + { + int index = getIndex (uri, local); + + if (index < 0) + return null; + return getType (index); + } + + + /** + * <b>SAX1 AttributeList, SAX2 Attributes</b> method + * (don't invoke on parser); + */ + public String getType (String xmlName) + { + int index = getIndex (xmlName); + + if (index < 0) + return null; + return getType (index); + } + + + /** + * <b>SAX Attributes</b> method (don't invoke on parser); + */ + public String getValue (String uri, String local) + { + int index = getIndex (uri, local); + + if (index < 0) + return null; + return getValue (index); + } + + + /** + * <b>SAX1 AttributeList, SAX2 Attributes</b> method + * (don't invoke on parser); + */ + public String getValue (String xmlName) + { + int index = getIndex (xmlName); + + if (index < 0) + return null; + return getValue (index); + } + + + // + // Implementation of org.xml.sax.ext.Attributes2 + // + + + /** @return false unless the attribute was declared in the DTD. + * @throws java.lang.ArrayIndexOutOfBoundsException + * When the supplied index does not identify an attribute. + */ + public boolean isDeclared (int index) + { + if (index < 0 || index >= attributeCount) + throw new ArrayIndexOutOfBoundsException (); + return attributeDeclared [index]; + } + + /** @return false unless the attribute was declared in the DTD. + * @throws java.lang.IllegalArgumentException + * When the supplied names do not identify an attribute. + */ + public boolean isDeclared (java.lang.String qName) + { + int index = getIndex (qName); + if (index < 0) + throw new IllegalArgumentException (); + return attributeDeclared [index]; + } + + /** @return false unless the attribute was declared in the DTD. + * @throws java.lang.IllegalArgumentException + * When the supplied names do not identify an attribute. + */ + public boolean isDeclared (java.lang.String uri, java.lang.String localName) + { + int index = getIndex (uri, localName); + if (index < 0) + throw new IllegalArgumentException (); + return attributeDeclared [index]; + } + + + /** + * <b>SAX-ext Attributes2</b> method (don't invoke on parser); + */ + public boolean isSpecified (int index) + { + if (index < 0 || index >= attributeCount) + throw new ArrayIndexOutOfBoundsException (); + return attributeSpecified [index]; + } + + /** + * <b>SAX-ext Attributes2</b> method (don't invoke on parser); + */ + public boolean isSpecified (String uri, String local) + { + int index = getIndex (uri, local); + + if (index < 0) + throw new IllegalArgumentException (); + return attributeSpecified [index]; + } + + /** + * <b>SAX-ext Attributes2</b> method (don't invoke on parser); + */ + public boolean isSpecified (String xmlName) + { + int index = getIndex (xmlName); + + if (index < 0) + throw new IllegalArgumentException (); + return attributeSpecified [index]; + } + + + // + // Implementation of org.xml.sax.Locator. + // + + /** + * <b>SAX Locator</b> method (don't invoke on parser); + */ + public String getPublicId () + { + return null; // FIXME track public IDs too + } + + /** + * <b>SAX Locator</b> method (don't invoke on parser); + */ + public String getSystemId () + { + if (entityStack.empty ()) + return null; + else + return (String) entityStack.peek (); + } + + /** + * <b>SAX Locator</b> method (don't invoke on parser); + */ + public int getLineNumber () + { + return parser.getLineNumber (); + } + + /** + * <b>SAX Locator</b> method (don't invoke on parser); + */ + public int getColumnNumber () + { + return parser.getColumnNumber (); + } + + // adapter between SAX2 content handler and SAX1 document handler callbacks + private static class Adapter implements ContentHandler + { + private DocumentHandler docHandler; + + Adapter (DocumentHandler dh) + { docHandler = dh; } + + + public void setDocumentLocator (Locator l) + { docHandler.setDocumentLocator (l); } + + public void startDocument () throws SAXException + { docHandler.startDocument (); } + + public void processingInstruction (String target, String data) + throws SAXException + { docHandler.processingInstruction (target, data); } + + public void startPrefixMapping (String prefix, String uri) + { /* ignored */ } + + public void startElement ( + String namespace, + String local, + String name, + Attributes attrs + ) throws SAXException + { docHandler.startElement (name, (AttributeList) attrs); } + + public void characters (char buf [], int offset, int len) + throws SAXException + { docHandler.characters (buf, offset, len); } + + public void ignorableWhitespace (char buf [], int offset, int len) + throws SAXException + { docHandler.ignorableWhitespace (buf, offset, len); } + + public void skippedEntity (String name) + { /* ignored */ } + + public void endElement (String u, String l, String name) + throws SAXException + { docHandler.endElement (name); } + + public void endPrefixMapping (String prefix) + { /* ignored */ } + + public void endDocument () throws SAXException + { docHandler.endDocument (); } + } +} + +class Attribute +{ + + String name; + String value; + String nameSpace; + String localName; + + Attribute(String name, String value) + { + this.name = name; + this.value = value; + this.nameSpace = ""; + } +} + diff --git a/libjava/gnu/xml/aelfred2/XmlParser.java b/libjava/gnu/xml/aelfred2/XmlParser.java new file mode 100644 index 0000000..f4abf22 --- /dev/null +++ b/libjava/gnu/xml/aelfred2/XmlParser.java @@ -0,0 +1,5113 @@ +/* XmlParser.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. + +Partly derived from code which carried the following notice: + + Copyright (c) 1997, 1998 by Microstar Software Ltd. + + AElfred is free for both commercial and non-commercial use and + redistribution, provided that Microstar's copyright and disclaimer are + retained intact. You are free to modify AElfred for your own use and + to redistribute AElfred with your modifications, provided that the + modifications are clearly documented. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + merchantability or fitness for a particular purpose. Please use it AT + YOUR OWN RISK. +*/ + +package gnu.xml.aelfred2; + +import java.io.BufferedInputStream; +import java.io.CharConversionException; +import java.io.EOFException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLConnection; + +// maintaining 1.1 compatibility for now ... +// Iterator and Hashmap ought to be faster +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Stack; + +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + + +/** + * Parse XML documents and return parse events through call-backs. + * Use the <code>SAXDriver</code> class as your entry point, as all + * internal parser interfaces are subject to change. + * + * @author Written by David Megginson <dmeggins@microstar.com> + * (version 1.2a with bugfixes) + * @author Updated by David Brownell <dbrownell@users.sourceforge.net> + * @see SAXDriver + */ +final class XmlParser +{ + // avoid slow per-character readCh() + private final static boolean USE_CHEATS = true; + + + ////////////////////////////////////////////////////////////////////// + // Constructors. + //////////////////////////////////////////////////////////////////////// + + + /** + * Construct a new parser with no associated handler. + * @see #setHandler + * @see #parse + */ + // package private + XmlParser () + { + } + + + /** + * Set the handler that will receive parsing events. + * @param handler The handler to receive callback events. + * @see #parse + */ + // package private + void setHandler (SAXDriver handler) + { + this.handler = handler; + } + + + /** + * Parse an XML document from the character stream, byte stream, or URI + * that you provide (in that order of preference). Any URI that you + * supply will become the base URI for resolving relative URI, and may + * be used to acquire a reader or byte stream. + * + * <p> Only one thread at a time may use this parser; since it is + * private to this package, post-parse cleanup is done by the caller, + * which MUST NOT REUSE the parser (just null it). + * + * @param systemId Absolute URI of the document; should never be null, + * but may be so iff a reader <em>or</em> a stream is provided. + * @param publicId The public identifier of the document, or null. + * @param reader A character stream; must be null if stream isn't. + * @param stream A byte input stream; must be null if reader isn't. + * @param encoding The suggested encoding, or null if unknown. + * @exception java.lang.Exception Basically SAXException or IOException + */ + // package private + void doParse ( + String systemId, + String publicId, + Reader reader, + InputStream stream, + String encoding + ) throws Exception + { + if (handler == null) + throw new IllegalStateException ("no callback handler"); + + initializeVariables (); + + // predeclare the built-in entities here (replacement texts) + // we don't need to intern(), since we're guaranteed literals + // are always (globally) interned. + setInternalEntity ("amp", "&"); + setInternalEntity ("lt", "<"); + setInternalEntity ("gt", ">"); + setInternalEntity ("apos", "'"); + setInternalEntity ("quot", """); + + try { + // pushURL first to ensure locator is correct in startDocument + // ... it might report an IO or encoding exception. + handler.startDocument (); + pushURL (false, "[document]", + // default baseURI: null + new String [] { publicId, systemId, null}, + reader, stream, encoding, false); + + parseDocument (); + } catch (EOFException e){ + //empty input + error("empty document, with no root element."); + }finally { + if (reader != null) + try { reader.close (); + } catch (IOException e) { /* ignore */ } + if (stream != null) + try { stream.close (); + } catch (IOException e) { /* ignore */ } + if (is != null) + try { is.close (); + } catch (IOException e) { /* ignore */ } + if (reader != null) + try { + reader.close (); + } catch (IOException e) { /* ignore */ + } + scratch = null; + } + } + + + //////////////////////////////////////////////////////////////////////// + // Constants. + //////////////////////////////////////////////////////////////////////// + + // + // Constants for element content type. + // + + /** + * Constant: an element has not been declared. + * @see #getElementContentType + */ + public final static int CONTENT_UNDECLARED = 0; + + /** + * Constant: the element has a content model of ANY. + * @see #getElementContentType + */ + public final static int CONTENT_ANY = 1; + + /** + * Constant: the element has declared content of EMPTY. + * @see #getElementContentType + */ + public final static int CONTENT_EMPTY = 2; + + /** + * Constant: the element has mixed content. + * @see #getElementContentType + */ + public final static int CONTENT_MIXED = 3; + + /** + * Constant: the element has element content. + * @see #getElementContentType + */ + public final static int CONTENT_ELEMENTS = 4; + + + // + // Constants for the entity type. + // + + /** + * Constant: the entity has not been declared. + * @see #getEntityType + */ + public final static int ENTITY_UNDECLARED = 0; + + /** + * Constant: the entity is internal. + * @see #getEntityType + */ + public final static int ENTITY_INTERNAL = 1; + + /** + * Constant: the entity is external, non-parsable data. + * @see #getEntityType + */ + public final static int ENTITY_NDATA = 2; + + /** + * Constant: the entity is external XML data. + * @see #getEntityType + */ + public final static int ENTITY_TEXT = 3; + + + // + // Attribute type constants are interned literal strings. + // + + // + // Constants for supported encodings. "external" is just a flag. + // + private final static int ENCODING_EXTERNAL = 0; + private final static int ENCODING_UTF_8 = 1; + private final static int ENCODING_ISO_8859_1 = 2; + private final static int ENCODING_UCS_2_12 = 3; + private final static int ENCODING_UCS_2_21 = 4; + private final static int ENCODING_UCS_4_1234 = 5; + private final static int ENCODING_UCS_4_4321 = 6; + private final static int ENCODING_UCS_4_2143 = 7; + private final static int ENCODING_UCS_4_3412 = 8; + private final static int ENCODING_ASCII = 9; + + + // + // Constants for attribute default value. + // + + /** + * Constant: the attribute is not declared. + * @see #getAttributeDefaultValueType + */ + public final static int ATTRIBUTE_DEFAULT_UNDECLARED = 30; + + /** + * Constant: the attribute has a literal default value specified. + * @see #getAttributeDefaultValueType + * @see #getAttributeDefaultValue + */ + public final static int ATTRIBUTE_DEFAULT_SPECIFIED = 31; + + /** + * Constant: the attribute was declared #IMPLIED. + * @see #getAttributeDefaultValueType + */ + public final static int ATTRIBUTE_DEFAULT_IMPLIED = 32; + + /** + * Constant: the attribute was declared #REQUIRED. + * @see #getAttributeDefaultValueType + */ + public final static int ATTRIBUTE_DEFAULT_REQUIRED = 33; + + /** + * Constant: the attribute was declared #FIXED. + * @see #getAttributeDefaultValueType + * @see #getAttributeDefaultValue + */ + public final static int ATTRIBUTE_DEFAULT_FIXED = 34; + + + // + // Constants for input. + // + private final static int INPUT_NONE = 0; + private final static int INPUT_INTERNAL = 1; + private final static int INPUT_STREAM = 3; + private final static int INPUT_READER = 5; + + + // + // Flags for reading literals. + // + // expand general entity refs (attribute values in dtd and content) + private final static int LIT_ENTITY_REF = 2; + // normalize this value (space chars) (attributes, public ids) + private final static int LIT_NORMALIZE = 4; + // literal is an attribute value + private final static int LIT_ATTRIBUTE = 8; + // don't expand parameter entities + private final static int LIT_DISABLE_PE = 16; + // don't expand [or parse] character refs + private final static int LIT_DISABLE_CREF = 32; + // don't parse general entity refs + private final static int LIT_DISABLE_EREF = 64; + // literal is a public ID value + private final static int LIT_PUBID = 256; + + + // + // Flags affecting PE handling in DTDs (if expandPE is true). + // PEs expand with space padding, except inside literals. + // + private final static int CONTEXT_NORMAL = 0; + private final static int CONTEXT_LITERAL = 1; + + + ////////////////////////////////////////////////////////////////////// + // Error reporting. + ////////////////////////////////////////////////////////////////////// + + + /** + * Report an error. + * @param message The error message. + * @param textFound The text that caused the error (or null). + * @see SAXDriver#error + * @see #line + */ + private void error (String message, String textFound, String textExpected) + throws SAXException + { + if (textFound != null) { + message = message + " (found \"" + textFound + "\")"; + } + if (textExpected != null) { + message = message + " (expected \"" + textExpected + "\")"; + } + handler.fatal (message); + + // "can't happen" + throw new SAXException (message); + } + + + /** + * Report a serious error. + * @param message The error message. + * @param textFound The text that caused the error (or null). + */ + private void error (String message, char textFound, String textExpected) + throws SAXException + { + error (message, new Character (textFound).toString (), textExpected); + } + + /** Report typical case fatal errors. */ + private void error (String message) + throws SAXException + { + handler.fatal (message); + } + + + ////////////////////////////////////////////////////////////////////// + // Major syntactic productions. + ////////////////////////////////////////////////////////////////////// + + + /** + * Parse an XML document. + * <pre> + * [1] document ::= prolog element Misc* + * </pre> + * <p>This is the top-level parsing function for a single XML + * document. As a minimum, a well-formed document must have + * a document element, and a valid document must have a prolog + * (one with doctype) as well. + */ + private void parseDocument () + throws Exception + { + try { // added by MHK + boolean sawDTD = parseProlog (); + require ('<'); + parseElement (!sawDTD); + } catch (EOFException ee) { // added by MHK + error("premature end of file", "[EOF]", null); + } + + try { + parseMisc (); //skip all white, PIs, and comments + char c = readCh (); //if this doesn't throw an exception... + error ("unexpected characters after document end", c, null); + } catch (EOFException e) { + return; + } + } + + static final char startDelimComment [] = { '<', '!', '-', '-' }; + static final char endDelimComment [] = { '-', '-' }; + + /** + * Skip a comment. + * <pre> + * [15] Comment ::= '<!--' ((Char - '-') | ('-' (Char - '-')))* "-->" + * </pre> + * <p> (The <code><!--</code> has already been read.) + */ + private void parseComment () + throws Exception + { + char c; + boolean saved = expandPE; + + expandPE = false; + parseUntil (endDelimComment); + require ('>'); + expandPE = saved; + handler.comment (dataBuffer, 0, dataBufferPos); + dataBufferPos = 0; + } + + static final char startDelimPI [] = { '<', '?' }; + static final char endDelimPI [] = { '?', '>' }; + + /** + * Parse a processing instruction and do a call-back. + * <pre> + * [16] PI ::= '<?' PITarget + * (S (Char* - (Char* '?>' Char*)))? + * '?>' + * [17] PITarget ::= Name - ( ('X'|'x') ('M'|m') ('L'|l') ) + * </pre> + * <p> (The <code><?</code> has already been read.) + */ + private void parsePI () + throws SAXException, IOException + { + String name; + boolean saved = expandPE; + + expandPE = false; + name = readNmtoken (true); + //NE08 + if (name.indexOf(':') >= 0) + error ("Illegal character(':') in processing instruction name ", name, null); + if ("xml".equalsIgnoreCase (name)) + error ("Illegal processing instruction target", name, null); + if (!tryRead (endDelimPI)) { + requireWhitespace (); + parseUntil (endDelimPI); + } + expandPE = saved; + handler.processingInstruction (name, dataBufferToString ()); + } + + + static final char endDelimCDATA [] = { ']', ']', '>' }; + + private boolean isDirtyCurrentElement; + + /** + * Parse a CDATA section. + * <pre> + * [18] CDSect ::= CDStart CData CDEnd + * [19] CDStart ::= '<![CDATA[' + * [20] CData ::= (Char* - (Char* ']]>' Char*)) + * [21] CDEnd ::= ']]>' + * </pre> + * <p> (The '<![CDATA[' has already been read.) + */ + private void parseCDSect () + throws Exception + { + parseUntil (endDelimCDATA); + dataBufferFlush (); + } + + + /** + * Parse the prolog of an XML document. + * <pre> + * [22] prolog ::= XMLDecl? Misc* (Doctypedecl Misc*)? + * </pre> + * <p>We do not look for the XML declaration here, because it was + * handled by pushURL (). + * @see pushURL + * @return true if a DTD was read. + */ + private boolean parseProlog () + throws Exception + { + parseMisc (); + + if (tryRead ("<!DOCTYPE")) { + parseDoctypedecl (); + parseMisc (); + return true; + } + return false; + } + + private void checkLegalVersion (String version) + throws SAXException + { + int len = version.length (); + for (int i = 0; i < len; i++) { + char c = version.charAt (i); + if ('0' <= c && c <= '9') + continue; + if (c == '_' || c == '.' || c == ':' || c == '-') + continue; + if ('a' <= c && c <= 'z') + continue; + if ('A' <= c && c <= 'Z') + continue; + error ("illegal character in version", version, "1.0"); + } + } + + + /** + * Parse the XML declaration. + * <pre> + * [23] XMLDecl ::= '<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>' + * [24] VersionInfo ::= S 'version' Eq + * ("'" VersionNum "'" | '"' VersionNum '"' ) + * [26] VersionNum ::= ([a-zA-Z0-9_.:] | '-')* + * [32] SDDecl ::= S 'standalone' Eq + * ( "'"" ('yes' | 'no') "'"" | '"' ("yes" | "no") '"' ) + * [80] EncodingDecl ::= S 'encoding' Eq + * ( "'" EncName "'" | "'" EncName "'" ) + * [81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')* + * </pre> + * <p> (The <code><?xml</code> and whitespace have already been read.) + * @return the encoding in the declaration, uppercased; or null + * @see #parseTextDecl + * @see #setupDecoding + */ + private String parseXMLDecl (boolean ignoreEncoding) + throws SAXException, IOException + { + String version; + String encodingName = null; + String standalone = null; + int flags = LIT_DISABLE_CREF | LIT_DISABLE_PE | LIT_DISABLE_EREF; + String inputEncoding = null; + + switch (this.encoding) + { + case ENCODING_EXTERNAL: + case ENCODING_UTF_8: + inputEncoding = "UTF-8"; + break; + case ENCODING_ISO_8859_1: + inputEncoding = "ISO-8859-1"; + break; + case ENCODING_UCS_2_12: + inputEncoding = "UTF-16BE"; + break; + case ENCODING_UCS_2_21: + inputEncoding = "UTF-16LE"; + break; + } + + // Read the version. + require ("version"); + parseEq (); + checkLegalVersion (version = readLiteral (flags)); + if (!version.equals ("1.0")){ + if(version.equals ("1.1")){ + handler.warn ("expected XML version 1.0, not: " + version); + xmlVersion = XML_11; + }else { + error("illegal XML version", version, "1.0 or 1.1"); + } + } + else + xmlVersion = XML_10; + // Try reading an encoding declaration. + boolean white = tryWhitespace (); + + if (tryRead ("encoding")) { + if (!white) + error ("whitespace required before 'encoding='"); + parseEq (); + encodingName = readLiteral (flags); + if (!ignoreEncoding) + setupDecoding (encodingName); + } + + // Try reading a standalone declaration + if (encodingName != null) + white = tryWhitespace (); + if (tryRead ("standalone")) { + if (!white) + error ("whitespace required before 'standalone='"); + parseEq (); + standalone = readLiteral (flags); + if ("yes".equals (standalone)) + docIsStandalone = true; + else if (!"no".equals (standalone)) + error ("standalone flag must be 'yes' or 'no'"); + } + + skipWhitespace (); + require ("?>"); + + if (inputEncoding == null) + { + inputEncoding = encodingName; + } + handler.xmlDecl(version, encodingName, "yes".equals(standalone), + inputEncoding); + + return encodingName; + } + + + /** + * Parse a text declaration. + * <pre> + * [79] TextDecl ::= '<?xml' VersionInfo? EncodingDecl S? '?>' + * [80] EncodingDecl ::= S 'encoding' Eq + * ( '"' EncName '"' | "'" EncName "'" ) + * [81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')* + * </pre> + * <p> (The <code><?xml</code>' and whitespace have already been read.) + * @return the encoding in the declaration, uppercased; or null + * @see #parseXMLDecl + * @see #setupDecoding + */ + private String parseTextDecl (boolean ignoreEncoding) + throws SAXException, IOException + { + String encodingName = null; + int flags = LIT_DISABLE_CREF | LIT_DISABLE_PE | LIT_DISABLE_EREF; + + // Read an optional version. + if (tryRead ("version")) { + String version; + parseEq (); + checkLegalVersion (version = readLiteral (flags)); + + if (version.equals ("1.1")){ + if (xmlVersion == XML_10){ + error ("external subset has later version number.", "1.0", version); + } + handler.warn ("expected XML version 1.0, not: " + version); + xmlVersion = XML_11; + }else if(!version.equals ("1.0")) { + error("illegal XML version", version, "1.0 or 1.1"); + } + requireWhitespace (); + } + + + // Read the encoding. + require ("encoding"); + parseEq (); + encodingName = readLiteral (flags); + if (!ignoreEncoding) + setupDecoding (encodingName); + + skipWhitespace (); + require ("?>"); + + return encodingName; + } + + + /** + * Sets up internal state so that we can decode an entity using the + * specified encoding. This is used when we start to read an entity + * and we have been given knowledge of its encoding before we start to + * read any data (e.g. from a SAX input source or from a MIME type). + * + * <p> It is also used after autodetection, at which point only very + * limited adjustments to the encoding may be used (switching between + * related builtin decoders). + * + * @param encodingName The name of the encoding specified by the user. + * @exception IOException if the encoding isn't supported either + * internally to this parser, or by the hosting JVM. + * @see #parseXMLDecl + * @see #parseTextDecl + */ + private void setupDecoding (String encodingName) + throws SAXException, IOException + { + encodingName = encodingName.toUpperCase (); + + // ENCODING_EXTERNAL indicates an encoding that wasn't + // autodetected ... we can use builtin decoders, or + // ones from the JVM (InputStreamReader). + + // Otherwise we can only tweak what was autodetected, and + // only for single byte (ASCII derived) builtin encodings. + + // ASCII-derived encodings + if (encoding == ENCODING_UTF_8 || encoding == ENCODING_EXTERNAL) { + if (encodingName.equals ("ISO-8859-1") + || encodingName.equals ("8859_1") + || encodingName.equals ("ISO8859_1") + ) { + encoding = ENCODING_ISO_8859_1; + return; + } else if (encodingName.equals ("US-ASCII") + || encodingName.equals ("ASCII")) { + encoding = ENCODING_ASCII; + return; + } else if (encodingName.equals ("UTF-8") + || encodingName.equals ("UTF8")) { + encoding = ENCODING_UTF_8; + return; + } else if (encoding != ENCODING_EXTERNAL) { + // used to start with a new reader ... + throw new UnsupportedEncodingException (encodingName); + } + // else fallthrough ... + // it's ASCII-ish and something other than a builtin + } + + // Unicode and such + if (encoding == ENCODING_UCS_2_12 || encoding == ENCODING_UCS_2_21) { + if (!(encodingName.equals ("ISO-10646-UCS-2") + || encodingName.equals ("UTF-16") + || encodingName.equals ("UTF-16BE") + || encodingName.equals ("UTF-16LE"))) + error ("unsupported Unicode encoding", + encodingName, + "UTF-16"); + return; + } + + // four byte encodings + if (encoding == ENCODING_UCS_4_1234 + || encoding == ENCODING_UCS_4_4321 + || encoding == ENCODING_UCS_4_2143 + || encoding == ENCODING_UCS_4_3412) { + // Strictly: "UCS-4" == "UTF-32BE"; also, "UTF-32LE" exists + if (!encodingName.equals ("ISO-10646-UCS-4")) + error ("unsupported 32-bit encoding", + encodingName, + "ISO-10646-UCS-4"); + return; + } + + // assert encoding == ENCODING_EXTERNAL + // if (encoding != ENCODING_EXTERNAL) + // throw new RuntimeException ("encoding = " + encoding); + + if (encodingName.equals ("UTF-16BE")) { + encoding = ENCODING_UCS_2_12; + return; + } + if (encodingName.equals ("UTF-16LE")) { + encoding = ENCODING_UCS_2_21; + return; + } + + // We couldn't use the builtin decoders at all. But we can try to + // create a reader, since we haven't messed up buffering. Tweak + // the encoding name if necessary. + + if (encodingName.equals ("UTF-16") + || encodingName.equals ("ISO-10646-UCS-2")) + encodingName = "Unicode"; + // Ignoring all the EBCDIC aliases here + + reader = new InputStreamReader (is, encodingName); + sourceType = INPUT_READER; + } + + + /** + * Parse miscellaneous markup outside the document element and DOCTYPE + * declaration. + * <pre> + * [27] Misc ::= Comment | PI | S + * </pre> + */ + private void parseMisc () + throws Exception + { + while (true) { + skipWhitespace (); + if (tryRead (startDelimPI)) { + parsePI (); + } else if (tryRead (startDelimComment)) { + parseComment (); + } else { + return; + } + } + } + + + /** + * Parse a document type declaration. + * <pre> + * [28] doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? + * ('[' (markupdecl | PEReference | S)* ']' S?)? '>' + * </pre> + * <p> (The <code><!DOCTYPE</code> has already been read.) + */ + private void parseDoctypedecl () + throws Exception + { + String rootName, ids[]; + + // Read the document type name. + requireWhitespace (); + rootName = readNmtoken (true); + + // Read the External subset's IDs + skipWhitespace (); + ids = readExternalIds (false, true); + + // report (a) declaration of name, (b) lexical info (ids) + handler.doctypeDecl (rootName, ids [0], ids [1]); + + // Internal subset is parsed first, if present + skipWhitespace (); + if (tryRead ('[')) { + + // loop until the subset ends + while (true) { + doReport = expandPE = true; + skipWhitespace (); + doReport = expandPE = false; + if (tryRead (']')) { + break; // end of subset + } else { + // WFC, PEs in internal subset (only between decls) + peIsError = expandPE = true; + parseMarkupdecl (); + peIsError = expandPE = false; + } + } + } + skipWhitespace (); + require ('>'); + + // Read the external subset, if any + InputSource subset; + + if (ids [1] == null) + subset = handler.getExternalSubset (rootName, + handler.getSystemId ()); + else + subset = null; + if (ids [1] != null || subset != null) { + pushString (null, ">"); + + // NOTE: [dtd] is so we say what SAX2 expects, + // though it's misleading (subset, not entire dtd) + if (ids [1] != null) + pushURL (true, "[dtd]", ids, null, null, null, true); + else { + handler.warn ("modifying document by adding external subset"); + pushURL (true, "[dtd]", + new String [] { subset.getPublicId (), + subset.getSystemId (), null }, + subset.getCharacterStream (), + subset.getByteStream (), + subset.getEncoding (), + false); + } + + // Loop until we end up back at '>' + while (true) { + doReport = expandPE = true; + skipWhitespace (); + doReport = expandPE = false; + if (tryRead ('>')) { + break; + } else { + expandPE = true; + parseMarkupdecl (); + expandPE = false; + } + } + + // the ">" string isn't popped yet + if (inputStack.size () != 1) + error ("external subset has unmatched '>'"); + } + + // done dtd + handler.endDoctype (); + expandPE = false; + doReport = true; + } + + + /** + * Parse a markup declaration in the internal or external DTD subset. + * <pre> + * [29] markupdecl ::= elementdecl | Attlistdecl | EntityDecl + * | NotationDecl | PI | Comment + * [30] extSubsetDecl ::= (markupdecl | conditionalSect + * | PEReference | S) * + * </pre> + * <p> Reading toplevel PE references is handled as a lexical issue + * by the caller, as is whitespace. + */ + private void parseMarkupdecl () + throws Exception + { + char saved [] = null; + boolean savedPE = expandPE; + + // prevent "<%foo;" and ensures saved entity is right + require ('<'); + unread ('<'); + expandPE = false; + + if (tryRead ("<!ELEMENT")) { + saved = readBuffer; + expandPE = savedPE; + parseElementDecl (); + } else if (tryRead ("<!ATTLIST")) { + saved = readBuffer; + expandPE = savedPE; + parseAttlistDecl (); + } else if (tryRead ("<!ENTITY")) { + saved = readBuffer; + expandPE = savedPE; + parseEntityDecl (); + } else if (tryRead ("<!NOTATION")) { + saved = readBuffer; + expandPE = savedPE; + parseNotationDecl (); + } else if (tryRead (startDelimPI)) { + saved = readBuffer; + expandPE = savedPE; + parsePI (); + } else if (tryRead (startDelimComment)) { + saved = readBuffer; + expandPE = savedPE; + parseComment (); + } else if (tryRead ("<![")) { + saved = readBuffer; + expandPE = savedPE; + if (inputStack.size () > 0) + parseConditionalSect (saved); + else + error ("conditional sections illegal in internal subset"); + } else { + error ("expected markup declaration"); + } + + // VC: Proper Decl/PE Nesting + if (readBuffer != saved) + handler.verror ("Illegal Declaration/PE nesting"); + } + + + /** + * Parse an element, with its tags. + * <pre> + * [39] element ::= EmptyElementTag | STag content ETag + * [40] STag ::= '<' Name (S Attribute)* S? '>' + * [44] EmptyElementTag ::= '<' Name (S Attribute)* S? '/>' + * </pre> + * <p> (The '<' has already been read.) + * <p>NOTE: this method actually chains onto parseContent (), if necessary, + * and parseContent () will take care of calling parseETag (). + */ + private void parseElement (boolean maybeGetSubset) + throws Exception + { + String gi; + char c; + int oldElementContent = currentElementContent; + String oldElement = currentElement; + Object element []; + + // This is the (global) counter for the + // array of specified attributes. + tagAttributePos = 0; + + // Read the element type name. + gi = readNmtoken (true); + + // If we saw no DTD, and this is the document root element, + // let the application modify the input stream by providing one. + if (maybeGetSubset) { + InputSource subset = handler.getExternalSubset (gi, + handler.getSystemId ()); + if (subset != null) { + String publicId = subset.getPublicId (); + String systemId = subset.getSystemId (); + + handler.warn ("modifying document by adding DTD"); + handler.doctypeDecl (gi, publicId, systemId); + pushString (null, ">"); + + // NOTE: [dtd] is so we say what SAX2 expects, + // though it's misleading (subset, not entire dtd) + pushURL (true, "[dtd]", + new String [] { publicId, systemId, null }, + subset.getCharacterStream (), + subset.getByteStream (), + subset.getEncoding (), + false); + + // Loop until we end up back at '>' + while (true) { + doReport = expandPE = true; + skipWhitespace (); + doReport = expandPE = false; + if (tryRead ('>')) { + break; + } else { + expandPE = true; + parseMarkupdecl (); + expandPE = false; + } + } + + // the ">" string isn't popped yet + if (inputStack.size () != 1) + error ("external subset has unmatched '>'"); + + handler.endDoctype (); + } + } + + // Determine the current content type. + currentElement = gi; + element = (Object []) elementInfo.get (gi); + currentElementContent = getContentType (element, CONTENT_ANY); + + // Read the attributes, if any. + // After this loop, "c" is the closing delimiter. + boolean white = tryWhitespace (); + c = readCh (); + while (c != '/' && c != '>') { + unread (c); + if (!white) + error ("need whitespace between attributes"); + parseAttribute (gi); + white = tryWhitespace (); + c = readCh (); + } + + // Supply any defaulted attributes. + Enumeration atts = declaredAttributes (element); + if (atts != null) { + String aname; +loop: + while (atts.hasMoreElements ()) { + aname = (String) atts.nextElement (); + // See if it was specified. + for (int i = 0; i < tagAttributePos; i++) { + if (tagAttributes [i] == aname) { + continue loop; + } + } + // ... or has a default + String value = getAttributeDefaultValue (gi, aname); + + if (value == null) + continue; + handler.attribute (aname, value, false); + } + } + + // Figure out if this is a start tag + // or an empty element, and dispatch an + // event accordingly. + switch (c) { + case '>': + handler.startElement (gi); + parseContent (); + break; + case '/': + require ('>'); + handler.startElement (gi); + handler.endElement (gi); + break; + } + + // Restore the previous state. + currentElement = oldElement; + currentElementContent = oldElementContent; + } + + + /** + * Parse an attribute assignment. + * <pre> + * [41] Attribute ::= Name Eq AttValue + * </pre> + * @param name The name of the attribute's element. + * @see SAXDriver#attribute + */ + private void parseAttribute (String name) + throws Exception + { + String aname; + String type; + String value; + int flags = LIT_ATTRIBUTE | LIT_ENTITY_REF; + + // Read the attribute name. + aname = readNmtoken (true); + type = getAttributeType (name, aname); + + // Parse '=' + parseEq (); + + // Read the value, normalizing whitespace + // unless it is CDATA. + if (handler.getFeature (SAXDriver.FEATURE + "string-interning")) { + if (type == "CDATA" || type == null) { + value = readLiteral (flags); + } else { + value = readLiteral (flags | LIT_NORMALIZE); + } + } else { + if (type.equals("CDATA") || type == null) { + value = readLiteral (flags); + } else { + value = readLiteral (flags | LIT_NORMALIZE); + } + } + + // WFC: no duplicate attributes + for (int i = 0; i < tagAttributePos; i++) + if (aname.equals (tagAttributes [i])) + error ("duplicate attribute", aname, null); + + // Inform the handler about the + // attribute. + handler.attribute (aname, value, true); + dataBufferPos = 0; + + // Note that the attribute has been + // specified. + if (tagAttributePos == tagAttributes.length) { + String newAttrib[] = new String [tagAttributes.length * 2]; + System.arraycopy (tagAttributes, 0, newAttrib, 0, tagAttributePos); + tagAttributes = newAttrib; + } + tagAttributes [tagAttributePos++] = aname; + } + + + /** + * Parse an equals sign surrounded by optional whitespace. + * <pre> + * [25] Eq ::= S? '=' S? + * </pre> + */ + private void parseEq () + throws SAXException, IOException + { + skipWhitespace (); + require ('='); + skipWhitespace (); + } + + + /** + * Parse an end tag. + * <pre> + * [42] ETag ::= '</' Name S? '>' + * </pre> + * <p>NOTE: parseContent () chains to here, we already read the + * "</". + */ + private void parseETag () + throws Exception + { + require (currentElement); + skipWhitespace (); + require ('>'); + handler.endElement (currentElement); + // not re-reporting any SAXException re bogus end tags, + // even though that diagnostic might be clearer ... + } + + + /** + * Parse the content of an element. + * <pre> + * [43] content ::= (element | CharData | Reference + * | CDSect | PI | Comment)* + * [67] Reference ::= EntityRef | CharRef + * </pre> + * <p> NOTE: consumes ETtag. + */ + private void parseContent () + throws Exception + { + char c; + + while (true) { + // consume characters (or ignorable whitspace) until delimiter + parseCharData (); + + // Handle delimiters + c = readCh (); + switch (c) { + + case '&': // Found "&" + c = readCh (); + if (c == '#') { + parseCharRef (); + } else { + unread (c); + parseEntityRef (true); + } + isDirtyCurrentElement = true; + break; + + case '<': // Found "<" + dataBufferFlush (); + c = readCh (); + switch (c) { + case '!': // Found "<!" + c = readCh (); + switch (c) { + case '-': // Found "<!-" + require ('-'); + isDirtyCurrentElement = false; + parseComment (); + break; + case '[': // Found "<![" + isDirtyCurrentElement = false; + require ("CDATA["); + handler.startCDATA (); + inCDATA = true; + parseCDSect (); + inCDATA = false; + handler.endCDATA (); + break; + default: + error ("expected comment or CDATA section", c, null); + break; + } + break; + + case '?': // Found "<?" + isDirtyCurrentElement = false; + parsePI (); + break; + + case '/': // Found "</" + isDirtyCurrentElement = false; + parseETag (); + return; + + default: // Found "<" followed by something else + isDirtyCurrentElement = false; + unread (c); + parseElement (false); + break; + } + } + } + + } + + + /** + * Parse an element type declaration. + * <pre> + * [45] elementdecl ::= '<!ELEMENT' S Name S contentspec S? '>' + * </pre> + * <p> NOTE: the '<!ELEMENT' has already been read. + */ + private void parseElementDecl () + throws Exception + { + String name; + + requireWhitespace (); + // Read the element type name. + name = readNmtoken (true); + + requireWhitespace (); + // Read the content model. + parseContentspec (name); + + skipWhitespace (); + require ('>'); + } + + + /** + * Content specification. + * <pre> + * [46] contentspec ::= 'EMPTY' | 'ANY' | Mixed | elements + * </pre> + */ + private void parseContentspec (String name) + throws Exception + { +// FIXME: move elementDecl() into setElement(), pass EMTPY/ANY ... + if (tryRead ("EMPTY")) { + setElement (name, CONTENT_EMPTY, null, null); + if (!skippedPE) + handler.getDeclHandler ().elementDecl (name, "EMPTY"); + return; + } else if (tryRead ("ANY")) { + setElement (name, CONTENT_ANY, null, null); + if (!skippedPE) + handler.getDeclHandler ().elementDecl (name, "ANY"); + return; + } else { + String model; + char saved []; + + require ('('); + saved = readBuffer; + dataBufferAppend ('('); + skipWhitespace (); + if (tryRead ("#PCDATA")) { + dataBufferAppend ("#PCDATA"); + parseMixed (saved); + model = dataBufferToString (); + setElement (name, CONTENT_MIXED, model, null); + } else { + parseElements (saved); + model = dataBufferToString (); + setElement (name, CONTENT_ELEMENTS, model, null); + } + if (!skippedPE) + handler.getDeclHandler ().elementDecl (name, model); + } + } + + /** + * Parse an element-content model. + * <pre> + * [47] elements ::= (choice | seq) ('?' | '*' | '+')? + * [49] choice ::= '(' S? cp (S? '|' S? cp)+ S? ')' + * [50] seq ::= '(' S? cp (S? ',' S? cp)* S? ')' + * </pre> + * + * <p> NOTE: the opening '(' and S have already been read. + * + * @param saved Buffer for entity that should have the terminal ')' + */ + private void parseElements (char saved []) + throws Exception + { + char c; + char sep; + + // Parse the first content particle + skipWhitespace (); + parseCp (); + + // Check for end or for a separator. + skipWhitespace (); + c = readCh (); + switch (c) { + case ')': + // VC: Proper Group/PE Nesting + if (readBuffer != saved) + handler.verror ("Illegal Group/PE nesting"); + + dataBufferAppend (')'); + c = readCh (); + switch (c) { + case '*': + case '+': + case '?': + dataBufferAppend (c); + break; + default: + unread (c); + } + return; + case ',': // Register the separator. + case '|': + sep = c; + dataBufferAppend (c); + break; + default: + error ("bad separator in content model", c, null); + return; + } + + // Parse the rest of the content model. + while (true) { + skipWhitespace (); + parseCp (); + skipWhitespace (); + c = readCh (); + if (c == ')') { + // VC: Proper Group/PE Nesting + if (readBuffer != saved) + handler.verror ("Illegal Group/PE nesting"); + + dataBufferAppend (')'); + break; + } else if (c != sep) { + error ("bad separator in content model", c, null); + return; + } else { + dataBufferAppend (c); + } + } + + // Check for the occurrence indicator. + c = readCh (); + switch (c) { + case '?': + case '*': + case '+': + dataBufferAppend (c); + return; + default: + unread (c); + return; + } + } + + + /** + * Parse a content particle. + * <pre> + * [48] cp ::= (Name | choice | seq) ('?' | '*' | '+')? + * </pre> + */ + private void parseCp () + throws Exception + { + if (tryRead ('(')) { + dataBufferAppend ('('); + parseElements (readBuffer); + } else { + dataBufferAppend (readNmtoken (true)); + char c = readCh (); + switch (c) { + case '?': + case '*': + case '+': + dataBufferAppend (c); + break; + default: + unread (c); + break; + } + } + } + + + /** + * Parse mixed content. + * <pre> + * [51] Mixed ::= '(' S? ( '#PCDATA' (S? '|' S? Name)*) S? ')*' + * | '(' S? ('#PCDATA') S? ')' + * </pre> + * + * @param saved Buffer for entity that should have the terminal ')' + */ + private void parseMixed (char saved []) + throws Exception + { + // Check for PCDATA alone. + skipWhitespace (); + if (tryRead (')')) { + // VC: Proper Group/PE Nesting + if (readBuffer != saved) + handler.verror ("Illegal Group/PE nesting"); + + dataBufferAppend (")*"); + tryRead ('*'); + return; + } + + // Parse mixed content. + skipWhitespace (); + while (!tryRead (")")) { + require ('|'); + dataBufferAppend ('|'); + skipWhitespace (); + dataBufferAppend (readNmtoken (true)); + skipWhitespace (); + } + + // VC: Proper Group/PE Nesting + if (readBuffer != saved) + handler.verror ("Illegal Group/PE nesting"); + + require ('*'); + dataBufferAppend (")*"); + } + + + /** + * Parse an attribute list declaration. + * <pre> + * [52] AttlistDecl ::= '<!ATTLIST' S Name AttDef* S? '>' + * </pre> + * <p>NOTE: the '<!ATTLIST' has already been read. + */ + private void parseAttlistDecl () + throws Exception + { + String elementName; + + requireWhitespace (); + elementName = readNmtoken (true); + boolean white = tryWhitespace (); + while (!tryRead ('>')) { + if (!white) + error ("whitespace required before attribute definition"); + parseAttDef (elementName); + white = tryWhitespace (); + } + } + + + /** + * Parse a single attribute definition. + * <pre> + * [53] AttDef ::= S Name S AttType S DefaultDecl + * </pre> + */ + private void parseAttDef (String elementName) + throws Exception + { + String name; + String type; + String enumer = null; + + // Read the attribute name. + name = readNmtoken (true); + + // Read the attribute type. + requireWhitespace (); + type = readAttType (); + + // Get the string of enumerated values if necessary. + if (handler.getFeature (SAXDriver.FEATURE + "string-interning")) { + if ("ENUMERATION" == type || "NOTATION" == type) + enumer = dataBufferToString (); + } else { + if ("ENUMERATION".equals(type) || "NOTATION".equals(type)) + enumer = dataBufferToString (); + } + + // Read the default value. + requireWhitespace (); + parseDefault (elementName, name, type, enumer); + } + + + /** + * Parse the attribute type. + * <pre> + * [54] AttType ::= StringType | TokenizedType | EnumeratedType + * [55] StringType ::= 'CDATA' + * [56] TokenizedType ::= 'ID' | 'IDREF' | 'IDREFS' | 'ENTITY' + * | 'ENTITIES' | 'NMTOKEN' | 'NMTOKENS' + * [57] EnumeratedType ::= NotationType | Enumeration + * </pre> + */ + private String readAttType () + throws Exception + { + if (tryRead ('(')) { + parseEnumeration (false); + return "ENUMERATION"; + } else { + String typeString = readNmtoken (true); + if (handler.getFeature (SAXDriver.FEATURE + "string-interning")) { + if ("NOTATION" == typeString) { + parseNotationType (); + return typeString; + } else if ("CDATA" == typeString + || "ID" == typeString + || "IDREF" == typeString + || "IDREFS" == typeString + || "ENTITY" == typeString + || "ENTITIES" == typeString + || "NMTOKEN" == typeString + || "NMTOKENS" == typeString) + return typeString; + } else { + if ("NOTATION".equals(typeString)) { + parseNotationType (); + return typeString; + } else if ("CDATA".equals(typeString) + || "ID".equals(typeString) + || "IDREF".equals(typeString) + || "IDREFS".equals(typeString) + || "ENTITY".equals(typeString) + || "ENTITIES".equals(typeString) + || "NMTOKEN".equals(typeString) + || "NMTOKENS".equals(typeString)) + return typeString; + } + error ("illegal attribute type", typeString, null); + return null; + } + } + + + /** + * Parse an enumeration. + * <pre> + * [59] Enumeration ::= '(' S? Nmtoken (S? '|' S? Nmtoken)* S? ')' + * </pre> + * <p>NOTE: the '(' has already been read. + */ + private void parseEnumeration (boolean isNames) + throws Exception + { + dataBufferAppend ('('); + + // Read the first token. + skipWhitespace (); + dataBufferAppend (readNmtoken (isNames)); + // Read the remaining tokens. + skipWhitespace (); + while (!tryRead (')')) { + require ('|'); + dataBufferAppend ('|'); + skipWhitespace (); + dataBufferAppend (readNmtoken (isNames)); + skipWhitespace (); + } + dataBufferAppend (')'); + } + + + /** + * Parse a notation type for an attribute. + * <pre> + * [58] NotationType ::= 'NOTATION' S '(' S? NameNtoks + * (S? '|' S? name)* S? ')' + * </pre> + * <p>NOTE: the 'NOTATION' has already been read + */ + private void parseNotationType () + throws Exception + { + requireWhitespace (); + require ('('); + + parseEnumeration (true); + } + + + /** + * Parse the default value for an attribute. + * <pre> + * [60] DefaultDecl ::= '#REQUIRED' | '#IMPLIED' + * | (('#FIXED' S)? AttValue) + * </pre> + */ + private void parseDefault ( + String elementName, + String name, + String type, + String enumer + ) throws Exception + { + int valueType = ATTRIBUTE_DEFAULT_SPECIFIED; + String value = null; + int flags = LIT_ATTRIBUTE; + boolean saved = expandPE; + String defaultType = null; + + // LIT_ATTRIBUTE forces '<' checks now (ASAP) and turns whitespace + // chars to spaces (doesn't matter when that's done if it doesn't + // interfere with char refs expanding to whitespace). + + if (!skippedPE) { + flags |= LIT_ENTITY_REF; + if (handler.getFeature (SAXDriver.FEATURE + "string-interning")) { + if ("CDATA" != type) + flags |= LIT_NORMALIZE; + } else { + if (!"CDATA".equals(type)) + flags |= LIT_NORMALIZE; + } + } + + expandPE = false; + if (tryRead ('#')) { + if (tryRead ("FIXED")) { + defaultType = "#FIXED"; + valueType = ATTRIBUTE_DEFAULT_FIXED; + requireWhitespace (); + value = readLiteral (flags); + } else if (tryRead ("REQUIRED")) { + defaultType = "#REQUIRED"; + valueType = ATTRIBUTE_DEFAULT_REQUIRED; + } else if (tryRead ("IMPLIED")) { + defaultType = "#IMPLIED"; + valueType = ATTRIBUTE_DEFAULT_IMPLIED; + } else { + error ("illegal keyword for attribute default value"); + } + } else + value = readLiteral (flags); + expandPE = saved; + setAttribute (elementName, name, type, enumer, value, valueType); + if (handler.getFeature (SAXDriver.FEATURE + "string-interning")) { + if ("ENUMERATION" == type) + type = enumer; + else if ("NOTATION" == type) + type = "NOTATION " + enumer; + } else { + if ("ENUMERATION".equals(type)) + type = enumer; + else if ("NOTATION".equals(type)) + type = "NOTATION " + enumer; + } + if (!skippedPE) handler.getDeclHandler () + .attributeDecl (elementName, name, type, defaultType, value); + } + + + /** + * Parse a conditional section. + * <pre> + * [61] conditionalSect ::= includeSect || ignoreSect + * [62] includeSect ::= '<![' S? 'INCLUDE' S? '[' + * extSubsetDecl ']]>' + * [63] ignoreSect ::= '<![' S? 'IGNORE' S? '[' + * ignoreSectContents* ']]>' + * [64] ignoreSectContents ::= Ignore + * ('<![' ignoreSectContents* ']]>' Ignore )* + * [65] Ignore ::= Char* - (Char* ( '<![' | ']]>') Char* ) + * </pre> + * <p> NOTE: the '>![' has already been read. + */ + private void parseConditionalSect (char saved []) + throws Exception + { + skipWhitespace (); + if (tryRead ("INCLUDE")) { + skipWhitespace (); + require ('['); + // VC: Proper Conditional Section/PE Nesting + if (readBuffer != saved) + handler.verror ("Illegal Conditional Section/PE nesting"); + skipWhitespace (); + while (!tryRead ("]]>")) { + parseMarkupdecl (); + skipWhitespace (); + } + } else if (tryRead ("IGNORE")) { + skipWhitespace (); + require ('['); + // VC: Proper Conditional Section/PE Nesting + if (readBuffer != saved) + handler.verror ("Illegal Conditional Section/PE nesting"); + int nesting = 1; + char c; + expandPE = false; + for (int nest = 1; nest > 0;) { + c = readCh (); + switch (c) { + case '<': + if (tryRead ("![")) { + nest++; + } + case ']': + if (tryRead ("]>")) { + nest--; + } + } + } + expandPE = true; + } else { + error ("conditional section must begin with INCLUDE or IGNORE"); + } + } + + private void parseCharRef () + throws SAXException, IOException + { + parseCharRef (true /* do flushDataBuffer by default */); + } + + /** + * Try to read a character reference without consuming data from buffer. + * <pre> + * [66] CharRef ::= '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';' + * </pre> + * <p>NOTE: the '&#' has already been read. + */ + private void tryReadCharRef () + throws SAXException, IOException + { + int value = 0; + char c; + + if (tryRead ('x')) { +loop1: + while (true) { + c = readCh (); + int n; + switch (c) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + n = c - '0'; + break; + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + n = (c - 'a') + 10; + break; + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + n = (c - 'A') + 10; + break; + case ';': + break loop1; + default: + error ("illegal character in character reference", c, null); + break loop1; + } + value *= 16; + value += n; + } + } else { +loop2: + while (true) { + c = readCh (); + switch (c) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + value *= 10; + value += c - '0'; + break; + case ';': + break loop2; + default: + error ("illegal character in character reference", c, null); + break loop2; + } + } + } + + // check for character refs being legal XML + if ((value < 0x0020 + && ! (value == '\n' || value == '\t' || value == '\r')) + || (value >= 0xD800 && value <= 0xDFFF) + || value == 0xFFFE || value == 0xFFFF + || value > 0x0010ffff) + error ("illegal XML character reference U+" + + Integer.toHexString (value)); + + // Check for surrogates: 00000000 0000xxxx yyyyyyyy zzzzzzzz + // (1101|10xx|xxyy|yyyy + 1101|11yy|zzzz|zzzz: + if (value > 0x0010ffff) { + // too big for surrogate + error ("character reference " + value + " is too large for UTF-16", + new Integer (value).toString (), null); + } + + } + + /** + * Read and interpret a character reference. + * <pre> + * [66] CharRef ::= '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';' + * </pre> + * <p>NOTE: the '&#' has already been read. + */ + private void parseCharRef (boolean doFlush) + throws SAXException, IOException + { + int value = 0; + char c; + + if (tryRead ('x')) { +loop1: + while (true) { + c = readCh (); + int n; + switch (c) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + n = c - '0'; + break; + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + n = (c - 'a') + 10; + break; + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + n = (c - 'A') + 10; + break; + case ';': + break loop1; + default: + error ("illegal character in character reference", c, null); + break loop1; + } + value *= 16; + value += n; + } + } else { +loop2: + while (true) { + c = readCh (); + switch (c) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + value *= 10; + value += c - '0'; + break; + case ';': + break loop2; + default: + error ("illegal character in character reference", c, null); + break loop2; + } + } + } + + // check for character refs being legal XML + if ((value < 0x0020 + && ! (value == '\n' || value == '\t' || value == '\r')) + || (value >= 0xD800 && value <= 0xDFFF) + || value == 0xFFFE || value == 0xFFFF + || value > 0x0010ffff) + error ("illegal XML character reference U+" + + Integer.toHexString (value)); + + // Check for surrogates: 00000000 0000xxxx yyyyyyyy zzzzzzzz + // (1101|10xx|xxyy|yyyy + 1101|11yy|zzzz|zzzz: + if (value <= 0x0000ffff) { + // no surrogates needed + dataBufferAppend ((char) value); + } else if (value <= 0x0010ffff) { + value -= 0x10000; + // > 16 bits, surrogate needed + dataBufferAppend ((char) (0xd800 | (value >> 10))); + dataBufferAppend ((char) (0xdc00 | (value & 0x0003ff))); + } else { + // too big for surrogate + error ("character reference " + value + " is too large for UTF-16", + new Integer (value).toString (), null); + } + if (doFlush) dataBufferFlush (); + } + + + /** + * Parse and expand an entity reference. + * <pre> + * [68] EntityRef ::= '&' Name ';' + * </pre> + * <p>NOTE: the '&' has already been read. + * @param externalAllowed External entities are allowed here. + */ + private void parseEntityRef (boolean externalAllowed) + throws SAXException, IOException + { + String name; + + name = readNmtoken (true); + require (';'); + switch (getEntityType (name)) { + case ENTITY_UNDECLARED: + // NOTE: XML REC describes amazingly convoluted handling for + // this case. Nothing as meaningful as being a WFness error + // unless the processor might _legitimately_ not have seen a + // declaration ... which is what this implements. + String message; + + message = "reference to undeclared general entity " + name; + if (skippedPE && !docIsStandalone) { + handler.verror (message); + // we don't know this entity, and it might be external... + if (externalAllowed) + handler.skippedEntity (name); + } else + error (message); + break; + case ENTITY_INTERNAL: + pushString (name, getEntityValue (name)); + + //workaround for possible input pop before marking + //the buffer reading position + char t = readCh (); + unread (t); + int bufferPosMark = readBufferPos; + + int end = readBufferPos + getEntityValue (name).length(); + for(int k = readBufferPos; k < end; k++){ + t = readCh (); + if (t == '&'){ + t = readCh (); + if (t == '#'){ + //try to match a character ref + tryReadCharRef (); + + //everything has been read + if (readBufferPos >= end) + break; + k = readBufferPos; + continue; + } + else if (Character.isLetter(t)){ + //looks like an entity ref + unread (t); + readNmtoken (true); + require (';'); + + //everything has been read + if (readBufferPos >= end) + break; + k = readBufferPos; + continue; + } + error(" malformed entity reference"); + } + + } + readBufferPos = bufferPosMark; + break; + case ENTITY_TEXT: + if (externalAllowed) { + pushURL (false, name, getEntityIds (name), + null, null, null, true); + } else { + error ("reference to external entity in attribute value.", + name, null); + } + break; + case ENTITY_NDATA: + if (externalAllowed) { + error ("unparsed entity reference in content", name, null); + } else { + error ("reference to external entity in attribute value.", + name, null); + } + break; + default: + throw new RuntimeException (); + } + } + + + /** + * Parse and expand a parameter entity reference. + * <pre> + * [69] PEReference ::= '%' Name ';' + * </pre> + * <p>NOTE: the '%' has already been read. + */ + private void parsePEReference () + throws SAXException, IOException + { + String name; + + name = "%" + readNmtoken (true); + require (';'); + switch (getEntityType (name)) { + case ENTITY_UNDECLARED: + // VC: Entity Declared + handler.verror ("reference to undeclared parameter entity " + name); + + // we should disable handling of all subsequent declarations + // unless this is a standalone document (info discarded) + break; + case ENTITY_INTERNAL: + if (inLiteral) + pushString (name, getEntityValue (name)); + else + pushString (name, ' ' + getEntityValue (name) + ' '); + break; + case ENTITY_TEXT: + if (!inLiteral) + pushString (null, " "); + pushURL (true, name, getEntityIds (name), null, null, null, true); + if (!inLiteral) + pushString (null, " "); + break; + } + } + + /** + * Parse an entity declaration. + * <pre> + * [70] EntityDecl ::= GEDecl | PEDecl + * [71] GEDecl ::= '<!ENTITY' S Name S EntityDef S? '>' + * [72] PEDecl ::= '<!ENTITY' S '%' S Name S PEDef S? '>' + * [73] EntityDef ::= EntityValue | (ExternalID NDataDecl?) + * [74] PEDef ::= EntityValue | ExternalID + * [75] ExternalID ::= 'SYSTEM' S SystemLiteral + * | 'PUBLIC' S PubidLiteral S SystemLiteral + * [76] NDataDecl ::= S 'NDATA' S Name + * </pre> + * <p>NOTE: the '<!ENTITY' has already been read. + */ + private void parseEntityDecl () + throws Exception + { + boolean peFlag = false; + int flags = 0; + + // Check for a parameter entity. + expandPE = false; + requireWhitespace (); + if (tryRead ('%')) { + peFlag = true; + requireWhitespace (); + } + expandPE = true; + + // Read the entity name, and prepend + // '%' if necessary. + String name = readNmtoken (true); + //NE08 + if (name.indexOf(':') >= 0) + error ("Illegal character(':') in entity name ", name, null); + if (peFlag) { + name = "%" + name; + } + + // Read the entity value. + requireWhitespace (); + char c = readCh (); + unread (c); + if (c == '"' || c == '\'') { + // Internal entity ... replacement text has expanded refs + // to characters and PEs, but not to general entities + String value = readLiteral (flags); + setInternalEntity (name, value); + } else { + // Read the external IDs + String ids [] = readExternalIds (false, false); + + // Check for NDATA declaration. + boolean white = tryWhitespace (); + if (!peFlag && tryRead ("NDATA")) { + if (!white) + error ("whitespace required before NDATA"); + requireWhitespace (); + String notationName = readNmtoken (true); + if (!skippedPE) { + setExternalEntity (name, ENTITY_NDATA, ids, notationName); + handler.unparsedEntityDecl (name, ids, notationName); + } + } else if (!skippedPE) { + setExternalEntity (name, ENTITY_TEXT, ids, null); + handler.getDeclHandler () + .externalEntityDecl (name, ids [0], + handler.resolveURIs () + // FIXME: ASSUMES not skipped + // "false" forces error on bad URI + ? handler.absolutize (ids [2], ids [1], false) + : ids [1]); + } + } + + // Finish the declaration. + skipWhitespace (); + require ('>'); + } + + + /** + * Parse a notation declaration. + * <pre> + * [82] NotationDecl ::= '<!NOTATION' S Name S + * (ExternalID | PublicID) S? '>' + * [83] PublicID ::= 'PUBLIC' S PubidLiteral + * </pre> + * <P>NOTE: the '<!NOTATION' has already been read. + */ + private void parseNotationDecl () + throws Exception + { + String nname, ids[]; + + + requireWhitespace (); + nname = readNmtoken (true); + //NE08 + if (nname.indexOf(':') >= 0) + error ("Illegal character(':') in notation name ", nname, null); + requireWhitespace (); + + // Read the external identifiers. + ids = readExternalIds (true, false); + + // Register the notation. + setNotation (nname, ids); + + skipWhitespace (); + require ('>'); + } + + + /** + * Parse character data. + * <pre> + * [14] CharData ::= [^<&]* - ([^<&]* ']]>' [^<&]*) + * </pre> + */ + private void parseCharData () + throws Exception + { + char c; + int state = 0; + boolean pureWhite = false; + + // assert (dataBufferPos == 0); + + // are we expecting pure whitespace? it might be dirty... + if ((currentElementContent == CONTENT_ELEMENTS) && !isDirtyCurrentElement) + pureWhite = true; + + // always report right out of readBuffer + // to minimize (pointless) buffer copies + while (true) { + int lineAugment = 0; + int columnAugment = 0; + int i; + +loop: + for (i = readBufferPos; i < readBufferLength; i++) { + switch (c = readBuffer [i]) { + case '\n': + lineAugment++; + columnAugment = 0; + // pureWhite unmodified + break; + case '\r': // should not happen!! + case '\t': + case ' ': + // pureWhite unmodified + columnAugment++; + break; + case '&': + case '<': + columnAugment++; + // pureWhite unmodified + // CLEAN end of text sequence + state = 1; + break loop; + case ']': + // that's not a whitespace char, and + // can not terminate pure whitespace either + pureWhite = false; + if ((i + 2) < readBufferLength) { + if (readBuffer [i + 1] == ']' + && readBuffer [i + 2] == '>') { + // ERROR end of text sequence + state = 2; + break loop; + } + } else { + // FIXME missing two end-of-buffer cases + } + columnAugment++; + break; + default: + if ((c < 0x0020 || c > 0xFFFD) + || ((c >= 0x007f) && (c <= 0x009f) && (c != 0x0085) + && xmlVersion == XML_11)) + error ("illegal XML character U+" + + Integer.toHexString (c)); + // that's not a whitespace char + pureWhite = false; + columnAugment++; + } + } + + // report text thus far + if (lineAugment > 0) { + line += lineAugment; + column = columnAugment; + } else { + column += columnAugment; + } + + // report characters/whitspace + int length = i - readBufferPos; + + if (length != 0) { + if (pureWhite) + handler.ignorableWhitespace (readBuffer, + readBufferPos, length); + else + handler.charData (readBuffer, readBufferPos, length); + readBufferPos = i; + } + + if (state != 0) + break; + + // fill next buffer from this entity, or + // pop stack and continue with previous entity + unread (readCh ()); + } + if (!pureWhite) + isDirtyCurrentElement = true; + // finish, maybe with error + if (state != 1) // finish, no error + error ("character data may not contain ']]>'"); + } + + + ////////////////////////////////////////////////////////////////////// + // High-level reading and scanning methods. + ////////////////////////////////////////////////////////////////////// + + /** + * Require whitespace characters. + */ + private void requireWhitespace () + throws SAXException, IOException + { + char c = readCh (); + if (isWhitespace (c)) { + skipWhitespace (); + } else { + error ("whitespace required", c, null); + } + } + + + /** + * Skip whitespace characters. + * <pre> + * [3] S ::= (#x20 | #x9 | #xd | #xa)+ + * </pre> + */ + private void skipWhitespace () + throws SAXException, IOException + { + // Start with a little cheat. Most of + // the time, the white space will fall + // within the current read buffer; if + // not, then fall through. + if (USE_CHEATS) { + int lineAugment = 0; + int columnAugment = 0; + +loop: + for (int i = readBufferPos; i < readBufferLength; i++) { + switch (readBuffer [i]) { + case ' ': + case '\t': + case '\r': + columnAugment++; + break; + case '\n': + lineAugment++; + columnAugment = 0; + break; + case '%': + if (expandPE) + break loop; + // else fall through... + default: + readBufferPos = i; + if (lineAugment > 0) { + line += lineAugment; + column = columnAugment; + } else { + column += columnAugment; + } + return; + } + } + } + + // OK, do it the slow way. + char c = readCh (); + while (isWhitespace (c)) { + c = readCh (); + } + unread (c); + } + + + /** + * Read a name or (when parsing an enumeration) name token. + * <pre> + * [5] Name ::= (Letter | '_' | ':') (NameChar)* + * [7] Nmtoken ::= (NameChar)+ + * </pre> + */ + private String readNmtoken (boolean isName) + throws SAXException, IOException + { + char c; + + if (USE_CHEATS) { +loop: + for (int i = readBufferPos; i < readBufferLength; i++) { + c = readBuffer [i]; + switch (c) { + case '%': + if (expandPE) + break loop; + // else fall through... + + // What may legitimately come AFTER a name/nmtoken? + case '<': case '>': case '&': + case ',': case '|': case '*': case '+': case '?': + case ')': + case '=': + case '\'': case '"': + case '[': + case ' ': case '\t': case '\r': case '\n': + case ';': + case '/': + int start = readBufferPos; + if (i == start) + error ("name expected", readBuffer [i], null); + readBufferPos = i; + return intern (readBuffer, start, i - start); + + default: +// FIXME ... per IBM's OASIS test submission, these: +// ? U+06dd +// Combining U+309B + //these switches are kind of ugly but at least we won't + //have to go over the whole lits for each char + if (isName && i == readBufferPos){ + char c2 = (char) (c & 0x00f0); + switch (c & 0xff00){ + //starting with 01 + case 0x0100: + switch (c2){ + case 0x0030: + if (c == 0x0132 || c == 0x0133 || c == 0x013f) + error ("Not a name start character, U+" + + Integer.toHexString (c)); + break; + case 0x0040: + if (c == 0x0140 || c == 0x0149) + error ("Not a name start character, U+" + + Integer.toHexString (c)); + break; + case 0x00c0: + if (c == 0x01c4 || c == 0x01cc) + error ("Not a name start character, U+" + + Integer.toHexString (c)); + break; + case 0x00f0: + if (c == 0x01f1 || c == 0x01f3) + error ("Not a name start character, U+" + + Integer.toHexString (c)); + break; + case 0x00b0: + if (c == 0x01f1 || c == 0x01f3) + error ("Not a name start character, U+" + + Integer.toHexString (c)); + break; + default: + if (c == 0x017f) + error ("Not a name start character, U+" + + Integer.toHexString (c)); + } + + break; + //starting with 11 + case 0x1100: + switch (c2){ + case 0x0000: + if (c == 0x1104 || c == 0x1108 || + c == 0x110a || c == 0x110d) + error ("Not a name start character, U+" + + Integer.toHexString (c)); + break; + case 0x0030: + if (c == 0x113b || c == 0x113f) + error ("Not a name start character, U+" + + Integer.toHexString (c)); + break; + case 0x0040: + if (c == 0x1141 || c == 0x114d + || c == 0x114f ) + error ("Not a name start character, U+" + + Integer.toHexString (c)); + break; + case 0x0050: + if (c == 0x1151 || c == 0x1156) + error ("Not a name start character, U+" + + Integer.toHexString (c)); + break; + case 0x0060: + if (c == 0x1162 || c == 0x1164 + || c == 0x1166 || c == 0x116b + || c == 0x116f) + error ("Not a name start character, U+" + + Integer.toHexString (c)); + break; + case 0x00b0: + if (c == 0x11b6 || c == 0x11b9 + || c == 0x11bb || c == 0x116f) + error ("Not a name start character, U+" + + Integer.toHexString (c)); + break; + default: + if (c == 0x1174 || c == 0x119f + || c == 0x11ac || c == 0x11c3 + || c == 0x11f1) + error ("Not a name start character, U+" + + Integer.toHexString (c)); + } + break; + default: + if (c == 0x0e46 || c == 0x1011 + || c == 0x212f || c == 0x0587 + || c == 0x0230 ) + error ("Not a name start character, U+" + + Integer.toHexString (c)); + } + } + // punt on exact tests from Appendix A; approximate + // them using the Unicode ID start/part rules + if (i == readBufferPos && isName) { + if (!Character.isUnicodeIdentifierStart (c) + && c != ':' && c != '_') + error ("Not a name start character, U+" + + Integer.toHexString (c)); + } else if (!Character.isUnicodeIdentifierPart (c) + && c != '-' && c != ':' && c != '_' && c != '.' + && !isExtender (c)) + error ("Not a name character, U+" + + Integer.toHexString (c)); + } + } + } + + nameBufferPos = 0; + + // Read the first character. +loop: + while (true) { + c = readCh (); + switch (c) { + case '%': + case '<': case '>': case '&': + case ',': case '|': case '*': case '+': case '?': + case ')': + case '=': + case '\'': case '"': + case '[': + case ' ': case '\t': case '\n': case '\r': + case ';': + case '/': + unread (c); + if (nameBufferPos == 0) { + error ("name expected"); + } + // punt on exact tests from Appendix A, but approximate them + if (isName + && !Character.isUnicodeIdentifierStart ( + nameBuffer [0]) + && ":_".indexOf (nameBuffer [0]) == -1) + error ("Not a name start character, U+" + + Integer.toHexString (nameBuffer [0])); + String s = intern (nameBuffer, 0, nameBufferPos); + nameBufferPos = 0; + return s; + default: + // punt on exact tests from Appendix A, but approximate them + + if ((nameBufferPos != 0 || !isName) + && !Character.isUnicodeIdentifierPart (c) + && ":-_.".indexOf (c) == -1 + && !isExtender (c)) + error ("Not a name character, U+" + + Integer.toHexString (c)); + if (nameBufferPos >= nameBuffer.length) + nameBuffer = + (char[]) extendArray (nameBuffer, + nameBuffer.length, nameBufferPos); + nameBuffer [nameBufferPos++] = c; + } + } + } + + private static boolean isExtender (char c) + { + // [88] Extender ::= ... + return c == 0x00b7 || c == 0x02d0 || c == 0x02d1 || c == 0x0387 + || c == 0x0640 || c == 0x0e46 || c == 0x0ec6 || c == 0x3005 + || (c >= 0x3031 && c <= 0x3035) + || (c >= 0x309d && c <= 0x309e) + || (c >= 0x30fc && c <= 0x30fe); + } + + + /** + * Read a literal. With matching single or double quotes as + * delimiters (and not embedded!) this is used to parse: + * <pre> + * [9] EntityValue ::= ... ([^%&] | PEReference | Reference)* ... + * [10] AttValue ::= ... ([^<&] | Reference)* ... + * [11] SystemLiteral ::= ... (URLchar - "'")* ... + * [12] PubidLiteral ::= ... (PubidChar - "'")* ... + * </pre> + * as well as the quoted strings in XML and text declarations + * (for version, encoding, and standalone) which have their + * own constraints. + */ + private String readLiteral (int flags) + throws SAXException, IOException + { + char delim, c; + int startLine = line; + boolean saved = expandPE; + boolean savedReport = doReport; + + // Find the first delimiter. + delim = readCh (); + if (delim != '"' && delim != '\'') { + error ("expected '\"' or \"'\"", delim, null); + return null; + } + inLiteral = true; + if ((flags & LIT_DISABLE_PE) != 0) + expandPE = false; + doReport = false; + + // Each level of input source has its own buffer; remember + // ours, so we won't read the ending delimiter from any + // other input source, regardless of entity processing. + char ourBuf [] = readBuffer; + + // Read the literal. + try { + c = readCh (); + boolean ampRead = false; +loop: + while (! (c == delim && readBuffer == ourBuf)) { + switch (c) { + // attributes and public ids are normalized + // in almost the same ways + case '\n': + case '\r': + if ((flags & (LIT_ATTRIBUTE | LIT_PUBID)) != 0) + c = ' '; + break; + case '\t': + if ((flags & LIT_ATTRIBUTE) != 0) + c = ' '; + break; + case '&': + c = readCh (); + // Char refs are expanded immediately, except for + // all the cases where it's deferred. + if (c == '#') { + if ((flags & LIT_DISABLE_CREF) != 0) { + dataBufferAppend ('&'); + break; + } + parseCharRef (false /* Do not do flushDataBuffer */); + + // exotic WFness risk: this is an entity literal, + // dataBuffer [dataBufferPos - 1] == '&', and + // following chars are a _partial_ entity/char ref + + // It looks like an entity ref ... + } else { + unread (c); + // Expand it? + if ((flags & LIT_ENTITY_REF) > 0) { + parseEntityRef (false); + if (String.valueOf (readBuffer).equals("&")) + ampRead = true; + //Is it just data? + } else if ((flags & LIT_DISABLE_EREF) != 0) { + dataBufferAppend ('&'); + + // OK, it will be an entity ref -- expanded later. + } else { + String name = readNmtoken (true); + require (';'); + dataBufferAppend ('&'); + dataBufferAppend (name); + dataBufferAppend (';'); + } + } + c = readCh (); + continue loop; + + case '<': + // and why? Perhaps so "&foo;" expands the same + // inside and outside an attribute? + if ((flags & LIT_ATTRIBUTE) != 0) + error ("attribute values may not contain '<'"); + break; + + // We don't worry about case '%' and PE refs, readCh does. + + default: + break; + } + dataBufferAppend (c); + c = readCh (); + } + } catch (EOFException e) { + error ("end of input while looking for delimiter (started on line " + + startLine + ')', null, new Character (delim).toString ()); + } + inLiteral = false; + expandPE = saved; + doReport = savedReport; + + // Normalise whitespace if necessary. + if ((flags & LIT_NORMALIZE) > 0) { + dataBufferNormalize (); + } + + // Return the value. + return dataBufferToString (); + } + + + /** + * Try reading external identifiers. + * A system identifier is not required for notations. + * @param inNotation Are we parsing a notation decl? + * @param isSubset Parsing external subset decl (may be omitted)? + * @return A three-member String array containing the identifiers, + * or nulls. Order: public, system, baseURI. + */ + private String[] readExternalIds (boolean inNotation, boolean isSubset) + throws Exception + { + char c; + String ids[] = new String [3]; + int flags = LIT_DISABLE_CREF | LIT_DISABLE_PE | LIT_DISABLE_EREF; + + if (tryRead ("PUBLIC")) { + requireWhitespace (); + ids [0] = readLiteral (LIT_NORMALIZE | LIT_PUBID | flags); + if (inNotation) { + skipWhitespace (); + c = readCh (); + unread (c); + if (c == '"' || c == '\'') { + ids [1] = readLiteral (flags); + } + } else { + requireWhitespace (); + ids [1] = readLiteral (flags); + } + + for (int i = 0; i < ids [0].length (); i++) { + c = ids [0].charAt (i); + if (c >= 'a' && c <= 'z') + continue; + if (c >= 'A' && c <= 'Z') + continue; + if (" \r\n0123456789-' ()+,./:=?;!*#@$_%".indexOf (c) != -1) + continue; + error ("illegal PUBLIC id character U+" + + Integer.toHexString (c)); + } + } else if (tryRead ("SYSTEM")) { + requireWhitespace (); + ids [1] = readLiteral (flags); + } else if (!isSubset) + error ("missing SYSTEM or PUBLIC keyword"); + + if (ids [1] != null) { + if (ids [1].indexOf ('#') != -1) + handler.verror ("SYSTEM id has a URI fragment: " + ids [1]); + ids [2] = handler.getSystemId (); + if (ids [2] == null) + handler.warn ("No base URI; hope URI is absolute: " + + ids [1]); + } + + return ids; + } + + + /** + * Test if a character is whitespace. + * <pre> + * [3] S ::= (#x20 | #x9 | #xd | #xa)+ + * </pre> + * @param c The character to test. + * @return true if the character is whitespace. + */ + private final boolean isWhitespace (char c) + { + if (c > 0x20) + return false; + if (c == 0x20 || c == 0x0a || c == 0x09 || c == 0x0d) + return true; + return false; // illegal ... + } + + + ////////////////////////////////////////////////////////////////////// + // Utility routines. + ////////////////////////////////////////////////////////////////////// + + + /** + * Add a character to the data buffer. + */ + private void dataBufferAppend (char c) + { + // Expand buffer if necessary. + if (dataBufferPos >= dataBuffer.length) + dataBuffer = + (char[]) extendArray (dataBuffer, + dataBuffer.length, dataBufferPos); + dataBuffer [dataBufferPos++] = c; + } + + + /** + * Add a string to the data buffer. + */ + private void dataBufferAppend (String s) + { + dataBufferAppend (s.toCharArray (), 0, s.length ()); + } + + + /** + * Append (part of) a character array to the data buffer. + */ + private void dataBufferAppend (char ch[], int start, int length) + { + dataBuffer = (char[]) + extendArray (dataBuffer, dataBuffer.length, + dataBufferPos + length); + + System.arraycopy (ch, start, dataBuffer, dataBufferPos, length); + dataBufferPos += length; + } + + + /** + * Normalise space characters in the data buffer. + */ + private void dataBufferNormalize () + { + int i = 0; + int j = 0; + int end = dataBufferPos; + + // Skip spaces at the start. + while (j < end && dataBuffer [j] == ' ') { + j++; + } + + // Skip whitespace at the end. + while (end > j && dataBuffer [end - 1] == ' ') { + end --; + } + + // Start copying to the left. + while (j < end) { + + char c = dataBuffer [j++]; + + // Normalise all other spaces to + // a single space. + if (c == ' ') { + while (j < end && dataBuffer [j++] == ' ') + continue; + dataBuffer [i++] = ' '; + dataBuffer [i++] = dataBuffer [j - 1]; + } else { + dataBuffer [i++] = c; + } + } + + // The new length is <= the old one. + dataBufferPos = i; + } + + + /** + * Convert the data buffer to a string. + */ + private String dataBufferToString () + { + String s = new String (dataBuffer, 0, dataBufferPos); + dataBufferPos = 0; + return s; + } + + + /** + * Flush the contents of the data buffer to the handler, as + * appropriate, and reset the buffer for new input. + */ + private void dataBufferFlush () + throws SAXException + { + if (currentElementContent == CONTENT_ELEMENTS + && dataBufferPos > 0 + && !inCDATA + ) { + // We can't just trust the buffer to be whitespace, there + // are (error) cases when it isn't + for (int i = 0; i < dataBufferPos; i++) { + if (!isWhitespace (dataBuffer [i])) { + handler.charData (dataBuffer, 0, dataBufferPos); + dataBufferPos = 0; + } + } + if (dataBufferPos > 0) { + handler.ignorableWhitespace (dataBuffer, 0, dataBufferPos); + dataBufferPos = 0; + } + } else if (dataBufferPos > 0) { + handler.charData (dataBuffer, 0, dataBufferPos); + dataBufferPos = 0; + } + } + + + /** + * Require a string to appear, or throw an exception. + * <p><em>Precondition:</em> Entity expansion is not required. + * <p><em>Precondition:</em> data buffer has no characters that + * will get sent to the application. + */ + private void require (String delim) + throws SAXException, IOException + { + int length = delim.length (); + char ch []; + + if (length < dataBuffer.length) { + ch = dataBuffer; + delim.getChars (0, length, ch, 0); + } else + ch = delim.toCharArray (); + + if (USE_CHEATS + && length <= (readBufferLength - readBufferPos)) { + int offset = readBufferPos; + + for (int i = 0; i < length; i++, offset++) + if (ch [i] != readBuffer [offset]) + error ("required string", null, delim); + readBufferPos = offset; + + } else { + for (int i = 0; i < length; i++) + require (ch [i]); + } + } + + + /** + * Require a character to appear, or throw an exception. + */ + private void require (char delim) + throws SAXException, IOException + { + char c = readCh (); + + if (c != delim) { + error ("required character", c, new Character (delim).toString ()); + } + } + + + /** + * Create an interned string from a character array. + * Ælfred uses this method to create an interned version + * of all names and name tokens, so that it can test equality + * with <code>==</code> instead of <code>String.equals ()</code>. + * + * <p>This is much more efficient than constructing a non-interned + * string first, and then interning it. + * + * @param ch an array of characters for building the string. + * @param start the starting position in the array. + * @param length the number of characters to place in the string. + * @return an interned string. + * @see #intern (String) + * @see java.lang.String#intern + */ + public String intern (char ch[], int start, int length) + { + int index = 0; + int hash = 0; + Object bucket []; + + // Generate a hash code. This is a widely used string hash, + // often attributed to Brian Kernighan. + for (int i = start; i < start + length; i++) + hash = 31 * hash + ch [i]; + hash = (hash & 0x7fffffff) % SYMBOL_TABLE_LENGTH; + + // Get the bucket -- consists of {array,String} pairs + if ((bucket = symbolTable [hash]) == null) { + // first string in this bucket + bucket = new Object [8]; + + // Search for a matching tuple, and + // return the string if we find one. + } else { + while (index < bucket.length) { + char chFound [] = (char []) bucket [index]; + + // Stop when we hit an empty entry. + if (chFound == null) + break; + + // If they're the same length, check for a match. + if (chFound.length == length) { + for (int i = 0; i < chFound.length; i++) { + // continue search on failure + if (ch [start + i] != chFound [i]) { + break; + } else if (i == length - 1) { + // That's it, we have a match! + return (String) bucket [index + 1]; + } + } + } + index += 2; + } + // Not found -- we'll have to add it. + + // Do we have to grow the bucket? + bucket = (Object []) extendArray (bucket, bucket.length, index); + } + symbolTable [hash] = bucket; + + // OK, add it to the end of the bucket -- "local" interning. + // Intern "globally" to let applications share interning benefits. + // That is, "!=" and "==" work on our strings, not just equals(). + String s = new String (ch, start, length).intern (); + bucket [index] = s.toCharArray (); + bucket [index + 1] = s; + return s; + } + + /** + * Ensure the capacity of an array, allocating a new one if + * necessary. Usually extends only for name hash collisions. + */ + private Object extendArray (Object array, int currentSize, int requiredSize) + { + if (requiredSize < currentSize) { + return array; + } else { + Object newArray = null; + int newSize = currentSize * 2; + + if (newSize <= requiredSize) + newSize = requiredSize + 1; + + if (array instanceof char[]) + newArray = new char [newSize]; + else if (array instanceof Object[]) + newArray = new Object [newSize]; + else + throw new RuntimeException (); + + System.arraycopy (array, 0, newArray, 0, currentSize); + return newArray; + } + } + + + ////////////////////////////////////////////////////////////////////// + // XML query routines. + ////////////////////////////////////////////////////////////////////// + + + boolean isStandalone () { return docIsStandalone; } + + + // + // Elements + // + + private int getContentType (Object element [], int defaultType) + { + int retval; + + if (element == null) + return defaultType; + retval = ((Integer) element [0]).intValue (); + if (retval == CONTENT_UNDECLARED) + retval = defaultType; + return retval; + } + + + /** + * Look up the content type of an element. + * @param name The element type name. + * @return An integer constant representing the content type. + * @see #CONTENT_UNDECLARED + * @see #CONTENT_ANY + * @see #CONTENT_EMPTY + * @see #CONTENT_MIXED + * @see #CONTENT_ELEMENTS + */ + public int getElementContentType (String name) + { + Object element [] = (Object []) elementInfo.get (name); + return getContentType (element, CONTENT_UNDECLARED); + } + + + /** + * Register an element. + * Array format: + * [0] element type name + * [1] content model (mixed, elements only) + * [2] attribute hash table + */ + private void setElement ( + String name, + int contentType, + String contentModel, + Hashtable attributes + ) throws SAXException + { + if (skippedPE) + return; + + Object element [] = (Object []) elementInfo.get (name); + + // first <!ELEMENT ...> or <!ATTLIST ...> for this type? + if (element == null) { + element = new Object [3]; + element [0] = new Integer (contentType); + element [1] = contentModel; + element [2] = attributes; + elementInfo.put (name, element); + return; + } + + // <!ELEMENT ...> declaration? + if (contentType != CONTENT_UNDECLARED) { + // ... following an associated <!ATTLIST ...> + if (((Integer) element [0]).intValue () == CONTENT_UNDECLARED) { + element [0] = new Integer (contentType); + element [1] = contentModel; + } else + // VC: Unique Element Type Declaration + handler.verror ("multiple declarations for element type: " + + name); + } + + // first <!ATTLIST ...>, before <!ELEMENT ...> ? + else if (attributes != null) + element [2] = attributes; + } + + + /** + * Look up the attribute hash table for an element. + * The hash table is the second item in the element array. + */ + private Hashtable getElementAttributes (String name) + { + Object element[] = (Object[]) elementInfo.get (name); + if (element == null) + return null; + else + return (Hashtable) element [2]; + } + + + + // + // Attributes + // + + /** + * Get the declared attributes for an element type. + * @param elname The name of the element type. + * @return An Enumeration of all the attributes declared for + * a specific element type. The results will be valid only + * after the DTD (if any) has been parsed. + * @see #getAttributeType + * @see #getAttributeEnumeration + * @see #getAttributeDefaultValueType + * @see #getAttributeDefaultValue + * @see #getAttributeExpandedValue + */ + private Enumeration declaredAttributes (Object element []) + { + Hashtable attlist; + + if (element == null) + return null; + if ((attlist = (Hashtable) element [2]) == null) + return null; + return attlist.keys (); + } + + /** + * Get the declared attributes for an element type. + * @param elname The name of the element type. + * @return An Enumeration of all the attributes declared for + * a specific element type. The results will be valid only + * after the DTD (if any) has been parsed. + * @see #getAttributeType + * @see #getAttributeEnumeration + * @see #getAttributeDefaultValueType + * @see #getAttributeDefaultValue + * @see #getAttributeExpandedValue + */ + public Enumeration declaredAttributes (String elname) + { + return declaredAttributes ((Object []) elementInfo.get (elname)); + } + + + /** + * Retrieve the declared type of an attribute. + * @param name The name of the associated element. + * @param aname The name of the attribute. + * @return An interend string denoting the type, or null + * indicating an undeclared attribute. + */ + public String getAttributeType (String name, String aname) + { + Object attribute[] = getAttribute (name, aname); + if (attribute == null) { + return null; + } else { + return (String) attribute [0]; + } + } + + + /** + * Retrieve the allowed values for an enumerated attribute type. + * @param name The name of the associated element. + * @param aname The name of the attribute. + * @return A string containing the token list. + */ + public String getAttributeEnumeration (String name, String aname) + { + Object attribute[] = getAttribute (name, aname); + if (attribute == null) { + return null; + } else { + // assert: attribute [0] is "ENUMERATION" or "NOTATION" + return (String) attribute [3]; + } + } + + + /** + * Retrieve the default value of a declared attribute. + * @param name The name of the associated element. + * @param aname The name of the attribute. + * @return The default value, or null if the attribute was + * #IMPLIED or simply undeclared and unspecified. + * @see #getAttributeExpandedValue + */ + public String getAttributeDefaultValue (String name, String aname) + { + Object attribute[] = getAttribute (name, aname); + if (attribute == null) { + return null; + } else { + return (String) attribute [1]; + } + } + + /* + +// FIXME: Leaving this in, until W3C finally resolves the confusion +// between parts of the XML 2nd REC about when entity declararations +// are guaranteed to be known. Current code matches what section 5.1 +// (conformance) describes, but some readings of the self-contradicting +// text in 4.1 (the "Entity Declared" WFC and VC) seem to expect that +// attribute expansion/normalization must be deferred in some cases +// (just TRY to identify them!). + + * Retrieve the expanded value of a declared attribute. + * <p>General entities (and char refs) will be expanded (once). + * @param name The name of the associated element. + * @param aname The name of the attribute. + * @return The expanded default value, or null if the attribute was + * #IMPLIED or simply undeclared + * @see #getAttributeDefaultValue + public String getAttributeExpandedValue (String name, String aname) + throws Exception + { + Object attribute[] = getAttribute (name, aname); + + if (attribute == null) { + return null; + } else if (attribute [4] == null && attribute [1] != null) { + // we MUST use the same buf for both quotes else the literal + // can't be properly terminated + char buf [] = new char [1]; + int flags = LIT_ENTITY_REF | LIT_ATTRIBUTE; + String type = getAttributeType (name, aname); + + if (type != "CDATA" && type != null) + flags |= LIT_NORMALIZE; + buf [0] = '"'; + pushCharArray (null, buf, 0, 1); + pushString (null, (String) attribute [1]); + pushCharArray (null, buf, 0, 1); + attribute [4] = readLiteral (flags); + } + return (String) attribute [4]; + } + */ + + /** + * Retrieve the default value mode of a declared attribute. + * @see #ATTRIBUTE_DEFAULT_SPECIFIED + * @see #ATTRIBUTE_DEFAULT_IMPLIED + * @see #ATTRIBUTE_DEFAULT_REQUIRED + * @see #ATTRIBUTE_DEFAULT_FIXED + */ + public int getAttributeDefaultValueType (String name, String aname) + { + Object attribute[] = getAttribute (name, aname); + if (attribute == null) { + return ATTRIBUTE_DEFAULT_UNDECLARED; + } else { + return ((Integer) attribute [2]).intValue (); + } + } + + + /** + * Register an attribute declaration for later retrieval. + * Format: + * - String type + * - String default value + * - int value type + * - enumeration + * - processed default value + */ + private void setAttribute (String elName, String name, String type, + String enumeration, + String value, int valueType) + throws Exception + { + Hashtable attlist; + + if (skippedPE) + return; + + // Create a new hashtable if necessary. + attlist = getElementAttributes (elName); + if (attlist == null) + attlist = new Hashtable (); + + // ignore multiple attribute declarations! + if (attlist.get (name) != null) { + // warn ... + return; + } else { + Object attribute [] = new Object [5]; + attribute [0] = type; + attribute [1] = value; + attribute [2] = new Integer (valueType); + attribute [3] = enumeration; + attribute [4] = null; + attlist.put (name, attribute); + + // save; but don't overwrite any existing <!ELEMENT ...> + setElement (elName, CONTENT_UNDECLARED, null, attlist); + } + } + + + /** + * Retrieve the array representing an attribute declaration. + */ + private Object[] getAttribute (String elName, String name) + { + Hashtable attlist; + + attlist = getElementAttributes (elName); + if (attlist == null) + return null; + return (Object[]) attlist.get (name); + } + + + // + // Entities + // + + /** + * Find the type of an entity. + * @returns An integer constant representing the entity type. + * @see #ENTITY_UNDECLARED + * @see #ENTITY_INTERNAL + * @see #ENTITY_NDATA + * @see #ENTITY_TEXT + */ + public int getEntityType (String ename) + { + Object entity[] = (Object[]) entityInfo.get (ename); + if (entity == null) { + return ENTITY_UNDECLARED; + } else { + return ((Integer) entity [0]).intValue (); + } + } + + + /** + * Return an external entity's identifier array. + * @param ename The name of the external entity. + * @return Three element array containing (in order) the entity's + * public identifier, system identifier, and base URI. Null if + * the entity was not declared as an external entity. + * @see #getEntityType + */ + public String [] getEntityIds (String ename) + { + Object entity[] = (Object[]) entityInfo.get (ename); + if (entity == null) { + return null; + } else { + return (String []) entity [1]; + } + } + + + /** + * Return an internal entity's replacement text. + * @param ename The name of the internal entity. + * @return The entity's replacement text, or null if + * the entity was not declared as an internal entity. + * @see #getEntityType + */ + public String getEntityValue (String ename) + { + Object entity[] = (Object[]) entityInfo.get (ename); + if (entity == null) { + return null; + } else { + return (String) entity [3]; + } + } + + + /** + * Register an entity declaration for later retrieval. + */ + private void setInternalEntity (String eName, String value) + throws SAXException + { + if (skippedPE) + return; + + if (entityInfo.get (eName) == null) { + Object entity[] = new Object [5]; + entity [0] = new Integer (ENTITY_INTERNAL); +// FIXME: shrink!! [2] useless + entity [3] = value; + entityInfo.put (eName, entity); + } + if (handler.getFeature (SAXDriver.FEATURE + "string-interning")) { + if ("lt" == eName || "gt" == eName || "quot" == eName + || "apos" == eName || "amp" == eName) + return; + } else { + if ("lt".equals(eName) || "gt".equals(eName) || "quot".equals(eName) + || "apos".equals(eName) || "amp".equals(eName)) + return; + } + handler.getDeclHandler () + .internalEntityDecl (eName, value); + } + + + /** + * Register an external entity declaration for later retrieval. + */ + private void setExternalEntity (String eName, int eClass, + String ids [], String nName) + { + if (entityInfo.get (eName) == null) { + Object entity[] = new Object [5]; + entity [0] = new Integer (eClass); + entity [1] = ids; +// FIXME: shrink!! [2] no longer used, [4] irrelevant given [0] + entity [4] = nName; + entityInfo.put (eName, entity); + } + } + + + // + // Notations. + // + + /** + * Report a notation declaration, checking for duplicates. + */ + private void setNotation (String nname, String ids []) + throws SAXException + { + if (skippedPE) + return; + + handler.notationDecl (nname, ids); + if (notationInfo.get (nname) == null) + notationInfo.put (nname, nname); + else + // VC: Unique Notation Name + handler.verror ("Duplicate notation name decl: " + nname); + } + + + // + // Location. + // + + + /** + * Return the current line number. + */ + public int getLineNumber () + { + return line; + } + + + /** + * Return the current column number. + */ + public int getColumnNumber () + { + return column; + } + + + ////////////////////////////////////////////////////////////////////// + // High-level I/O. + ////////////////////////////////////////////////////////////////////// + + + /** + * Read a single character from the readBuffer. + * <p>The readDataChunk () method maintains the buffer. + * <p>If we hit the end of an entity, try to pop the stack and + * keep going. + * <p> (This approach doesn't really enforce XML's rules about + * entity boundaries, but this is not currently a validating + * parser). + * <p>This routine also attempts to keep track of the current + * position in external entities, but it's not entirely accurate. + * @return The next available input character. + * @see #unread (char) + * @see #readDataChunk + * @see #readBuffer + * @see #line + * @return The next character from the current input source. + */ + private char readCh () + throws SAXException, IOException + { + // As long as there's nothing in the + // read buffer, try reading more data + // (for an external entity) or popping + // the entity stack (for either). + while (readBufferPos >= readBufferLength) { + switch (sourceType) { + case INPUT_READER: + case INPUT_STREAM: + readDataChunk (); + while (readBufferLength < 1) { + popInput (); + if (readBufferLength < 1) { + readDataChunk (); + } + } + break; + + default: + + popInput (); + break; + } + } + + char c = readBuffer [readBufferPos++]; + + if (c == '\n') { + line++; + column = 0; + } else { + if (c == '<') { + /* the most common return to parseContent () ... NOP */ + } else if (((c < 0x0020 && (c != '\t') && (c != '\r')) || c > 0xFFFD) + || ((c >= 0x007f) && (c <= 0x009f) && (c != 0x0085) + && xmlVersion == XML_11)) + error ("illegal XML character U+" + + Integer.toHexString (c)); + + // If we're in the DTD and in a context where PEs get expanded, + // do so ... 1/14/2000 errata identify those contexts. There + // are also spots in the internal subset where PE refs are fatal + // errors, hence yet another flag. + else if (c == '%' && expandPE) { + if (peIsError) + error ("PE reference within decl in internal subset."); + parsePEReference (); + return readCh (); + } + column++; + } + + return c; + } + + + /** + * Push a single character back onto the current input stream. + * <p>This method usually pushes the character back onto + * the readBuffer. + * <p>I don't think that this would ever be called with + * readBufferPos = 0, because the methods always reads a character + * before unreading it, but just in case, I've added a boundary + * condition. + * @param c The character to push back. + * @see #readCh + * @see #unread (char[]) + * @see #readBuffer + */ + private void unread (char c) + throws SAXException + { + // Normal condition. + if (c == '\n') { + line--; + column = -1; + } + if (readBufferPos > 0) { + readBuffer [--readBufferPos] = c; + } else { + pushString (null, new Character (c).toString ()); + } + } + + + /** + * Push a char array back onto the current input stream. + * <p>NOTE: you must <em>never</em> push back characters that you + * haven't actually read: use pushString () instead. + * @see #readCh + * @see #unread (char) + * @see #readBuffer + * @see #pushString + */ + private void unread (char ch[], int length) + throws SAXException + { + for (int i = 0; i < length; i++) { + if (ch [i] == '\n') { + line--; + column = -1; + } + } + if (length < readBufferPos) { + readBufferPos -= length; + } else { + pushCharArray (null, ch, 0, length); + } + } + + + /** + * Push, or skip, a new external input source. + * The source will be some kind of parsed entity, such as a PE + * (including the external DTD subset) or content for the body. + * + * @param url The java.net.URL object for the entity. + * @see SAXDriver#resolveEntity + * @see #pushString + * @see #sourceType + * @see #pushInput + * @see #detectEncoding + * @see #sourceType + * @see #readBuffer + */ + private void pushURL ( + boolean isPE, + String ename, + String ids [], // public, system, baseURI + Reader reader, + InputStream stream, + String encoding, + boolean doResolve + ) throws SAXException, IOException + { + boolean ignoreEncoding; + String systemId; + InputSource source; + + if (!isPE) + dataBufferFlush (); + + scratch.setPublicId (ids [0]); + scratch.setSystemId (ids [1]); + + // See if we should skip or substitute the entity. + // If we're not skipping, resolving reports startEntity() + // and updates the (handler's) stack of URIs. + if (doResolve) { + // assert (stream == null && reader == null && encoding == null) + source = handler.resolveEntity (isPE, ename, scratch, ids [2]); + if (source == null) { + handler.warn ("skipping entity: " + ename); + handler.skippedEntity (ename); + if (isPE) + skippedPE = true; + return; + } + + // we might be using alternate IDs/encoding + systemId = source.getSystemId (); + // The following warning and setting systemId was deleted bcause + // the application has the option of not setting systemId + // provided that it has set the characte/byte stream. + /* + if (systemId == null) { + handler.warn ("missing system ID, using " + ids [1]); + systemId = ids [1]; + } + */ + } else { + // "[document]", or "[dtd]" via getExternalSubset() + scratch.setCharacterStream (reader); + scratch.setByteStream (stream); + scratch.setEncoding (encoding); + source = scratch; + systemId = ids [1]; + if (handler.getFeature (SAXDriver.FEATURE + "string-interning")) { + handler.startExternalEntity (ename, systemId, + "[document]" == ename); + } else { + handler.startExternalEntity (ename, systemId, + "[document]".equals(ename)); + } + } + + // we may have been given I/O streams directly + if (source.getCharacterStream () != null) { + if (source.getByteStream () != null) + error ("InputSource has two streams!"); + reader = source.getCharacterStream (); + } else if (source.getByteStream () != null) { + encoding = source.getEncoding (); + if (encoding == null) + stream = source.getByteStream (); + else try { + reader = new InputStreamReader ( + source.getByteStream (), + encoding); + } catch (IOException e) { + stream = source.getByteStream (); + } + } else if (systemId == null) + error ("InputSource has no URI!"); + scratch.setCharacterStream (null); + scratch.setByteStream (null); + scratch.setEncoding (null); + + // Push the existing status. + pushInput (ename); + + // Create a new read buffer. + // (Note the four-character margin) + readBuffer = new char [READ_BUFFER_MAX + 4]; + readBufferPos = 0; + readBufferLength = 0; + readBufferOverflow = -1; + is = null; + line = 1; + column = 0; + currentByteCount = 0; + + // If there's an explicit character stream, just + // ignore encoding declarations. + if (reader != null) { + sourceType = INPUT_READER; + this.reader = reader; + tryEncodingDecl (true); + return; + } + + // Else we handle the conversion, and need to ensure + // it's done right. + sourceType = INPUT_STREAM; + if (stream != null) { + is = stream; + } else { + // We have to open our own stream to the URL. + URL url = new URL (systemId); + + externalEntity = url.openConnection (); + externalEntity.connect (); + is = externalEntity.getInputStream (); + } + + // If we get to here, there must be + // an InputStream available. + if (!is.markSupported ()) { + is = new BufferedInputStream (is); + } + + // Get any external encoding label. + if (encoding == null && externalEntity != null) { + // External labels can be untrustworthy; filesystems in + // particular often have the wrong default for content + // that wasn't locally originated. Those we autodetect. + if (!"file".equals (externalEntity.getURL ().getProtocol ())) { + int temp; + + // application/xml;charset=something;otherAttr=... + // ... with many variants on 'something' + encoding = externalEntity.getContentType (); + + // MHK code (fix for Saxon 5.5.1/007): + // protect against encoding==null + if (encoding==null) { + temp = -1; + } else { + temp = encoding.indexOf ("charset"); + } + + // RFC 2376 sez MIME text defaults to ASCII, but since the + // JDK will create a MIME type out of thin air, we always + // autodetect when there's no explicit charset attribute. + if (temp < 0) + encoding = null; // autodetect + else { + // only this one attribute + if ((temp = encoding.indexOf (';')) > 0) + encoding = encoding.substring (0, temp); + + if ((temp = encoding.indexOf ('=', temp + 7)) > 0) { + encoding = encoding.substring (temp + 1); + + // attributes can have comment fields (RFC 822) + if ((temp = encoding.indexOf ('(')) > 0) + encoding = encoding.substring (0, temp); + // ... and values may be quoted + if ((temp = encoding.indexOf ('"')) > 0) + encoding = encoding.substring (temp + 1, + encoding.indexOf ('"', temp + 2)); + encoding.trim (); + } else { + handler.warn ("ignoring illegal MIME attribute: " + + encoding); + encoding = null; + } + } + } + } + + // if we got an external encoding label, use it ... + if (encoding != null) { + this.encoding = ENCODING_EXTERNAL; + setupDecoding (encoding); + ignoreEncoding = true; + + // ... else autodetect from first bytes. + } else { + detectEncoding (); + ignoreEncoding = false; + } + + // Read any XML or text declaration. + // If we autodetected, it may tell us the "real" encoding. + try { + tryEncodingDecl (ignoreEncoding); + } catch (UnsupportedEncodingException x) { + encoding = x.getMessage (); + + // if we don't handle the declared encoding, + // try letting a JVM InputStreamReader do it + try { + if (sourceType != INPUT_STREAM) + throw x; + + is.reset (); + readBufferPos = 0; + readBufferLength = 0; + readBufferOverflow = -1; + line = 1; + currentByteCount = column = 0; + + sourceType = INPUT_READER; + this.reader = new InputStreamReader (is, encoding); + is = null; + + tryEncodingDecl (true); + + } catch (IOException e) { + error ("unsupported text encoding", + encoding, + null); + } + } + } + + + /** + * Check for an encoding declaration. This is the second part of the + * XML encoding autodetection algorithm, relying on detectEncoding to + * get to the point that this part can read any encoding declaration + * in the document (using only US-ASCII characters). + * + * <p> Because this part starts to fill parser buffers with this data, + * it's tricky to setup a reader so that Java's built-in decoders can be + * used for the character encodings that aren't built in to this parser + * (such as EUC-JP, KOI8-R, Big5, etc). + * + * @return any encoding in the declaration, uppercased; or null + * @see detectEncoding + */ + private String tryEncodingDecl (boolean ignoreEncoding) + throws SAXException, IOException + { + // Read the XML/text declaration. + if (tryRead ("<?xml")) { + if (tryWhitespace ()) { + if (inputStack.size () > 0) { + return parseTextDecl (ignoreEncoding); + } else { + return parseXMLDecl (ignoreEncoding); + } + } else { + // <?xml-stylesheet ...?> or similar + unread ('l'); + unread ('m'); + unread ('x'); + unread ('?'); + unread ('<'); + } + } + return null; + } + + + /** + * Attempt to detect the encoding of an entity. + * <p>The trick here (as suggested in the XML standard) is that + * any entity not in UTF-8, or in UCS-2 with a byte-order mark, + * <b>must</b> begin with an XML declaration or an encoding + * declaration; we simply have to look for "<?xml" in various + * encodings. + * <p>This method has no way to distinguish among 8-bit encodings. + * Instead, it sets up for UTF-8, then (possibly) revises its assumption + * later in setupDecoding (). Any ASCII-derived 8-bit encoding + * should work, but most will be rejected later by setupDecoding (). + * @see #tryEncoding (byte[], byte, byte, byte, byte) + * @see #tryEncoding (byte[], byte, byte) + * @see #setupDecoding + */ + private void detectEncoding () + throws SAXException, IOException + { + byte signature[] = new byte [4]; + + // Read the first four bytes for + // autodetection. + is.mark (4); + is.read (signature); + is.reset (); + + // + // FIRST: four byte encodings (who uses these?) + // + if (tryEncoding (signature, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x3c)) { + // UCS-4 must begin with "<?xml" + // 0x00 0x00 0x00 0x3c: UCS-4, big-endian (1234) + // "UTF-32BE" + encoding = ENCODING_UCS_4_1234; + + } else if (tryEncoding (signature, (byte) 0x3c, (byte) 0x00, + (byte) 0x00, (byte) 0x00)) { + // 0x3c 0x00 0x00 0x00: UCS-4, little-endian (4321) + // "UTF-32LE" + encoding = ENCODING_UCS_4_4321; + + } else if (tryEncoding (signature, (byte) 0x00, (byte) 0x00, + (byte) 0x3c, (byte) 0x00)) { + // 0x00 0x00 0x3c 0x00: UCS-4, unusual (2143) + encoding = ENCODING_UCS_4_2143; + + } else if (tryEncoding (signature, (byte) 0x00, (byte) 0x3c, + (byte) 0x00, (byte) 0x00)) { + // 0x00 0x3c 0x00 0x00: UCS-4, unusual (3421) + encoding = ENCODING_UCS_4_3412; + + // 00 00 fe ff UCS_4_1234 (with BOM) + // ff fe 00 00 UCS_4_4321 (with BOM) + } + + // + // SECOND: two byte encodings + // note ... with 1/14/2000 errata the XML spec identifies some + // more "broken UTF-16" autodetection cases, with no XML decl, + // which we don't handle here (that's legal too). + // + else if (tryEncoding (signature, (byte) 0xfe, (byte) 0xff)) { + // UCS-2 with a byte-order marker. (UTF-16) + // 0xfe 0xff: UCS-2, big-endian (12) + encoding = ENCODING_UCS_2_12; + is.read (); is.read (); + + } else if (tryEncoding (signature, (byte) 0xff, (byte) 0xfe)) { + // UCS-2 with a byte-order marker. (UTF-16) + // 0xff 0xfe: UCS-2, little-endian (21) + encoding = ENCODING_UCS_2_21; + is.read (); is.read (); + + } else if (tryEncoding (signature, (byte) 0x00, (byte) 0x3c, + (byte) 0x00, (byte) 0x3f)) { + // UTF-16BE (otherwise, malformed UTF-16) + // 0x00 0x3c 0x00 0x3f: UCS-2, big-endian, no byte-order mark + encoding = ENCODING_UCS_2_12; + error ("no byte-order mark for UCS-2 entity"); + + } else if (tryEncoding (signature, (byte) 0x3c, (byte) 0x00, + (byte) 0x3f, (byte) 0x00)) { + // UTF-16LE (otherwise, malformed UTF-16) + // 0x3c 0x00 0x3f 0x00: UCS-2, little-endian, no byte-order mark + encoding = ENCODING_UCS_2_21; + error ("no byte-order mark for UCS-2 entity"); + } + + // + // THIRD: ASCII-derived encodings, fixed and variable lengths + // + else if (tryEncoding (signature, (byte) 0x3c, (byte) 0x3f, + (byte) 0x78, (byte) 0x6d)) { + // ASCII derived + // 0x3c 0x3f 0x78 0x6d: UTF-8 or other 8-bit markup (read ENCODING) + encoding = ENCODING_UTF_8; + prefetchASCIIEncodingDecl (); + + } else if (signature [0] == (byte) 0xef + && signature [1] == (byte) 0xbb + && signature [2] == (byte) 0xbf) { + // 0xef 0xbb 0xbf: UTF-8 BOM (not part of document text) + // this un-needed notion slipped into XML 2nd ed through a + // "non-normative" erratum; now required by MSFT and UDDI, + // and E22 made it normative. + encoding = ENCODING_UTF_8; + is.read (); is.read (); is.read (); + + } else { + // 4c 6f a7 94 ... we don't understand EBCDIC flavors + // ... but we COULD at least kick in some fixed code page + + // (default) UTF-8 without encoding/XML declaration + encoding = ENCODING_UTF_8; + } + } + + + /** + * Check for a four-byte signature. + * <p>Utility routine for detectEncoding (). + * <p>Always looks for some part of "<?XML" in a specific encoding. + * @param sig The first four bytes read. + * @param b1 The first byte of the signature + * @param b2 The second byte of the signature + * @param b3 The third byte of the signature + * @param b4 The fourth byte of the signature + * @see #detectEncoding + */ + private static boolean tryEncoding ( + byte sig[], byte b1, byte b2, byte b3, byte b4) + { + return (sig [0] == b1 && sig [1] == b2 + && sig [2] == b3 && sig [3] == b4); + } + + + /** + * Check for a two-byte signature. + * <p>Looks for a UCS-2 byte-order mark. + * <p>Utility routine for detectEncoding (). + * @param sig The first four bytes read. + * @param b1 The first byte of the signature + * @param b2 The second byte of the signature + * @see #detectEncoding + */ + private static boolean tryEncoding (byte sig[], byte b1, byte b2) + { + return ((sig [0] == b1) && (sig [1] == b2)); + } + + + /** + * This method pushes a string back onto input. + * <p>It is useful either as the expansion of an internal entity, + * or for backtracking during the parse. + * <p>Call pushCharArray () to do the actual work. + * @param s The string to push back onto input. + * @see #pushCharArray + */ + private void pushString (String ename, String s) + throws SAXException + { + char ch[] = s.toCharArray (); + pushCharArray (ename, ch, 0, ch.length); + } + + + /** + * Push a new internal input source. + * <p>This method is useful for expanding an internal entity, + * or for unreading a string of characters. It creates a new + * readBuffer containing the characters in the array, instead + * of characters converted from an input byte stream. + * @param ch The char array to push. + * @see #pushString + * @see #pushURL + * @see #readBuffer + * @see #sourceType + * @see #pushInput + */ + private void pushCharArray (String ename, char ch[], int start, int length) + throws SAXException + { + // Push the existing status + pushInput (ename); + if (ename != null && doReport) { + dataBufferFlush (); + handler.startInternalEntity (ename); + } + sourceType = INPUT_INTERNAL; + readBuffer = ch; + readBufferPos = start; + readBufferLength = length; + readBufferOverflow = -1; + } + + + /** + * Save the current input source onto the stack. + * <p>This method saves all of the global variables associated with + * the current input source, so that they can be restored when a new + * input source has finished. It also tests for entity recursion. + * <p>The method saves the following global variables onto a stack + * using a fixed-length array: + * <ol> + * <li>sourceType + * <li>externalEntity + * <li>readBuffer + * <li>readBufferPos + * <li>readBufferLength + * <li>line + * <li>encoding + * </ol> + * @param ename The name of the entity (if any) causing the new input. + * @see #popInput + * @see #sourceType + * @see #externalEntity + * @see #readBuffer + * @see #readBufferPos + * @see #readBufferLength + * @see #line + * @see #encoding + */ + private void pushInput (String ename) + throws SAXException + { + // Check for entity recursion. + if (ename != null) { + Enumeration entities = entityStack.elements (); + while (entities.hasMoreElements ()) { + String e = (String) entities.nextElement (); + if (e != null && e == ename) { + error ("recursive reference to entity", ename, null); + } + } + } + entityStack.push (ename); + + // Don't bother if there is no current input. + if (sourceType == INPUT_NONE) { + return; + } + + // Set up a snapshot of the current + // input source. + Object input[] = new Object [12]; + + input [0] = new Integer (sourceType); + input [1] = externalEntity; + input [2] = readBuffer; + input [3] = new Integer (readBufferPos); + input [4] = new Integer (readBufferLength); + input [5] = new Integer (line); + input [6] = new Integer (encoding); + input [7] = new Integer (readBufferOverflow); + input [8] = is; + input [9] = new Integer (currentByteCount); + input [10] = new Integer (column); + input [11] = reader; + + // Push it onto the stack. + inputStack.push (input); + } + + + /** + * Restore a previous input source. + * <p>This method restores all of the global variables associated with + * the current input source. + * @exception java.io.EOFException + * If there are no more entries on the input stack. + * @see #pushInput + * @see #sourceType + * @see #externalEntity + * @see #readBuffer + * @see #readBufferPos + * @see #readBufferLength + * @see #line + * @see #encoding + */ + private void popInput () + throws SAXException, IOException + { + String ename = (String) entityStack.pop (); + + if (ename != null && doReport) + dataBufferFlush (); + switch (sourceType) { + case INPUT_STREAM: + handler.endExternalEntity (ename); + is.close (); + break; + case INPUT_READER: + handler.endExternalEntity (ename); + reader.close (); + break; + case INPUT_INTERNAL: + if (ename != null && doReport) + handler.endInternalEntity (ename); + break; + } + + // Throw an EOFException if there + // is nothing else to pop. + if (inputStack.isEmpty ()) { + throw new EOFException ("no more input"); + } + + Object input [] = (Object[]) inputStack.pop (); + + sourceType = ((Integer) input [0]).intValue (); + externalEntity = (URLConnection) input [1]; + readBuffer = (char[]) input [2]; + readBufferPos = ((Integer) input [3]).intValue (); + readBufferLength = ((Integer) input [4]).intValue (); + line = ((Integer) input [5]).intValue (); + encoding = ((Integer) input [6]).intValue (); + readBufferOverflow = ((Integer) input [7]).intValue (); + is = (InputStream) input [8]; + currentByteCount = ((Integer) input [9]).intValue (); + column = ((Integer) input [10]).intValue (); + reader = (Reader) input [11]; + } + + + /** + * Return true if we can read the expected character. + * <p>Note that the character will be removed from the input stream + * on success, but will be put back on failure. Do not attempt to + * read the character again if the method succeeds. + * @param delim The character that should appear next. For a + * insensitive match, you must supply this in upper-case. + * @return true if the character was successfully read, or false if + * it was not. + * @see #tryRead (String) + */ + private boolean tryRead (char delim) + throws SAXException, IOException + { + char c; + + // Read the character + c = readCh (); + + // Test for a match, and push the character + // back if the match fails. + if (c == delim) { + return true; + } else { + unread (c); + return false; + } + } + + + /** + * Return true if we can read the expected string. + * <p>This is simply a convenience method. + * <p>Note that the string will be removed from the input stream + * on success, but will be put back on failure. Do not attempt to + * read the string again if the method succeeds. + * <p>This method will push back a character rather than an + * array whenever possible (probably the majority of cases). + * @param delim The string that should appear next. + * @return true if the string was successfully read, or false if + * it was not. + * @see #tryRead (char) + */ + private boolean tryRead (String delim) + throws SAXException, IOException + { + return tryRead (delim.toCharArray ()); + } + + private boolean tryRead (char ch []) + throws SAXException, IOException + { + char c; + + // Compare the input, character- + // by character. + + for (int i = 0; i < ch.length; i++) { + c = readCh (); + if (c != ch [i]) { + unread (c); + if (i != 0) { + unread (ch, i); + } + return false; + } + } + return true; + } + + + + /** + * Return true if we can read some whitespace. + * <p>This is simply a convenience method. + * <p>This method will push back a character rather than an + * array whenever possible (probably the majority of cases). + * @return true if whitespace was found. + */ + private boolean tryWhitespace () + throws SAXException, IOException + { + char c; + c = readCh (); + if (isWhitespace (c)) { + skipWhitespace (); + return true; + } else { + unread (c); + return false; + } + } + + + /** + * Read all data until we find the specified string. + * This is useful for scanning CDATA sections and PIs. + * <p>This is inefficient right now, since it calls tryRead () + * for every character. + * @param delim The string delimiter + * @see #tryRead (String, boolean) + * @see #readCh + */ + private void parseUntil (String delim) + throws SAXException, IOException + { + parseUntil (delim.toCharArray ()); + } + + private void parseUntil (char delim []) + throws SAXException, IOException + { + char c; + int startLine = line; + + try { + while (!tryRead (delim)) { + c = readCh (); + dataBufferAppend (c); + } + } catch (EOFException e) { + error ("end of input while looking for delimiter " + + "(started on line " + startLine + + ')', null, new String (delim)); + } + } + + + ////////////////////////////////////////////////////////////////////// + // Low-level I/O. + ////////////////////////////////////////////////////////////////////// + + + /** + * Prefetch US-ASCII XML/text decl from input stream into read buffer. + * Doesn't buffer more than absolutely needed, so that when an encoding + * decl says we need to create an InputStreamReader, we can discard our + * buffer and reset(). Caller knows the first chars of the decl exist + * in the input stream. + */ + private void prefetchASCIIEncodingDecl () + throws SAXException, IOException + { + int ch; + readBufferPos = readBufferLength = 0; + + is.mark (readBuffer.length); + while (true) { + ch = is.read (); + readBuffer [readBufferLength++] = (char) ch; + switch (ch) { + case (int) '>': + return; + case -1: + error ("file ends before end of XML or encoding declaration.", + null, "?>"); + } + if (readBuffer.length == readBufferLength) + error ("unfinished XML or encoding declaration"); + } + } + + /** + * Read a chunk of data from an external input source. + * <p>This is simply a front-end that fills the rawReadBuffer + * with bytes, then calls the appropriate encoding handler. + * @see #encoding + * @see #rawReadBuffer + * @see #readBuffer + * @see #filterCR + * @see #copyUtf8ReadBuffer + * @see #copyIso8859_1ReadBuffer + * @see #copyUcs_2ReadBuffer + * @see #copyUcs_4ReadBuffer + */ + private void readDataChunk () + throws SAXException, IOException + { + int count; + + // See if we have any overflow (filterCR sets for CR at end) + if (readBufferOverflow > -1) { + readBuffer [0] = (char) readBufferOverflow; + readBufferOverflow = -1; + readBufferPos = 1; + sawCR = true; + } else { + readBufferPos = 0; + sawCR = false; + } + + // input from a character stream. + if (sourceType == INPUT_READER) { + count = reader.read (readBuffer, + readBufferPos, READ_BUFFER_MAX - readBufferPos); + if (count < 0) + readBufferLength = readBufferPos; + else + readBufferLength = readBufferPos + count; + if (readBufferLength > 0) + filterCR (count >= 0); + sawCR = false; + return; + } + + // Read as many bytes as possible into the raw buffer. + count = is.read (rawReadBuffer, 0, READ_BUFFER_MAX); + + // Dispatch to an encoding-specific reader method to populate + // the readBuffer. In most parser speed profiles, these routines + // show up at the top of the CPU usage chart. + if (count > 0) { + switch (encoding) { + // one byte builtins + case ENCODING_ASCII: + copyIso8859_1ReadBuffer (count, (char) 0x0080); + break; + case ENCODING_UTF_8: + copyUtf8ReadBuffer (count); + break; + case ENCODING_ISO_8859_1: + copyIso8859_1ReadBuffer (count, (char) 0); + break; + + // two byte builtins + case ENCODING_UCS_2_12: + copyUcs2ReadBuffer (count, 8, 0); + break; + case ENCODING_UCS_2_21: + copyUcs2ReadBuffer (count, 0, 8); + break; + + // four byte builtins + case ENCODING_UCS_4_1234: + copyUcs4ReadBuffer (count, 24, 16, 8, 0); + break; + case ENCODING_UCS_4_4321: + copyUcs4ReadBuffer (count, 0, 8, 16, 24); + break; + case ENCODING_UCS_4_2143: + copyUcs4ReadBuffer (count, 16, 24, 0, 8); + break; + case ENCODING_UCS_4_3412: + copyUcs4ReadBuffer (count, 8, 0, 24, 16); + break; + } + } else + readBufferLength = readBufferPos; + + readBufferPos = 0; + + // Filter out all carriage returns if we've seen any + // (including any saved from a previous read) + if (sawCR) { + filterCR (count >= 0); + sawCR = false; + + // must actively report EOF, lest some CRs get lost. + if (readBufferLength == 0 && count >= 0) + readDataChunk (); + } + + if (count > 0) + currentByteCount += count; + } + + + /** + * Filter carriage returns in the read buffer. + * CRLF becomes LF; CR becomes LF. + * @param moreData true iff more data might come from the same source + * @see #readDataChunk + * @see #readBuffer + * @see #readBufferOverflow + */ + private void filterCR (boolean moreData) + { + int i, j; + + readBufferOverflow = -1; + +loop: + for (i = j = readBufferPos; j < readBufferLength; i++, j++) { + switch (readBuffer [j]) { + case '\r': + if (j == readBufferLength - 1) { + if (moreData) { + readBufferOverflow = '\r'; + readBufferLength--; + } else // CR at end of buffer + readBuffer [i++] = '\n'; + break loop; + } else if (readBuffer [j + 1] == '\n') { + j++; + } + readBuffer [i] = '\n'; + break; + + case '\n': + default: + readBuffer [i] = readBuffer [j]; + break; + } + } + readBufferLength = i; + } + + /** + * Convert a buffer of UTF-8-encoded bytes into UTF-16 characters. + * <p>When readDataChunk () calls this method, the raw bytes are in + * rawReadBuffer, and the final characters will appear in + * readBuffer. + * <p>Note that as of Unicode 3.1, good practice became a requirement, + * so that each Unicode character has exactly one UTF-8 representation. + * @param count The number of bytes to convert. + * @see #readDataChunk + * @see #rawReadBuffer + * @see #readBuffer + * @see #getNextUtf8Byte + */ + private void copyUtf8ReadBuffer (int count) + throws SAXException, IOException + { + int i = 0; + int j = readBufferPos; + int b1; + char c = 0; + + /* + // check once, so the runtime won't (if it's smart enough) + if (count < 0 || count > rawReadBuffer.length) + throw new ArrayIndexOutOfBoundsException (Integer.toString (count)); + */ + + while (i < count) { + b1 = rawReadBuffer [i++]; + + // Determine whether we are dealing + // with a one-, two-, three-, or four- + // byte sequence. + if (b1 < 0) { + if ((b1 & 0xe0) == 0xc0) { + // 2-byte sequence: 00000yyyyyxxxxxx = 110yyyyy 10xxxxxx + c = (char) (((b1 & 0x1f) << 6) + | getNextUtf8Byte (i++, count)); + if (c < 0x0080) + encodingError ("Illegal two byte UTF-8 sequence", + c, 0); + //Sec 2.11 + // [1] the two-character sequence #xD #xA + // [2] the two-character sequence #xD #x85 + if ((c == 0x0085 || c == 0x000a) && sawCR) + continue; + + // Sec 2.11 + // [3] the single character #x85 + + if(c == 0x0085 && xmlVersion == XML_11) + readBuffer[j++] = '\r'; + } else if ((b1 & 0xf0) == 0xe0) { + // 3-byte sequence: + // zzzzyyyyyyxxxxxx = 1110zzzz 10yyyyyy 10xxxxxx + // most CJKV characters + c = (char) (((b1 & 0x0f) << 12) | + (getNextUtf8Byte (i++, count) << 6) | + getNextUtf8Byte (i++, count)); + //sec 2.11 + //[4] the single character #x2028 + if(c == 0x2028 && xmlVersion == XML_11){ + readBuffer[j++] = '\r'; + sawCR = true; + continue; + } + if (c < 0x0800 || (c >= 0xd800 && c <= 0xdfff)) + encodingError ("Illegal three byte UTF-8 sequence", + c, 0); + } else if ((b1 & 0xf8) == 0xf0) { + // 4-byte sequence: 11101110wwwwzzzzyy + 110111yyyyxxxxxx + // = 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx + // (uuuuu = wwww + 1) + // "Surrogate Pairs" ... from the "Astral Planes" + // Unicode 3.1 assigned the first characters there + int iso646 = b1 & 07; + iso646 = (iso646 << 6) + getNextUtf8Byte (i++, count); + iso646 = (iso646 << 6) + getNextUtf8Byte (i++, count); + iso646 = (iso646 << 6) + getNextUtf8Byte (i++, count); + + if (iso646 <= 0xffff) { + encodingError ("Illegal four byte UTF-8 sequence", + iso646, 0); + } else { + if (iso646 > 0x0010ffff) + encodingError ( + "UTF-8 value out of range for Unicode", + iso646, 0); + iso646 -= 0x010000; + readBuffer [j++] = (char) (0xd800 | (iso646 >> 10)); + readBuffer [j++] = (char) (0xdc00 | (iso646 & 0x03ff)); + continue; + } + } else { + // The five and six byte encodings aren't supported; + // they exceed the Unicode (and XML) range. + encodingError ( + "unsupported five or six byte UTF-8 sequence", + 0xff & b1, i); + // NOTREACHED + c = 0; + } + } else { + // 1-byte sequence: 000000000xxxxxxx = 0xxxxxxx + // (US-ASCII character, "common" case, one branch to here) + c = (char) b1; + } + readBuffer [j++] = c; + if (c == '\r') + sawCR = true; + } + // How many characters have we read? + readBufferLength = j; + } + + + /** + * Return the next byte value in a UTF-8 sequence. + * If it is not possible to get a byte from the current + * entity, throw an exception. + * @param pos The current position in the rawReadBuffer. + * @param count The number of bytes in the rawReadBuffer + * @return The significant six bits of a non-initial byte in + * a UTF-8 sequence. + * @exception EOFException If the sequence is incomplete. + */ + private int getNextUtf8Byte (int pos, int count) + throws SAXException, IOException + { + int val; + + // Take a character from the buffer + // or from the actual input stream. + if (pos < count) { + val = rawReadBuffer [pos]; + } else { + val = is.read (); + if (val == -1) { + encodingError ("unfinished multi-byte UTF-8 sequence at EOF", + -1, pos); + } + } + + // Check for the correct bits at the start. + if ((val & 0xc0) != 0x80) { + encodingError ("bad continuation of multi-byte UTF-8 sequence", + val, pos + 1); + } + + // Return the significant bits. + return (val & 0x3f); + } + + + /** + * Convert a buffer of US-ASCII or ISO-8859-1-encoded bytes into + * UTF-16 characters. + * + * <p>When readDataChunk () calls this method, the raw bytes are in + * rawReadBuffer, and the final characters will appear in + * readBuffer. + * + * @param count The number of bytes to convert. + * @param mask For ASCII conversion, 0x7f; else, 0xff. + * @see #readDataChunk + * @see #rawReadBuffer + * @see #readBuffer + */ + private void copyIso8859_1ReadBuffer (int count, char mask) + throws IOException + { + int i, j; + for (i = 0, j = readBufferPos; i < count; i++, j++) { + char c = (char) (rawReadBuffer [i] & 0xff); + if ((c & mask) != 0) + throw new CharConversionException ("non-ASCII character U+" + + Integer.toHexString (c)); + if (c == 0x0085 && xmlVersion == XML_11) + c = '\r'; + readBuffer [j] = c; + if (c == '\r') { + sawCR = true; + } + } + readBufferLength = j; + } + + + /** + * Convert a buffer of UCS-2-encoded bytes into UTF-16 characters + * (as used in Java string manipulation). + * + * <p>When readDataChunk () calls this method, the raw bytes are in + * rawReadBuffer, and the final characters will appear in + * readBuffer. + * @param count The number of bytes to convert. + * @param shift1 The number of bits to shift byte 1. + * @param shift2 The number of bits to shift byte 2 + * @see #readDataChunk + * @see #rawReadBuffer + * @see #readBuffer + */ + private void copyUcs2ReadBuffer (int count, int shift1, int shift2) + throws SAXException + { + int j = readBufferPos; + + if (count > 0 && (count % 2) != 0) { + encodingError ("odd number of bytes in UCS-2 encoding", -1, count); + } + // The loops are faster with less internal brancing; hence two + if (shift1 == 0) { // "UTF-16-LE" + for (int i = 0; i < count; i += 2) { + char c = (char) (rawReadBuffer [i + 1] << 8); + c |= 0xff & rawReadBuffer [i]; + readBuffer [j++] = c; + if (c == '\r') + sawCR = true; + } + } else { // "UTF-16-BE" + for (int i = 0; i < count; i += 2) { + char c = (char) (rawReadBuffer [i] << 8); + c |= 0xff & rawReadBuffer [i + 1]; + readBuffer [j++] = c; + if (c == '\r') + sawCR = true; + } + } + readBufferLength = j; + } + + + /** + * Convert a buffer of UCS-4-encoded bytes into UTF-16 characters. + * + * <p>When readDataChunk () calls this method, the raw bytes are in + * rawReadBuffer, and the final characters will appear in + * readBuffer. + * <p>Java has Unicode chars, and this routine uses surrogate pairs + * for ISO-10646 values between 0x00010000 and 0x000fffff. An + * exception is thrown if the ISO-10646 character has no Unicode + * representation. + * + * @param count The number of bytes to convert. + * @param shift1 The number of bits to shift byte 1. + * @param shift2 The number of bits to shift byte 2 + * @param shift3 The number of bits to shift byte 2 + * @param shift4 The number of bits to shift byte 2 + * @see #readDataChunk + * @see #rawReadBuffer + * @see #readBuffer + */ + private void copyUcs4ReadBuffer (int count, int shift1, int shift2, + int shift3, int shift4) + throws SAXException + { + int j = readBufferPos; + + if (count > 0 && (count % 4) != 0) { + encodingError ( + "number of bytes in UCS-4 encoding not divisible by 4", + -1, count); + } + for (int i = 0; i < count; i += 4) { + int value = (((rawReadBuffer [i] & 0xff) << shift1) | + ((rawReadBuffer [i + 1] & 0xff) << shift2) | + ((rawReadBuffer [i + 2] & 0xff) << shift3) | + ((rawReadBuffer [i + 3] & 0xff) << shift4)); + if (value < 0x0000ffff) { + readBuffer [j++] = (char) value; + if (value == (int) '\r') { + sawCR = true; + } + } else if (value < 0x0010ffff) { + value -= 0x010000; + readBuffer [j++] = (char) (0xd8 | ((value >> 10) & 0x03ff)); + readBuffer [j++] = (char) (0xdc | (value & 0x03ff)); + } else { + encodingError ("UCS-4 value out of range for Unicode", + value, i); + } + } + readBufferLength = j; + } + + + /** + * Report a character encoding error. + */ + private void encodingError (String message, int value, int offset) + throws SAXException + { + if (value != -1) + message = message + " (character code: 0x" + + Integer.toHexString (value) + ')'; + error (message); + } + + + ////////////////////////////////////////////////////////////////////// + // Local Variables. + ////////////////////////////////////////////////////////////////////// + + /** + * Re-initialize the variables for each parse. + */ + private void initializeVariables () + { + // First line + line = 1; + column = 0; + + // Set up the buffers for data and names + dataBufferPos = 0; + dataBuffer = new char [DATA_BUFFER_INITIAL]; + nameBufferPos = 0; + nameBuffer = new char [NAME_BUFFER_INITIAL]; + + // Set up the DTD hash tables + elementInfo = new Hashtable (); + entityInfo = new Hashtable (); + notationInfo = new Hashtable (); + skippedPE = false; + + // Set up the variables for the current + // element context. + currentElement = null; + currentElementContent = CONTENT_UNDECLARED; + + // Set up the input variables + sourceType = INPUT_NONE; + inputStack = new Stack (); + entityStack = new Stack (); + externalEntity = null; + tagAttributePos = 0; + tagAttributes = new String [100]; + rawReadBuffer = new byte [READ_BUFFER_MAX]; + readBufferOverflow = -1; + + scratch = new InputSource (); + + inLiteral = false; + expandPE = false; + peIsError = false; + + doReport = false; + + inCDATA = false; + + symbolTable = new Object [SYMBOL_TABLE_LENGTH][]; + } + + + // + // The current XML handler interface. + // + private SAXDriver handler; + + // + // I/O information. + // + private Reader reader; // current reader + private InputStream is; // current input stream + private int line; // current line number + private int column; // current column number + private int sourceType; // type of input source + private Stack inputStack; // stack of input soruces + private URLConnection externalEntity; // current external entity + private int encoding; // current character encoding + private int currentByteCount; // bytes read from current source + private InputSource scratch; // temporary + + // + // Buffers for decoded but unparsed character input. + // + private char readBuffer []; + private int readBufferPos; + private int readBufferLength; + private int readBufferOverflow; // overflow from last data chunk. + + + // + // Buffer for undecoded raw byte input. + // + private final static int READ_BUFFER_MAX = 16384; + private byte rawReadBuffer []; + + + // + // Buffer for attribute values, char refs, DTD stuff. + // + private static int DATA_BUFFER_INITIAL = 4096; + private char dataBuffer []; + private int dataBufferPos; + + // + // Buffer for parsed names. + // + private static int NAME_BUFFER_INITIAL = 1024; + private char nameBuffer []; + private int nameBufferPos; + + // + // Save any standalone flag + // + private boolean docIsStandalone; + + // + // Hashtables for DTD information on elements, entities, and notations. + // Populated until we start ignoring decls (because of skipping a PE) + // + private Hashtable elementInfo; + private Hashtable entityInfo; + private Hashtable notationInfo; + private boolean skippedPE; + + + // + // Element type currently in force. + // + private String currentElement; + private int currentElementContent; + + // + // Stack of entity names, to detect recursion. + // + private Stack entityStack; + + // + // PE expansion is enabled in most chunks of the DTD, not all. + // When it's enabled, literals are treated differently. + // + private boolean inLiteral; + private boolean expandPE; + private boolean peIsError; + + // + // can't report entity expansion inside two constructs: + // - attribute expansions (internal entities only) + // - markup declarations (parameter entities only) + // + private boolean doReport; + + // + // Symbol table, for caching interned names. + // + // These show up wherever XML names or nmtokens are used: naming elements, + // attributes, PIs, notations, entities, and enumerated attribute values. + // + // NOTE: This hashtable doesn't grow. The default size is intended to be + // rather large for most documents. Example: one snapshot of the DocBook + // XML 4.1 DTD used only about 350 such names. As a rule, only pathological + // documents (ones that don't reuse names) should ever see much collision. + // + // Be sure that SYMBOL_TABLE_LENGTH always stays prime, for best hashing. + // "2039" keeps the hash table size at about two memory pages on typical + // 32 bit hardware. + // + private final static int SYMBOL_TABLE_LENGTH = 2039; + + private Object symbolTable [][]; + + // + // Hash table of attributes found in current start tag. + // + private String tagAttributes []; + private int tagAttributePos; + + // + // Utility flag: have we noticed a CR while reading the last + // data chunk? If so, we will have to go back and normalise + // CR or CR/LF line ends. + // + private boolean sawCR; + + // + // Utility flag: are we in CDATA? If so, whitespace isn't ignorable. + // + private boolean inCDATA; + + // + // Xml version. + // + private static final int XML_10 = 0; + private static final int XML_11 = 1; + private int xmlVersion = XML_10; +} diff --git a/libjava/gnu/xml/aelfred2/XmlReader.java b/libjava/gnu/xml/aelfred2/XmlReader.java new file mode 100644 index 0000000..96c9c72 --- /dev/null +++ b/libjava/gnu/xml/aelfred2/XmlReader.java @@ -0,0 +1,315 @@ +/* XmlReader.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.aelfred2; + +import java.io.IOException; +import java.util.Locale; + +import org.xml.sax.*; +import org.xml.sax.ext.*; + +import gnu.xml.pipeline.EventFilter; +import gnu.xml.pipeline.ValidationConsumer; + + +/** + * This SAX2 parser optionally layers a validator over the Ælfred2 + * SAX2 parser. While this will not evaluate every XML validity constraint, + * it does support all the validity constraints that are of any real utility + * outside the strict SGML-compatible world. See the documentation for the + * SAXDriver class for information about the SAX2 features and properties + * that are supported, and documentation for the ValidationConsumer for + * information about what validity constraints may not be supported. + * (Ælfred2 tests some of those, even in non-validating mode, to + * achieve better conformance.) + * + * <p> Note that due to its internal construction, you can't change most + * handlers until parse() returns. This diverges slightly from SAX, which + * expects later binding to be supported. Early binding involves less + * runtime overhead, which is an issue for event pipelines as used inside + * this parser. Rather than relying on the parser to handle late binding + * to your own handlers, do it yourself. + * + * @see SAXDriver + * @see gnu.xml.pipeline.ValidationConsumer + * + * @author David Brownell + */ +public final class XmlReader implements XMLReader +{ + private SAXDriver aelfred2 = new SAXDriver (); + private EventFilter filter = new EventFilter (); + private boolean isValidating; + private boolean active; + + + /** Constructs a SAX Parser. */ + public XmlReader () + { } + + /** + * Constructs a SAX Parser, optionally treating validity errors + * as if they were fatal errors. + */ + public XmlReader (boolean invalidIsFatal) + { + if (invalidIsFatal) + setErrorHandler (new DefaultHandler2 () { + public void error (SAXParseException e) + throws SAXException + { throw e; } + }); + } + + /** + * <b>SAX2</b>: Returns the object used to report the logical + * content of an XML document. + */ + public ContentHandler getContentHandler () + { return filter.getContentHandler (); } + + /** + * <b>SAX2</b>: Assigns the object used to report the logical + * content of an XML document. + * @exception IllegalStateException if called mid-parse + */ + public void setContentHandler (ContentHandler handler) + { + if (active) + throw new IllegalStateException ("already parsing"); + filter.setContentHandler (handler); + } + + /** + * <b>SAX2</b>: Returns the object used to process declarations related + * to notations and unparsed entities. + */ + public DTDHandler getDTDHandler () + { return filter.getDTDHandler (); } + + /** + * <b>SAX1</b> Assigns DTD handler + * @exception IllegalStateException if called mid-parse + */ + public void setDTDHandler (DTDHandler handler) + { + if (active) + throw new IllegalStateException ("already parsing"); + filter.setDTDHandler (handler); + } + + /** + * <b>SAX2</b>: Returns the object used when resolving external + * entities during parsing (both general and parameter entities). + */ + public EntityResolver getEntityResolver () + { return aelfred2.getEntityResolver (); } + + /** <b>SAX1</b> Assigns parser's entity resolver */ + public void setEntityResolver (EntityResolver handler) + { aelfred2.setEntityResolver (handler); } + + /** + * <b>SAX2</b>: Returns the object used to receive callbacks for XML + * errors of all levels (fatal, nonfatal, warning); this is never null; + */ + public ErrorHandler getErrorHandler () + { return aelfred2.getErrorHandler (); } + + /** + * <b>SAX1</b> Assigns error handler + * @exception IllegalStateException if called mid-parse + */ + public void setErrorHandler (ErrorHandler handler) + { + if (active) + throw new IllegalStateException ("already parsing"); + aelfred2.setErrorHandler (handler); + } + + /** + * <b>SAX2</b>: Assigns the specified property. + * @exception IllegalStateException if called mid-parse + */ + public void setProperty (String propertyId, Object value) + throws SAXNotRecognizedException, SAXNotSupportedException + { + if (active) + throw new IllegalStateException ("already parsing"); + if (getProperty (propertyId) != value) + filter.setProperty (propertyId, value); + } + + /** + * <b>SAX2</b>: Returns the specified property. + */ + public Object getProperty (String propertyId) + throws SAXNotRecognizedException + { + if ((SAXDriver.PROPERTY + "declaration-handler") + .equals (propertyId) + || (SAXDriver.PROPERTY + "lexical-handler") + .equals (propertyId)) + return filter.getProperty (propertyId); + throw new SAXNotRecognizedException (propertyId); + } + + private void forceValidating () + throws SAXNotRecognizedException, SAXNotSupportedException + { + aelfred2.setFeature ( + SAXDriver.FEATURE + "namespace-prefixes", + true); + aelfred2.setFeature ( + SAXDriver.FEATURE + "external-general-entities", + true); + aelfred2.setFeature ( + SAXDriver.FEATURE + "external-parameter-entities", + true); + } + + /** + * <b>SAX2</b>: Sets the state of features supported in this parser. + * Note that this parser requires reporting of namespace prefixes when + * validating. + */ + public void setFeature (String featureId, boolean state) + throws SAXNotRecognizedException, SAXNotSupportedException + { + boolean value = getFeature (featureId); + + if (state == value) + return; + + if ((SAXDriver.FEATURE + "validation").equals (featureId)) { + if (active) + throw new SAXNotSupportedException ("already parsing"); + if (state) + forceValidating (); + isValidating = state; + } else + aelfred2.setFeature (featureId, state); + } + + /** + * <b>SAX2</b>: Tells whether this parser supports the specified feature. + * At this time, this directly parallels the underlying SAXDriver, + * except that validation is optionally supported. + * + * @see SAXDriver + */ + public boolean getFeature (String featureId) + throws SAXNotRecognizedException, SAXNotSupportedException + { + if ((SAXDriver.FEATURE + "validation").equals (featureId)) + return isValidating; + + return aelfred2.getFeature (featureId); + } + + /** + * <b>SAX1</b>: Sets the locale used for diagnostics; currently, + * only locales using the English language are supported. + * @param locale The locale for which diagnostics will be generated + */ + public void setLocale (Locale locale) + throws SAXException + { aelfred2.setLocale (locale); } + + /** + * <b>SAX1</b>: Preferred API to parse an XML document, using a + * system identifier (URI). + */ + public void parse (String systemId) + throws SAXException, IOException + { + parse (new InputSource (systemId)); + } + + /** + * <b>SAX1</b>: Underlying API to parse an XML document, used + * directly when no URI is available. When this is invoked, + * and the parser is set to validate, some features will be + * automatically reset to appropriate values: for reporting + * namespace prefixes, and incorporating external entities. + * + * @param source The XML input source. + * + * @exception IllegalStateException if called mid-parse + * @exception SAXException The handlers may throw any SAXException, + * and the parser normally throws SAXParseException objects. + * @exception IOException IOExceptions are normally through through + * the parser if there are problems reading the source document. + */ + public void parse (InputSource source) + throws SAXException, IOException + { + EventFilter next; + boolean nsdecls; + + synchronized (aelfred2) { + if (active) + throw new IllegalStateException ("already parsing"); + active = true; + } + + // set up the output pipeline + if (isValidating) { + forceValidating (); + next = new ValidationConsumer (filter); + } else + next = filter; + + // connect pipeline and error handler + // don't let _this_ call to bind() affect xmlns* attributes + nsdecls = aelfred2.getFeature ( + SAXDriver.FEATURE + "namespace-prefixes"); + EventFilter.bind (aelfred2, next); + if (!nsdecls) + aelfred2.setFeature ( + SAXDriver.FEATURE + "namespace-prefixes", + false); + + // parse, clean up + try { + aelfred2.parse (source); + } finally { + active = false; + } + } +} diff --git a/libjava/gnu/xml/aelfred2/package.html b/libjava/gnu/xml/aelfred2/package.html new file mode 100644 index 0000000..e204258 --- /dev/null +++ b/libjava/gnu/xml/aelfred2/package.html @@ -0,0 +1,506 @@ +<!DOCTYPE html PUBLIC + '-//W3C//DTD XHTML 1.0 Transitional//EN' + 'http://www.w3.org/TR/xhtml1/DTD/transitional.dtd'> + +<html><head> + <title>package overview</title> +<!-- +/* + * Copyright (C) 1999,2000,2001 The Free Software Foundation, Inc. + */ +--> +</head><body> + +<p> This package contains Ælfred2, which includes an +enhanced SAX2-compatible version of the Ælfred +non-validating XML parser, a modular (and hence optional) +DTD validating parser, and modular (and hence optional) +JAXP glue to those. +Use these like any other SAX2 parsers. </p> + +<ul> + <li><a href="#about">About Ælfred</a><ul> + <li><a href="#principles">Design Principles</a></li> + <li><a href="#name">About the Name Ælfred</a></li> + <li><a href="#encodings">Character Encodings</a></li> + <li><a href="#violations">Known Conformance Violations</a></li> + <li><a href="#copyright">Licensing</a></li> + </ul></li> + + <li><a href="#changes">Changes Since the Last Microstar Release</a><ul> + <li><a href="#sax2">SAX2 Support</a></li> + <li><a href="#validation">Validation</a></li> + <li><a href="#smaller">You Want Smaller?</a></li> + <li><a href="#bugfixes">Bugs Fixed</a></li> + </ul></li> + +</ul> + +<h2><a name="about">About Ælfred</a></h2> + +<p>Ælfred is a XML parser written in the java programming language. + +<h3><a name="principles">Design Principles</a></h3> + +<p>In most Java applets and applications, XML should not be the central +feature; instead, XML is the means to another end, such as loading +configuration information, reading meta-data, or parsing transactions.</p> + +<p> When an XML parser is only a single component of a much larger +program, it cannot be large, slow, or resource-intensive. With Java +applets, in particular, code size is a significant issue. The standard +modem is still not operating at 56 Kbaud, or sometimes even with data +compression. Assuming an uncompressed 28.8 Kbaud modem, only about +3 KBytes can be downloaded in one second; compression often doubles +that speed, but a V.90 modem may not provide another doubling. When +used with embedded processors, similar size concerns apply. </p> + +<p> Ælfred is designed for easy and efficient use over the Internet, +based on the following principles: </p> <ol> + +<li> Ælfred must be as small as possible, so that it doesn't add too + much to an applet's download time. </li> + +<li> Ælfred must use as few class files as possible, to minimize the + number of HTTP connections necessary. (The use of JAR files has made this + be less of a concern.) </li> + +<li> Ælfred must be compatible with most or all Java implementations + and platforms. (Write once, run anywhere.) </li> + +<li> Ælfred must use as little memory as possible, so that it does + not take away resources from the rest of your program. (It doesn't force + you to use DOM or a similar costly data structure API.)</li> + +<li> Ælfred must run as fast as possible, so that it does not slow down + the rest of your program. </li> + +<li> Ælfred must produce correct output for well-formed and valid + documents, but need not reject every document that is not valid or + not well-formed. (In Ælfred2, correctness was a bigger concern + than in the original version; and a validation option is available.) </li> + +<li> Ælfred must provide full internationalization from the first + release. (Ælfred2 now automatically handles all encodings + supported by the underlying JVM; previous versions handled only + UTF-8, UTF_16, ASCII, and ISO-8859-1.)</li> + +</ol> + +<p>As you can see from this list, Ælfred is designed for production +use, but neither validation nor perfect conformance was a requirement. +Good validating parsers exist, including one in this package, +and you should use them as appropriate. (See conformance reviews +available at <a href="http://www.xml.com/">http://www.xml.com</a>) +</p> + +<p> One of the main goals of Ælfred2 was to significantly improve +conformance, while not significantly affecting the other goals stated above. +Since the only use of this parser is with SAX, some classes could be +removed, and so the overall size of Ælfred was actually reduced. +Subsequent performance work produced a notable speedup (over twenty +percent on larger files). That is, the tradeoffs between speed, size, and +conformance were re-targeted towards conformance and support of newer APIs +(SAX2), with a a positive performance impact. </p> + +<p> The role anticipated for this version of Ælfred is as a +lightweight Free Software SAX parser that can be used in essentially every +Java program where the handful of conformance violations (noted below) +are acceptable. +That certainly includes applets, and +nowadays one must also mention embedded systems as being even more +size-critical. +At this writing, all parsers that are more conformant are +significantly larger, even when counting the optional +validation support in this version of Ælfred. </p> + + +<h3><a name="name">About the Name <em>Ælfred</em></a></h3> + +<p>Ælfred the Great (AElfred in ASCII) was King of Wessex, and +some say of King of England, at the time of his death in 899 AD. +Ælfred introduced a wide-spread literacy program in the hope that +his people would learn to read English, at least, if Latin was too +difficult for them. This Ælfred hopes to bring another sort of +literacy to Java, using XML, at least, if full SGML is too difficult.</p> + +<p>The initial Æ ligature ("AE)" is also a reminder that XML is +not limited to ASCII.</p> + + +<h3><a name="encodings">Character Encodings</a></h3> + +<p> The Ælfred parser currently builds in support for a handful +of input encodings. Of course these include UTF-8 and UTF-16, which +all XML parsers are required to support:</p> <ul> + + <li> UTF-8 ... the standard eight bit encoding, used unless + you provide an encoding declaration or a MIME charset tag.</li> + + <li> US-ASCII ... an extremely common seven bit encoding, + which happens to be a subset of UTF-8 and ISO-8859-1 as well + as many other encodings. XHTML web pages using US-ASCII + (without an encoding declaration) are probably more + widely interoperable than those in any other encoding. </li> + + <li> ISO-8859-1 ... includes accented characters used in + much of western Europe (but excluding the Euro currency + symbol).</li> + + <li> UTF-16 ... with several variants, this encodes each + sixteen bit Unicode character in sixteen bits of output. + Variants include UTF-16BE (big endian, no byte order mark), + UTF-16LE (little endian, no byte order mark), and + ISO-10646-UCS-2 (an older and less used encoding, using a + version of Unicode without surrogate pairs). This is + essentially the native encoding used by Java. </li> + + <li> ISO-10646-UCS-4 ... a seldom-used four byte encoding, + also known as UTF-32BE. Four byte order variants are supported, + including one known as UTF-32LE. Some operating systems + standardized on UCS-4 despite its significant size penalty, + in anticipation that Unicode (even with surrogate pairs) + would eventually become limiting. UCS-4 permits encoding + of non-Unicode characters, which Java can't represent (and + XML doesn't allow). + </li> + + </ul> + +<p> If you use any encoding other than UTF-8 or UTF-16 you should +make sure to label your data appropriately: </p> + +<blockquote> +<?xml version="1.0" encoding="<b>ISO-8859-15</b>"?> +</blockquote> + +<p> Encodings accessed through <code>java.io.InputStreamReader</code> +are now fully supported for both external labels (such as MIME types) +and internal types (as shown above). +There is one limitation in the support for internal labels: +the encodings must be derived from the US-ASCII encoding, +the EBCDIC family of encodings is not recognized. +Note that Java defines its +own encoding names, which don't always correspond to the standard +Internet encoding names defined by the IETF/IANA, and that Java +may even <em>require</em> use of nonstandard encoding names. +Please report +such problems; some of them can be worked around in this parser, +and many can be worked around by using external labels. +</p> + +<p>Note that if you are using the Euro symbol with an fixed length +eight bit encoding, you should probably be using the encoding label +<em>iso-8859-15</em> or, with a Microsoft OS, <em>cp-1252</em>. +Of course, UTF-8 and UTF-16 handle the Euro symbol directly. +</p> + + +<h3><a name="violations">Known Conformance Violations</a></h3> + +<p>Known conformance issues should be of negligible importance for +most applications, and include: </p><ul> + + <li> Rather than following the voluminous "Appendix B" rules about + what characters may appear in names (and name tokens), the Unicode + rules embedded in <em>java.lang.Character</em> are used. + This means mostly that some names are inappropriately accepted, + though a few are inappropriately rejected. (It's much simpler + to avoid that much special case code. Recent OASIS/NIST test + cases may have these rules be realistically testable.) </li> + + <li> Text containing "]]>" is not rejected unless it fully resides + in an internal buffer ... which is, thankfully, the typical case. This + text is illegal, but sometimes appears in illegal attempts to + nest CDATA sections. (Not catching that boundary condition + substantially simplifies parsing text.) </li> + + <li> Surrogate characters that aren't correctly paired are ignored + rather than rejected, unless they were encoded using UTF-8. (This + simplifies parsing text.) Unicode 3.1 assigned the first characters + to those character codes, in early 2001, so few documents (or tools) + use such characters in any case. </li> + + <li> Declarations following references to an undefined parameter + entity reference are not ignored. (Not maintaining and using state + about this validity error simplifies declaration handling; few + XML parsers address this constraint in any case.) </li> + + <li> Well formedness constraints for general entity references + are not enforced. (The code to handle the "content" production + is merged with the element parsing code, making it hard to reuse + for this additional situation.) </li> + +</ul> + +<p> When tested against the July 12, 1999 version of the OASIS +XML Conformance test suite, an earlier version passed 1057 of 1067 tests. +That contrasts with the original version, which passed 867. The +current parser is top-ranked in terms of conformance, as is its +validating sibling (which has some additional conformance violations +imposed on it by SAX2 API deficiencies as well as some of the more +curious SGML layering artifacts found in the XML specification). </p> + +<p> The XML 1.0 specification itself was not without problems, +and after some delays the W3C has come out with a revised +"second edition" specification. While that doesn't resolve all +the problems identified the XML specification, many of the most +egregious problems have been resolved. (You still need to drink +magic Kool-Aid before some DTD-related issues make sense.) +To the extent possible, this parser conforms to that second +edition specification, and does well against corrected versions +of the OASIS/NIST XML conformance test cases. See <a href= +"http://xmlconf.sourceforge.net">http://xmlconf.sourceforge.net</a> +for more information about SAX2/XML conformance testing. </p> + + +<h3><a name="copyright">Copyright and distribution terms</a></h3> + +<p> +The software in this package is distributed under the GNU General Public +License (with a special exception described below). +</p> + +<p> +A copy of GNU General Public License (GPL) is included in this distribution, +in the file COPYING. If you do not have the source code, it is available at: + + <a href="http://www.gnu.org/software/classpath/">http://www.gnu.org/software/classpath/</a> +</p> + +<pre> + Linking this library statically or dynamically with other modules is + making a combined work based on this library. Thus, the terms and + conditions of the GNU General Public License cover the whole + combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent + modules, and to copy and distribute the resulting executable under + terms of your choice, provided that you also meet, for each linked + independent module, the terms and conditions of the license of that + module. An independent module is a module which is not derived from + or based on this library. If you modify this library, you may extend + this exception to your version of the library, but you are not + obligated to do so. If you do not wish to do so, delete this + exception statement from your version. + + Parts derived from code which carried the following notice: + + Copyright (c) 1997, 1998 by Microstar Software Ltd. + + AElfred is free for both commercial and non-commercial use and + redistribution, provided that Microstar's copyright and disclaimer are + retained intact. You are free to modify AElfred for your own use and + to redistribute AElfred with your modifications, provided that the + modifications are clearly documented. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + merchantability or fitness for a particular purpose. Please use it AT + YOUR OWN RISK. +</pre> + +<p> Some of this documentation was modified from the original +Ælfred README.txt file. All of it has been updated. </p> + +</p> + + +<h2><a name="changes">Changes Since the last Microstar Release</a></h2> + +<p> As noted above, Microstar has not updated this parser since +the summer of 1998, when it released version 1.2a on its web site. +This release is intended to benefit the developer community by +refocusing the API on SAX2, and improving conformance to the extent +that most developers should not need to use another XML parser. </p> + +<p> The code has been cleaned up (referring to the XML 1.0 spec in +all the production numbers in +comments, rather than some preliminary draft, for one example) and +has been sped up a bit as well. +JAXP support has been added, although developers are still +strongly encouraged to use the SAX2 APIs directly. </p> + + +<h3><a name="sax2">SAX2 Support</a></h3> + +<p> The original version of Ælfred did not support the +SAX2 APIs. </p> + +<p> This version supports the SAX2 APIs, exposing the standard +boolean feature descriptors. It supports the "DeclHandler" property +to provide access to all DTD declarations not already exposed +through the SAX1 API. The "LexicalHandler" property is supported, +exposing entity boundaries (including the unnamed external subset) and +things like comments and CDATA boundaries. SAX1 compatibility is +currently provided.</p> + + +<h3><a name="validation">Validation</a></h3> + +<p> In the 'pipeline' package in this same software distribution is an +<a href="../pipeline/ValidationConsumer.html">XML Validation component</a> +using any full SAX2 event stream (including all document type declarations) +to validate. There is now a <a href="XmlReader.html">XmlReader</a> class +which combines that class and this enhanced Ælfred parser, creating +an optionally validating SAX2 parser. </p> + +<p> As noted in the documentation for that validating component, certain +validity constraints can't reliably be tested by a layered validator. +These include all constraints relying on +layering violations (exposing XML at the level of tokens or below, +required since XML isn't a context-free grammar), some that +SAX2 doesn't support, and a few others. The resulting validating +parser is conformant enough for most applications that aren't doing +strange SGML tricks with DTDs. +Moreover, that validating filter can be used without +a parser ... any application component that emits SAX event streams +can DTD-validate its output on demand. </p> + +<h3><a name="smaller">You want Smaller?</a></h3> + +<p> You'll have noticed that the original version of Ælfred +had small size as a top goal. Ælfred2 normally includes a +DTD validation layer, but you can package without that. +Similarly, JAXP factory support is available but optional. +Then the main added cost due to this revision are for +supporting the SAX2 API itself; DTD validation is as +cleanly layered as allowed by SAX2.</p> + +<h3><a name="bugfixes">Bugs Fixed</a></h3> + +<p> Bugs fixed in Ælfred2 include: </p> + +<ol> + <li> Originally Ælfred didn't close file descriptors, which + led to file descriptor leakage on programs which ran for any + length of time. </li> + + <li> NOTATION declarations without system identifiers are + now handled correctly. </li> + + <li> DTD events are now reported for all invocations of a + given parser, not just the first one. </li> + + <li> More correct character handling: <ul> + + <li> Rejects out-of-range characters, both in text and in + character references. </li> + + <li> Correctly handles character references that expand to + surrogate pairs. </li> + + <li> Correctly handles UTF-8 encodings of surrogate pairs. </li> + + <li> Correctly handles Unicode 3.1 rules about illegal UTF-8 + encodings: there is only one legal encoding per character. </li> + + <li> PUBLIC identifiers are now rejected if they have illegal + characters. </li> + + <li> The parser is more correct about what characters are allowed + in names and name tokens. Uses Unicode rules (built in to Java) + rather than the voluminous XML rules, although some extensions + have been made to match XML rules more closely.</li> + + <li> Line ends are now normalized to newlines in all known + cases. </li> + + </ul></li> + + <li> Certain validity errors were previously treated as well + formedness violations. <ul> + + <li> Repeated declarations of an element type are no + longer fatal errors. </li> + + <li> Undeclared parameter entity references are no longer + fatal errors. </li> + + </ul></li> + + <li> Attribute handling is improved: <ul> + + <li> Whitespace must exist between attributes. </li> + + <li> Only one value for a given attribute is permitted. </li> + + <li> ATTLIST declarations don't need to declare attributes. </li> + + <li> Attribute values are normalized when required. </li> + + <li> Tabs in attribute values are normalized to spaces. </li> + + <li> Attribute values containing a literal "<" are rejected. </li> + + </ul></li> + + <li> More correct entity handling: <ul> + + <li> Whitespace must precede NDATA when declaring unparsed + entities.</li> + + <li> Parameter entity declarations may not have NDATA annotations. </li> + + <li> The XML specification has a bug in that it doesn't specify + that certain contexts exist within which parameter entity + expansion must not be performed. Lacking an offical erratum, + this parser now disables such expansion inside comments, + processing instructions, ignored sections, public identifiers, + and parts of entity declarations. </li> + + <li> Entity expansions that include quote characters no longer + confuse parsing of strings using such expansions. </li> + + <li> Whitespace in the values of internal entities is not mapped + to space characters. </li> + + <li> General Entity references in attribute defaults within the + DTD now cause fatal errors when the entity is not defined at the + time it is referenced. </li> + + <li> Malformed general entity references in entity declarations are + now detected. </li> + + </ul></li> + + <li> Neither conditional sections + nor parameter entity references within markup declarations + are permitted in the internal subset. </li> + + <li> Processing instructions whose target names are "XML" + (ignoring case) are now rejected. </li> + + <li> Comments may not include "--".</li> + + <li> Most "]]>" sequences in text are rejected. </li> + + <li> Correct syntax for standalone declarations is enforced. </li> + + <li> Setting a locale for diagnostics only produces an exception + if the language of that locale isn't English. </li> + + <li> Some more encoding names are recognized. These include the + Unicode 3.0 variants of UTF-16 (UTF-16BE, UTF-16LE) as well as + US-ASCII and a few commonly seen synonyms. </li> + + <li> Text (from character content, PIs, or comments) large enough + not to fit into internal buffers is now handled correctly even in + some cases which were originally handled incorrectly.</li> + + <li> Content is now reported for element types for which attributes + have been declared, but no content model is known. (Such documents + are invalid, but may still be well formed.) </li> + +</ol> + +<p> Other bugs may also have been fixed. </p> + +<p> For better overall validation support, some of the validity +constraints that can't be verified using the SAX2 event stream +are now reported directly by Ælfred2. </p> + +</body></html> + diff --git a/libjava/gnu/xml/dom/Consumer.java b/libjava/gnu/xml/dom/Consumer.java new file mode 100644 index 0000000..1aad5be --- /dev/null +++ b/libjava/gnu/xml/dom/Consumer.java @@ -0,0 +1,373 @@ +/* Consumer.java -- + Copyright (C) 2001,2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.xml.dom; + +import java.io.IOException; + +import org.w3c.dom.Document; +import org.w3c.dom.DocumentType; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Node; +import org.w3c.dom.Text; + +import org.xml.sax.Attributes; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.ext.Attributes2; + +import gnu.xml.pipeline.DomConsumer; +import gnu.xml.pipeline.EventConsumer; + + +/** + * Event consumer which constructs DOM documents using the implementation + * in this package, using SAX2 events. This packages various backdoors + * into this DOM implementation, as needed to address DOM requirements + * that can't be met by strictly conforming implementations of DOM. + * + * <p> These requirements all relate to {@link DocumentType} nodes and + * features of that node type. These features are normally not used, + * because that interface only exposes a subset of the information found + * in DTDs. More, that subset does not include the most important typing + * information. For example, it excludes element content models and + * attribute typing. It does expose some entity management issues, + * although entity management doesn't relate to document typing. + * + * <p> Note that SAX2 does not expose the literal text of the DTD's + * internal subset, so it will not be present in DOM trees constructed + * using this API. (Though with a good SAX2 implementation, it could + * be partially recreated...) + * + * @author David Brownell + */ +public class Consumer extends DomConsumer +{ + /** + * Constructs an unconfigured event consumer, + * as a terminus in a SAX event pipeline. + */ + // used by PipelineFactory [terminus] + public Consumer () + throws SAXException + { + super (DomDocument.class); + setHandler (new Backdoor (this)); + } + + /** + * Constructs an unconfigured event consumer, + * as a stage in a SAX event pipeline. + */ + // used by PipelineFactory [filter] + public Consumer (EventConsumer next) + throws SAXException + { + super (DomDocument.class, next); + setHandler (new Backdoor (this)); + } + + /** + * Implements the backdoors needed by DOM. + * All methods in this class use implementation-specific APIs that are + * implied by the DOM specification (needed to implement testable + * behavior) but which are excluded from the DOM specification. + */ + public static class Backdoor extends DomConsumer.Handler + { + /** + * Constructor. + * @param consumer must have been initialized to use the + * {@link DomDocument} class (or a subclass) for + * constructing DOM trees + */ + protected Backdoor (DomConsumer consumer) + throws SAXException + { super (consumer); } + + // helper routine + private DomDoctype getDoctype () + throws SAXException + { + DomDocument doc = (DomDocument) getDocument (); + DocumentType dt = doc.getDoctype (); + + if (dt == null) + throw new SAXException ("doctype missing!"); + return (DomDoctype) dt; + } + + // SAX2 "lexical" event + public void startDTD (String name, String publicId, String systemId) + throws SAXException + { + DomDocument doc = (DomDocument) getDocument (); + + super.startDTD (name, publicId, systemId); + // DOM L2 doctype creation model is bizarre + DomDoctype dt = new DomDoctype (doc, name, publicId, systemId); + doc.appendChild (dt); + } + + // SAX2 "lexical" event + public void endDTD () + throws SAXException + { + super.endDTD (); + // DOM L2 has no way to make things readonly + getDoctype ().makeReadonly (); + } + + // SAX1 DTD event + public void notationDecl ( + String name, + String publicId, String systemId + ) throws SAXException + { + // DOM L2 can't create/save notation nodes + getDoctype ().declareNotation (name, publicId, systemId); + } + + // SAX1 DTD event + public void unparsedEntityDecl ( + String name, + String publicId, String systemId, + String notationName + ) throws SAXException + { + // DOM L2 can't create/save entity nodes + getDoctype ().declareEntity (name, publicId, systemId, + notationName); + } + + // SAX2 declaration event + public void internalEntityDecl (String name, String value) + throws SAXException + { + // DOM L2 can't create/save entity nodes + // NOTE: this doesn't save the value as a child of this + // node, though it could realistically do so. + getDoctype ().declareEntity (name, null, null, null); + } + + // SAX2 declaration event + public void externalEntityDecl ( + String name, + String publicId, + String systemId + ) throws SAXException + { + // DOM L2 can't create/save entity nodes + // NOTE: DOM allows for these to have children, if + // they don't have unbound namespace references. + getDoctype ().declareEntity (name, publicId, systemId, null); + } + + // SAX2 element + public void startElement ( + String uri, + String localName, + String qName, + Attributes atts + ) throws SAXException + { + Node top; + + super.startElement (uri, localName, qName, atts); + + // might there be more work? + top = getTop (); + if (!top.hasAttributes () || !(atts instanceof Attributes2)) + return; + + // remember any attributes that got defaulted + DomNamedNodeMap map = (DomNamedNodeMap) top.getAttributes (); + Attributes2 attrs = (Attributes2) atts; + int length = atts.getLength (); + + //map.compact (); + for (int i = 0; i < length; i++) { + if (attrs.isSpecified (i)) + continue; + + // value was defaulted. + String temp = attrs.getQName (i); + DomAttr attr; + + if ("".equals (temp)) + attr = (DomAttr) map.getNamedItemNS (attrs.getURI (i), + atts.getLocalName (i)); + else + attr = (DomAttr) map.getNamedItem (temp); + + // DOM L2 can't write this flag, only read it + attr.setSpecified (false); + } + } + + public void endElement ( + String uri, + String localName, + String qName + ) throws SAXException + { + DomNode top = (DomNode) getTop (); + top.compact (); + super.endElement (uri, localName, qName); + } + + protected Text createText ( + boolean isCDATA, + char buf [], + int off, + int len + ) { + DomDocument doc = (DomDocument) getDocument (); + + if (isCDATA) + return doc.createCDATASection (buf, off, len); + else + return doc.createTextNode (buf, off, len); + } + + public void elementDecl(String name, String model) + throws SAXException + { + getDoctype().elementDecl(name, model); + } + + public void attributeDecl ( + String ename, + String aname, + String type, + String mode, + String value + ) throws SAXException + { + getDoctype().attributeDecl(ename, aname, type, mode, value); + /* + if (value == null && !"ID".equals (type)) + return; + + DomDoctype.ElementInfo info; + + info = getDoctype ().getElementInfo (ename); + if (value != null) + info.setAttrDefault (aname, value); + if ("ID".equals (type)) + info.setIdAttr (aname); + */ + + } + + // force duplicate name checking off while we're + // using parser output (don't duplicate the work) + public void startDocument () throws SAXException + { + super.startDocument (); + + DomDocument doc = (DomDocument) getDocument (); + doc.setStrictErrorChecking(false); + doc.setBuilding(true); + } + + /** + * Required by DOM Level 3 to report document parameters + */ + public void xmlDecl(String version, + String encoding, + boolean standalone, + String inputEncoding) + throws SAXException + { + super.xmlDecl(version, encoding, standalone, inputEncoding); + + DomDocument doc = (DomDocument) getDocument(); + doc.setXmlEncoding(encoding); + doc.setInputEncoding(inputEncoding); + } + + public void endDocument () + throws SAXException + { + DomDocument doc = (DomDocument) getDocument (); + doc.setStrictErrorChecking(true); + doc.setBuilding(false); + doc.compact (); + DomDoctype doctype = (DomDoctype) doc.getDoctype(); + if (doctype != null) + { + doctype.makeReadonly(); + } + super.endDocument (); + } + + // these three methods collaborate to populate entity + // refs, marking contents readonly on end-of-entity + + public boolean canPopulateEntityRefs () + { return true; } + + public void startEntity (String name) + throws SAXException + { + if (name.charAt (0) == '%' || "[dtd]".equals (name)) + return; + super.startEntity (name); + + DomNode top = (DomNode) getTop (); + + if (top.getNodeType () == Node.ENTITY_REFERENCE_NODE) + top.readonly = false; + } + + public void endEntity (String name) + throws SAXException + { + if (name.charAt (0) == '%' || "[dtd]".equals (name)) + return; + DomNode top = (DomNode) getTop (); + + if (top.getNodeType () == Node.ENTITY_REFERENCE_NODE) { + top.compact (); + top.makeReadonly (); + } + super.endEntity (name); + } + } +} diff --git a/libjava/gnu/xml/dom/DTDAttributeTypeInfo.java b/libjava/gnu/xml/dom/DTDAttributeTypeInfo.java new file mode 100644 index 0000000..53e5fbd --- /dev/null +++ b/libjava/gnu/xml/dom/DTDAttributeTypeInfo.java @@ -0,0 +1,84 @@ +/* DTDAttributeTypeInfo.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import org.w3c.dom.TypeInfo; + +/** + * Attribute type information supplied by a DTD attribute declaration. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class DTDAttributeTypeInfo + implements TypeInfo +{ + + final String elementName; + final String name; + final String type; + final String mode; + final String value; + + DTDAttributeTypeInfo(String elementName, String name, String type, + String mode, String value) + { + this.elementName = elementName; + this.name = name; + this.type = type; + this.mode = mode; + this.value = value; + } + + public final String getTypeName() + { + return type; + } + + public final String getTypeNamespace() + { + return "http://www.w3.org/TR/REC-xml"; + } + + public final boolean isDerivedFrom(String typeNamespace, String typeName, + int derivationMethod) + { + return false; + } + +} + diff --git a/libjava/gnu/xml/dom/DTDElementTypeInfo.java b/libjava/gnu/xml/dom/DTDElementTypeInfo.java new file mode 100644 index 0000000..535ea3c --- /dev/null +++ b/libjava/gnu/xml/dom/DTDElementTypeInfo.java @@ -0,0 +1,112 @@ +/* DTDElementTypeInfo.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import java.util.HashMap; +import java.util.Iterator; +import org.w3c.dom.TypeInfo; + +/** + * Element type information provided by a DTD element declaration. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class DTDElementTypeInfo + implements TypeInfo +{ + + final String name; + String model; + HashMap attributes; + String idAttrName; + + DTDElementTypeInfo(String name, String model) + { + this.name = name; + this.model = model; + } + + public final String getTypeName() + { + return null; + } + + public final String getTypeNamespace() + { + return "http://www.w3.org/TR/REC-xml"; + } + + public final boolean isDerivedFrom(String typeNamespace, String typeName, + int derivationMethod) + { + return false; + } + + DTDAttributeTypeInfo getAttributeTypeInfo(String name) + { + if (attributes == null) + { + return null; + } + return (DTDAttributeTypeInfo) attributes.get(name); + } + + void setAttributeTypeInfo(String name, DTDAttributeTypeInfo info) + { + if (attributes == null) + { + attributes = new HashMap(); + } + attributes.put(name, info); + if ("ID".equals(info.type)) + { + idAttrName = name; + } + } + + Iterator attributes() + { + if (attributes == null) + { + return null; + } + return attributes.values().iterator(); + } + +} + diff --git a/libjava/gnu/xml/dom/DomAttr.java b/libjava/gnu/xml/dom/DomAttr.java new file mode 100644 index 0000000..2890430 --- /dev/null +++ b/libjava/gnu/xml/dom/DomAttr.java @@ -0,0 +1,379 @@ +/* DomAttr.java -- + Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.TypeInfo; +import org.w3c.dom.events.MutationEvent; + + +/** + * <p> "Attr" implementation. In DOM, attributes cost quite a lot of + * memory because their values are complex structures rather than just + * simple strings. To reduce your costs, avoid having more than one + * child of an attribute; stick to a single Text node child, and ignore + * even that by using the attribute's "nodeValue" property.</p> + * + * <p> As a bit of general advice, only look at attribute modification + * events through the DOMAttrModified event (sent to the associated + * element). Implementations are not guaranteed to report other events + * in the same order, so you're very likely to write nonportable code if + * you monitor events at the "children of Attr" level.</p> + * + * <p> At this writing, not all attribute modifications will cause the + * DOMAttrModified event to be triggered ... only the ones using the string + * methods (setNodeValue, setValue, and Element.setAttribute) to modify + * those values. That is, if you manipulate those children directly, + * elements won't get notified that attribute values have changed. + * The natural fix for that will report other modifications, but won't + * be able to expose "previous" attribute value; it'll need to be cached + * or something (at which point why bother using child nodes). </p> + * + * <p><em>You are strongly advised not to use "children" of any attribute + * nodes you work with.</em> </p> + * + * @author David Brownell + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class DomAttr + extends DomNsNode + implements Attr +{ + + private boolean specified; + private String value; // string value cache + + /** + * Constructs an Attr node associated with the specified document. + * The "specified" flag is initialized to true, since this DOM has + * no current "back door" mechanisms to manage default values so + * that every value must effectively be "specified". + * + * <p>This constructor should only be invoked by a Document as part of + * its createAttribute functionality, or through a subclass which is + * similarly used in a "Sub-DOM" style layer. + * + * @param owner The document with which this node is associated + * @param namespaceURI Combined with the local part of the name, + * this is used to uniquely identify a type of attribute + * @param name Name of this attribute, which may include a prefix + */ + protected DomAttr(DomDocument owner, String namespaceURI, String name) + { + super(ATTRIBUTE_NODE, owner, namespaceURI, name); + specified = true; + length = 1; + + // XXX register self to get insertion/removal events + // and character data change events and when they happen, + // report self-mutation + } + + /** + * <b>DOM L1</b> + * Returns the attribute name (same as getNodeName) + */ + public final String getName() + { + return getNodeName(); + } + + /** + * <b>DOM L1</b> + * Returns true if a parser reported this was in the source text. + */ + public final boolean getSpecified() + { + return specified; + } + + /** + * Records whether this attribute was in the source text. + */ + public final void setSpecified(boolean value) + { + specified = value; + } + + /** + * <b>DOM L1</b> + * Returns the attribute value, with character and entity + * references substituted. + * <em>NOTE: entity refs as children aren't currently handled.</em> + */ + public String getNodeValue() + { + // If we have a simple node-value, use that + if (first == null) + { + return (value == null) ? "" : value; + } + // Otherwise collect child node-values + StringBuffer buf = new StringBuffer(); + for (DomNode ctx = first; ctx != null; ctx = ctx.next) + { + switch (ctx.nodeType) + { + case Node.TEXT_NODE: + buf.append(ctx.getNodeValue()); + break; + case Node.ENTITY_REFERENCE_NODE: + // TODO + break; + } + } + return buf.toString(); + } + + /** + * <b>DOM L1</b> + * Assigns the value of the attribute; it will have one child, + * which is a text node with the specified value (same as + * setNodeValue). + */ + public final void setValue(String value) + { + setNodeValue(value); + } + + /** + * <b>DOM L1</b> + * Returns the value of the attribute as a non-null string; same + * as getNodeValue. + * <em>NOTE: entity refs as children aren't currently handled.</em> + */ + public final String getValue() + { + return getNodeValue(); + } + + /** + * <b>DOM L1</b> + * Assigns the attribute value; using this API, no entity or + * character references will exist. + * Causes a DOMAttrModified mutation event to be sent. + */ + public void setNodeValue(String value) + { + if (readonly) + { + throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR); + } + if (value == null) + { + value = ""; + } + String oldValue = getNodeValue(); + while (last != null) + { + removeChild(last); + } + // don't create a new node just for this... + /* + Node text = owner.createTextNode(value); + appendChild(text); + */ + this.value = value; + length = 1; + specified = true; + + mutating(oldValue, value, MutationEvent.MODIFICATION); + } + + public final Node getFirstChild() + { + // Create a child text node if necessary + if (first == null) + { + length = 0; + Node text = owner.createTextNode((value == null) ? "" : value); + appendChild(text); + } + return first; + } + + public final Node getLastChild() + { + // Create a child text node if necessary + if (last == null) + { + length = 0; + Node text = owner.createTextNode((value == null) ? "" : value); + appendChild(text); + } + return last; + } + + public Node item(int index) + { + // Create a child text node if necessary + if (first == null) + { + length = 0; + Node text = owner.createTextNode((value == null) ? "" : value); + appendChild(text); + } + return super.item(index); + } + + /** + * <b>DOM L2</b> + * Returns the element with which this attribute is associated. + */ + public final Element getOwnerElement() + { + return (Element) parent; + } + + public final Node getNextSibling() + { + return null; + } + + public final Node getPreviousSibling() + { + return null; + } + + public Node getParentNode() + { + return null; + } + + /** + * Records the element with which this attribute is associated. + */ + public final void setOwnerElement(Element e) + { + if (parent != null) + { + throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR); + } + if (!(e instanceof DomElement)) + { + throw new DomEx(DomEx.WRONG_DOCUMENT_ERR); + } + parent = (DomElement) e; + depth = parent.depth + 1; + } + + /** + * The base URI of an Attr is always <code>null</code>. + */ + public final String getBaseURI() + { + return null; + } + + /** + * Shallow clone of the attribute, breaking all ties with any + * elements. + */ + public Object clone() + { + DomAttr retval = (DomAttr) super.clone(); + retval.specified = true; + return retval; + } + + private void mutating(String oldValue, String newValue, short why) + { + if (!reportMutations || parent == null) + { + return; + } + + // EVENT: DOMAttrModified, target = parent, + // prev/new values provided, also attr name + MutationEvent event; + + event = (MutationEvent) createEvent ("MutationEvents"); + event.initMutationEvent ("DOMAttrModified", + true /* bubbles */, false /* nocancel */, + null, oldValue, newValue, getNodeName (), why); + parent.dispatchEvent (event); + } + + // DOM Level 3 methods + + public TypeInfo getSchemaTypeInfo() + { + if (parent != null) + { + // DTD implementation + DomDoctype doctype = (DomDoctype) parent.owner.getDoctype(); + if (doctype != null) + { + return doctype.getAttributeTypeInfo(parent.getNodeName(), + getNodeName()); + } + // TODO XML Schema implementation + } + return null; + } + + public boolean isId() + { + if (parent != null) + { + DomDoctype doctype = (DomDoctype) parent.owner.getDoctype(); + if (doctype != null) + { + DTDAttributeTypeInfo info = + doctype.getAttributeTypeInfo(parent.getNodeName(), + getNodeName()); + if (info != null && "ID".equals(info.type)) + { + return true; + } + } + DomElement element = (DomElement) parent; + if (element.userIdAttrs != null && + element.userIdAttrs.contains(this)) + { + return true; + } + // TODO XML Schema implementation + } + return false; + } + +} + diff --git a/libjava/gnu/xml/dom/DomCDATA.java b/libjava/gnu/xml/dom/DomCDATA.java new file mode 100644 index 0000000..f6f471c6 --- /dev/null +++ b/libjava/gnu/xml/dom/DomCDATA.java @@ -0,0 +1,91 @@ +/* DomCDATA.java -- + Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import org.w3c.dom.CDATASection; + +/** + * <p> "CDATASection" implementation. + * This is a non-core DOM class, supporting the "XML" feature. + * CDATA sections are just ways to represent text using different + * delimeters. </p> + * + * <p> <em>You are strongly advised not to use CDATASection nodes.</em> + * The advantage of having slightly prettier ways to print text that may + * have lots of embedded XML delimiters, such as "&" and "<", + * can be dwarfed by the cost of dealing with multiple kinds of text + * nodes in all your algorithms. </p> + * + * @author David Brownell + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class DomCDATA + extends DomText + implements CDATASection +{ + + /** + * Constructs a CDATA section node associated with the specified + * document and holding the specified data. + * + * <p>This constructor should only be invoked by a Document as part of + * its createCDATASection functionality, or through a subclass which is + * similarly used in a "Sub-DOM" style layer. + * + */ + protected DomCDATA(DomDocument owner, String value) + { + super(CDATA_SECTION_NODE, owner, value); + } + + protected DomCDATA(DomDocument owner, char buf [], int off, int len) + { + super(CDATA_SECTION_NODE, owner, buf, off, len); + } + + /** + * <b>DOM L1</b> + * Returns the string "#cdata-section". + */ + final public String getNodeName() + { + return "#cdata-section"; + } + +} + diff --git a/libjava/gnu/xml/dom/DomCharacterData.java b/libjava/gnu/xml/dom/DomCharacterData.java new file mode 100644 index 0000000..48ccb82 --- /dev/null +++ b/libjava/gnu/xml/dom/DomCharacterData.java @@ -0,0 +1,311 @@ +/* DomCharacterData.java -- + Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import org.w3c.dom.CharacterData; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.events.MutationEvent; + + +/** + * <p> Abstract "CharacterData" implementation. This + * facilitates reusing code in classes implementing subtypes of that DOM + * interface (Text, Comment, CDATASection). </p> + * + * @author David Brownell + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public abstract class DomCharacterData + extends DomNode + implements CharacterData +{ + + private String text; + + // package private + DomCharacterData(short nodeType, DomDocument doc, String value) + { + super(nodeType, doc); + text = (value == null) ? "" : value; + } + + // package private + DomCharacterData(short nodeType, DomDocument doc, + char[] buf, int offset, int length) + { + super(nodeType, doc); + text = (buf == null) ? "" : new String(buf, offset, length); + } + + /** + * <b>DOM L1</b> + * Appends the specified data to the value of this node. + * Causes a DOMCharacterDataModified mutation event to be reported. + */ + public void appendData(String arg) + { + if (isReadonly()) + { + throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR); + } + String value = text + arg; + mutating(value); + text = value; + } + + /** + * <b>DOM L1</b> + * Modifies the value of this node. + * Causes a DOMCharacterDataModified mutation event to be reported. + */ + public void deleteData(int offset, int count) + { + if (isReadonly()) + { + throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR); + } + char[] raw = text.toCharArray(); + if (offset < 0 || count < 0 || offset > raw.length) + { + throw new DomEx(DomEx.INDEX_SIZE_ERR); + } + if ((offset + count) > raw.length) + { + count = raw.length - offset; + } + if (count == 0) + { + return; + } + try + { + char[] buf = new char[raw.length - count]; + System.arraycopy(raw, 0, buf, 0, offset); + System.arraycopy(raw, offset + count, buf, offset, + raw.length - (offset + count)); + String value = new String(buf); + mutating(value); + text = value; + } + catch (IndexOutOfBoundsException x) + { + throw new DomEx(DomEx.INDEX_SIZE_ERR); + } + } + + /** + * <b>DOM L1</b> + * Returns the value of this node. + */ + public String getNodeValue() + { + return text; + } + + /** + * <b>DOM L1</b> + * Returns the value of this node; same as getNodeValue. + */ + public final String getData() + { + return text; + } + + /** + * <b>DOM L1</b> + * Returns the length of the data. + */ + public int getLength() + { + return text.length(); + } + + /** + * <b>DOM L1</b> + * Modifies the value of this node. + */ + public void insertData(int offset, String arg) + { + if (isReadonly()) + { + throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR); + } + char[] raw = text.toCharArray(); + char[] tmp = arg.toCharArray (); + char[] buf = new char[raw.length + tmp.length]; + + try + { + System.arraycopy(raw, 0, buf, 0, offset); + System.arraycopy(tmp, 0, buf, offset, tmp.length); + System.arraycopy(raw, offset, buf, offset + tmp.length, + raw.length - offset); + String value = new String(buf); + mutating(value); + text = value; + } + catch (IndexOutOfBoundsException x) + { + throw new DomEx(DomEx.INDEX_SIZE_ERR); + } + } + + /** + * <b>DOM L1</b> + * Modifies the value of this node. Causes DOMCharacterDataModified + * mutation events to be reported (at least one). + */ + public void replaceData(int offset, int count, String arg) + { + if (readonly) + { + throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR); + } + char[] raw = text.toCharArray(); + + // deleteData + if (offset < 0 || count < 0 || offset > raw.length) + { + throw new DomEx(DomEx.INDEX_SIZE_ERR); + } + if ((offset + count) > raw.length) + { + count = raw.length - offset; + } + try + { + char[] buf = new char[raw.length - count]; + System.arraycopy(raw, 0, buf, 0, offset); + System.arraycopy(raw, offset + count, buf, offset, + raw.length - (offset + count)); + + // insertData + char[] tmp = arg.toCharArray (); + char[] buf2 = new char[buf.length + tmp.length]; + System.arraycopy(raw, 0, buf, 0, offset); + System.arraycopy(tmp, 0, buf, offset, tmp.length); + System.arraycopy(raw, offset, buf, offset + tmp.length, + raw.length - offset); + String value = new String(buf); + mutating(value); + text = value; + } + catch (IndexOutOfBoundsException x) + { + throw new DomEx(DomEx.INDEX_SIZE_ERR); + } + } + + /** + * <b>DOM L1</b> + * Assigns the value of this node. + * Causes a DOMCharacterDataModified mutation event to be reported. + */ + public void setNodeValue(String value) + { + if (isReadonly()) + { + throw new DomEx (DomEx.NO_MODIFICATION_ALLOWED_ERR); + } + if (value == null) + { + value = ""; + } + mutating(value); + text = value; + } + + /** + * <b>DOM L1</b> + * Assigns the value of this node; same as setNodeValue. + */ + final public void setData(String data) + { + setNodeValue(data); + } + + /** + * <b>DOM L1</b> + * Returns the specified substring. + */ + public String substringData(int offset, int count) + { + try + { + return text.substring(offset, count); + } + catch (StringIndexOutOfBoundsException e) + { + if (offset >= 0 && count >= 0 && offset < text.length()) + { + return text.substring(offset); + } + throw new DomEx(DomEx.INDEX_SIZE_ERR); + } + } + + /** + * The base URI for character data is <code>null</code>. + * @since DOM Level 3 Core + */ + public final String getBaseURI() + { + return null; + } + + private void mutating(String newValue) + { + if (!reportMutations) + { + return; + } + + // EVENT: DOMCharacterDataModified, target = this, + // prev/new values provided + MutationEvent event; + + event = (MutationEvent) createEvent("MutationEvents"); + event.initMutationEvent("DOMCharacterDataModified", + true /* bubbles */, false /* nocancel */, + null, text, newValue, null, (short) 0); + dispatchEvent(event); + } + +} + diff --git a/libjava/gnu/xml/dom/DomComment.java b/libjava/gnu/xml/dom/DomComment.java new file mode 100644 index 0000000..d1d84d1 --- /dev/null +++ b/libjava/gnu/xml/dom/DomComment.java @@ -0,0 +1,81 @@ +/* DomComment.java -- + Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import org.w3c.dom.Comment; + +/** + * <p> "Comment" implementation. + * Comments hold data intended for direct consumption by people; + * programs should only use ProcessingInstruction nodes. Note that + * since SAX makes comment reporting optional, XML systems that + * rely on comments (such as by using this class) will often lose + * those comments at some point in the processing pipeline. </p> + * + * @author David Brownell + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class DomComment + extends DomCharacterData + implements Comment +{ + + /** + * Constructs a comment node associated with the specified + * document and holding the specified data. + * + * <p>This constructor should only be invoked by a Document as part of + * its createComment functionality, or through a subclass which is + * similarly used in a "Sub-DOM" style layer. + */ + protected DomComment(DomDocument owner, String value) + { + super(COMMENT_NODE, owner, value); + } + + /** + * <b>DOM L1</b> + * Returns the string "#comment". + */ + final public String getNodeName() + { + return "#comment"; + } + +} + diff --git a/libjava/gnu/xml/dom/DomDoctype.java b/libjava/gnu/xml/dom/DomDoctype.java new file mode 100644 index 0000000..35fb613 --- /dev/null +++ b/libjava/gnu/xml/dom/DomDoctype.java @@ -0,0 +1,454 @@ +/* DomDoctype.java -- + Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import java.util.HashMap; +import org.w3c.dom.DocumentType; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Entity; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.Notation; + +/** + * <p> "DocumentType" implementation (with no extensions for supporting + * any document typing information). This is a non-core DOM class, + * supporting the "XML" feature. </p> + * + * <p> <em>Few XML applications will actually care about this partial + * DTD support</em>, since it doesn't expose any (!) of the data typing + * facilities which can motivate applications to use DTDs. It does not + * expose element content models, or information about attribute typing + * rules. Plus the information it exposes isn't very useful; as one example, + * DOM exposes information about unparsed ENTITY objects, which is only used + * with certain element attributes, but does not expose the information about + * those attributes which is needed to apply that data! </p> + * + * <p> Also, note that there are no nonportable ways to associate even the + * notation and entity information exposed by DOM with a DocumentType. While + * there is a DOM L2 method to construct a DocumentType, it only gives access + * to the textual content of the <!DOCTYPE ...> declaration. </p> + * + * <p> In short, <em>you are strongly advised not to rely on this incomplete + * DTD functionality</em> in your application code.</p> + * + * @see DomEntity + * @see DomEntityReference + * @see DomNotation + * + * @author David Brownell + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class DomDoctype + extends DomExtern + implements DocumentType +{ + + private DomNamedNodeMap notations; + private DomNamedNodeMap entities; + private final DOMImplementation implementation; + private String subset; + + private HashMap elements = new HashMap(); + private boolean ids; + + /** + * Constructs a DocumentType node associated with the specified + * implementation, with the specified name. + * + * <p>This constructor should only be invoked by a DOMImplementation as + * part of its createDocumentType functionality, or through a subclass + * which is similarly used in a "Sub-DOM" style layer. + * + * <p> Note that at this time there is no standard SAX API granting + * access to the internal subset text, so that relying on that value + * is not currently portable. + * + * @param impl The implementation with which this object is associated + * @param name Name of this root element + * @param publicId If non-null, provides the external subset's + * PUBLIC identifier + * @param systemId If non-null, provides the external subset's + * SYSTEM identifier + * @param internalSubset Provides the literal value (unparsed, no + * entities expanded) of the DTD's internal subset. + */ + protected DomDoctype(DOMImplementation impl, + String name, + String publicId, + String systemId, + String internalSubset) + { + super(DOCUMENT_TYPE_NODE, null, name, publicId, systemId); + implementation = impl; + subset = internalSubset; + } + + /** + * JAXP builder constructor. + * @param doc the document + * @param name the name of the document element + * @param publicId the public ID of the document type declaration + * @param systemId the system ID of the document type declaration + */ + public DomDoctype(DomDocument doc, + String name, + String publicId, + String systemId) + { + super(DOCUMENT_TYPE_NODE, doc, name, publicId, systemId); + implementation = doc.getImplementation(); + } + + /** + * <b>DOM L1</b> + * Returns the root element's name (just like getNodeName). + */ + final public String getName() + { + return getNodeName(); + } + + /** + * <b>DOM L1</b> + * Returns information about any general entities declared + * in the DTD. + * + * <p><em>Note: DOM L1 doesn't throw a DOMException here, but + * then it doesn't have the strange construction rules of L2.</em> + * + * @exception DOMException HIERARCHY_REQUEST_ERR if the DocumentType + * is not associated with a document. + */ + public NamedNodeMap getEntities() + { + if (entities == null) + { + entities = new DomNamedNodeMap(this, Node.ENTITY_NODE); + } + return entities; + } + + /** + * Records the declaration of a general entity in this DocumentType. + * + * @param name Name of the entity + * @param publicId If non-null, provides the entity's PUBLIC identifier + * @param systemId Provides the entity's SYSTEM identifier + * @param notation If non-null, provides the entity's notation + * (indicating an unparsed entity) + * @return The Entity that was declared, or null if the entity wasn't + * recorded (because it's a parameter entity or because an entity with + * this name was already declared). + * + * @exception DOMException NO_MODIFICATION_ALLOWED_ERR if the + * DocumentType is no longer writable. + * @exception DOMException HIERARCHY_REQUEST_ERR if the DocumentType + * is not associated with a document. + */ + public Entity declareEntity(String name, + String publicId, + String systemId, + String notation) + { + DomEntity entity; + + if (name.charAt(0) == '%' || "[dtd]".equals(name)) + { + return null; + } + if (isReadonly()) + { + throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR); + } + getEntities(); + + DomDocument.checkName(name, (owner != null) ? + "1.1".equals(owner.getXmlVersion()) : false); + if (entities.getNamedItem(name) != null) + { + return null; + } + + entity = new DomEntity(owner, name, publicId, systemId, notation); + entities.setNamedItem(entity); + return entity; + } + + /** + * <b>DOM L1</b> + * Returns information about any notations declared in the DTD. + * + * <p><em>Note: DOM L1 doesn't throw a DOMException here, but + * then it doesn't have the strange construction rules of L2.</em> + * + * @exception DOMException HIERARCHY_REQUEST_ERR if the DocumentType + * is not associated with a document. + */ + public NamedNodeMap getNotations() + { + if (notations == null) + { + notations = new DomNamedNodeMap(this, Node.NOTATION_NODE); + } + return notations; + } + + /** + * Records the declaration of a notation in this DocumentType. + * + * @param name Name of the notation + * @param publicId If non-null, provides the notation's PUBLIC identifier + * @param systemId If non-null, provides the notation's SYSTEM identifier + * @return The notation that was declared. + * + * @exception DOMException NO_MODIFICATION_ALLOWED_ERR if the + * DocumentType is no longer writable. + * @exception DOMException HIERARCHY_REQUEST_ERR if the DocumentType + * is not associated with a document. + */ + public Notation declareNotation(String name, + String publicId, + String systemId) + { + DomNotation notation; + + if (isReadonly()) + { + throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR); + } + getNotations(); + + DomDocument.checkName(name, (owner != null) ? + "1.1".equals(owner.getXmlVersion()) : false); + + notation = new DomNotation(owner, name, publicId, systemId); + notations.setNamedItem(notation); + return notation; + } + + /** + * <b>DOM L2</b> + * Returns the internal subset of the document, as a string of unparsed + * XML declarations (and comments, PIs, whitespace); or returns null if + * there is no such subset. There is no vendor-independent expectation + * that this attribute be set, or that declarations found in it be + * reflected in the <em>entities</em> or <em>notations</em> attributes + * of this Document "Type" object. + * + * <p> Some application-specific XML profiles require that documents + * only use specific PUBLIC identifiers, without an internal subset + * to modify the interperetation of the declarations associated with + * that PUBLIC identifier through some standard. + */ + public String getInternalSubset() + { + return subset; + } + + /** + * The base URI of a DocumentType is always <code>null</code>. + * See the Infoset Mapping, appendix C. + */ + public final String getBaseURI() + { + return null; + } + + /** + * Sets the internal "readonly" flag so the node and its associated + * data (only lists of entities and notations, no type information + * at the moment) can't be changed. + */ + public void makeReadonly() + { + super.makeReadonly(); + if (entities != null) + { + entities.makeReadonly(); + } + if (notations != null) + { + notations.makeReadonly(); + } + } + + void setOwner(DomDocument doc) + { + if (entities != null) + { + for (DomNode ctx = entities.first; ctx != null; ctx = ctx.next) + { + ctx.setOwner(doc); + } + } + if (notations != null) + { + for (DomNode ctx = notations.first; ctx != null; ctx = ctx.next) + { + ctx.setOwner(doc); + } + } + super.setOwner(doc); + } + + /** + * <b>DOM L2</b> + * Consults the DOM implementation to determine if the requested + * feature is supported. + */ + final public boolean supports(String feature, String version) + { + return implementation.hasFeature(feature, version); + } + + /** + * Returns the implementation associated with this document type. + */ + final public DOMImplementation getImplementation() + { + return implementation; + } + + public void elementDecl(String name, String model) + { + DTDElementTypeInfo info = getElementTypeInfo(name); + if (info == null) + { + info = new DTDElementTypeInfo(name, model); + elements.put(name, info); + } + else + { + info.model = model; + } + } + + DTDElementTypeInfo getElementTypeInfo(String name) + { + return (DTDElementTypeInfo) elements.get(name); + } + + public void attributeDecl(String eName, String aName, String type, + String mode, String value) + { + DTDAttributeTypeInfo info = new DTDAttributeTypeInfo(eName, aName, type, + mode, value); + DTDElementTypeInfo elementInfo = getElementTypeInfo(eName); + if (elementInfo == null) + { + elementInfo = new DTDElementTypeInfo(eName, null); + elements.put(eName, elementInfo); + } + elementInfo.setAttributeTypeInfo(aName, info); + if ("ID".equals(type)) + { + ids = true; + } + } + + DTDAttributeTypeInfo getAttributeTypeInfo(String elementName, String name) + { + DTDElementTypeInfo elementInfo = + (DTDElementTypeInfo) elements.get(elementName); + return (elementInfo == null) ? null : + elementInfo.getAttributeTypeInfo(name); + } + + boolean hasIds() + { + return ids; + } + + public boolean isSameNode(Node arg) + { + if (equals(arg)) + { + return true; + } + if (!(arg instanceof DocumentType)) + { + return false; + } + DocumentType doctype = (DocumentType) arg; + if (!equal(getPublicId(), doctype.getPublicId())) + { + return false; + } + if (!equal(getSystemId(), doctype.getSystemId())) + { + return false; + } + if (!equal(getInternalSubset(), doctype.getInternalSubset())) + { + return false; + } + // TODO entities + // TODO notations + return true; + } + + /** + * Shallow clone of the doctype, except that associated + * entities and notations are (deep) cloned. + */ + public Object clone() + { + DomDoctype node = (DomDoctype) super.clone(); + + if (entities != null) + { + node.entities = new DomNamedNodeMap(node, Node.ENTITY_NODE); + for (DomNode ctx = entities.first; ctx != null; ctx = ctx.next) + { + node.entities.setNamedItem(ctx.cloneNode(true)); + } + } + if (notations != null) + { + node.notations = new DomNamedNodeMap(node, Node.NOTATION_NODE); + for (DomNode ctx = notations.first; ctx != null; ctx = ctx.next) + { + node.notations.setNamedItem(ctx.cloneNode(true)); + } + } + return node; + } + + +} diff --git a/libjava/gnu/xml/dom/DomDocument.java b/libjava/gnu/xml/dom/DomDocument.java new file mode 100644 index 0000000..10693ae --- /dev/null +++ b/libjava/gnu/xml/dom/DomDocument.java @@ -0,0 +1,1376 @@ +/* DomDocument.java -- + Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import java.util.Iterator; +import javax.xml.XMLConstants; + +import org.w3c.dom.Attr; +import org.w3c.dom.CDATASection; +import org.w3c.dom.Comment; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; +import org.w3c.dom.DocumentType; +import org.w3c.dom.DOMConfiguration; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.DOMException; +import org.w3c.dom.Element; +import org.w3c.dom.Entity; +import org.w3c.dom.EntityReference; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.Notation; +import org.w3c.dom.ProcessingInstruction; +import org.w3c.dom.Text; +import org.w3c.dom.UserDataHandler; +import org.w3c.dom.traversal.DocumentTraversal; +import org.w3c.dom.traversal.NodeFilter; +import org.w3c.dom.traversal.NodeIterator; +import org.w3c.dom.traversal.TreeWalker; +import org.w3c.dom.xpath.XPathEvaluator; +import org.w3c.dom.xpath.XPathException; +import org.w3c.dom.xpath.XPathExpression; +import org.w3c.dom.xpath.XPathNSResolver; + +/** + * <p> "Document" and "DocumentTraversal" implementation. + * + * <p> Note that when this checks names for legality, it uses an + * approximation of the XML rules, not the real ones. Specifically, + * it uses Unicode rules, with sufficient tweaks to pass a majority + * of basic XML conformance tests. (The huge XML character tables are + * hairy to implement.) + * + * @author David Brownell + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class DomDocument + extends DomNode + implements Document, DocumentTraversal, XPathEvaluator +{ + + private final DOMImplementation implementation; + private boolean checkingCharacters = true; + boolean checkingWellformedness = true; + + boolean building; // if true, skip mutation events in the tree + + DomDocumentConfiguration config; + + String inputEncoding; + String encoding; + String version = "1.0"; + boolean standalone; + String systemId; + + /** + * Constructs a Document node, associating it with an instance + * of the DomImpl class. + * + * <p> Note that this constructor disables character checking. + * It is normally used when connecting a DOM to an XML parser, + * and duplicating such checks is undesirable. When used for + * purposes other than connecting to a parser, you should + * re-enable that checking. + * + * @see #setCheckingCharacters + */ + public DomDocument() + { + this(new DomImpl()); + } + + /** + * Constructs a Document node, associating it with the specified + * implementation. This should only be used in conjunction with + * a specialized implementation; it will normally be called by + * that implementation. + * + * @see DomImpl + * @see #setCheckingCharacters + */ + protected DomDocument(DOMImplementation impl) + { + super(DOCUMENT_NODE, null); + implementation = impl; + } + + /** + * Sets the <code>building</code> flag. + * Mutation events in the document are not reported. + */ + public void setBuilding(boolean flag) + { + building = flag; + } + + /** + * Sets whether to check for document well-formedness. + * If true, an exception will be raised if a second doctype or root + * element node is added to the document. + */ + public void setCheckWellformedness(boolean flag) + { + checkingWellformedness = flag; + } + + /** + * <b>DOM L1</b> + * Returns the constant "#document". + */ + final public String getNodeName() + { + return "#document"; + } + + /** + * <b>DOM L1</b> + * Returns the document's root element, or null. + */ + final public Element getDocumentElement() + { + for (DomNode ctx = first; ctx != null; ctx = ctx.next) + { + if (ctx.nodeType == ELEMENT_NODE) + { + return (Element) ctx; + } + } + return null; + } + + /** + * <b>DOM L1</b> + * Returns the document's DocumentType, or null. + */ + final public DocumentType getDoctype() + { + for (DomNode ctx = first; ctx != null; ctx = ctx.next) + { + if (ctx.nodeType == DOCUMENT_TYPE_NODE) + { + return (DocumentType) ctx; + } + } + return null; + } + + /** + * <b>DOM L1</b> + * Returns the document's DOMImplementation. + */ + final public DOMImplementation getImplementation() + { + return implementation; + } + + /** + * <b>DOM L1 (relocated in DOM L2)</b> + * Returns the element with the specified "ID" attribute, or null. + * + * <p>Returns null unless {@link Consumer} was used to populate internal + * DTD declaration information, using package-private APIs. If that + * internal DTD information is available, the document may be searched for + * the element with that ID. + */ + public Element getElementById(String id) + { + DomDoctype doctype = (DomDoctype) getDoctype(); + + if (doctype == null || !doctype.hasIds() + || id == null || id.length() == 0) + { + return null; + } + + // yes, this is linear in size of document. + // it'd be easy enough to maintain a hashtable. + Node current = getDocumentElement(); + Node temp; + + if (current == null) + { + return null; + } + while (current != this) + { + // done? + if (current.getNodeType() == ELEMENT_NODE) + { + DomElement element = (DomElement) current; + DTDElementTypeInfo info = + doctype.getElementTypeInfo(current.getNodeName()); + if (info != null && + id.equals(element.getAttribute(info.idAttrName))) + { + return element; + } + else if (element.userIdAttrs != null) + { + for (Iterator i = element.userIdAttrs.iterator(); + i.hasNext(); ) + { + Node idAttr = (Node) i.next(); + if (id.equals(idAttr.getNodeValue())) + { + return element; + } + } + } + } + + // descend? + if (current.hasChildNodes()) + { + current = current.getFirstChild(); + continue; + } + + // lateral? + temp = current.getNextSibling(); + if (temp != null) + { + current = temp; + continue; + } + + // back up ... + do + { + temp = current.getParentNode(); + if (temp == null) + { + return null; + } + current = temp; + temp = current.getNextSibling(); + } + while (temp == null); + current = temp; + } + return null; + } + + private void checkNewChild(Node newChild) + { + if (newChild.getNodeType() == ELEMENT_NODE + && getDocumentElement() != null) + { + throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR, + "document element already present: " + + getDocumentElement(), newChild, 0); + } + if (newChild.getNodeType() == DOCUMENT_TYPE_NODE + && getDoctype() != null) + { + throw new DomEx (DomEx.HIERARCHY_REQUEST_ERR, + "document type already present: " + + getDoctype(), newChild, 0); + } + } + + /** + * <b>DOM L1</b> + * Appends the specified node to this node's list of children, + * enforcing the constraints that there be only one root element + * and one document type child. + */ + public Node appendChild(Node newChild) + { + if (checkingWellformedness) + { + checkNewChild(newChild); + } + return super.appendChild(newChild); + } + + /** + * <b>DOM L1</b> + * Inserts the specified node in this node's list of children, + * enforcing the constraints that there be only one root element + * and one document type child. + */ + public Node insertBefore(Node newChild, Node refChild) + { + if (checkingWellformedness) + { + checkNewChild(newChild); + } + return super.insertBefore(newChild, refChild); + } + + /** + * <b>DOM L1</b> + * Replaces the specified node in this node's list of children, + * enforcing the constraints that there be only one root element + * and one document type child. + */ + public Node replaceChild(Node newChild, Node refChild) + { + if (checkingWellformedness && + ((newChild.getNodeType() == ELEMENT_NODE && + refChild.getNodeType() != ELEMENT_NODE) || + (newChild.getNodeType() == DOCUMENT_TYPE_NODE && + refChild.getNodeType() != DOCUMENT_TYPE_NODE))) + { + checkNewChild(newChild); + } + return super.replaceChild(newChild, refChild); + } + + // NOTE: DOM can't really tell when the name of an entity, + // notation, or PI must follow the namespace rules (excluding + // colons) instead of the XML rules (which allow them without + // much restriction). That's an API issue. verifyXmlName + // aims to enforce the XML rules, not the namespace rules. + + /** + * Throws a DOM exception if the specified name is not a legal XML 1.0 + * Name. + * @deprecated This method is deprecated and may be removed in future + * versions of GNU JAXP + */ + public static void verifyXmlName(String name) + { + // XXX why is this public? + checkName(name, false); + } + + static void checkName(String name, boolean xml11) + { + if (name == null) + { + throw new DomEx (DomEx.NAMESPACE_ERR, name, null, 0); + } + int len = name.length(); + if (len == 0) + { + throw new DomEx (DomEx.NAMESPACE_ERR, name, null, 0); + } + + // dog: rewritten to use the rules for XML 1.0 and 1.1 + + // Name start character + char c = name.charAt(0); + if (xml11) + { + // XML 1.1 + if ((c < 0x0041 || c > 0x005a) && + (c < 0x0061 || c > 0x007a) && + c != ':' && c != '_' && + (c < 0x00c0 || c > 0x00d6) && + (c < 0x00d8 || c > 0x00f6) && + (c < 0x00f8 || c > 0x02ff) && + (c < 0x0370 || c > 0x037d) && + (c < 0x037f || c > 0x1fff) && + (c < 0x200c || c > 0x200d) && + (c < 0x2070 || c > 0x218f) && + (c < 0x2c00 || c > 0x2fef) && + (c < 0x3001 || c > 0xd7ff) && + (c < 0xf900 || c > 0xfdcf) && + (c < 0xfdf0 || c > 0xfffd) && + (c < 0x10000 || c > 0xeffff)) + { + throw new DomEx(DomEx.INVALID_CHARACTER_ERR, name, null, c); + } + } + else + { + // XML 1.0 + int type = Character.getType(c); + switch (type) + { + case Character.LOWERCASE_LETTER: // Ll + case Character.UPPERCASE_LETTER: // Lu + case Character.OTHER_LETTER: // Lo + case Character.TITLECASE_LETTER: // Lt + case Character.LETTER_NUMBER: // Nl + if ((c > 0xf900 && c < 0xfffe) || + (c >= 0x20dd && c <= 0x20e0)) + { + // Compatibility area and Unicode 2.0 exclusions + throw new DomEx(DomEx.INVALID_CHARACTER_ERR, name, null, c); + } + break; + default: + if (c != ':' && c != '_' && (c < 0x02bb || c > 0x02c1) && + c != 0x0559 && c != 0x06e5 && c != 0x06e6) + { + throw new DomEx(DomEx.INVALID_CHARACTER_ERR, name, null, c); + } + } + } + + // Subsequent characters + for (int i = 1; i < len; i++) + { + c = name.charAt(i); + if (xml11) + { + // XML 1.1 + if ((c < 0x0041 || c > 0x005a) && + (c < 0x0061 || c > 0x007a) && + (c < 0x0030 || c > 0x0039) && + c != ':' && c != '_' && c != '-' && c != '.' && + (c < 0x00c0 || c > 0x00d6) && + (c < 0x00d8 || c > 0x00f6) && + (c < 0x00f8 || c > 0x02ff) && + (c < 0x0370 || c > 0x037d) && + (c < 0x037f || c > 0x1fff) && + (c < 0x200c || c > 0x200d) && + (c < 0x2070 || c > 0x218f) && + (c < 0x2c00 || c > 0x2fef) && + (c < 0x3001 || c > 0xd7ff) && + (c < 0xf900 || c > 0xfdcf) && + (c < 0xfdf0 || c > 0xfffd) && + (c < 0x10000 || c > 0xeffff) && + c != 0x00b7 && + (c < 0x0300 || c > 0x036f) && + (c < 0x203f || c > 0x2040)) + { + throw new DomEx(DomEx.INVALID_CHARACTER_ERR, name, null, c); + } + } + else + { + // XML 1.0 + int type = Character.getType(c); + switch (type) + { + case Character.LOWERCASE_LETTER: // Ll + case Character.UPPERCASE_LETTER: // Lu + case Character.DECIMAL_DIGIT_NUMBER: // Nd + case Character.OTHER_LETTER: // Lo + case Character.TITLECASE_LETTER: // Lt + case Character.LETTER_NUMBER: // Nl + case Character.COMBINING_SPACING_MARK: // Mc + case Character.ENCLOSING_MARK: // Me + case Character.NON_SPACING_MARK: // Mn + case Character.MODIFIER_LETTER: // Lm + if ((c > 0xf900 && c < 0xfffe) || + (c >= 0x20dd && c <= 0x20e0)) + { + // Compatibility area and Unicode 2.0 exclusions + throw new DomEx(DomEx.INVALID_CHARACTER_ERR, name, null, c); + } + break; + default: + if (c != '-' && c != '.' && c != ':' && c != '_' && + c != 0x0387 && (c < 0x02bb || c > 0x02c1) && + c != 0x0559 && c != 0x06e5 && c != 0x06e6 && c != 0x00b7) + { + throw new DomEx(DomEx.INVALID_CHARACTER_ERR, name, null, c); + } + } + } + } + + // FIXME characters with a font or compatibility decomposition (i.e. + // those with a "compatibility formatting tag" in field 5 of the + // database -- marked by field 5 beginning with a "<") are not allowed. + } + + // package private + static void checkNCName(String name, boolean xml11) + { + checkName(name, xml11); + int len = name.length(); + int index = name.indexOf(':'); + if (index != -1) + { + if (index == 0 || index == (len - 1) || + name.lastIndexOf(':') != index) + { + throw new DomEx(DomEx.NAMESPACE_ERR, name, null, 0); + } + } + } + + // package private + static void checkChar(String value, boolean xml11) + { + char[] chars = value.toCharArray(); + checkChar(chars, 0, chars.length, xml11); + } + + static void checkChar(char[] buf, int off, int len, boolean xml11) + { + for (int i = 0; i < len; i++) + { + char c = buf[i]; + + // assume surrogate pairing checks out OK, for simplicity + if ((c >= 0x0020 && c <= 0xd7ff) || + (c == 0x000a || c == 0x000d || c == 0x0009) || + (c >= 0xe000 && c <= 0xfffd) || + (c >= 0x10000 && c <= 0x10ffff)) + { + continue; + } + if (xml11) + { + if ((c >= 0x0001 && c <= 0x001f) || + (c >= 0x007f && c <= 0x0084) || + (c >= 0x0086 && c <= 0x009f)) + { + continue; + } + } + throw new DomEx(DomEx.INVALID_CHARACTER_ERR, + new String(buf, off, len), null, c); + } + } + + /** + * <b>DOM L1</b> + * Returns a newly created element with the specified name. + */ + public Element createElement(String name) + { + Element element; + + if (checkingCharacters) + { + checkName(name, "1.1".equals(version)); + } + if (name.startsWith("xml:")) + { + element = createElementNS(null, name); + } + else + { + element = new DomElement(this, null, name); + } + defaultAttributes(element, name); + return element; + } + + /** + * <b>DOM L2</b> + * Returns a newly created element with the specified name + * and namespace information. + */ + public Element createElementNS(String namespaceURI, String name) + { + if (checkingCharacters) + { + checkNCName(name, "1.1".equals(version)); + } + + if ("".equals(namespaceURI)) + { + namespaceURI = null; + } + if (name.startsWith("xml:")) + { + if (namespaceURI != null + && !XMLConstants.XML_NS_URI.equals(namespaceURI)) + { + throw new DomEx(DomEx.NAMESPACE_ERR, + "xml namespace is always " + + XMLConstants.XML_NS_URI, this, 0); + } + namespaceURI = XMLConstants.XML_NS_URI; + } + else if (XMLConstants.XMLNS_ATTRIBUTE.equals(name) || + name.startsWith("xmlns:")) + { + throw new DomEx(DomEx.NAMESPACE_ERR, + "xmlns is reserved", this, 0); + } + else if (namespaceURI == null && name.indexOf(':') != -1) + { + throw new DomEx(DomEx.NAMESPACE_ERR, + "prefixed name '" + name + "' needs a URI", this, 0); + } + + Element element = new DomElement(this, namespaceURI, name); + defaultAttributes(element, name); + return element; + } + + private void defaultAttributes(Element element, String name) + { + DomDoctype doctype = (DomDoctype) getDoctype(); + if (doctype == null) + { + return; + } + + // default any attributes that need it + DTDElementTypeInfo info = doctype.getElementTypeInfo(name); + if (info != null) + { + for (Iterator i = info.attributes(); i != null && i.hasNext(); ) + { + DTDAttributeTypeInfo attr = (DTDAttributeTypeInfo) i.next(); + DomAttr node = (DomAttr) createAttribute(attr.name); + + String value = attr.value; + if (value == null) + { + value = ""; + } + node.setValue(value); + node.setSpecified(false); + element.setAttributeNode(node); + } + } + } + + /** + * <b>DOM L1</b> + * Returns a newly created document fragment. + */ + public DocumentFragment createDocumentFragment() + { + return new DomFragment(this); + } + + /** + * <b>DOM L1</b> + * Returns a newly created text node with the specified value. + */ + public Text createTextNode(String value) + { + if (checkingCharacters) + { + checkChar(value, "1.1".equals(version)); + } + return new DomText(this, value); + } + + /** + * Returns a newly created text node with the specified value. + */ + public Text createTextNode(char[] buf, int off, int len) + { + if (checkingCharacters) + { + checkChar(buf, off, len, "1.1".equals(version)); + } + return new DomText(this, buf, off, len); + } + + /** + * <b>DOM L1</b> + * Returns a newly created comment node with the specified value. + */ + public Comment createComment(String value) + { + if (checkingCharacters) + { + checkChar(value, "1.1".equals(version)); + } + return new DomComment(this, value); + } + + /** + * <b>DOM L1</b> + * Returns a newly created CDATA section node with the specified value. + */ + public CDATASection createCDATASection(String value) + { + if (checkingCharacters) + { + checkChar(value, "1.1".equals(version)); + } + return new DomCDATA(this, value); + } + + /** + * Returns a newly created CDATA section node with the specified value. + */ + public CDATASection createCDATASection(char[] buf, int off, int len) + { + if (checkingCharacters) + { + checkChar(buf, off, len, "1.1".equals(version)); + } + return new DomCDATA(this, buf, off, len); + } + + /** + * <b>DOM L1</b> + * Returns a newly created processing instruction. + */ + public ProcessingInstruction createProcessingInstruction(String target, + String data) + { + if (checkingCharacters) + { + boolean xml11 = "1.1".equals(version); + checkName(target, xml11); + if ("xml".equalsIgnoreCase(target)) + { + throw new DomEx(DomEx.SYNTAX_ERR, + "illegal PI target name", this, 0); + } + checkChar(data, xml11); + } + return new DomPI(this, target, data); + } + + /** + * <b>DOM L1</b> + * Returns a newly created attribute with the specified name. + */ + public Attr createAttribute(String name) + { + if (checkingCharacters) + { + checkName(name, "1.1".equals(version)); + } + if (name.startsWith("xml:")) + { + return createAttributeNS(XMLConstants.XML_NS_URI, name); + } + else if (XMLConstants.XMLNS_ATTRIBUTE.equals(name) || + name.startsWith("xmlns:")) + { + return createAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, name); + } + else + { + return new DomAttr(this, null, name); + } + } + + /** + * <b>DOM L2</b> + * Returns a newly created attribute with the specified name + * and namespace information. + */ + public Attr createAttributeNS(String namespaceURI, String name) + { + if (checkingCharacters) + { + checkNCName(name, "1.1".equals(version)); + } + + if ("".equals(namespaceURI)) + { + namespaceURI = null; + } + if (name.startsWith ("xml:")) + { + if (namespaceURI == null) + { + namespaceURI = XMLConstants.XML_NS_URI; + } + else if (!XMLConstants.XML_NS_URI.equals(namespaceURI)) + { + throw new DomEx(DomEx.NAMESPACE_ERR, + "xml namespace is always " + + XMLConstants.XML_NS_URI, this, 0); + } + } + else if (XMLConstants.XMLNS_ATTRIBUTE.equals(name) || + name.startsWith("xmlns:")) + { + if (namespaceURI == null) + { + namespaceURI = XMLConstants.XMLNS_ATTRIBUTE_NS_URI; + } + else if (!XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceURI)) + { + throw new DomEx(DomEx.NAMESPACE_ERR, + "xmlns namespace must be " + + XMLConstants.XMLNS_ATTRIBUTE_NS_URI, this, 0); + } + } + else if (namespaceURI == null && name.indexOf(':') != -1) + { + throw new DomEx(DomEx.NAMESPACE_ERR, + "prefixed name needs a URI: " + name, this, 0); + } + return new DomAttr(this, namespaceURI, name); + } + + /** + * <b>DOM L1</b> + * Returns a newly created reference to the specified entity. + * The caller should populate this with the appropriate children + * and then mark it as readonly. + * + * @see DomNode#makeReadonly + */ + public EntityReference createEntityReference(String name) + { + DomEntityReference ret = new DomEntityReference(this, name); + DocumentType doctype = getDoctype(); + if (doctype != null) + { + DomEntity ent = (DomEntity) doctype.getEntities().getNamedItem(name); + if (ent != null) + { + for (DomNode ctx = ent.first; ctx != null; ctx = ctx.next) + { + ret.appendChild(ctx.cloneNode(true)); + } + } + } + ret.makeReadonly(); + return ret; + } + + /** + * <b>DOM L2</b> + * Makes a copy of the specified node, with all nodes "owned" by + * this document and with children optionally copied. This type + * of standard utility has become, well, a standard utility. + * + * <p> Note that EntityReference nodes created through this method (either + * directly, or recursively) never have children, and that there is no + * portable way to associate them with such children. + * + * <p> Note also that there is no requirement that the specified node + * be associated with a different document. This differs from the + * <em>cloneNode</em> operation in that the node itself is not given + * an opportunity to participate, so that any information managed + * by node subclasses will be lost. + */ + public Node importNode(Node src, boolean deep) + { + Node dst = null; + switch (src.getNodeType()) + { + case TEXT_NODE: + dst = createTextNode(src.getNodeValue()); + break; + case CDATA_SECTION_NODE: + dst = createCDATASection(src.getNodeValue()); + break; + case COMMENT_NODE: + dst = createComment(src.getNodeValue()); + break; + case PROCESSING_INSTRUCTION_NODE: + dst = createProcessingInstruction(src.getNodeName(), + src.getNodeValue()); + break; + case NOTATION_NODE: + // NOTE: There's no standard way to create + // these, or add them to a doctype. Useless. + Notation notation = (Notation) src; + dst = new DomNotation(this, notation.getNodeName(), + notation.getPublicId(), + notation.getSystemId()); + break; + case ENTITY_NODE: + // NOTE: There's no standard way to create + // these, or add them to a doctype. Useless. + Entity entity = (Entity) src; + dst = new DomEntity(this, entity.getNodeName(), + entity.getPublicId(), + entity.getSystemId(), + entity.getNotationName()); + if (deep) + { + for (Node ctx = src.getFirstChild(); ctx != null; + ctx = ctx.getNextSibling()) + { + dst.appendChild(importNode(ctx, deep)); + } + } + break; + case ENTITY_REFERENCE_NODE: + dst = createEntityReference(src.getNodeName()); + break; + case DOCUMENT_FRAGMENT_NODE: + dst = new DomFragment(this); + if (deep) + { + for (Node ctx = src.getFirstChild(); ctx != null; + ctx = ctx.getNextSibling()) + { + dst.appendChild(importNode(ctx, deep)); + } + } + break; + case ATTRIBUTE_NODE: + String attr_nsuri = src.getNamespaceURI(); + if (attr_nsuri != null) + { + dst = createAttributeNS(attr_nsuri, src.getNodeName()); + } + else + { + dst = createAttribute(src.getNodeName()); + } + // this is _always_ done regardless of "deep" setting + for (Node ctx = src.getFirstChild(); ctx != null; + ctx = ctx.getNextSibling()) + { + dst.appendChild(importNode(ctx, false)); + } + break; + case ELEMENT_NODE: + String elem_nsuri = src.getNamespaceURI(); + if (elem_nsuri != null) + { + dst = createElementNS(elem_nsuri, src.getNodeName()); + } + else + { + dst = createElement(src.getNodeName()); + } + NamedNodeMap srcAttrs = src.getAttributes(); + NamedNodeMap dstAttrs = dst.getAttributes(); + int len = srcAttrs.getLength(); + for (int i = 0; i < len; i++) + { + Attr a = (Attr) srcAttrs.item(i); + Attr dflt; + + // maybe update defaulted attributes + dflt = (Attr) dstAttrs.getNamedItem(a.getNodeName()); + if (dflt != null) + { + String newval = a.getNodeValue(); + if (!dflt.getNodeValue().equals(newval) + || a.getSpecified () == true) + { + dflt.setNodeValue (newval); + } + continue; + } + + dstAttrs.setNamedItem((Attr) importNode(a, false)); + } + if (deep) + { + for (Node ctx = src.getFirstChild(); ctx != null; + ctx = ctx.getNextSibling()) + { + dst.appendChild(importNode(ctx, true)); + } + } + break; + // can't import document or doctype nodes + case DOCUMENT_NODE: + case DOCUMENT_TYPE_NODE: + // FALLTHROUGH + // can't import unrecognized or nonstandard nodes + default: + throw new DomEx(DomEx.NOT_SUPPORTED_ERR, null, src, 0); + } + + // FIXME cleanup a bit -- for deep copies, copy those + // children in one place, here (code sharing is healthy) + + if (src instanceof DomNode) + { + ((DomNode) src).notifyUserDataHandlers(UserDataHandler.NODE_IMPORTED, + src, dst); + } + return dst; + } + + /** + * <b>DOM L2 (Traversal)</b> + * Returns a newly created node iterator. Don't forget to detach + * this iterator when you're done using it! + * + * @see DomIterator + */ + public NodeIterator createNodeIterator(Node root, + int whatToShow, + NodeFilter filter, + boolean expandEntities) + { + return new DomNodeIterator(root, whatToShow, filter, expandEntities, + false); + } + + public TreeWalker createTreeWalker(Node root, + int whatToShow, + NodeFilter filter, + boolean expandEntities) + { + return new DomNodeIterator(root, whatToShow, filter, expandEntities, + true); + } + + // DOM Level 3 methods + + /** + * DOM L3 + */ + public String getInputEncoding() + { + return inputEncoding; + } + + public void setInputEncoding(String inputEncoding) + { + this.inputEncoding = inputEncoding; + } + + /** + * DOM L3 + */ + public String getXmlEncoding() + { + return encoding; + } + + public void setXmlEncoding(String encoding) + { + this.encoding = encoding; + } + + public boolean getXmlStandalone() + { + return standalone; + } + + public void setXmlStandalone(boolean xmlStandalone) + { + standalone = xmlStandalone; + } + + public String getXmlVersion() + { + return version; + } + + public void setXmlVersion(String xmlVersion) + { + if (xmlVersion == null) + { + xmlVersion = "1.0"; + } + if ("1.0".equals(xmlVersion) || + "1.1".equals(xmlVersion)) + { + version = xmlVersion; + } + else + { + throw new DomEx(DomEx.NOT_SUPPORTED_ERR); + } + } + + public boolean getStrictErrorChecking() + { + return checkingCharacters; + } + + public void setStrictErrorChecking(boolean strictErrorChecking) + { + checkingCharacters = strictErrorChecking; + } + + public String lookupPrefix(String namespaceURI) + { + Node root = getDocumentElement(); + return (root == null) ? null : root.lookupPrefix(namespaceURI); + } + + public boolean isDefaultNamespace(String namespaceURI) + { + Node root = getDocumentElement(); + return (root == null) ? false : root.isDefaultNamespace(namespaceURI); + } + + public String lookupNamespaceURI(String prefix) + { + Node root = getDocumentElement(); + return (root == null) ? null : root.lookupNamespaceURI(prefix); + } + + public String getBaseURI() + { + return getDocumentURI(); + /* + Node root = getDocumentElement(); + if (root != null) + { + NamedNodeMap attrs = root.getAttributes(); + Node xmlBase = attrs.getNamedItemNS(XMLConstants.XML_NS_URI, "base"); + if (xmlBase != null) + { + return xmlBase.getNodeValue(); + } + } + return systemId; + */ + } + + public String getDocumentURI() + { + return systemId; + } + + public void setDocumentURI(String documentURI) + { + systemId = documentURI; + } + + public Node adoptNode(Node source) + { + switch (source.getNodeType()) + { + case DOCUMENT_NODE: + case DOCUMENT_TYPE_NODE: + throw new DomEx(DomEx.NOT_SUPPORTED_ERR); + case ENTITY_NODE: + case NOTATION_NODE: + throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR); + } + if (source instanceof DomNode) + { + DomNode src = (DomNode) source; + DomNode dst = src; + if (dst.parent != null) + { + dst = (DomNode) dst.cloneNode(true); + } + dst.setOwner(this); + src.notifyUserDataHandlers(UserDataHandler.NODE_ADOPTED, src, dst); + return dst; + } + return null; + } + + public DOMConfiguration getDomConfig() + { + if (config == null) + { + config = new DomDocumentConfiguration(); + } + return config; + } + + public void normalizeDocument() + { + boolean save = building; + building = true; + normalizeNode(this); + building = save; + } + + void normalizeNode(DomNode node) + { + node.normalize(); + if (config != null) + { + switch (node.nodeType) + { + case CDATA_SECTION_NODE: + if (!config.cdataSections) + { + // replace CDATA section with text node + Text text = createTextNode(node.getNodeValue()); + node.parent.insertBefore(text, node); + node.parent.removeChild(node); + // merge adjacent text nodes + String data = text.getWholeText(); + node = (DomNode) text.replaceWholeText(data); + } + else if (config.splitCdataSections) + { + String value = node.getNodeValue(); + int i = value.indexOf("]]>"); + while (i != -1) + { + Node node2 = createCDATASection(value.substring(0, i)); + node.parent.insertBefore(node2, node); + value = value.substring(i + 3); + node.setNodeValue(value); + i = value.indexOf("]]>"); + } + } + break; + case COMMENT_NODE: + if (!config.comments) + { + node.parent.removeChild(node); + } + break; + case TEXT_NODE: + if (!config.elementContentWhitespace && + ((Text) node).isElementContentWhitespace()) + { + node.parent.removeChild(node); + } + break; + case ENTITY_REFERENCE_NODE: + if (!config.entities) + { + for (DomNode ctx = node.first; ctx != null; ) + { + DomNode ctxNext = ctx.next; + node.parent.insertBefore(ctx, node); + ctx = ctxNext; + } + node.parent.removeChild(node); + } + break; + case ELEMENT_NODE: + if (!config.namespaceDeclarations) + { + DomNamedNodeMap attrs = + (DomNamedNodeMap) node.getAttributes(); + boolean aro = attrs.readonly; + attrs.readonly = false; // Ensure we can delete if necessary + int len = attrs.getLength(); + for (int i = 0; i < len; i++) + { + Node attr = attrs.item(i); + String namespace = attr.getNamespaceURI(); + if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespace)) + { + attrs.removeNamedItemNS(namespace, + attr.getLocalName()); + i--; + len--; + } + } + attrs.readonly = aro; + } + break; + } + } + for (DomNode ctx = node.first; ctx != null; ) + { + DomNode ctxNext = ctx.next; + normalizeNode(ctx); + ctx = ctxNext; + } + } + + public Node renameNode(Node n, String namespaceURI, String qualifiedName) + throws DOMException + { + if (n instanceof DomNsNode) + { + DomNsNode src = (DomNsNode) n; + if (src == null) + { + throw new DomEx(DomEx.NOT_FOUND_ERR); + } + if (src.owner != this) + { + throw new DomEx(DomEx.WRONG_DOCUMENT_ERR, null, src, 0); + } + boolean xml11 = "1.1".equals(version); + checkName(qualifiedName, xml11); + int ci = qualifiedName.indexOf(':'); + if ("".equals(namespaceURI)) + { + namespaceURI = null; + } + if (namespaceURI != null) + { + checkNCName(qualifiedName, xml11); + String prefix = (ci == -1) ? "" : + qualifiedName.substring(0, ci); + if (XMLConstants.XML_NS_PREFIX.equals(prefix) && + !XMLConstants.XML_NS_URI.equals(namespaceURI)) + { + throw new DomEx(DomEx.NAMESPACE_ERR, + "xml namespace must be " + + XMLConstants.XML_NS_URI, src, 0); + } + else if (src.nodeType == ATTRIBUTE_NODE && + (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix) || + XMLConstants.XMLNS_ATTRIBUTE.equals(qualifiedName)) && + !XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceURI)) + { + throw new DomEx(DomEx.NAMESPACE_ERR, + "xmlns namespace must be " + + XMLConstants.XMLNS_ATTRIBUTE_NS_URI, src, 0); + } + if (XMLConstants.XML_NS_URI.equals(namespaceURI) && + !XMLConstants.XML_NS_PREFIX.equals(prefix)) + { + throw new DomEx(DomEx.NAMESPACE_ERR, + "xml namespace must be " + + XMLConstants.XML_NS_URI, src, 0); + } + else if (src.nodeType == ATTRIBUTE_NODE && + XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceURI) && + !(XMLConstants.XMLNS_ATTRIBUTE.equals(prefix) || + XMLConstants.XMLNS_ATTRIBUTE.equals(qualifiedName))) + { + throw new DomEx(DomEx.NAMESPACE_ERR, + "xmlns namespace must be " + + XMLConstants.XMLNS_ATTRIBUTE_NS_URI, src, 0); + } + + } + src.setNodeName(qualifiedName); + src.setNamespaceURI(namespaceURI); + src.notifyUserDataHandlers(UserDataHandler.NODE_RENAMED, src, src); + // TODO MutationNameEvents + // DOMElementNameChanged or DOMAttributeNameChanged + return src; + } + throw new DomEx(DomEx.NOT_SUPPORTED_ERR, null, n, 0); + } + + // -- XPathEvaluator -- + + public XPathExpression createExpression(String expression, + XPathNSResolver resolver) + throws XPathException, DOMException + { + return new DomXPathExpression(this, expression, resolver); + } + + public XPathNSResolver createNSResolver(Node nodeResolver) + { + return new DomXPathNSResolver(nodeResolver); + } + + public Object evaluate(String expression, + Node contextNode, + XPathNSResolver resolver, + short type, + Object result) + throws XPathException, DOMException + { + XPathExpression xpe = + new DomXPathExpression(this, expression, resolver); + return xpe.evaluate(contextNode, type, result); + } + +} + diff --git a/libjava/gnu/xml/dom/DomDocumentBuilder.java b/libjava/gnu/xml/dom/DomDocumentBuilder.java new file mode 100644 index 0000000..16ea2ae --- /dev/null +++ b/libjava/gnu/xml/dom/DomDocumentBuilder.java @@ -0,0 +1,160 @@ +/* DomDocumentBuilder.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import java.io.InputStream; +import java.io.IOException; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.ParserConfigurationException; +import org.w3c.dom.Document; +import org.w3c.dom.DOMConfiguration; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.ls.DOMImplementationLS; +import org.w3c.dom.ls.LSInput; +import org.w3c.dom.ls.LSParser; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * Document builder using the GNU DOM Load & Save implementation. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class DomDocumentBuilder + extends DocumentBuilder +{ + + final DOMImplementation impl; + final DOMImplementationLS ls; + final LSParser parser; + + DomDocumentBuilder(DOMImplementation impl, + DOMImplementationLS ls, + LSParser parser) + { + this.impl = impl; + this.ls = ls; + this.parser = parser; + } + + public boolean isNamespaceAware() + { + DOMConfiguration config = parser.getDomConfig(); + return ((Boolean) config.getParameter("namespaces")).booleanValue(); + } + + public boolean isValidating() + { + DOMConfiguration config = parser.getDomConfig(); + return ((Boolean) config.getParameter("validating")).booleanValue(); + } + + public boolean isXIncludeAware() + { + DOMConfiguration config = parser.getDomConfig(); + return ((Boolean) config.getParameter("xinclude-aware")).booleanValue(); + } + + public void setEntityResolver(EntityResolver resolver) + { + DOMConfiguration config = parser.getDomConfig(); + config.setParameter("entity-resolver", resolver); + } + + public void setErrorHandler(ErrorHandler handler) + { + DOMConfiguration config = parser.getDomConfig(); + config.setParameter("error-handler", handler); + } + + public DOMImplementation getDOMImplementation() + { + return impl; + } + + public Document newDocument() + { + return impl.createDocument(null, null, null); + } + + public Document parse(InputStream in) + throws SAXException, IOException + { + LSInput input = ls.createLSInput(); + input.setByteStream(in); + return parser.parse(input); + } + + public Document parse(InputStream in, String systemId) + throws SAXException, IOException + { + LSInput input = ls.createLSInput(); + input.setByteStream(in); + input.setSystemId(systemId); + return parser.parse(input); + } + + public Document parse(String systemId) + throws SAXException, IOException + { + return parser.parseURI(systemId); + } + + public Document parse(InputSource is) + throws SAXException, IOException + { + LSInput input = ls.createLSInput(); + InputStream in = is.getByteStream(); + if (in != null) + { + input.setByteStream(in); + } + else + { + input.setCharacterStream(is.getCharacterStream()); + } + input.setPublicId(is.getPublicId()); + input.setSystemId(is.getSystemId()); + input.setEncoding(is.getEncoding()); + return parser.parse(input); + } + +} + diff --git a/libjava/gnu/xml/dom/DomDocumentBuilderFactory.java b/libjava/gnu/xml/dom/DomDocumentBuilderFactory.java new file mode 100644 index 0000000..2d049bd --- /dev/null +++ b/libjava/gnu/xml/dom/DomDocumentBuilderFactory.java @@ -0,0 +1,128 @@ +/* DomDocumentBuilderFactory.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.FactoryConfigurationError; +import javax.xml.parsers.ParserConfigurationException; +import org.w3c.dom.DOMConfiguration; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.bootstrap.DOMImplementationRegistry; +import org.w3c.dom.ls.DOMImplementationLS; +import org.w3c.dom.ls.LSParser; + +/** + * Document builder factory that uses a DOM Level 3 Load & Save + * implementation. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class DomDocumentBuilderFactory + extends DocumentBuilderFactory +{ + + final DOMImplementation impl; + final DOMImplementationLS ls; + + public DomDocumentBuilderFactory() + { + try + { + DOMImplementationRegistry reg = + DOMImplementationRegistry.newInstance(); + impl = reg.getDOMImplementation("LS 3.0"); + if (impl == null) + { + throw new FactoryConfigurationError("no LS implementations found"); + } + ls = (DOMImplementationLS) impl; + } + catch (Exception e) + { + throw new FactoryConfigurationError(e); + } + } + + public DocumentBuilder newDocumentBuilder() + throws ParserConfigurationException + { + LSParser parser = ls.createLSParser(DOMImplementationLS.MODE_ASYNCHRONOUS, + "http://www.w3.org/TR/REC-xml"); + DOMConfiguration config = parser.getDomConfig(); + setParameter(config, "namespaces", + isNamespaceAware() ? Boolean.TRUE : Boolean.FALSE); + setParameter(config, "element-content-whitespace", + isIgnoringElementContentWhitespace() ? Boolean.FALSE : + Boolean.TRUE); + setParameter(config, "comments", + isIgnoringComments() ? Boolean.FALSE : Boolean.TRUE); + setParameter(config, "expand-entity-references", + isExpandEntityReferences() ? Boolean.TRUE : Boolean.FALSE); + setParameter(config, "coalescing", + isCoalescing() ? Boolean.TRUE : Boolean.FALSE); + setParameter(config, "validating", + isValidating() ? Boolean.TRUE : Boolean.FALSE); + setParameter(config, "xinclude-aware", + isXIncludeAware() ? Boolean.TRUE : Boolean.FALSE); + return new DomDocumentBuilder(impl, ls, parser); + } + + void setParameter(DOMConfiguration config, String name, Object value) + throws ParserConfigurationException + { + if (!config.canSetParameter(name, value)) + { + throw new ParserConfigurationException(name); + } + config.setParameter(name, value); + } + + public Object getAttribute(String name) + { + // TODO + return null; + } + + public void setAttribute(String name, Object value) + { + // TODO + } + +} + diff --git a/libjava/gnu/xml/dom/DomDocumentConfiguration.java b/libjava/gnu/xml/dom/DomDocumentConfiguration.java new file mode 100644 index 0000000..eebee09 --- /dev/null +++ b/libjava/gnu/xml/dom/DomDocumentConfiguration.java @@ -0,0 +1,259 @@ +/* DomDocumentConfiguration.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import java.util.Arrays; +import java.util.List; +import org.w3c.dom.DOMConfiguration; +import org.w3c.dom.DOMErrorHandler; +import org.w3c.dom.DOMException; +import org.w3c.dom.DOMStringList; + +/** + * Document configuration, used to store normalization and other parameters. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class DomDocumentConfiguration + implements DOMConfiguration, DOMStringList +{ + + private static final List SUPPORTED_PARAMETERS = + Arrays.asList(new String[] { "cdata-sections", + "comments", + "element-content-whitespace", + "entities", + "error-handler", + "namespace-declarations", + "split-cdata-sections", + "infoset"}); + + boolean cdataSections = true; + boolean comments = true; + boolean elementContentWhitespace = true; + boolean entities = true; + DOMErrorHandler errorHandler; + boolean namespaceDeclarations = true; + boolean splitCdataSections = true; + + public void setParameter(String name, Object value) + throws DOMException + { + name = name.toLowerCase(); + if ("cdata-sections".equals(name)) + { + cdataSections = "true".equals(value.toString()); + } + else if ("comments".equals(name)) + { + comments = "true".equals(value.toString()); + } + else if ("element-content-whitespace".equals(name)) + { + elementContentWhitespace = "true".equals(value.toString()); + } + else if ("entities".equals(name)) + { + entities = "true".equals(value.toString()); + } + else if ("error-handler".equals(name)) + { + try + { + errorHandler = (DOMErrorHandler) value; + } + catch (ClassCastException e) + { + throw new DomEx(DomEx.TYPE_MISMATCH_ERR, + value.getClass().getName(), null, 0); + } + } + else if ("namespace-declarations".equals(name)) + { + namespaceDeclarations = "true".equals(value.toString()); + } + else if ("split-cdata-sections".equals(name)) + { + comments = "true".equals(value.toString()); + } + else if ("infoset".equals(name)) + { + if ("true".equals(value.toString())) + { + entities = false; + cdataSections = false; + namespaceDeclarations = true; + elementContentWhitespace = true; + comments = true; + } + } + else if (("canonical-form".equals(name) || + "check-character-normalization".equals(name) || + "datatype-normalization".equals(name) || + "normalize-characters".equals(name) || + "validate".equals(name) || + "validate-if-schema".equals(name)) && + "false".equals(value.toString())) + { + // NOOP + } + else if (("namespaces".equals(name) || + "well-formed".equals(name)) && + "true".equals(value.toString())) + { + // NOOP + } + else + { + throw new DomEx(DomEx.NOT_SUPPORTED_ERR, name, null, 0); + } + } + + public Object getParameter(String name) + throws DOMException + { + name = name.toLowerCase(); + if ("cdata-sections".equals(name)) + { + return cdataSections ? Boolean.TRUE : Boolean.FALSE; + } + else if ("comments".equals(name)) + { + return comments ? Boolean.TRUE : Boolean.FALSE; + } + else if ("element-content-whitespace".equals(name)) + { + return elementContentWhitespace ? Boolean.TRUE : Boolean.FALSE; + } + else if ("entities".equals(name)) + { + return entities ? Boolean.TRUE : Boolean.FALSE; + } + else if ("error-handler".equals(name)) + { + return errorHandler; + } + else if ("namespace-declarations".equals(name)) + { + return namespaceDeclarations ? Boolean.TRUE : Boolean.FALSE; + } + else if ("split-cdata-sections".equals(name)) + { + return comments ? Boolean.TRUE : Boolean.FALSE; + } + else if ("canonical-form".equals(name) || + "check-character-normalization".equals(name) || + "datatype-normalization".equals(name) || + "normalize-characters".equals(name) || + "validate".equals(name) || + "validate-if-schema".equals(name)) + { + return Boolean.FALSE; + } + else if ("namespaces".equals(name) || + "well-formed".equals(name)) + { + return Boolean.TRUE; + } + else if ("infoset".equals(name)) + { + return (entities == false && + cdataSections == false && + namespaceDeclarations == true && + comments == true) ? Boolean.TRUE : Boolean.FALSE; + } + throw new DomEx(DomEx.NOT_SUPPORTED_ERR, name, null, 0); + } + + public boolean canSetParameter(String name, Object value) + { + name = name.toLowerCase(); + if ("error-handler".equals(name)) + { + return (value == null || value instanceof DOMErrorHandler); + } + else if (contains(name)) + { + return true; + } + else if ("canonical-form".equals(name) || + "check-character-normalization".equals(name) || + "datatype-normalization".equals(name) || + "normalize-characters".equals(name) || + "validate".equals(name) || + "validate-if-schema".equals(name)) + { + return "false".equals(value.toString()); + } + else if ("namespaces".equals(name) || + "well-formed".equals(name)) + { + return "true".equals(value.toString()); + } + return false; + } + + public DOMStringList getParameterNames() + { + return this; + } + + public String item(int i) + { + try + { + return (String) SUPPORTED_PARAMETERS.get(i); + } + catch (IndexOutOfBoundsException e) + { + return null; + } + } + + public int getLength() + { + return SUPPORTED_PARAMETERS.size(); + } + + public boolean contains(String str) + { + str = str.toLowerCase(); + return SUPPORTED_PARAMETERS.contains(str); + } + +} diff --git a/libjava/gnu/xml/dom/DomElement.java b/libjava/gnu/xml/dom/DomElement.java new file mode 100644 index 0000000..fb04638 --- /dev/null +++ b/libjava/gnu/xml/dom/DomElement.java @@ -0,0 +1,522 @@ +/* DomElement.java -- + Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import java.util.HashSet; +import java.util.Set; +import javax.xml.XMLConstants; + +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.TypeInfo; + +/** + * <p> "Element" implementation. + * + * @author David Brownell + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class DomElement + extends DomNsNode + implements Element +{ + + /** + * User-defined ID attributes. + * Used by DomAttr.isId and DomDocument.getElementById + */ + Set userIdAttrs; + + // Attributes are VERY expensive in DOM, and not just for + // this implementation. Avoid creating them. + private DomNamedNodeMap attributes; + + // xml:space cache + String xmlSpace = ""; + + /** + * Constructs an Element node associated with the specified document. + * + * <p>This constructor should only be invoked by a Document as part + * of its createElement functionality, or through a subclass which is + * similarly used in a "Sub-DOM" style layer. + * + * @param owner The document with which this node is associated + * @param namespaceURI Combined with the local part of the name, + * this is used to uniquely identify a type of element + * @param name Name of this element, which may include a prefix + */ + protected DomElement(DomDocument owner, String namespaceURI, String name) + { + super(ELEMENT_NODE, owner, namespaceURI, name); + } + + /** + * <b>DOM L1</b> + * Returns the element's attributes + */ + public NamedNodeMap getAttributes() + { + if (attributes == null) + { + attributes = new DomNamedNodeMap(this, Node.ATTRIBUTE_NODE); + } + return attributes; + } + + /** + * <b>DOM L2></b> + * Returns true iff this is an element node with attributes. + */ + public boolean hasAttributes() + { + return attributes != null && attributes.length != 0; + } + + /** + * Shallow clone of the element, except that associated + * attributes are (deep) cloned. + */ + public Object clone() + { + DomElement node = (DomElement) super.clone(); + + if (attributes != null) + { + node.attributes = new DomNamedNodeMap(node, Node.ATTRIBUTE_NODE); + for (DomNode ctx = attributes.first; ctx != null; ctx = ctx.next) + { + node.attributes.setNamedItemNS(ctx.cloneNode(true)); + } + } + return node; + } + + void setOwner(DomDocument doc) + { + if (attributes != null) + { + for (DomNode ctx = attributes.first; ctx != null; ctx = ctx.next) + { + ctx.setOwner(doc); + } + } + super.setOwner(doc); + } + + /** + * Marks this element, its children, and its associated attributes as + * readonly. + */ + public void makeReadonly() + { + super.makeReadonly(); + if (attributes != null) + { + attributes.makeReadonly(); + } + } + + /** + * <b>DOM L1</b> + * Returns the element name (same as getNodeName). + */ + final public String getTagName() + { + return getNodeName(); + } + + /** + * <b>DOM L1</b> + * Returns the value of the specified attribute, or an + * empty string. + */ + public String getAttribute(String name) + { + if ("xml:space" == name) // NB only works on interned string + { + // Use cached value + return xmlSpace; + } + Attr attr = getAttributeNode(name); + return (attr == null) ? "" : attr.getValue(); + } + + /** + * <b>DOM L2</b> + * Returns true if the element has an attribute with the + * specified name (specified or DTD defaulted). + */ + public boolean hasAttribute(String name) + { + return getAttributeNode(name) != null; + } + + /** + * <b>DOM L2</b> + * Returns true if the element has an attribute with the + * specified name (specified or DTD defaulted). + */ + public boolean hasAttributeNS(String namespaceURI, String local) + { + return getAttributeNodeNS(namespaceURI, local) != null; + } + + /** + * <b>DOM L2</b> + * Returns the value of the specified attribute, or an + * empty string. + */ + public String getAttributeNS(String namespaceURI, String local) + { + Attr attr = getAttributeNodeNS(namespaceURI, local); + return (attr == null) ? "" : attr.getValue(); + } + + /** + * <b>DOM L1</b> + * Returns the appropriate attribute node; the name is the + * nodeName property of the attribute. + */ + public Attr getAttributeNode(String name) + { + return (attributes == null) ? null : + (Attr) attributes.getNamedItem(name); + } + + /** + * <b>DOM L2</b> + * Returns the appropriate attribute node; the name combines + * the namespace name and the local part. + */ + public Attr getAttributeNodeNS(String namespace, String localPart) + { + return (attributes == null) ? null : + (Attr) attributes.getNamedItemNS(namespace, localPart); + } + + /** + * <b>DOM L1</b> + * Modifies an existing attribute to have the specified value, + * or creates a new one with that value. The name used is the + * nodeName value. + */ + public void setAttribute(String name, String value) + { + Attr attr = getAttributeNode(name); + if (attr != null) + { + attr.setNodeValue(value); + ((DomAttr) attr).setSpecified(true); + return; + } + attr = owner.createAttribute(name); + attr.setNodeValue(value); + setAttributeNode(attr); + } + + /** + * <b>DOM L2</b> + * Modifies an existing attribute to have the specified value, + * or creates a new one with that value. + */ + public void setAttributeNS(String uri, String aname, String value) + { + if (("xmlns".equals (aname) || aname.startsWith ("xmlns:")) + && !XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals (uri)) + { + throw new DomEx(DomEx.NAMESPACE_ERR, + "setting xmlns attribute to illegal value", this, 0); + } + + Attr attr = getAttributeNodeNS(uri, aname); + if (attr != null) + { + attr.setNodeValue(value); + return; + } + attr = owner.createAttributeNS(uri, aname); + attr.setNodeValue(value); + setAttributeNodeNS(attr); + } + + /** + * <b>DOM L1</b> + * Stores the specified attribute, optionally overwriting any + * existing one with that name. + */ + public Attr setAttributeNode(Attr attr) + { + return (Attr) getAttributes().setNamedItem(attr); + } + + /** + * <b>DOM L2</b> + * Stores the specified attribute, optionally overwriting any + * existing one with that name. + */ + public Attr setAttributeNodeNS(Attr attr) + { + return (Attr) getAttributes().setNamedItemNS(attr); + } + + /** + * <b>DOM L1</b> + * Removes the appropriate attribute node. + * If there is no such node, this is (bizarrely enough) a NOP so you + * won't see exceptions if your code deletes non-existent attributes. + * + * <p>Note that since there is no portable way for DOM to record + * DTD information, default values for attributes will never be + * provided automatically. + */ + public void removeAttribute(String name) + { + if (attributes == null) + { + return; + } + + try + { + attributes.removeNamedItem(name); + } + catch (DomEx e) + { + if (e.code != DomEx.NOT_FOUND_ERR) + { + throw e; + } + } + } + + /** + * <b>DOM L1</b> + * Removes the appropriate attribute node; the name is the + * nodeName property of the attribute. + * + * <p>Note that since there is no portable way for DOM to record + * DTD information, default values for attributes will never be + * provided automatically. + */ + public Attr removeAttributeNode(Attr node) + { + if (attributes == null) + { + throw new DomEx(DomEx.NOT_FOUND_ERR, null, node, 0); + } + return (Attr) attributes.removeNamedItem(node.getNodeName()); + } + + /** + * <b>DOM L2</b> + * Removes the appropriate attribute node; the name combines + * the namespace name and the local part. + * + * <p>Note that since there is no portable way for DOM to record + * DTD information, default values for attributes will never be + * provided automatically. + */ + public void removeAttributeNS(String namespace, String localPart) + { + if (attributes == null) + { + throw new DomEx(DomEx.NOT_FOUND_ERR, localPart, null, 0); + } + attributes.removeNamedItemNS (namespace, localPart); + } + + // DOM Level 3 methods + + public String lookupPrefix(String namespaceURI) + { + if (namespaceURI == null) + { + return null; + } + String namespace = getNamespaceURI(); + if (namespace != null && namespace.equals(namespaceURI)) + { + return getPrefix(); + } + if (attributes != null) + { + for (DomNode ctx = attributes.first; ctx != null; ctx = ctx.next) + { + if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI + .equals(ctx.getNamespaceURI())) + { + String value = ctx.getNodeValue(); + if (value.equals(namespaceURI)) + { + return ctx.getLocalName(); + } + } + } + } + return super.lookupPrefix(namespaceURI); + } + + public boolean isDefaultNamespace(String namespaceURI) + { + String namespace = getNamespaceURI(); + if (namespace != null && namespace.equals(namespaceURI)) + { + return getPrefix() == null; + } + if (attributes != null) + { + for (DomNode ctx = attributes.first; ctx != null; ctx = ctx.next) + { + if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI + .equals(ctx.getNamespaceURI())) + { + String qName = ctx.getNodeName(); + return (XMLConstants.XMLNS_ATTRIBUTE.equals(qName)); + } + } + } + return super.isDefaultNamespace(namespaceURI); + } + + public String lookupNamespaceURI(String prefix) + { + String namespace = getNamespaceURI(); + if (namespace != null && equal(prefix, getPrefix())) + { + return namespace; + } + if (attributes != null) + { + for (DomNode ctx = attributes.first; ctx != null; ctx = ctx.next) + { + if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI + .equals(ctx.getNamespaceURI())) + { + if (prefix == null) + { + if (XMLConstants.XMLNS_ATTRIBUTE.equals(ctx.getNodeName())) + { + return ctx.getNodeValue(); + } + } + else + { + if (prefix.equals(ctx.getLocalName())) + { + return ctx.getNodeValue(); + } + } + } + } + } + return super.lookupNamespaceURI(prefix); + } + + public String getBaseURI() + { + if (attributes != null) + { + Node xmlBase = + attributes.getNamedItemNS(XMLConstants.XML_NS_URI, "base"); + if (xmlBase != null) + { + return xmlBase.getNodeValue(); + } + } + return super.getBaseURI(); + } + + public TypeInfo getSchemaTypeInfo() + { + // DTD implementation + DomDoctype doctype = (DomDoctype) owner.getDoctype(); + if (doctype != null) + { + return doctype.getElementTypeInfo(getNodeName()); + } + // TODO XML Schema implementation + return null; + } + + public void setIdAttribute(String name, boolean isId) + { + NamedNodeMap attrs = getAttributes(); + Attr attr = (Attr) attrs.getNamedItem(name); + setIdAttributeNode(attr, isId); + } + + public void setIdAttributeNode(Attr attr, boolean isId) + { + if (readonly) + { + throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR); + } + if (attr == null || attr.getOwnerElement() != this) + { + throw new DomEx(DomEx.NOT_FOUND_ERR); + } + if (isId) + { + if (userIdAttrs == null) + { + userIdAttrs = new HashSet(); + } + userIdAttrs.add(attr); + } + else if (userIdAttrs != null) + { + userIdAttrs.remove(attr); + if (userIdAttrs.isEmpty()) + { + userIdAttrs = null; + } + } + } + + public void setIdAttributeNS(String namespaceURI, String localName, + boolean isId) + { + NamedNodeMap attrs = getAttributes(); + Attr attr = (Attr) attrs.getNamedItemNS(namespaceURI, localName); + setIdAttributeNode(attr, isId); + } + +} diff --git a/libjava/gnu/xml/dom/DomEntity.java b/libjava/gnu/xml/dom/DomEntity.java new file mode 100644 index 0000000..5432bbc --- /dev/null +++ b/libjava/gnu/xml/dom/DomEntity.java @@ -0,0 +1,147 @@ +/* DomEntity.java -- + Copyright (C) 1999,2000,2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import org.w3c.dom.Entity; + +/** + * <p> "Entity" implementation. This is a non-core DOM class, supporting the + * "XML" feature. There are two types of entities, neither of which works + * particularly well in this API:</p><dl> + * + * <dt><em>Unparsed Entities</em></dt> + * <dd>Since ENTITY/ENTITIES attributes, the only legal use of unparsed + * entities in XML, can't be detected with DOM, there isn't much point in + * trying to use unparsed entities in DOM applications. (XML Linking is + * working to provide a better version of this functionality.) </dd> + * + * <dt><em>Parsed Entities</em></dt> + * <dd> While the DOM specification permits nodes for parsed entities + * to have a readonly set of children, this is not required and there + * is no portable way to provide such children. <em>This implementation + * currently does not permit children to be added to Entities.</em> + * There are related issues with the use of EntityReference nodes. </dd> + * + * </dl> + * + * <p> In short, <em>avoid using this DOM functionality</em>. + * + * @see DomDoctype + * @see DomEntityReference + * @see DomNotation + * + * @author David Brownell + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class DomEntity + extends DomExtern + implements Entity +{ + + private String notation; + + /** + * Constructs an Entity node associated with the specified document, + * with the specified descriptive data. + * + * <p>This constructor should only be invoked by a DomDoctype as part + * of its declareEntity functionality, or through a subclass which is + * similarly used in a "Sub-DOM" style layer. + * + * @param owner The document with which this entity is associated + * @param name Name of this entity + * @param publicId If non-null, provides the entity's PUBLIC identifier + * @param systemId Provides the entity's SYSTEM identifier (URI) + * @param notation If non-null, provides the unparsed entity's notation. + */ + protected DomEntity(DomDocument owner, + String name, + String publicId, + String systemId, + String notation) + { + super(ENTITY_NODE, owner, name, publicId, systemId); + this.notation = notation; + + // NOTE: if notation == null, this is a parsed entity + // which could reasonably be given child nodes ... + makeReadonly(); + } + + /** + * <b>DOM L1</b> + * Returns the NOTATION identifier associated with this entity, if any. + */ + final public String getNotationName() + { + return notation; + } + + // DOM Level 3 methods + + public String getInputEncoding() + { + // TODO + return null; + } + + public String getXmlEncoding() + { + // TODO + return null; + } + + public String getXmlVersion() + { + // TODO + return null; + } + + /** + * The base URI of an external entity is its system ID. + * The base URI of an internal entity is the parent document's base URI. + * @since DOM Level 3 Core + */ + public String getBaseURI() + { + String systemId = getSystemId(); + return (systemId == null) ? owner.getBaseURI() : systemId; + } + +} + diff --git a/libjava/gnu/xml/dom/DomEntityReference.java b/libjava/gnu/xml/dom/DomEntityReference.java new file mode 100644 index 0000000..65efcfc --- /dev/null +++ b/libjava/gnu/xml/dom/DomEntityReference.java @@ -0,0 +1,130 @@ +/* DomEntityReference.java -- + Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import org.w3c.dom.DocumentType; +import org.w3c.dom.Entity; +import org.w3c.dom.EntityReference; + +/** + * <p> "EntityReference" implementation (reference to parsed entity). + * This is a non-core DOM class, supporting the "XML" feature. + * It does not represent builtin entities (such as "&amp;") + * or character references, which are always directly expanded in + * DOM trees.</p> + * + * <p> Note that while the DOM specification permits these nodes to have + * a readonly set of children, this is not required. Similarly, it does + * not require a DOM to couple EntityReference nodes with any Entity nodes + * that have the same entity name (and equivalent children). It also + * effectively guarantees that references created directly or indirectly + * through the <em>Document.ImportNode</em> method will not have children. + * The level of functionality you may get is extremely variable. + * + * <p> Also significant is that even at their most functional level, the fact + * that EntityReference children must be readonly has caused significant + * problems when modifying work products held in DOM trees. Other problems + * include issues related to undeclared namespace prefixes (and references + * to the current default namespace) that may be found in the text of such + * parsed entities nodes. These must be contextually bound as part of DOM + * tree construction. When such nodes are moved, the namespace associated + * with a given prefix (or default) may change to be in conflict with the + * namespace bound to the node at creation time. + * + * <p> In short, <em>avoid using this DOM functionality</em>. + * + * @see DomDoctype + * @see DomEntity + * + * @author David Brownell + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class DomEntityReference + extends DomNode + implements EntityReference +{ + + private String name; + + /** + * Constructs an EntityReference node associated with the specified + * document. The creator should populate this with whatever contents + * are appropriate, and then mark it as readonly. + * + * <p>This constructor should only be invoked by a Document as part of + * its createEntityReference functionality, or through a subclass which + * is similarly used in a "Sub-DOM" style layer. + * + * @see DomNode#makeReadonly + */ + protected DomEntityReference(DomDocument owner, String name) + { + super(ENTITY_REFERENCE_NODE, owner); + this.name = name; + } + + /** + * Returns the name of the referenced entity. + * @since DOM Level 1 Core + */ + public final String getNodeName() + { + return name; + } + + /** + * The base URI of an entity reference is the base URI where the entity + * declaration occurs. + * @since DOM Level 3 Core + */ + public final String getBaseURI() + { + DocumentType doctype = owner.getDoctype(); + if (doctype == null) + { + return null; + } + Entity entity = (Entity) doctype.getEntities().getNamedItem(name); + if (entity == null) + { + return null; + } + return entity.getBaseURI(); + } + +} diff --git a/libjava/gnu/xml/dom/DomEvent.java b/libjava/gnu/xml/dom/DomEvent.java new file mode 100644 index 0000000..4a52492 --- /dev/null +++ b/libjava/gnu/xml/dom/DomEvent.java @@ -0,0 +1,345 @@ +/* DomEvent.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import org.w3c.dom.*; +import org.w3c.dom.events.*; +import org.w3c.dom.views.AbstractView; // used by UIEvent + +/** + * "Event" implementation. Events are + * created (through DocumentEvent interface methods on the document object), + * and are sent to any target node in the document. + * + * <p> Applications may define application specific event subclasses, but + * should otherwise use the <em>DocumentTraversal</em> interface to acquire + * event objects. + * + * @author David Brownell + */ +public class DomEvent + implements Event +{ + + String type; // init + EventTarget target; + EventTarget currentNode; + short eventPhase; + boolean bubbles; // init + boolean cancelable; // init + long timeStamp; // ? + + /** Returns the event's type (name) as initialized */ + public final String getType() + { + return type; + } + + /** + * Returns event's target; delivery of an event is initiated + * by a <em>target.dispatchEvent(event)</em> invocation. + */ + public final EventTarget getTarget() + { + return target; + } + + /** + * Returns the target to which events are currently being + * delivered. When capturing or bubbling, this will not + * be what <em>getTarget</em> returns. + */ + public final EventTarget getCurrentTarget() + { + return currentNode; + } + + /** + * Returns CAPTURING_PHASE, AT_TARGET, or BUBBLING; + * only meaningful within EventListener.handleEvent + */ + public final short getEventPhase() + { + return eventPhase; + } + + /** + * Returns true if the news of the event bubbles to tree tops + * (as specified during initialization). + */ + public final boolean getBubbles() + { + return bubbles; + } + + /** + * Returns true if the default handling may be canceled + * (as specified during initialization). + */ + public final boolean getCancelable() + { + return cancelable; + } + + /** + * Returns the event's timestamp. + */ + public final long getTimeStamp() + { + return timeStamp; + } + + boolean stop; + boolean doDefault; + + /** + * Requests the event no longer be captured or bubbled; only + * listeners on the event target will see the event, if they + * haven't yet been notified. + * + * <p> <em> Avoid using this </em> except for application-specific + * events, for which you the protocol explicitly "blesses" the use + * of this with some event types. Otherwise, you are likely to break + * algorithms which depend on event notification either directly or + * through bubbling or capturing. </p> + * + * <p> Note that this method is not final, specifically to enable + * enforcing of policies about events always propagating. </p> + */ + public void stopPropagation() + { + stop = true; + } + + /** + * Requests that whoever dispatched the event not perform their + * default processing when event delivery completes. Initializes + * event timestamp. + */ + public final void preventDefault() + { + doDefault = false; + } + + /** Initializes basic event state. */ + public void initEvent(String typeArg, + boolean canBubbleArg, + boolean cancelableArg) + { + eventPhase = 0; + type = typeArg; + bubbles = canBubbleArg; + cancelable = cancelableArg; + timeStamp = System.currentTimeMillis(); + } + + /** Constructs, but does not initialize, an event. */ + public DomEvent(String type) + { + this.type = type; + } + + /** + * Returns a basic printable description of the event's type, + * state, and delivery conditions + */ + public String toString() + { + StringBuffer buf = new StringBuffer("[Event "); + buf.append(type); + switch (eventPhase) + { + case CAPTURING_PHASE: + buf.append(", CAPTURING"); + break; + case AT_TARGET: + buf.append(", AT TARGET"); + break; + case BUBBLING_PHASE: + buf.append(", BUBBLING"); + break; + default: + buf.append(", (inactive)"); + break; + } + if (bubbles && eventPhase != BUBBLING_PHASE) + { + buf.append(", bubbles"); + } + if (cancelable) + { + buf.append(", can cancel"); + } + // were we to provide subclass info, this's where it'd live + buf.append("]"); + return buf.toString(); + } + + /** + * "MutationEvent" implementation. + */ + public static final class DomMutationEvent + extends DomEvent + implements MutationEvent + { + + // package private + Node relatedNode; // init + + private String prevValue; // init + private String newValue; // init + + private String attrName; // init + private short attrChange; // init + + /** Returns any "related" node provided by this type of event */ + public final Node getRelatedNode() + { + return relatedNode; + } + + /** Returns any "previous value" provided by this type of event */ + public final String getPrevValue() + { + return prevValue; + } + + /** Returns any "new value" provided by this type of event */ + public final String getNewValue() + { + return newValue; + } + + /** For attribute change events, returns the attribute's name */ + public final String getAttrName() + { + return attrName; + } + + /** For attribute change events, returns how the attribuet changed */ + public final short getAttrChange() + { + return attrChange; + } + + /** Initializes a mutation event */ + public final void initMutationEvent(String typeArg, + boolean canBubbleArg, + boolean cancelableArg, + Node relatedNodeArg, + String prevValueArg, + String newValueArg, + String attrNameArg, + short attrChangeArg) + { + // super.initEvent is inlined here for speed + // (mutation events are issued on all DOM changes) + eventPhase = 0; + type = typeArg; + bubbles = canBubbleArg; + cancelable = cancelableArg; + timeStamp = System.currentTimeMillis(); + + relatedNode = relatedNodeArg; + prevValue = prevValueArg; + newValue = newValueArg; + attrName = attrNameArg; + attrChange = attrChangeArg; + } + + // clear everything that should be GC-able + void clear() + { + type = null; + target = null; + relatedNode = null; + currentNode = null; + prevValue = newValue = attrName = null; + } + + /** Constructs an uninitialized mutation event. */ + public DomMutationEvent(String type) + { + super(type); + } + + } + + /** + * "UIEvent" implementation. + */ + public static class DomUIEvent + extends DomEvent + implements UIEvent + { + + private AbstractView view; // init + private int detail; // init + + /** Constructs an uninitialized User Interface (UI) event */ + public DomUIEvent (String type) { super (type); } + + public final AbstractView getView () { return view; } + public final int getDetail () { return detail; } + + /** Initializes a UI event */ + public final void initUIEvent(String typeArg, + boolean canBubbleArg, + boolean cancelableArg, + AbstractView viewArg, + int detailArg) + { + super.initEvent(typeArg, canBubbleArg, cancelableArg); + view = viewArg; + detail = detailArg; + } + + } + + /* + + static final class DomMouseEvent extends DomUIEvent + implements MouseEvent + { + // another half dozen state variables/accessors + } + + */ + +} + diff --git a/libjava/gnu/xml/dom/DomEx.java b/libjava/gnu/xml/dom/DomEx.java new file mode 100644 index 0000000..aacac96 --- /dev/null +++ b/libjava/gnu/xml/dom/DomEx.java @@ -0,0 +1,175 @@ +/* DomEx.java -- + Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import org.w3c.dom.DOMException; +import org.w3c.dom.Node; + +/** + * <p> DOMException implementation. The version that + * is provided by the W3C is abstract, so it can't be instantiated. + * + * <p> This also provides a bit more information about the error + * that is being reported, in terms of the relevant DOM structures + * and data. + * + * @author David Brownell + */ +public class DomEx + extends DOMException +{ + + /** @serial Data that caused an error to be reported */ + private String data; + + /** @serial Node associated with the error. */ + private Node node; + + /** @serial Data associated with the error. */ + private int value; + + /** + * Constructs an exception, with the diagnostic message + * corresponding to the specified code. + */ + public DomEx(short code) + { + super(code, diagnostic(code)); + } + + /** + * Constructs an exception, with the diagnostic message + * corresponding to the specified code and additional + * information as provided. + */ + public DomEx(short code, String data, Node node, int value) + { + super(code, diagnostic(code)); + this.data = data; + this.node = node; + this.value = value; + } + + /** Returns the node to which the diagnotic applies, or null. */ + final public Node getNode() + { + return node; + } + + /** Returns data to which the diagnotic applies, or null. */ + final public String getData() + { + return data; + } + + /** Returns data to which the diagnotic applies, or null. */ + final public int getValue() + { + return value; + } + + /** + * Returns a diagnostic message that may be slightly more useful + * than the generic one, where possible. + */ + public String getMessage() + { + String retval = super.getMessage(); + + if (data != null) + { + retval += "\nMore Information: " + data; + } + if (value != 0) + { + retval += "\nNumber: " + value; + } + if (node != null) + { + retval += "\nNode Name: " + node.getNodeName(); + } + return retval; + } + + // these strings should be localizable. + + private static String diagnostic(short code) + { + switch (code) + { + // DOM L1: + case INDEX_SIZE_ERR: + return "An index or size is out of range."; + case DOMSTRING_SIZE_ERR: + return "A string is too big."; + case HIERARCHY_REQUEST_ERR: + return "The node doesn't belong here."; + case WRONG_DOCUMENT_ERR: + return "The node belongs in another document."; + case INVALID_CHARACTER_ERR: + return "That character is not permitted."; + case NO_DATA_ALLOWED_ERR: + return "This node does not permit data."; + case NO_MODIFICATION_ALLOWED_ERR: + return "No changes are allowed."; + case NOT_FOUND_ERR: + return "The node was not found in that context."; + case NOT_SUPPORTED_ERR: + return "That object is not supported."; + case INUSE_ATTRIBUTE_ERR: + return "The attribute belongs to a different element."; + + // DOM L2: + case INVALID_STATE_ERR: + return "The object is not usable."; + case SYNTAX_ERR: + return "An illegal string was provided."; + case INVALID_MODIFICATION_ERR: + return "An object's type may not be changed."; + case NAMESPACE_ERR: + return "The operation violates XML Namespaces."; + case INVALID_ACCESS_ERR: + return "Parameter or operation isn't supported by this node."; + case TYPE_MISMATCH_ERR: + return "The type of the argument is incompatible with the expected type."; + } + return "Reserved exception number: " + code; + } + +} + diff --git a/libjava/gnu/xml/dom/DomExtern.java b/libjava/gnu/xml/dom/DomExtern.java new file mode 100644 index 0000000..afd5301 --- /dev/null +++ b/libjava/gnu/xml/dom/DomExtern.java @@ -0,0 +1,117 @@ +/* DomExtern.java -- + Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +/** + * <p> Abstract implemention of nodes describing external DTD-related + * objects. This facilitates reusing code for Entity, Notation, and + * DocumentType (really, external subset) nodes. Such support is not + * part of the core DOM; it's for the "XML" feature. </p> + * + * <p> Note that you are strongly advised to avoid using the DOM + * features that take advantage of this class, since (as of L2) none + * of them is defined fully enough to permit full use of the + * XML feature they partially expose. </p> + * + * @author David Brownell + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public abstract class DomExtern + extends DomNode +{ + + private final String name; + private final String publicId; + private final String systemId; + + /** + * Constructs a node associated with the specified document, + * with the specified descriptive data. + * + * @param owner The document with which this object is associated + * @param name Name of this object + * @param publicId If non-null, provides the entity's PUBLIC identifier + * @param systemId If non-null, provides the entity's SYSTEM identifier + */ + // package private + DomExtern(short nodeType, + DomDocument owner, + String name, + String publicId, + String systemId) + { + super(nodeType, owner); + this.name = name; + this.publicId = publicId; + this.systemId = systemId; + } + + /** + * <b>DOM L1</b> + * Returns the SYSTEM identifier associated with this object, if any. + */ + public final String getSystemId() + { + return systemId; + } + + /** + * <b>DOM L1</b> + * Returns the PUBLIC identifier associated with this object, if any. + */ + public final String getPublicId() + { + return publicId; + } + + /** + * <b>DOM L1</b> + * Returns the object's name. + */ + public final String getNodeName() + { + return name; + } + + public final String getLocalName() + { + return name; + } + +} + diff --git a/libjava/gnu/xml/dom/DomFragment.java b/libjava/gnu/xml/dom/DomFragment.java new file mode 100644 index 0000000..44763a9 --- /dev/null +++ b/libjava/gnu/xml/dom/DomFragment.java @@ -0,0 +1,76 @@ +/* DomFragment.java -- + Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import org.w3c.dom.DocumentFragment; + +/** + * <p> "DocumentFragment" implementation. </p> + * + * @author David Brownell + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class DomFragment + extends DomNode + implements DocumentFragment +{ + + /** + * Constructs a DocumentFragment node associated with the + * specified document. + * + * <p>This constructor should only be invoked by a Document as part of + * its createDocumentFragment functionality, or through a subclass which + * is similarly used in a "Sub-DOM" style layer. + */ + protected DomFragment(DomDocument owner) + { + super(DOCUMENT_FRAGMENT_NODE, owner); + } + + /** + * <b>DOM L1</b> + * Returns the string "#document-fragment". + */ + final public String getNodeName() + { + return "#document-fragment"; + } + +} + diff --git a/libjava/gnu/xml/dom/DomImpl.java b/libjava/gnu/xml/dom/DomImpl.java new file mode 100644 index 0000000..1a16de3 --- /dev/null +++ b/libjava/gnu/xml/dom/DomImpl.java @@ -0,0 +1,260 @@ +/* DomImpl.java -- + Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import org.w3c.dom.Document; +import org.w3c.dom.DocumentType; +import org.w3c.dom.DOMException; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Element; +import org.w3c.dom.ls.DOMImplementationLS; +import org.w3c.dom.ls.LSInput; +import org.w3c.dom.ls.LSOutput; +import org.w3c.dom.ls.LSParser; +import org.w3c.dom.ls.LSSerializer; +import gnu.xml.dom.ls.DomLSInput; +import gnu.xml.dom.ls.DomLSOutput; +import gnu.xml.dom.ls.DomLSParser; +import gnu.xml.dom.ls.DomLSSerializer; + +/** + * <p> "DOMImplementation" implementation. </p> + * + * <p> At this writing, the following features are supported: + * "XML" (L1, L2, L3), + * "Events" (L2), "MutationEvents" (L2), "USER-Events" (a conformant extension), + * "HTMLEvents" (L2), "UIEvents" (L2), "Traversal" (L2), "XPath" (L3), + * "LS" (L3) "LS-Async" (L3). + * It is possible to compile the package so it doesn't support some of these + * features (notably, Traversal). + * + * @author David Brownell + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class DomImpl + implements DOMImplementation, DOMImplementationLS +{ + + /** + * Constructs a DOMImplementation object which supports + * "XML" and other DOM Level 2 features. + */ + public DomImpl() + { + } + + /** + * <b>DOM L1</b> + * Returns true if the specified feature and version are + * supported. Note that the case of the feature name is ignored. + */ + public boolean hasFeature(String name, String version) + { + if (name.length() == 0) + { + return false; + } + name = name.toLowerCase(); + if (name.charAt(0) == '+') + { + name = name.substring(1); + } + + if ("xml".equals(name) || "core".equals(name)) + { + return (version == null || + "".equals(version) || + "1.0".equals(version) || + "2.0".equals(version) || + "3.0".equals(version)); + + } + else if ("ls".equals(name) || "ls-async".equals(name)) + { + return (version == null || + "".equals(version) || + "3.0".equals(version)); + } + else if ("events".equals(name) + || "mutationevents".equals(name) + || "uievents".equals(name) + // || "mouseevents".equals(name) + || "htmlevents".equals(name)) + { + return (version == null || + "".equals(version) || + "2.0".equals(version)); + + // Extension: "USER-" prefix event types can + // be created and passed through the DOM. + + } + else if ("user-events".equals(name)) + { + return (version == null || + "".equals(version) || + "0.1".equals(version)); + + // NOTE: "hasFeature" for events is here interpreted to + // mean the DOM can manufacture those sorts of events, + // since actually choosing to report the events is more + // often part of the environment or application. It's + // only really an issue for mutation events. + + } + else if (DomNode.reportMutations + && "traversal".equals(name)) + { + return (version == null || + "".equals(version) || + "2.0".equals(version)); + } + else if ("xpath".equals(name)) + { + return (version == null || + "".equals(version) || + "3.0".equals(version)); + } + + // views + // stylesheets + // css, css2 + // range + + return false; + } + + /** + * <b>DOM L2</b> + * Creates and returns a DocumentType, associated with this + * implementation. This DocumentType can have no associated + * objects(notations, entities) until the DocumentType is + * first associated with a document. + * + * <p> Note that there is no implication that this DTD will + * be parsed by the DOM, or ever have contents. Moreover, the + * DocumentType created here can only be added to a document by + * the createDocument method(below). <em>That means that the only + * portable way to create a Document object is to start parsing, + * queue comment and processing instruction (PI) nodes, and then only + * create a DOM Document after <b>(a)</b> it's known if a DocumentType + * object is needed, and <b>(b) the name and namespace of the root + * element is known. Queued comment and PI nodes would then be + * inserted appropriately in the document prologue, both before and + * after the DTD node, and additional attributes assigned to the + * root element.</em> + *(One hopes that the final DOM REC fixes this serious botch.) + */ + public DocumentType createDocumentType(String rootName, + String publicId, + String systemId) + // CR2 deleted internal subset, ensuring DocumentType + // is 100% useless instead of just 90% so. + { + DomDocument.checkNCName(rootName, false); + return new DomDoctype(this, rootName, publicId, systemId, null); + } + + /** + * <b>DOM L2</b> + * Creates and returns a Document, populated only with a root element and + * optionally a document type(if that was provided). + */ + public Document createDocument(String namespaceURI, + String rootName, + DocumentType doctype) + { + Document doc = new DomDocument(this); + Element root = null; + + if (rootName != null) + { + root = doc.createElementNS(namespaceURI, rootName); + if (rootName.startsWith("xmlns:")) + { + throw new DomEx(DomEx.NAMESPACE_ERR, "xmlns is reserved", null, 0); + } + } + // Bleech -- L2 seemingly _requires_ omission of xmlns attributes. + if (doctype != null) + { + doc.appendChild(doctype); // handles WRONG_DOCUMENT error + } + if (root != null) + { + doc.appendChild(root); + } + return doc; + } + + // DOM Level 3 + + public Object getFeature(String feature, String version) + { + if (hasFeature(feature, version)) + { + return this; + } + return null; + } + + // -- DOMImplementationLS -- + + public LSParser createLSParser(short mode, String schemaType) + throws DOMException + { + return new DomLSParser(mode, schemaType); + } + + public LSSerializer createLSSerializer() + { + return new DomLSSerializer(); + } + + public LSInput createLSInput() + { + return new DomLSInput(); + } + + public LSOutput createLSOutput() + { + return new DomLSOutput(); + } + +} + diff --git a/libjava/gnu/xml/dom/DomIterator.java b/libjava/gnu/xml/dom/DomIterator.java new file mode 100644 index 0000000..3dfd964 --- /dev/null +++ b/libjava/gnu/xml/dom/DomIterator.java @@ -0,0 +1,374 @@ +/* DomIterator.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import java.util.Vector; + +import org.w3c.dom.Node; +import org.w3c.dom.events.Event; +import org.w3c.dom.events.EventListener; +import org.w3c.dom.events.EventTarget; +import org.w3c.dom.events.MutationEvent; +import org.w3c.dom.traversal.NodeFilter; +import org.w3c.dom.traversal.NodeIterator; + +/** + * <p> "NodeIterator" implementation, usable with any L2 DOM which + * supports MutationEvents. </p> + * + * @author David Brownell + */ +public final class DomIterator + implements NodeIterator, EventListener +{ + + private Node reference; + private boolean right; + private boolean done; + + private final Node root; + private final int whatToShow; + private final NodeFilter filter; + private final boolean expandEntityReferences; + + /** + * Constructs and initializes an iterator. + */ + protected DomIterator(Node root, + int whatToShow, + NodeFilter filter, + boolean entityReferenceExpansion) + { + if (!root.isSupported("MutationEvents", "2.0")) + { + throw new DomEx(DomEx.NOT_SUPPORTED_ERR, + "Iterator needs mutation events", root, 0); + } + + this.root = root; + this.whatToShow = whatToShow; + this.filter = filter; + this.expandEntityReferences = entityReferenceExpansion; + + // start condition: going right, seen nothing yet. + reference = null; + right = true; + + EventTarget target = (EventTarget) root; + target.addEventListener("DOMNodeRemoved", this, false); + } + + /** + * <b>DOM L2</b> + * Flags the iterator as done, unregistering its event listener so + * that the iterator can be garbage collected without relying on weak + * references (a "Java 2" feature) in the event subsystem. + */ + public void detach() + { + EventTarget target = (EventTarget) root; + target.removeEventListener("DOMNodeRemoved", this, false); + done = true; + } + + /** + * <b>DOM L2</b> + * Returns the flag controlling whether iteration descends + * through entity references. + */ + public boolean getExpandEntityReferences() + { + return expandEntityReferences; + } + + /** + * <b>DOM L2</b> + * Returns the filter provided during construction. + */ + public NodeFilter getFilter() + { + return filter; + } + + /** + * <b>DOM L2</b> + * Returns the root of the tree this is iterating through. + */ + public Node getRoot() + { + return root; + } + + /** + * <b>DOM L2</b> + * Returns the mask of flags provided during construction. + */ + public int getWhatToShow() + { + return whatToShow; + } + + /** + * <b>DOM L2</b> + * Returns the next node in a forward iteration, masked and filtered. + * Note that the node may be read-only due to entity expansions. + * A null return indicates the iteration is complete, but may still + * be processed backwards. + */ + public Node nextNode() + { + if (done) + { + throw new DomEx(DomEx.INVALID_STATE_ERR); + } + right = true; + return walk(true); + } + + /** + * <b>DOM L2</b> + * Returns the next node in a backward iteration, masked and filtered. + * Note that the node may be read-only due to entity expansions. + * A null return indicates the iteration is complete, but may still + * be processed forwards. + */ + public Node previousNode() + { + if (done) + { + throw new DomEx(DomEx.INVALID_STATE_ERR); + } + Node previous = reference; + right = false; + walk(false); + return previous; + } + + private boolean shouldShow(Node node) + // raises Runtime exceptions indirectly, via acceptNode() + { + if ((whatToShow & (1 << (node.getNodeType() - 1))) == 0) + { + return false; + } + if (filter == null) + { + return true; + } + return filter.acceptNode(node) == NodeFilter.FILTER_ACCEPT; + } + + // + // scenario: root = 1, sequence = 1 2 ... 3 4 + // forward walk: 1 2 ... 3 4 null + // then backward: 4 3 ... 2 1 null + // + // At the leftmost end, "previous" == null + // At the rightmost end, "previous" == 4 + // + // The current draft spec really seem to make no sense re the + // role of the reference node, so what it says is ignored here. + // + private Node walk(boolean forward) + { + Node here = reference; + + while ((here = successor(here, forward)) != null + && !shouldShow(here)) + { + continue; + } + if (here != null || !forward) + { + reference = here; + } + return here; + } + + private boolean isLeaf(Node here) + { + boolean leaf = !here.hasChildNodes(); + if (!leaf && !expandEntityReferences) + { + leaf = (here.getNodeType() == Node.ENTITY_REFERENCE_NODE); + } + return leaf; + } + + // + // Returns the immediate successor in a forward (or backward) + // document order walk, sans filtering ... except that it knows + // how to stop, returning null when done. This is a depth first + // preorder traversal when run in the forward direction. + // + private Node successor(Node here, boolean forward) + { + Node next; + + // the "leftmost" end is funky + if (here == null) + { + return forward ? root : null; + } + + // + // Forward, this is preorder: children before siblings. + // Backward, it's postorder: we saw the children already. + // + if (forward && !isLeaf(here)) + { + return here.getFirstChild(); + } + + // + // Siblings ... if forward, we visit them, if backwards + // we visit their children first. + // + if (forward) + { + if ((next = here.getNextSibling()) != null) + { + return next; + } + } + else if ((next = here.getPreviousSibling()) != null) + { + if (isLeaf(next)) + { + return next; + } + next = next.getLastChild(); + while (!isLeaf(next)) + { + next = next.getLastChild(); + } + return next; + } + + // + // We can't go down or lateral -- it's up, then. The logic is + // the converse of what's above: backwards is easy (the parent + // is next), forwards isn't. + // + next = here.getParentNode(); + if (!forward) + { + return next; + } + + Node temp = null; + while (next != null + && next != root + && (temp = next.getNextSibling()) == null) + { + next = next.getParentNode(); + } + if (next == root) + { + return null; + } + return temp; + } + + /** + * Not for public use. This lets the iterator know when its + * reference node will be removed from the tree, so that a new + * one may be selected. + * + * <p> This version works by watching removal events as they + * bubble up. So, don't prevent them from bubbling. + */ + public void handleEvent(Event e) + { + MutationEvent event; + Node ancestor, removed; + + if (reference == null + || !"DOMNodeRemoved".equals(e.getType()) + || e.getEventPhase() != Event.BUBBLING_PHASE) + { + return; + } + + event = (MutationEvent) e; + removed = (Node) event.getTarget(); + + // See if the removal will cause trouble for this iterator + // by being the reference node or an ancestor of it. + for (ancestor = reference; + ancestor != null && ancestor != root; + ancestor = ancestor.getParentNode()) + { + if (ancestor == removed) + { + break; + } + } + if (ancestor != removed) + { + return; + } + + // OK, it'll cause trouble. We want to make the "next" + // node in our current traversal direction seem right. + // So we pick the nearest node that's not getting removed, + // but go in the _opposite_ direction from our current + // traversal ... so the "next" doesn't skip anything. + Node candidate; + +search: + while ((candidate = walk(!right)) != null) + { + for (ancestor = candidate; + ancestor != null && ancestor != root; + ancestor = ancestor.getParentNode()) + { + if (ancestor == removed) + { + continue search; + } + } + return; + } + + // The current DOM WD talks about a special case here; + // I've not yet seen it. + } + +} + diff --git a/libjava/gnu/xml/dom/DomNSResolverContext.java b/libjava/gnu/xml/dom/DomNSResolverContext.java new file mode 100644 index 0000000..cf75ec1 --- /dev/null +++ b/libjava/gnu/xml/dom/DomNSResolverContext.java @@ -0,0 +1,90 @@ +/* DomNSResolverContext.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import java.util.Iterator; +import javax.xml.namespace.NamespaceContext; +import org.w3c.dom.xpath.XPathNSResolver; + +/** + * Namespace content wrapper for an XPathNSResolver. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class DomNSResolverContext + implements NamespaceContext, Iterator +{ + + final XPathNSResolver resolver; + + DomNSResolverContext(XPathNSResolver resolver) + { + this.resolver = resolver; + } + + public String getNamespaceURI(String prefix) + { + return resolver.lookupNamespaceURI(prefix); + } + + public String getPrefix(String namespaceURI) + { + return null; + } + + public Iterator getPrefixes(String namespaceURI) + { + return this; + } + + public boolean hasNext() + { + return false; + } + + public Object next() + { + return null; + } + + public void remove() + { + throw new UnsupportedOperationException(); + } + +} diff --git a/libjava/gnu/xml/dom/DomNamedNodeMap.java b/libjava/gnu/xml/dom/DomNamedNodeMap.java new file mode 100644 index 0000000..af75942 --- /dev/null +++ b/libjava/gnu/xml/dom/DomNamedNodeMap.java @@ -0,0 +1,416 @@ +/* DomNamedNodeMap.java -- + Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +/** + * <p> "NamedNodeMap" implementation. </p> + * Used mostly to hold element attributes, but sometimes also + * to list notations or entities. + * + * @author David Brownell + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class DomNamedNodeMap + implements NamedNodeMap +{ + + final DomNode owner; + final short type; + + DomNode first; + int length; + boolean readonly; + + // package private + DomNamedNodeMap(DomNode owner, short type) + { + this.owner = owner; + this.type = type; + } + + /** + * Exposes the internal "readonly" flag. In DOM, all NamedNodeMap + * objects found in a DocumentType object are read-only (after + * they are fully constructed), and those holding attributes of + * a readonly element will also be readonly. + */ + public final boolean isReadonly() + { + return readonly; + } + + /** + * Sets the internal "readonly" flag so the node and its + * children can't be changed. + */ + public void makeReadonly() + { + readonly = true; + for (DomNode ctx = first; ctx != null; ctx = ctx.next) + { + ctx.makeReadonly(); + } + } + + /** + * <b>DOM L1</b> + * Returns the named item from the map, or null; names are just + * the nodeName property. + */ + public Node getNamedItem(String name) + { + for (DomNode ctx = first; ctx != null; ctx = ctx.next) + { + if (ctx.getNodeName().equals(name)) + { + return ctx; + } + } + return null; + } + + /** + * <b>DOM L2</b> + * Returns the named item from the map, or null; names are the + * localName and namespaceURI properties, ignoring any prefix. + */ + public Node getNamedItemNS(String namespaceURI, String localName) + { + if ("".equals(namespaceURI)) + { + namespaceURI = null; + } + for (DomNode ctx = first; ctx != null; ctx = ctx.next) + { + String name = ctx.getLocalName(); + if ((localName == null && name == null) || + (localName != null && localName.equals(name))) + { + String uri = ctx.getNamespaceURI(); + if ("".equals(uri)) + { + uri = null; + } + if ((namespaceURI == null && uri == null) || + (namespaceURI != null && namespaceURI.equals(uri))) + { + return ctx; + } + } + } + return null; + } + + /** + * <b>DOM L1</b> + * Stores the named item into the map, optionally overwriting + * any existing node with that name. The name used is just + * the nodeName attribute. + */ + public Node setNamedItem(Node arg) + { + return setNamedItem(arg, false); + } + + /** + * <b>DOM L2</b> + * Stores the named item into the map, optionally overwriting + * any existing node with that fully qualified name. The name + * used incorporates the localName and namespaceURI properties, + * and ignores any prefix. + */ + public Node setNamedItemNS(Node arg) + { + return setNamedItem(arg, true); + } + + Node setNamedItem(Node arg, boolean ns) + { + if (readonly) + { + throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR); + } + + DomNode node = (DomNode) arg; + if (node.owner != owner.owner) + { + throw new DomEx(DomEx.WRONG_DOCUMENT_ERR); + } + if (node.nodeType != type) + { + throw new DomEx(DomEx.HIERARCHY_REQUEST_ERR); + } + if (node.nodeType == Node.ATTRIBUTE_NODE) + { + DomNode element = node.parent; + if (element != null && element != owner) + { + throw new DomEx(DomEx.INUSE_ATTRIBUTE_ERR); + } + node.parent = owner; + node.depth = owner.depth + 1; + } + + String nodeName = node.getNodeName(); + String localName = ns ? node.getLocalName() : null; + String namespaceURI = ns ? node.getNamespaceURI() : null; + if ("".equals(namespaceURI)) + { + namespaceURI = null; + } + + // maybe attribute ADDITION events (?) + DomNode last = null; + for (DomNode ctx = first; ctx != null; ctx = ctx.next) + { + boolean test = false; + if (ns) + { + String tln = ctx.getLocalName(); + if (tln.equals(localName)) + { + String tu = ctx.getNamespaceURI(); + if ((tu == null && namespaceURI == null) || + (tu != null && tu.equals(namespaceURI))) + { + test = true; + } + } + } + else + { + test = ctx.getNodeName().equals(nodeName); + } + if (test) + { + // replace + node.previous = ctx.previous; + node.next = ctx.next; + if (ctx.previous != null) + { + ctx.previous.next = node; + } + if (ctx.next != null) + { + ctx.next.previous = node; + } + if (first == ctx) + { + first = node; + } + reparent(node, nodeName, ctx.index); + ctx.parent = null; + ctx.next = null; + ctx.previous = null; + ctx.setDepth(0); + ctx.index = 0; + return ctx; + } + last = ctx; + } + // append + if (last != null) + { + last.next = node; + node.previous = last; + } + else + { + first = node; + } + length++; + reparent(node, nodeName, 0); + return null; + } + + void reparent(DomNode node, String nodeName, int i) + { + node.parent = owner; + node.setDepth(owner.depth + 1); + // index renumbering + for (DomNode ctx = node; ctx != null; ctx = ctx.next) + { + ctx.index = i++; + } + // cache xml:space + boolean xmlSpace = "xml:space".equals(nodeName); + if (xmlSpace && owner instanceof DomElement) + { + ((DomElement) owner).xmlSpace = node.getNodeValue(); + } + } + + /** + * <b>DOM L1</b> + * Removes the named item from the map, or reports an exception; + * names are just the nodeName property. + */ + public Node removeNamedItem(String name) + { + return removeNamedItem(null, name, false); + } + + /** + * <b>DOM L2</b> + * Removes the named item from the map, or reports an exception; + * names are the localName and namespaceURI properties. + */ + public Node removeNamedItemNS(String namespaceURI, String localName) + { + return removeNamedItem(namespaceURI, localName, true); + } + + Node removeNamedItem(String uri, String name, boolean ns) + { + if (readonly) + { + throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR); + } + + // report attribute REMOVAL event? + + for (DomNode ctx = first; ctx != null; ctx = ctx.next) + { + boolean test = false; + String nodeName = ctx.getNodeName(); + if (ns) + { + String tln = ctx.getLocalName(); + if (tln.equals(name)) + { + String tu = ctx.getNamespaceURI(); + if ((tu == null && uri == null) || + (tu != null && tu.equals(uri))) + { + test = true; + } + } + } + else + { + test = nodeName.equals(name); + } + if (test) + { + // uncache xml:space + boolean xmlSpace = "xml:space".equals(nodeName); + if (xmlSpace && owner instanceof DomElement) + { + ((DomElement) owner).xmlSpace = ""; + } + // is this a default attribute? + if (ctx.nodeType == Node.ATTRIBUTE_NODE) + { + String def = getDefaultValue(ctx.getNodeName()); + if (def != null) + { + ctx.setNodeValue(def); + ((DomAttr) ctx).setSpecified(false); + return null; + } + } + // remove + if (ctx == first) + { + first = ctx.next; + } + if (ctx.previous != null) + { + ctx.previous.next = ctx.next; + } + if (ctx.next != null) + { + ctx.next.previous = ctx.previous; + } + length--; + ctx.previous = null; + ctx.next = null; + ctx.parent = null; + ctx.setDepth(0); + ctx.index = 0; + return ctx; + } + } + throw new DomEx(DomEx.NOT_FOUND_ERR); + } + + String getDefaultValue(String name) + { + DomDoctype doctype = (DomDoctype) owner.owner.getDoctype(); + if (doctype == null) + { + return null; + } + DTDAttributeTypeInfo info = + doctype.getAttributeTypeInfo(owner.getNodeName(), name); + if (info == null) + { + return null; + } + return info.value; + } + + /** + * <b>DOM L1</b> + * Returns the indexed item from the map, or null. + */ + public Node item(int index) + { + DomNode ctx = first; + int count = 0; + while (ctx != null && count < index) + { + ctx = ctx.next; + count++; + } + return ctx; + } + + /** + * <b>DOM L1</b> + * Returns the length of the map. + */ + public int getLength() + { + return length; + } + +} diff --git a/libjava/gnu/xml/dom/DomNode.java b/libjava/gnu/xml/dom/DomNode.java new file mode 100644 index 0000000..3858fe8 --- /dev/null +++ b/libjava/gnu/xml/dom/DomNode.java @@ -0,0 +1,2180 @@ +/* DomNode.java -- + Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import javax.xml.XMLConstants; + +import org.w3c.dom.Document; +import org.w3c.dom.DOMException; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.Text; +import org.w3c.dom.UserDataHandler; +import org.w3c.dom.events.DocumentEvent; +import org.w3c.dom.events.Event; +import org.w3c.dom.events.EventException; +import org.w3c.dom.events.EventListener; +import org.w3c.dom.events.EventTarget; +import org.w3c.dom.events.MutationEvent; +import org.w3c.dom.traversal.NodeFilter; +import org.w3c.dom.traversal.NodeIterator; +import org.w3c.dom.traversal.TreeWalker; + +/** + * <p> "Node", "EventTarget", and "DocumentEvent" implementation. + * This provides most of the core DOM functionality; only more + * specialized features are provided by subclasses. Those subclasses may + * have some particular constraints they must implement, by overriding + * methods defined here. Such constraints are noted here in the method + * documentation. </p> + * + * <p> Note that you can create events with type names prefixed with "USER-", + * and pass them through this DOM. This lets you use the DOM event scheme + * for application specific purposes, although you must use a predefined event + * structure (such as MutationEvent) to pass data along with those events. + * Test for existence of this feature with the "USER-Events" DOM feature + * name.</p> + * + * <p> Other kinds of events you can send include the "html" events, + * like "load", "unload", "abort", "error", and "blur"; and the mutation + * events. If this DOM has been compiled with mutation event support + * enabled, it will send mutation events when you change parts of the + * tree; otherwise you may create and send such events yourself, but + * they won't be generated by the DOM itself. </p> + * + * <p> Note that there is a namespace-aware name comparison method, + * <em>nameAndTypeEquals</em>, which compares the names (and types) of + * two nodes in conformance with the "Namespaces in XML" specification. + * While mostly intended for use with elements and attributes, this should + * also be helpful for ProcessingInstruction nodes and some others which + * do not have namespace URIs. + * + * @author David Brownell + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public abstract class DomNode + implements Node, NodeList, EventTarget, DocumentEvent, Cloneable, Comparable +{ + + // package private + //final static String xmlNamespace = "http://www.w3.org/XML/1998/namespace"; + //final static String xmlnsURI = "http://www.w3.org/2000/xmlns/"; + + // tunable + // NKIDS_* affects arrays of children (which grow) + // (currently) fixed size: + // ANCESTORS_* is for event capture/bubbling, # ancestors + // NOTIFICATIONS_* is for per-node event delivery, # events + private static final int NKIDS_DELTA = 8; + private static final int ANCESTORS_INIT = 20; + private static final int NOTIFICATIONS_INIT = 10; + + // tunable: enable mutation events or not? Enabling it costs about + // 10-15% in DOM construction time, last time it was measured. + + // package private !!! + static final boolean reportMutations = true; + + // locking protocol changeable only within this class + private static final Object lockNode = new Object(); + + // NON-FINAL class data + + // Optimize event dispatch by not allocating memory each time + private static boolean dispatchDataLock; + private static DomNode[] ancestors = new DomNode[ANCESTORS_INIT]; + private static ListenerRecord[] notificationSet + = new ListenerRecord[NOTIFICATIONS_INIT]; + + // Ditto for the (most common) event object itself! + private static boolean eventDataLock; + private static DomEvent.DomMutationEvent mutationEvent + = new DomEvent.DomMutationEvent(null); + + // + // PER-INSTANCE DATA + // + + DomDocument owner; + DomNode parent; // parent node; + DomNode previous; // previous sibling node + DomNode next; // next sibling node + DomNode first; // first child node + DomNode last; // last child node + int index; // index of this node in its parent's children + int depth; // depth of the node in the document + int length; // number of children + final short nodeType; + + // Bleech ... "package private" so a builder can populate entity refs. + // writable during construction. DOM spec is nasty. + boolean readonly; + + // event registrations + private ListenerRecord[] listeners; + private int nListeners; + + // DOM Level 3 userData dictionary. + private HashMap userData; + private HashMap userDataHandlers; + + // + // Some of the methods here are declared 'final' because + // knowledge about their implementation is built into this + // class -- for both integrity and performance. + // + + /** + * Reduces space utilization for this node. + */ + public void compact() + { + if (listeners != null && listeners.length != nListeners) + { + if (nListeners == 0) + { + listeners = null; + } + else + { + ListenerRecord[] l = new ListenerRecord[nListeners]; + System.arraycopy(listeners, 0, l, 0, nListeners); + listeners = l; + } + } + } + + /** + * Constructs a node and associates it with its owner. Only + * Document and DocumentType nodes may be created with no owner, + * and DocumentType nodes get an owner as soon as they are + * associated with a document. + */ + protected DomNode(short nodeType, DomDocument owner) + { + this.nodeType = nodeType; + + if (owner == null) + { + // DOM calls never go down this path + if (nodeType != DOCUMENT_NODE && nodeType != DOCUMENT_TYPE_NODE) + { + throw new IllegalArgumentException ("no owner!"); + } + } + this.owner = owner; + } + + + /** + * <b>DOM L1</b> + * Returns null; Element subclasses must override this method. + */ + public NamedNodeMap getAttributes() + { + return null; + } + + /** + * <b>DOM L2></b> + * Returns true iff this is an element node with attributes. + */ + public boolean hasAttributes() + { + return false; + } + + /** + * <b>DOM L1</b> + * Returns a list, possibly empty, of the children of this node. + * In this implementation, to conserve memory, nodes are the same + * as their list of children. This can have ramifications for + * subclasses, which may need to provide their own getLength method + * for reasons unrelated to the NodeList method of the same name. + */ + public NodeList getChildNodes() + { + return this; + } + + /** + * <b>DOM L1</b> + * Returns the first child of this node, or null if there are none. + */ + public Node getFirstChild() + { + return first; + } + + /** + * <b>DOM L1</b> + * Returns the last child of this node, or null if there are none. + */ + public Node getLastChild() + { + return last; + } + + /** + * <b>DOM L1</b> + * Returns true if this node has children. + */ + public boolean hasChildNodes() + { + return length != 0; + } + + + /** + * Exposes the internal "readonly" flag. In DOM, children of + * entities and entity references are readonly, as are the + * objects associated with DocumentType objets. + */ + public final boolean isReadonly() + { + return readonly; + } + + /** + * Sets the internal "readonly" flag so this subtree can't be changed. + * Subclasses need to override this method for any associated content + * that's not a child node, such as an element's attributes or the + * (few) declarations associated with a DocumentType. + */ + public void makeReadonly() + { + readonly = true; + for (DomNode child = first; child != null; child = child.next) + { + child.makeReadonly(); + } + } + + /** + * Used to adopt a node to a new document. + */ + void setOwner(DomDocument doc) + { + this.owner = doc; + for (DomNode ctx = first; ctx != null; ctx = ctx.next) + { + ctx.setOwner(doc); + } + } + + // just checks the node for inclusion -- may be called many + // times (docfrag) before anything is allowed to change + private void checkMisc(DomNode child) + { + if (readonly && !owner.building) + { + throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR, + null, this, 0); + } + for (DomNode ctx = this; ctx != null; ctx = ctx.parent) + { + if (child == ctx) + { + throw new DomEx(DomEx.HIERARCHY_REQUEST_ERR, + "can't make ancestor into a child", this, 0); + } + } + + DomDocument owner = (nodeType == DOCUMENT_NODE) ? (DomDocument) this : + this.owner; + DomDocument childOwner = child.owner; + short childNodeType = child.nodeType; + + if (childOwner != owner) + { + // new in DOM L2, this case -- patch it up later, in reparent() + if (!(childNodeType == DOCUMENT_TYPE_NODE && childOwner == null)) + { + throw new DomEx(DomEx.WRONG_DOCUMENT_ERR, + null, child, 0); + } + } + + // enforce various structural constraints + switch (nodeType) + { + case DOCUMENT_NODE: + switch (childNodeType) + { + case ELEMENT_NODE: + case PROCESSING_INSTRUCTION_NODE: + case COMMENT_NODE: + case DOCUMENT_TYPE_NODE: + return; + } + break; + + case ATTRIBUTE_NODE: + switch (childNodeType) + { + case TEXT_NODE: + case ENTITY_REFERENCE_NODE: + return; + } + break; + + case DOCUMENT_FRAGMENT_NODE: + case ENTITY_REFERENCE_NODE: + case ELEMENT_NODE: + case ENTITY_NODE: + switch (childNodeType) + { + case ELEMENT_NODE: + case TEXT_NODE: + case COMMENT_NODE: + case PROCESSING_INSTRUCTION_NODE: + case CDATA_SECTION_NODE: + case ENTITY_REFERENCE_NODE: + return; + } + break; + } + if (owner.checkingWellformedness) + { + throw new DomEx(DomEx.HIERARCHY_REQUEST_ERR, + "can't append " + nodeTypeToString(childNodeType) + + " to node of type " + nodeTypeToString(nodeType), + this, 0); + } + } + + // Here's hoping a good optimizer will detect the case when the + // next several methods are never called, and won't allocate + // object code space of any kind. (Case: not reporting any + // mutation events. We can also remove some static variables + // listed above.) + + private void insertionEvent(DomEvent.DomMutationEvent event, + DomNode target) + { + if (owner == null || owner.building) + { + return; + } + boolean doFree = false; + + if (event == null) + { + event = getMutationEvent(); + } + if (event != null) + { + doFree = true; + } + else + { + event = new DomEvent.DomMutationEvent(null); + } + event.initMutationEvent("DOMNodeInserted", + true /* bubbles */, false /* nocancel */, + this /* related */, null, null, null, (short) 0); + target.dispatchEvent(event); + + // XXX should really visit every descendant of 'target' + // and sent a DOMNodeInsertedIntoDocument event to it... + // bleech, there's no way to keep that acceptably fast. + + if (doFree) + { + event.target = null; + event.relatedNode = null; + event.currentNode = null; + eventDataLock = false; + } // else we created work for the GC + } + + private void removalEvent(DomEvent.DomMutationEvent event, + DomNode target) + { + if (owner == null || owner.building) + { + return; + } + boolean doFree = false; + + if (event == null) + { + event = getMutationEvent(); + } + if (event != null) + { + doFree = true; + } + else + { + event = new DomEvent.DomMutationEvent(null); + } + event.initMutationEvent("DOMNodeRemoved", + true /* bubbles */, false /* nocancel */, + this /* related */, null, null, null, (short) 0); + target.dispatchEvent(event); + + // XXX should really visit every descendant of 'target' + // and sent a DOMNodeRemovedFromDocument event to it... + // bleech, there's no way to keep that acceptably fast. + + event.target = null; + event.relatedNode = null; + event.currentNode = null; + if (doFree) + { + eventDataLock = false; + } + // else we created more work for the GC + } + + // + // Avoid creating lots of memory management work, by using a simple + // allocation strategy for the mutation event objects that get used + // at least once per tree modification. We can't use stack allocation, + // so we do the next simplest thing -- more or less, static allocation. + // Concurrent notifications should be rare, anyway. + // + // Returns the preallocated object, which needs to be carefully freed, + // or null to indicate the caller needs to allocate their own. + // + static private DomEvent.DomMutationEvent getMutationEvent() + { + synchronized (lockNode) + { + if (eventDataLock) + { + return null; + } + eventDataLock = true; + return mutationEvent; + } + } + + // NOTE: this is manually inlined in the insertion + // and removal event methods above; change in sync. + static private void freeMutationEvent() + { + // clear fields to enable GC + mutationEvent.clear(); + eventDataLock = false; + } + + void setDepth(int depth) + { + this.depth = depth; + for (DomNode ctx = first; ctx != null; ctx = ctx.next) + { + ctx.setDepth(depth + 1); + } + } + + /** + * <b>DOM L1</b> + * Appends the specified node to this node's list of children. + * Document subclasses must override this to enforce the restrictions + * that there be only one element and document type child. + * + * <p> Causes a DOMNodeInserted mutation event to be reported. + * Will first cause a DOMNodeRemoved event to be reported if the + * parameter already has a parent. If the new child is a document + * fragment node, both events will be reported for each child of + * the fragment; the order in which children are removed and + * inserted is implementation-specific. + * + * <p> If this DOM has been compiled without mutation event support, + * these events will not be reported. + */ + public Node appendChild(Node newChild) + { + try + { + DomNode child = (DomNode) newChild; + + if (child.nodeType == DOCUMENT_FRAGMENT_NODE) + { + // Append all nodes in the fragment to this node + for (DomNode ctx = child.first; ctx != null; ctx = ctx.next) + { + checkMisc(ctx); + } + for (DomNode ctx = child.first; ctx != null; ) + { + DomNode ctxNext = ctx.next; + appendChild(ctx); + ctx = ctxNext; + } + } + else + { + checkMisc(child); + if (child.parent != null) + { + child.parent.removeChild(child); + } + child.parent = this; + child.index = length++; + child.setDepth(depth + 1); + child.next = null; + if (last == null) + { + first = child; + child.previous = null; + } + else + { + last.next = child; + child.previous = last; + } + last = child; + + if (reportMutations) + { + insertionEvent(null, child); + } + } + + return child; + } + catch (ClassCastException e) + { + throw new DomEx(DomEx.WRONG_DOCUMENT_ERR, + null, newChild, 0); + } + } + + /** + * <b>DOM L1</b> + * Inserts the specified node in this node's list of children. + * Document subclasses must override this to enforce the restrictions + * that there be only one element and document type child. + * + * <p> Causes a DOMNodeInserted mutation event to be reported. Will + * first cause a DOMNodeRemoved event to be reported if the newChild + * parameter already has a parent. If the new child is a document + * fragment node, both events will be reported for each child of + * the fragment; the order in which children are removed and inserted + * is implementation-specific. + * + * <p> If this DOM has been compiled without mutation event support, + * these events will not be reported. + */ + public Node insertBefore(Node newChild, Node refChild) + { + if (refChild == null) + { + return appendChild(newChild); + } + + try + { + DomNode child = (DomNode) newChild; + DomNode ref = (DomNode) refChild; + + if (child.nodeType == DOCUMENT_FRAGMENT_NODE) + { + // Append all nodes in the fragment to this node + for (DomNode ctx = child.first; ctx != null; ctx = ctx.next) + { + checkMisc(ctx); + } + for (DomNode ctx = child.first; ctx != null; ) + { + DomNode ctxNext = ctx.next; + insertBefore(ctx, ref); + ctx = ctxNext; + } + } + else + { + checkMisc(child); + if (ref == null || ref.parent != this) + { + throw new DomEx(DomEx.NOT_FOUND_ERR, null, ref, 0); + } + if (ref == child) + { + throw new DomEx(DomEx.HIERARCHY_REQUEST_ERR, + "can't insert node before itself", ref, 0); + } + + if (child.parent != null) + { + child.parent.removeChild(child); + } + child.parent = this; + int i = ref.index; + child.setDepth(depth + 1); + child.next = ref; + if (ref.previous != null) + { + ref.previous.next = child; + } + child.previous = ref.previous; + ref.previous = child; + if (first == ref) + { + first = child; + } + // index renumbering + for (DomNode ctx = child; ctx != null; ctx = ctx.next) + { + ctx.index = i++; + } + + if (reportMutations) + { + insertionEvent(null, child); + } + } + + return child; + } + catch (ClassCastException e) + { + throw new DomEx(DomEx.WRONG_DOCUMENT_ERR, + null, newChild, 0); + } + } + + /** + * <b>DOM L1</b> + * Replaces the specified node in this node's list of children. + * Document subclasses must override this to test the restrictions + * that there be only one element and document type child. + * + * <p> Causes DOMNodeRemoved and DOMNodeInserted mutation event to be + * reported. Will cause another DOMNodeRemoved event to be reported if + * the newChild parameter already has a parent. These events may be + * delivered in any order, except that the event reporting removal + * from such an existing parent will always be delivered before the + * event reporting its re-insertion as a child of some other node. + * The order in which children are removed and inserted is implementation + * specific. + * + * <p> If your application needs to depend on the in which those removal + * and insertion events are delivered, don't use this API. Instead, + * invoke the removeChild and insertBefore methods directly, to guarantee + * a specific delivery order. Similarly, don't use document fragments, + * Otherwise your application code may not work on a DOM which implements + * this method differently. + * + * <p> If this DOM has been compiled without mutation event support, + * these events will not be reported. + */ + public Node replaceChild(Node newChild, Node refChild) + { + try + { + DomNode child = (DomNode) newChild; + DomNode ref = (DomNode) refChild; + + DomEvent.DomMutationEvent event = getMutationEvent(); + boolean doFree = (event != null); + + if (child.nodeType == DOCUMENT_FRAGMENT_NODE) + { + // Append all nodes in the fragment to this node + for (DomNode ctx = child.first; ctx != null; ctx = ctx.next) + { + checkMisc(ctx); + } + if (ref == null || ref.parent != this) + { + throw new DomEx(DomEx.NOT_FOUND_ERR, null, ref, 0); + } + + if (reportMutations) + { + removalEvent(event, ref); + } + length--; + length += child.length; + + if (child.length == 0) + { + // Removal + if (ref.previous != null) + { + ref.previous.next = ref.next; + } + if (ref.next != null) + { + ref.next.previous = ref.previous; + } + if (first == ref) + { + first = ref.next; + } + if (last == ref) + { + last = ref.previous; + } + } + else + { + int i = ref.index; + for (DomNode ctx = child.first; ctx != null; ctx = ctx.next) + { + // Insertion + ctx.parent = this; + ctx.index = i++; + ctx.setDepth(ref.depth); + if (ctx == child.first) + { + ctx.previous = ref.previous; + } + if (ctx == child.last) + { + ctx.next = ref.next; + } + } + if (first == ref) + { + first = child.first; + } + if (last == ref) + { + last = child.last; + } + } + } + else + { + checkMisc(child); + if (ref == null || ref.parent != this) + { + throw new DomEx(DomEx.NOT_FOUND_ERR, null, ref, 0); + } + + if (reportMutations) + { + removalEvent(event, ref); + } + + if (child.parent != null) + { + child.parent.removeChild(child); + } + child.parent = this; + child.index = ref.index; + child.setDepth(ref.depth); + if (ref.previous != null) + { + ref.previous.next = child; + } + child.previous = ref.previous; + if (ref.next != null) + { + ref.next.previous = child; + } + child.next = ref.next; + if (first == ref) + { + first = child; + } + if (last == ref) + { + last = child; + } + + if (reportMutations) + { + insertionEvent(event, child); + } + if (doFree) + { + freeMutationEvent(); + } + } + ref.parent = null; + ref.index = 0; + ref.setDepth(0); + ref.previous = null; + ref.next = null; + + return ref; + } + catch (ClassCastException e) + { + throw new DomEx(DomEx.WRONG_DOCUMENT_ERR, + null, newChild, 0); + } + } + + /** + * <b>DOM L1</b> + * Removes the specified child from this node's list of children, + * or else reports an exception. + * + * <p> Causes a DOMNodeRemoved mutation event to be reported. + * + * <p> If this DOM has been compiled without mutation event support, + * these events will not be reported. + */ + public Node removeChild(Node refChild) + { + try + { + DomNode ref = (DomNode) refChild; + + if (ref == null || ref.parent != this) + { + throw new DomEx(DomEx.NOT_FOUND_ERR, null, ref, 0); + } + if (readonly && !owner.building) + { + throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR, + null, this, 0); + } + + for (DomNode child = first; child != null; child = child.next) + { + if (child == ref) + { + if (reportMutations) + { + removalEvent(null, child); + } + + length--; + if (ref.previous != null) + { + ref.previous.next = ref.next; + } + if (ref.next != null) + { + ref.next.previous = ref.previous; + } + if (first == ref) + { + first = ref.next; + } + if (last == ref) + { + last = ref.previous; + } + // renumber indices + int i = 0; + for (DomNode ctx = first; ctx != null; ctx = ctx.next) + { + ctx.index = i++; + } + ref.parent = null; + ref.setDepth(0); + ref.index = 0; + ref.previous = null; + ref.next = null; + + return ref; + } + } + throw new DomEx(DomEx.NOT_FOUND_ERR, + "that's no child of mine", refChild, 0); + } + catch (ClassCastException e) + { + throw new DomEx(DomEx.WRONG_DOCUMENT_ERR, + null, refChild, 0); + } + } + + /** + * <b>DOM L1 (NodeList)</b> + * Returns the item with the specified index in this NodeList, + * else null. + */ + public Node item(int index) + { + DomNode child = first; + int count = 0; + while (child != null && count < index) + { + child = child.next; + count++; + } + return child; + } + + /** + * <b>DOM L1 (NodeList)</b> + * Returns the number of elements in this NodeList. + * (Note that many interfaces have a "Length" property, not just + * NodeList, and if a node subtype must implement one of those, + * it will also need to override getChildNodes.) + */ + public int getLength() + { + return length; + } + + /** + * Minimize extra space consumed by this node to hold children and event + * listeners. + */ + public void trimToSize() + { + if (listeners != null && listeners.length != nListeners) + { + ListenerRecord[] newKids = new ListenerRecord[length]; + System.arraycopy(listeners, 0, newKids, 0, nListeners); + listeners = newKids; + } + } + + /** + * <b>DOM L1</b> + * Returns the previous sibling, if one is known. + */ + public Node getNextSibling() + { + return next; + } + + /** + * <b>DOM L1</b> + * Returns the previous sibling, if one is known. + */ + public Node getPreviousSibling() + { + return previous; + } + + /** + * <b>DOM L1</b> + * Returns the parent node, if one is known. + */ + public Node getParentNode() + { + return parent; + } + + /** + * <b>DOM L2</b> + * Consults the DOM implementation to determine if the requested + * feature is supported. DocumentType subclasses must override + * this method, and associate themselves directly with the + * DOMImplementation node used. (This method relies on being able + * to access the DOMImplementation from the owner document, but + * DocumentType nodes can be created without an owner.) + */ + public boolean isSupported(String feature, String version) + { + Document doc = owner; + DOMImplementation impl = null; + + if (doc == null && nodeType == DOCUMENT_NODE) + { + doc = (Document) this; + } + + if (doc == null) + { + // possible for DocumentType + throw new IllegalStateException ("unbound ownerDocument"); + } + + impl = doc.getImplementation(); + return impl.hasFeature(feature, version); + } + + /** + * <b>DOM L1 (modified in L2)</b> + * Returns the owner document. This is only null for Document nodes, + * and (new in L2) for DocumentType nodes which have not yet been + * associated with the rest of their document. + */ + final public Document getOwnerDocument() + { + return owner; + } + + /** + * <b>DOM L1</b> + * Does nothing; this must be overridden (along with the + * getNodeValue method) for nodes with a non-null defined value. + */ + public void setNodeValue(String value) + { + } + + /** + * <b>DOM L1</b> + * Returns null; this must be overridden for nodes types with + * a defined value, along with the setNodeValue method. + */ + public String getNodeValue() + { + return null; + } + + /** This forces GCJ compatibility. + * Without this method GCJ is unable to compile to byte code. + */ + public final short getNodeType() + { + return nodeType; + } + + /** This forces GCJ compatibility. + * Without this method GCJ seems unable to natively compile GNUJAXP. + */ + public abstract String getNodeName(); + + /** + * <b>DOM L2</b> + * Does nothing; this must be overridden (along with the + * getPrefix method) for element and attribute nodes. + */ + public void setPrefix(String prefix) + { + } + + /** + * <b>DOM L2</b> + * Returns null; this must be overridden for element and + * attribute nodes. + */ + public String getPrefix() + { + return null; + } + + /** + * <b>DOM L2</b> + * Returns null; this must be overridden for element and + * attribute nodes. + */ + public String getNamespaceURI() + { + return null; + } + + /** + * <b>DOM L2</b> + * Returns the node name; this must be overridden for element and + * attribute nodes. + */ + public String getLocalName() + { + return null; + } + + /** + * <b>DOM L1</b> + * Returns a clone of this node which optionally includes cloned + * versions of child nodes. Clones are always mutable, except for + * entity reference nodes. + */ + public Node cloneNode(boolean deep) + { + DomNode node = (DomNode) clone(); + + if (deep) + { + DomDocument doc = (nodeType == DOCUMENT_NODE) ? + (DomDocument) node : node.owner; + for (DomNode ctx = first; ctx != null; ctx = ctx.next) + { + DomNode newChild = (DomNode) ctx.cloneNode(deep); + newChild.setOwner(doc); + node.appendChild(newChild); + } + } + + if (nodeType == ENTITY_REFERENCE_NODE) + { + node.makeReadonly(); + } + notifyUserDataHandlers(UserDataHandler.NODE_CLONED, this, node); + return node; + } + + void notifyUserDataHandlers(short op, Node src, Node dst) + { + if (userDataHandlers != null) + { + for (Iterator i = userDataHandlers.entrySet().iterator(); i.hasNext(); ) + { + Map.Entry entry = (Map.Entry) i.next(); + String key = (String) entry.getKey(); + UserDataHandler handler = (UserDataHandler) entry.getValue(); + Object data = userData.get(key); + handler.handle(op, key, data, src, dst); + } + } + } + + /** + * Clones this node; roughly equivalent to cloneNode(false). + * Element subclasses must provide a new implementation which + * invokes this method to handle the basics, and then arranges + * to clone any element attributes directly. Attribute subclasses + * must make similar arrangements, ensuring that existing ties to + * elements are broken by cloning. + */ + public Object clone() + { + try + { + DomNode node = (DomNode) super.clone(); + + node.parent = null; + node.depth = 0; + node.index = 0; + node.length = 0; + node.first = null; + node.last = null; + node.previous = null; + node.next = null; + + node.readonly = false; + node.listeners = null; + node.nListeners = 0; + return node; + + } + catch (CloneNotSupportedException x) + { + throw new Error("clone didn't work"); + } + } + + // the elements-by-tagname stuff is needed for both + // elements and documents ... this is in lieu of a + // common base class between Node and NodeNS. + + /** + * <b>DOM L1</b> + * Creates a NodeList giving array-style access to elements with + * the specified name. Access is fastest if indices change by + * small values, and the DOM is not modified. + */ + public NodeList getElementsByTagName(String tag) + { + return new ShadowList(null, tag); + } + + /** + * <b>DOM L2</b> + * Creates a NodeList giving array-style access to elements with + * the specified namespace and local name. Access is fastest if + * indices change by small values, and the DOM is not modified. + */ + public NodeList getElementsByTagNameNS(String namespace, String local) + { + return new ShadowList(namespace, local); + } + + + // + // This shadow class is GC-able even when the live list it shadows + // can't be, because of event registration hookups. Its finalizer + // makes that live list become GC-able. + // + final class ShadowList + implements NodeList + { + + private LiveNodeList liveList; + + ShadowList(String ns, String local) + { + liveList = new LiveNodeList(ns, local); + } + + public void finalize() + { + liveList.detach(); + liveList = null; + } + + public Node item(int index) + { + return liveList.item(index); + } + + public int getLength() + { + return liveList.getLength(); + } + } + + final class LiveNodeList + implements NodeList, EventListener, NodeFilter + { + + private final boolean matchAnyURI; + private final boolean matchAnyName; + private final String elementURI; + private final String elementName; + + private DomIterator current; + private int lastIndex; + + LiveNodeList(String uri, String name) + { + elementURI = uri; + elementName = name; + matchAnyURI = "*".equals(uri); + matchAnyName = "*".equals(name); + + DomNode.this.addEventListener("DOMNodeInserted", this, true); + DomNode.this.addEventListener("DOMNodeRemoved", this, true); + } + + void detach() + { + current.detach(); + current = null; + + DomNode.this.removeEventListener("DOMNodeInserted", this, true); + DomNode.this.removeEventListener("DOMNodeRemoved", this, true); + } + + public short acceptNode(Node element) + { + if (element == DomNode.this) + { + return FILTER_SKIP; + } + + // use namespace-aware matching ... + if (elementURI != null) + { + if (!(matchAnyURI + || elementURI.equals(element.getNamespaceURI()))) + { + return FILTER_SKIP; + } + if (!(matchAnyName + || elementName.equals(element.getLocalName()))) + { + return FILTER_SKIP; + } + + // ... or qName-based kind. + } + else + { + if (!(matchAnyName + || elementName.equals(element.getNodeName()))) + { + return FILTER_SKIP; + } + } + return FILTER_ACCEPT; + } + + private DomIterator createIterator() + { + return new DomIterator(DomNode.this, + NodeFilter.SHOW_ELEMENT, + this, /* filter */ + true /* expand entity refs */ + ); + } + + public void handleEvent(Event e) + { + MutationEvent mutation = (MutationEvent) e; + Node related = mutation.getRelatedNode(); + + // XXX if it's got children ... check all kids too, they + // will invalidate our saved index + + if (related.getNodeType() != Node.ELEMENT_NODE || + related.getNodeName() != elementName || + related.getNamespaceURI() != elementURI) + { + return; + } + + current = null; + } + + public Node item(int index) + { + if (current == null) + { + current = createIterator(); + lastIndex = -1; + } + + // last node or before? go backwards + if (index <= lastIndex) { + while (index != lastIndex) { + current.previousNode (); + lastIndex--; + } + Node ret = current.previousNode (); + current = null; + return ret; + } + + // somewhere after last node + while (++lastIndex != index) + current.nextNode (); + Node ret = current.nextNode (); + current = null; + return ret; + } + + public int getLength() + { + int retval = 0; + NodeIterator iter = createIterator(); + + while (iter.nextNode() != null) + { + retval++; + } + current = null; + return retval; + } + + } + + // + // EventTarget support + // + static final class ListenerRecord + { + + String type; + EventListener listener; + boolean useCapture; + + // XXX use JDK 1.2 java.lang.ref.WeakReference to listener, + // and we can both get rid of "shadow" classes and remove + // the need for applications to apply similar trix ... but + // JDK 1.2 support isn't generally available yet + + ListenerRecord(String type, EventListener listener, boolean useCapture) + { + this.type = type.intern(); + this.listener = listener; + this.useCapture = useCapture; + } + + boolean equals(ListenerRecord rec) + { + return listener == rec.listener + && useCapture == rec.useCapture + && type == rec.type; + } + + } + + /** + * <b>DOM L2 (Events)</b> + * Returns an instance of the specified type of event object. + * Understands about DOM Mutation, HTML, and UI events. + * + * <p>If the name of the event type begins with "USER-", then an object + * implementing the "Event" class will be returned; this provides a + * limited facility for application-defined events to use the DOM event + * infrastructure. Alternatively, use one of the standard DOM event + * classes and initialize it using use such a "USER-" event type name; + * or defin, instantiate, and initialize an application-specific subclass + * of DomEvent and pass that to dispatchEvent(). + * + * @param eventType Identifies the particular DOM feature module + * defining the type of event, such as "MutationEvents". + * <em>The event "name" is a different kind of "type".</em> + */ + public Event createEvent(String eventType) + { + eventType = eventType.toLowerCase(); + + if ("mutationevents".equals(eventType)) + { + return new DomEvent.DomMutationEvent(null); + } + + if ("htmlevents".equals(eventType) + || "events".equals(eventType) + || "user-events".equals(eventType)) + { + return new DomEvent(null); + } + + if ("uievents".equals(eventType)) + { + return new DomEvent.DomUIEvent(null); + } + + // mouse events + + throw new DomEx(DomEx.NOT_SUPPORTED_ERR, + eventType, null, 0); + } + + /** + * <b>DOM L2 (Events)</b> + * Registers an event listener's interest in a class of events. + */ + public final void addEventListener(String type, + EventListener listener, + boolean useCapture) + { + if (listeners == null) + { + listeners = new ListenerRecord[1]; + } + else if (nListeners == listeners.length) + { + ListenerRecord[] newListeners = + new ListenerRecord[listeners.length + NKIDS_DELTA]; + System.arraycopy(listeners, 0, newListeners, 0, nListeners); + listeners = newListeners; + } + + // prune duplicates + ListenerRecord record; + + record = new ListenerRecord(type, listener, useCapture); + for (int i = 0; i < nListeners; i++) + { + if (record.equals(listeners[i])) + { + return; + } + } + listeners [nListeners++] = record; + } + + // XXX this exception should be discarded from DOM + + // this class can be instantiated, unlike the one in the spec + static final class DomEventException + extends EventException + { + + DomEventException() + { + super(UNSPECIFIED_EVENT_TYPE_ERR, "unspecified event type"); + } + + } + + /** + * <b>DOM L2 (Events)</b> + * Delivers an event to all relevant listeners, returning true if the + * caller should perform their default action. Note that the event + * must have been provided by the createEvent() method on this + * class, else it can't be dispatched. + * + * @see #createEvent + * + * @exception NullPointerException When a null event is passed. + * @exception ClassCastException When the event wasn't provided by + * the createEvent method, or otherwise isn't a DomEvent. + * @exception EventException If the event type wasn't specified + */ + public final boolean dispatchEvent(Event event) + throws EventException + { + DomEvent e = (DomEvent) event; + DomNode[] ancestors = null; + int ancestorMax = 0; + boolean haveDispatchDataLock = false; + + if (e.type == null) + { + throw new DomEventException(); + } + + e.doDefault = true; + e.target = this; + + // + // Typical case: one nonrecursive dispatchEvent call at a time + // for this class. If that's our case, we can avoid allocating + // garbage, which is overall a big win. Even with advanced GCs + // that deal well with short-lived garbage, and wayfast allocators, + // it still helps. + // + // Remember -- EVERY mutation goes though here at least once. + // + // When populating a DOM tree, trying to send mutation events is + // the primary cost; this dominates the critical path. + // + try + { + DomNode current; + int index; + boolean haveAncestorRegistrations = false; + ListenerRecord[] notificationSet; + int ancestorLen; + + synchronized (lockNode) + { + if (!dispatchDataLock) + { + haveDispatchDataLock = dispatchDataLock = true; + notificationSet = DomNode.notificationSet; + ancestors = DomNode.ancestors; + } + else + { + notificationSet = new ListenerRecord[NOTIFICATIONS_INIT]; + ancestors = new DomNode[ANCESTORS_INIT]; + } + ancestorLen = ancestors.length; + } + + // XXX autogrow ancestors ... based on statistics + + // Climb to the top of this subtree and handle capture, letting + // each node (from the top down) capture until one stops it or + // until we get to this one. + + for (index = 0, current = parent; + current != null && index < ancestorLen; + index++, current = current.parent) + { + if (current.nListeners != 0) + { + haveAncestorRegistrations = true; + } + ancestors [index] = current; + } + if (current != null) + { + throw new RuntimeException("dispatchEvent capture stack size"); + } + + ancestorMax = index; + e.stop = false; + + if (haveAncestorRegistrations) + { + e.eventPhase = Event.CAPTURING_PHASE; + while (!e.stop && index-- > 0) + { + current = ancestors [index]; + if (current.nListeners != 0) + { + notifyNode(e, current, true, notificationSet); + } + } + } + + // Always deliver events to the target node (this) + // unless stopPropagation was called. If we saw + // no registrations yet (typical!), we never will. + if (!e.stop && nListeners != 0) + { + e.eventPhase = Event.AT_TARGET; + notifyNode (e, this, false, notificationSet); + } + else if (!haveAncestorRegistrations) + { + e.stop = true; + } + + // If the event bubbles and propagation wasn't halted, + // walk back up the ancestor list. Stop bubbling when + // any bubbled event handler stops it. + + if (!e.stop && e.bubbles) + { + e.eventPhase = Event.BUBBLING_PHASE; + for (index = 0; + !e.stop + && index < ancestorMax + && (current = ancestors[index]) != null; + index++) + { + if (current.nListeners != 0) + { + notifyNode(e, current, false, notificationSet); + } + } + } + e.eventPhase = 0; + + // Caller chooses whether to perform the default + // action based on return from this method. + return e.doDefault; + + } + finally + { + if (haveDispatchDataLock) + { + // synchronize to force write ordering + synchronized (lockNode) + { + // null out refs to ensure they'll be GC'd + for (int i = 0; i < ancestorMax; i++) + { + ancestors [i] = null; + } + // notificationSet handled by notifyNode + + dispatchDataLock = false; + } + } + } + } + + private void notifyNode(DomEvent e, + DomNode current, + boolean capture, + ListenerRecord[] notificationSet) + { + int count = 0; + + // do any of this set of listeners get notified? + for (int i = 0; i < current.nListeners; i++) + { + ListenerRecord rec = current.listeners[i]; + + if (rec.useCapture != capture) + { + continue; + } + if (!e.type.equals (rec.type)) + { + continue; + } + if (count < notificationSet.length) + { + notificationSet[count++] = rec; + } + else + // XXX fire up some cheap growth algorithm + throw new RuntimeException("Event notification set size exceeded"); + } + + // Notify just those listeners + e.currentNode = current; + for (int i = 0; i < count; i++) + { + try + { + // Late in the DOM CR process (3rd or 4th CR?) the + // removeEventListener spec became asymmetric with respect + // to addEventListener ... effect is now immediate. + for (int j = 0; j < current.nListeners; j++) + { + if (current.listeners[j].equals(notificationSet[i])) + { + notificationSet[i].listener.handleEvent(e); + break; + } + } + + } + catch (Exception x) + { + // ignore all exceptions + } + notificationSet[i] = null; // free for GC + } + } + + /** + * <b>DOM L2 (Events)</b> + * Unregisters an event listener. + */ + public final void removeEventListener(String type, + EventListener listener, + boolean useCapture) + { + for (int i = 0; i < nListeners; i++) + { + if (listeners[i].listener != listener) + { + continue; + } + if (listeners[i].useCapture != useCapture) + { + continue; + } + if (!listeners[i].type.equals(type)) + { + continue; + } + + if (nListeners == 1) + { + listeners = null; + nListeners = 0; + } + else + { + for (int j = i + 1; j < nListeners; j++) + { + listeners[i++] = listeners[j++]; + } + listeners[--nListeners] = null; + } + break; + } + // no exceptions reported + } + + /** + * <b>DOM L1 (relocated in DOM L2)</b> + * In this node and all contained nodes (including attributes if + * relevant) merge adjacent text nodes. This is done while ignoring + * text which happens to use CDATA delimiters). + */ + public final void normalize() + { + // Suspend readonly status + boolean saved = readonly; + readonly = false; + for (DomNode ctx = first; ctx != null; ctx = ctx.next) + { + switch (ctx.nodeType) + { + case TEXT_NODE: + while (ctx.next != null && ctx.next.nodeType == TEXT_NODE) + { + Text text = (Text) ctx; + text.appendData(ctx.next.getNodeValue()); + removeChild(ctx.next); + } + break; + case ELEMENT_NODE: + NamedNodeMap attrs = ctx.getAttributes(); + int len = attrs.getLength(); + for (int i = 0; i < len; i++) + { + attrs.item(i).normalize(); + } + // Fall through + case DOCUMENT_NODE: + case DOCUMENT_FRAGMENT_NODE: + case ATTRIBUTE_NODE: + case ENTITY_REFERENCE_NODE: + ctx.normalize(); + break; + } + } + readonly = saved; + } + + /** + * Returns true iff node types match, and either (a) both nodes have no + * namespace and their getNodeName() values are the same, or (b) both + * nodes have the same getNamespaceURI() and same getLocalName() values. + * + * <p>Note that notion of a "Per-Element-Type" attribute name scope, as + * found in a non-normative appendix of the XML Namespaces specification, + * is not supported here. Your application must implement that notion, + * typically by not bothering to check nameAndTypeEquals for attributes + * without namespace URIs unless you already know their elements are + * nameAndTypeEquals. + */ + public boolean nameAndTypeEquals(Node other) + { + if (other == this) + { + return true; + } + // node types must match + if (nodeType != other.getNodeType()) + { + return false; + } + + // if both have namespaces, do a "full" comparision + // this is a "global" partition + String ns1 = this.getNamespaceURI(); + String ns2 = other.getNamespaceURI(); + + if (ns1 != null && ns2 != null) + { + return ns1.equals(ns2) && + getLocalName().equals(other.getLocalName()); + } + + // if neither has a namespace, this is a "no-namespace" name. + if (ns1 == null && ns2 == null) + { + if (!getNodeName().equals(other.getNodeName())) + { + return false; + } + // can test the non-normative "per-element-type" scope here. + // if this is an attribute node and both nodes have been bound + // to elements (!!), then return the nameAndTypeEquals() + // comparison of those elements. + return true; + } + + // otherwise they're unequal: one scoped, one not. + return false; + } + + // DOM Level 3 methods + + public String getBaseURI() + { + return (parent != null) ? parent.getBaseURI() : null; + } + + public short compareDocumentPosition(Node other) + throws DOMException + { + return (short) compareTo(other); + } + + /** + * DOM nodes have a natural ordering: document order. + */ + public final int compareTo(Object other) + { + if (other instanceof DomNode) + { + DomNode n1 = this; + DomNode n2 = (DomNode) other; + if (n1.owner != n2.owner) + { + return 0; + } + int d1 = n1.depth, d2 = n2.depth; + int delta = d1 - d2; + while (d1 > d2) + { + n1 = n1.parent; + d1--; + } + while (d2 > d1) + { + n2 = n2.parent; + d2--; + } + int c = compareTo2(n1, n2); + return (c != 0) ? c : delta; + } + return 0; + } + + /** + * Compare two nodes at the same depth. + */ + final int compareTo2(DomNode n1, DomNode n2) + { + if (n1 == n2 || n1.depth == 0 || n2.depth == 0) + { + return 0; + } + int c = compareTo2(n1.parent, n2.parent); + return (c != 0) ? c : n1.index - n2.index; + } + + public final String getTextContent() + throws DOMException + { + return getTextContent(true); + } + + final String getTextContent(boolean topLevel) + throws DOMException + { + switch (nodeType) + { + case ELEMENT_NODE: + case ENTITY_NODE: + case ENTITY_REFERENCE_NODE: + case DOCUMENT_FRAGMENT_NODE: + StringBuffer buffer = new StringBuffer(); + for (DomNode ctx = first; ctx != null; ctx = ctx.next) + { + String textContent = ctx.getTextContent(false); + if (textContent != null) + { + buffer.append(textContent); + } + } + return buffer.toString(); + case TEXT_NODE: + case CDATA_SECTION_NODE: + if (((Text) this).isElementContentWhitespace()) + { + return ""; + } + return getNodeValue(); + case ATTRIBUTE_NODE: + return getNodeValue(); + case COMMENT_NODE: + case PROCESSING_INSTRUCTION_NODE: + return topLevel ? getNodeValue() : ""; + default: + return null; + } + } + + public void setTextContent(String textContent) + throws DOMException + { + switch (nodeType) + { + case ELEMENT_NODE: + case ATTRIBUTE_NODE: + case ENTITY_NODE: + case ENTITY_REFERENCE_NODE: + case DOCUMENT_FRAGMENT_NODE: + for (DomNode ctx = first; ctx != null; ) + { + DomNode n = ctx.next; + removeChild(ctx); + ctx = n; + } + if (textContent != null) + { + Text text = owner.createTextNode(textContent); + appendChild(text); + } + break; + case TEXT_NODE: + case CDATA_SECTION_NODE: + case COMMENT_NODE: + case PROCESSING_INSTRUCTION_NODE: + setNodeValue(textContent); + break; + } + } + + public boolean isSameNode(Node other) + { + return this == other; + } + + public String lookupPrefix(String namespaceURI) + { + return (parent == null || parent == owner) ? null : + parent.lookupPrefix(namespaceURI); + } + + public boolean isDefaultNamespace(String namespaceURI) + { + return (parent == null || parent == owner) ? false : + parent.isDefaultNamespace(namespaceURI); + } + + public String lookupNamespaceURI(String prefix) + { + return (parent == null || parent == owner) ? null : + parent.lookupNamespaceURI(prefix); + } + + public boolean isEqualNode(Node arg) + { + if (this == arg) + { + return true; + } + if (arg == null) + { + return false; + } + if (nodeType != arg.getNodeType() || + !equal(getNodeName(), arg.getNodeName()) || + !equal(getLocalName(), arg.getLocalName()) || + !equal(getNamespaceURI(), arg.getNamespaceURI()) || + !equal(getPrefix(), arg.getPrefix()) || + !equal(getNodeValue(), arg.getNodeValue())) + { + return false; + } + // Children + Node argCtx = arg.getFirstChild(); + getFirstChild(); // because of DomAttr lazy children + for (DomNode ctx = first; ctx != null; ctx = ctx.next) + { + if (!ctx.isEqualNode(argCtx)) + { + return false; + } + argCtx = argCtx.getNextSibling(); + } + if (argCtx != null) + { + return false; + } + + // TODO Attr NamedNodeMap + // TODO DocumentType + return true; + } + + boolean equal(String arg1, String arg2) + { + return ((arg1 == null && arg2 == null) || + (arg1 != null && arg1.equals(arg2))); + } + + public Object getFeature(String feature, String version) + { + DOMImplementation impl = (nodeType == DOCUMENT_NODE) ? + ((Document) this).getImplementation() : owner.getImplementation(); + if (impl.hasFeature(feature, version)) + { + return this; + } + return null; + } + + public Object setUserData(String key, Object data, UserDataHandler handler) + { + if (userData == null) + { + userData = new HashMap(); + } + if (handler != null) + { + if (userDataHandlers == null) + { + userDataHandlers = new HashMap(); + } + userDataHandlers.put(key, handler); + } + return userData.put(key, data); + } + + public Object getUserData(String key) + { + if (userData == null) + { + return null; + } + return userData.get(key); + } + + public String toString() + { + String nodeName = getNodeName(); + String nodeValue = getNodeValue(); + StringBuffer buf = new StringBuffer(getClass().getName()); + buf.append('['); + if (nodeName != null) + { + buf.append(nodeName); + } + if (nodeValue != null) + { + if (nodeName != null) + { + buf.append('='); + } + buf.append('\''); + buf.append(encode(nodeValue)); + buf.append('\''); + } + buf.append(']'); + return buf.toString(); + } + + String encode(String value) + { + StringBuffer buf = null; + int len = value.length(); + for (int i = 0; i < len; i++) + { + char c = value.charAt(i); + if (c == '\n') + { + if (buf == null) + { + buf = new StringBuffer(value.substring(0, i)); + } + buf.append("\\n"); + } + else if (c == '\r') + { + if (buf == null) + { + buf = new StringBuffer(value.substring(0, i)); + } + buf.append("\\r"); + } + else if (buf != null) + { + buf.append(c); + } + } + return (buf != null) ? buf.toString() : value; + } + + String nodeTypeToString(short nodeType) + { + switch (nodeType) + { + case ELEMENT_NODE: + return "ELEMENT_NODE"; + case ATTRIBUTE_NODE: + return "ATTRIBUTE_NODE"; + case TEXT_NODE: + return "TEXT_NODE"; + case CDATA_SECTION_NODE: + return "CDATA_SECTION_NODE"; + case DOCUMENT_NODE: + return "DOCUMENT_NODE"; + case DOCUMENT_TYPE_NODE: + return "DOCUMENT_TYPE_NODE"; + case COMMENT_NODE: + return "COMMENT_NODE"; + case PROCESSING_INSTRUCTION_NODE: + return "PROCESSING_INSTRUCTION_NODE"; + case DOCUMENT_FRAGMENT_NODE: + return "DOCUMENT_FRAGMENT_NODE"; + case ENTITY_NODE: + return "ENTITY_NODE"; + case ENTITY_REFERENCE_NODE: + return "ENTITY_REFERENCE_NODE"; + case NOTATION_NODE: + return "NOTATION_NODE"; + default: + return "UNKNOWN"; + } + } + +} + diff --git a/libjava/gnu/xml/dom/DomNodeIterator.java b/libjava/gnu/xml/dom/DomNodeIterator.java new file mode 100644 index 0000000..c08e90a --- /dev/null +++ b/libjava/gnu/xml/dom/DomNodeIterator.java @@ -0,0 +1,327 @@ +/* DomNodeIterator.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ +package gnu.xml.dom; + +import org.w3c.dom.DOMException; +import org.w3c.dom.Node; +import org.w3c.dom.traversal.NodeFilter; +import org.w3c.dom.traversal.NodeIterator; +import org.w3c.dom.traversal.TreeWalker; + +/** + * Node iterator and tree walker. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class DomNodeIterator + implements NodeIterator, TreeWalker +{ + + Node root; + final int whatToShow; + final NodeFilter filter; + final boolean entityReferenceExpansion; + final boolean walk; + Node current; + + public DomNodeIterator(Node root, int whatToShow, NodeFilter filter, + boolean entityReferenceExpansion, boolean walk) + { + if (root == null) + { + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "null root"); + } + this.root = root; + this.whatToShow = whatToShow; + this.filter = filter; + this.entityReferenceExpansion = entityReferenceExpansion; + this.walk = walk; + current = root; + } + + public Node getRoot() + { + return root; + } + + public int getWhatToShow() + { + return whatToShow; + } + + public NodeFilter getFilter() + { + return filter; + } + + public boolean getExpandEntityReferences() + { + return entityReferenceExpansion; + } + + public Node nextNode() + throws DOMException + { + if (root == null) + { + throw new DOMException(DOMException.INVALID_STATE_ERR, "null root"); + } + Node ret; + do + { + if (current.equals(root)) + { + ret = root.getFirstChild(); + } + else if (walk) + { + ret = current.getFirstChild(); + if (ret == null) + { + ret = current.getNextSibling(); + } + if (ret == null) + { + Node tmp = current; + ret = tmp.getParentNode(); + while (!ret.equals(root) && tmp.equals(ret.getLastChild())) + { + tmp = ret; + ret = tmp.getParentNode(); + } + if (ret.equals(root)) + { + ret = null; + } + else + { + ret = ret.getNextSibling(); + } + } + } + else + { + ret = current.getNextSibling(); + } + } + while (!accept(ret)); + current = (ret == null) ? current : ret; + return ret; + } + + public Node previousNode() + throws DOMException + { + if (root == null) + { + throw new DOMException(DOMException.INVALID_STATE_ERR, "null root"); + } + Node ret; + do + { + if (current.equals(root)) + { + ret = current.getLastChild(); + } + else if (walk) + { + ret = current.getLastChild(); + if (ret == null) + { + ret = current.getPreviousSibling(); + } + if (ret == null) + { + Node tmp = current; + ret = tmp.getParentNode(); + while (!ret.equals(root) && tmp.equals(ret.getFirstChild())) + { + tmp = ret; + ret = tmp.getParentNode(); + } + if (ret.equals(root)) + { + ret = null; + } + else + { + ret = ret.getPreviousSibling(); + } + } + } + else + { + ret = current.getPreviousSibling(); + } + } + while (!accept(ret)); + current = (ret == null) ? current : ret; + return ret; + } + + public Node getCurrentNode() + { + return current; + } + + public void setCurrentNode(Node current) + throws DOMException + { + if (current == null) + { + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "null root"); + } + this.current = current; + } + + public Node parentNode() + { + Node ret = current.getParentNode(); + if (!accept (ret)) + { + ret = null; + } + current = (ret == null) ? current : ret; + return ret; + } + + public Node firstChild () + { + Node ret = current.getFirstChild(); + while (!accept(ret)) + { + ret = ret.getNextSibling(); + } + current = (ret == null) ? current : ret; + return ret; + } + + public Node lastChild() + { + Node ret = current.getLastChild(); + while (!accept(ret)) + { + ret = ret.getPreviousSibling(); + } + current = (ret == null) ? current : ret; + return ret; + } + + public Node previousSibling() + { + Node ret = current.getPreviousSibling(); + while (!accept(ret)) + { + ret = ret.getPreviousSibling(); + } + current = (ret == null) ? current : ret; + return ret; + } + + public Node nextSibling() + { + Node ret = current.getNextSibling(); + while (!accept(ret)) + { + ret = ret.getNextSibling(); + } + current = (ret == null) ? current : ret; + return ret; + } + + public void detach() + { + root = null; + } + + boolean accept(Node node) + { + if (node == null) + { + return true; + } + boolean ret; + switch (node.getNodeType()) + { + case Node.ATTRIBUTE_NODE: + ret = (whatToShow & NodeFilter.SHOW_ATTRIBUTE) != 0; + break; + case Node.CDATA_SECTION_NODE: + ret = (whatToShow & NodeFilter.SHOW_CDATA_SECTION) != 0; + break; + case Node.COMMENT_NODE: + ret = (whatToShow & NodeFilter.SHOW_COMMENT) != 0; + break; + case Node.DOCUMENT_NODE: + ret = (whatToShow & NodeFilter.SHOW_DOCUMENT) != 0; + break; + case Node.DOCUMENT_FRAGMENT_NODE: + ret = (whatToShow & NodeFilter.SHOW_DOCUMENT_FRAGMENT) != 0; + break; + case Node.DOCUMENT_TYPE_NODE: + ret = (whatToShow & NodeFilter.SHOW_DOCUMENT_TYPE) != 0; + break; + case Node.ELEMENT_NODE: + ret = (whatToShow & NodeFilter.SHOW_ELEMENT) != 0; + break; + case Node.ENTITY_NODE: + ret = (whatToShow & NodeFilter.SHOW_ENTITY) != 0; + break; + case Node.ENTITY_REFERENCE_NODE: + ret = (whatToShow & NodeFilter.SHOW_ENTITY_REFERENCE) != 0; + ret = ret && entityReferenceExpansion; + break; + case Node.NOTATION_NODE: + ret = (whatToShow & NodeFilter.SHOW_NOTATION) != 0; + break; + case Node.PROCESSING_INSTRUCTION_NODE: + ret = (whatToShow & NodeFilter.SHOW_PROCESSING_INSTRUCTION) != 0; + break; + case Node.TEXT_NODE: + ret = (whatToShow & NodeFilter.SHOW_TEXT) != 0; + break; + default: + ret = true; + } + if (ret && filter != null) + { + ret = (filter.acceptNode(node) == NodeFilter.FILTER_ACCEPT); + } + return ret; + } + +} diff --git a/libjava/gnu/xml/dom/DomNotation.java b/libjava/gnu/xml/dom/DomNotation.java new file mode 100644 index 0000000..d666699 --- /dev/null +++ b/libjava/gnu/xml/dom/DomNotation.java @@ -0,0 +1,103 @@ +/* DomNotation.java -- + Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import org.w3c.dom.Notation; + +/** + * <p> "Notation" implementation. This is a non-core DOM class, supporting + * the "XML" feature. </p> + * + * <p> Although unparsed entities using this notation can be detected using + * DOM, neither NOTATIONS nor ENTITY/ENTITIES attributes can be so detected. + * More, there is no portable way to construct a Notation node, so there's + * no way that vendor-neutral DOM construction APIs could even report a + * NOTATION used to identify the intended meaning of a ProcessingInstruction. + * </p> + * + * <p> In short, <em>avoid using this DOM functionality</em>. + * + * @see DomDoctype + * @see DomEntity + * @see DomPI + * + * @author David Brownell + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class DomNotation + extends DomExtern + implements Notation +{ + + /** + * Constructs a Notation node associated with the specified document, + * with the specified descriptive data. Note that at least one of + * the PUBLIC and SYSTEM identifiers must be provided; unlike other + * external objects in XML, notations may have only a PUBLIC identifier. + * + * <p>This constructor should only be invoked by a DomDoctype object + * as part of its declareNotation functionality, or through a subclass + * which is similarly used in a "Sub-DOM" style layer. + * + * @param owner The document with which this notation is associated + * @param name Name of this notation + * @param publicId If non-null, provides the notation's PUBLIC identifier + * @param systemId If non-null, rovides the notation's SYSTEM identifier + */ + protected DomNotation(DomDocument owner, + String name, + String publicId, + String systemId) + { + super(NOTATION_NODE, owner, name, publicId, systemId); + makeReadonly(); + } + + /** + * The base URI of an external entity is its system ID. + * The base URI of an internal entity is the parent document's base URI. + * @since DOM Level 3 Core + */ + public String getBaseURI() + { + String systemId = getSystemId(); + return (systemId == null) ? owner.getBaseURI() : systemId; + } + +} + diff --git a/libjava/gnu/xml/dom/DomNsNode.java b/libjava/gnu/xml/dom/DomNsNode.java new file mode 100644 index 0000000..ea41fff --- /dev/null +++ b/libjava/gnu/xml/dom/DomNsNode.java @@ -0,0 +1,197 @@ +/* DomNsNode.java -- + Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import javax.xml.XMLConstants; + +/** + * <p> Abstract implemention of namespace support. This facilitates + * sharing code for attribute and element nodes. + * + * @author David Brownell + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public abstract class DomNsNode + extends DomNode +{ + + private String name; + private String namespace; + private String prefix; + private String localName; + + /** + * Constructs a node associated with the specified document, and + * with the specified namespace information. + * + * @param owner The document with which this entity is associated + * @param namespaceURI Combined with the local part of the name, + * this identifies a type of element or attribute; may be null. + * If this is the empty string, it is reassigned as null so that + * applications only need to test that case. + * @param name Name of this node, which may include a prefix + */ + // package private + DomNsNode(short nodeType, DomDocument owner, String namespaceURI, String name) + { + super(nodeType, owner); + setNodeName(name); + setNamespaceURI(namespaceURI); + } + + /** + * <b>DOM L1</b> + * Returns the node's name, including any namespace prefix. + */ + public final String getNodeName() + { + return name; + } + + final void setNodeName(String name) + { + this.name = name.intern(); + int index = name.indexOf(':'); + if (index == -1) + { + prefix = null; + localName = this.name; + } + else + { + prefix = name.substring(0, index).intern(); + localName = name.substring(index + 1).intern(); + } + } + + /** + * <b>DOM L2</b> + * Returns the node's namespace URI + * <em>or null</em> if the node name is not namespace scoped. + */ + public final String getNamespaceURI() + { + return namespace; + } + + final void setNamespaceURI(String namespaceURI) + { + if ("".equals(namespaceURI)) + { + namespaceURI = null; + } + namespace = (namespaceURI == null) ? null : namespaceURI.intern(); + } + + /** + * <b>DOM L2</b> + * Returns any prefix part of the node's name (before any colon). + */ + public final String getPrefix() + { + return prefix; + } + + /** + * <b>DOM L2</b> + * Assigns the prefix part of the node's name (before any colon). + */ + public final void setPrefix(String prefix) + { + if (readonly) + { + throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR); + } + + if (prefix == null) + { + name = localName; + return; + } + else if (namespace == null) + { + throw new DomEx(DomEx.NAMESPACE_ERR, + "can't set prefix, node has no namespace URI", + this, 0); + } + + DomDocument.checkName(prefix, "1.1".equals(owner.getXmlVersion())); + if (prefix.indexOf (':') != -1) + { + throw new DomEx(DomEx.NAMESPACE_ERR, + "illegal prefix " + prefix, this, 0); + } + + if (XMLConstants.XML_NS_PREFIX.equals(prefix) + && !XMLConstants.XML_NS_URI.equals(namespace)) + { + throw new DomEx(DomEx.NAMESPACE_ERR, + "xml namespace is always " + + XMLConstants.XML_NS_URI, this, 0); + } + + if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) + { + if (namespace != null || getNodeType() != ATTRIBUTE_NODE) + { + throw new DomEx(DomEx.NAMESPACE_ERR, + "xmlns attribute prefix is reserved", this, 0); + } + } + else if (getNodeType () == ATTRIBUTE_NODE + && (XMLConstants.XMLNS_ATTRIBUTE.equals(name) || + name.startsWith("xmlns:"))) + { + throw new DomEx(DomEx.NAMESPACE_ERR, + "namespace declarations can't change names", this, 0); + } + + this.prefix = prefix.intern(); + } + + /** + * <b>DOM L2</b> + * Returns the local part of the node's name (after any colon). + */ + public final String getLocalName() + { + return localName; + } + +} + diff --git a/libjava/gnu/xml/dom/DomPI.java b/libjava/gnu/xml/dom/DomPI.java new file mode 100644 index 0000000..dedf375 --- /dev/null +++ b/libjava/gnu/xml/dom/DomPI.java @@ -0,0 +1,145 @@ +/* DomPI.java -- + Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import org.w3c.dom.ProcessingInstruction; + +/** + * <p> "ProcessingInstruction" (PI) implementation. + * This is a non-core DOM class, supporting the "XML" feature. </p> + * + * <p> Unlike other DOM APIs in the "XML" feature, this one fully + * exposes the functionality it describes. So there is no reason + * inherent in DOM to avoid using this API, unless you want to rely + * on NOTATION declarations to associate meaning with your PIs; + * there is no vendor-neutal way to record those notations in DOM.</p> + * + * <p> Also of note is that PI support is part of SAX, so that XML + * systems using PIs can choose among multiple APIs. </p> + * + * @see DomNotation + * + * @author David Brownell + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class DomPI + extends DomNode + implements ProcessingInstruction +{ + + private String target; + private String data; + + /** + * Constructs a ProcessingInstruction node associated with the + * specified document, with the specified data. + * + * <p>This constructor should only be invoked by a Document object as + * part of its createProcessingInstruction functionality, or through + * a subclass which is similarly used in a "Sub-DOM" style layer. + */ + protected DomPI(DomDocument owner, String target, String data) + { + super(PROCESSING_INSTRUCTION_NODE, owner); + this.target = target; + this.data = data; + } + + /** + * <b>DOM L1</b> + * Returns the target of the processing instruction. + */ + public final String getTarget() + { + return target; + } + + /** + * <b>DOM L1</b> + * Returns the target of the processing instruction + * (same as getTarget). + */ + public final String getNodeName() + { + return target; + } + + /** + * <b>DOM L1</b> + * Returns the data associated with the processing instruction. + */ + public final String getData() + { + return data; + } + + /** + * <b>DOM L1</b> + * Returns the data associated with the processing instruction + * (same as getData). + */ + public final String getNodeValue() + { + return data; + } + + /** + * <b>DOM L1</b> + * Assigns the data associated with the processing instruction; + * same as setNodeValue. + */ + public final void setData(String data) + { + setNodeValue(data); + } + + /** + * <b>DOM L1</b> + * Assigns the data associated with the processing instruction. + */ + public final void setNodeValue(String data) + { + if (isReadonly()) + { + throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR); + } + this.data = data; + } + +} + diff --git a/libjava/gnu/xml/dom/DomText.java b/libjava/gnu/xml/dom/DomText.java new file mode 100644 index 0000000..d714655 --- /dev/null +++ b/libjava/gnu/xml/dom/DomText.java @@ -0,0 +1,220 @@ +/* DomText.java -- + Copyright (C) 1999, 2000, 2001, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import org.w3c.dom.DOMException; +import org.w3c.dom.Text; + +/** + * <p> "Text" implementation. </p> + * + * @author David Brownell + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class DomText + extends DomCharacterData + implements Text +{ + + // NOTE: deleted unused per-instance "isIgnorable" + // support to reclaim its space. + + /** + * Constructs a text node associated with the specified + * document and holding the specified data. + * + * <p>This constructor should only be invoked by a Document object + * as part of its createTextNode functionality, or through a subclass + * which is similarly used in a "Sub-DOM" style layer. + */ + protected DomText(DomDocument owner, String value) + { + super(TEXT_NODE, owner, value); + } + + protected DomText(DomDocument owner, char[] buf, int off, int len) + { + super(TEXT_NODE, owner, buf, off, len); + } + + // Used by DomCDATA + DomText(short nodeType, DomDocument owner, String value) + { + super(nodeType, owner, value); + } + + DomText(short nodeType, DomDocument owner, char[] buf, int off, int len) + { + super(nodeType, owner, buf, off, len); + } + + /** + * <b>DOM L1</b> + * Returns the string "#text". + */ + // can't be 'final' with CDATA subclassing + public String getNodeName() + { + return "#text"; + } + + /** + * <b>DOM L1</b> + * Splits this text node in two parts at the offset, returning + * the new text node (the sibling with the second part). + */ + public Text splitText(int offset) + { + if (isReadonly()) + { + throw new DomEx(DomEx.NO_MODIFICATION_ALLOWED_ERR); + } + try + { + String text = getNodeValue(); + String before = text.substring(0, offset); + String after = text.substring(offset); + Text next; + + if (getNodeType() == TEXT_NODE) + { + next = owner.createTextNode(after); + } + else // CDATA_SECTION_NODE + { + next = owner.createCDATASection(after); + } + + if (this.next != null) + { + parent.insertBefore(next, this.next); + } + else + { + parent.appendChild(next); + } + setNodeValue(before); + return next; + + } + catch (IndexOutOfBoundsException x) + { + throw new DomEx(DomEx.INDEX_SIZE_ERR); + } + } + + // DOM Level 3 + + public boolean isElementContentWhitespace() + { + if (parent != null) + { + DomDoctype doctype = (DomDoctype) owner.getDoctype(); + if (doctype != null) + { + DTDElementTypeInfo info = + doctype.getElementTypeInfo(parent.getNodeName()); + if (info != null) + { + if (info.model == null && info.model.indexOf("#PCDATA") != -1) + { + return false; + } + return getNodeValue().trim().length() == 0; + } + } + } + return false; + } + + public String getWholeText() + { + DomNode ref = this; + DomNode ctx; + for (ctx = previous; ctx != null && + (ctx.nodeType == TEXT_NODE || ctx.nodeType == CDATA_SECTION_NODE); + ctx = ctx.previous) + { + ref = ctx; + } + StringBuffer buf = new StringBuffer(ref.getNodeValue()); + for (ctx = ref.next; ctx != null && + (ctx.nodeType == TEXT_NODE || ctx.nodeType == CDATA_SECTION_NODE); + ctx = ctx.next) + { + buf.append(ctx.getNodeValue()); + } + return buf.toString (); + } + + public Text replaceWholeText(String content) + throws DOMException + { + boolean isEmpty = (content == null || content.length () == 0); + if (!isEmpty) + { + setNodeValue(content); + } + + DomNode ref = this; + DomNode ctx; + for (ctx = previous; ctx != null && + (ctx.nodeType == TEXT_NODE || ctx.nodeType == CDATA_SECTION_NODE); + ctx = ctx.previous) + { + ref = ctx; + } + ctx = ref.next; + if ((isEmpty || ref != this) && parent != null) + { + parent.removeChild(ref); + } + for (; ctx != null && + (ctx.nodeType == TEXT_NODE || ctx.nodeType == CDATA_SECTION_NODE); + ctx = ref) + { + ref = ctx.next; + if ((isEmpty || ctx != this) && parent != null) + { + parent.removeChild(ctx); + } + } + return (isEmpty) ? null : this; + } + +} diff --git a/libjava/gnu/xml/dom/DomXPathExpression.java b/libjava/gnu/xml/dom/DomXPathExpression.java new file mode 100644 index 0000000..d92670d --- /dev/null +++ b/libjava/gnu/xml/dom/DomXPathExpression.java @@ -0,0 +1,148 @@ +/* DomXPathExpression.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import javax.xml.namespace.QName; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import org.w3c.dom.DOMException; +import org.w3c.dom.Node; +import org.w3c.dom.xpath.XPathException; +import org.w3c.dom.xpath.XPathNSResolver; +import org.w3c.dom.xpath.XPathResult; +import gnu.xml.xpath.DocumentOrderComparator; + +/** + * An XPath expression. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class DomXPathExpression +implements org.w3c.dom.xpath.XPathExpression +{ + + final DomDocument doc; + final XPathExpression expression; + final XPathNSResolver resolver; + + DomXPathExpression(DomDocument doc, String expression, + XPathNSResolver resolver) + throws XPathException + { + this.doc = doc; + this.resolver = resolver; + + XPathFactory factory = XPathFactory.newInstance(); + XPath xpath = factory.newXPath(); + if (resolver != null) + { + xpath.setNamespaceContext(new DomNSResolverContext(resolver)); + } + try + { + this.expression = xpath.compile(expression); + } + catch (XPathExpressionException e) + { + throw new XPathException(XPathException.INVALID_EXPRESSION_ERR, + e.getMessage ()); + } + } + + public Object evaluate(Node contextNode, short type, Object result) + throws XPathException, DOMException + { + try + { + QName typeName = null; + switch (type) + { + case XPathResult.BOOLEAN_TYPE: + typeName = XPathConstants.BOOLEAN; + break; + case XPathResult.NUMBER_TYPE: + typeName = XPathConstants.NUMBER; + break; + case XPathResult.STRING_TYPE: + typeName = XPathConstants.STRING; + break; + case XPathResult.ANY_UNORDERED_NODE_TYPE: + case XPathResult.FIRST_ORDERED_NODE_TYPE: + typeName = XPathConstants.NODE; + break; + case XPathResult.UNORDERED_NODE_ITERATOR_TYPE: + case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE: + case XPathResult.ORDERED_NODE_ITERATOR_TYPE: + case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE: + typeName = XPathConstants.NODESET; + break; + default: + throw new XPathException(XPathException.TYPE_ERR, null); + } + Object val = expression.evaluate(contextNode, typeName); + switch (type) + { + case XPathResult.ORDERED_NODE_ITERATOR_TYPE: + case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE: + // Sort the nodes + List ns = new ArrayList((Collection) val); + Collections.sort(ns, new DocumentOrderComparator()); + val = ns; + } + return new DomXPathResult(val, type); + } + catch (javax.xml.xpath.XPathException e) + { + throw new XPathException(XPathException.TYPE_ERR, e.getMessage()); + } + } + + public String toString () + { + return getClass ().getName () + "[expression=" + expression + "]"; + } + +} diff --git a/libjava/gnu/xml/dom/DomXPathNSResolver.java b/libjava/gnu/xml/dom/DomXPathNSResolver.java new file mode 100644 index 0000000..467327f --- /dev/null +++ b/libjava/gnu/xml/dom/DomXPathNSResolver.java @@ -0,0 +1,64 @@ +/* DomXPathNSResolver.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import org.w3c.dom.Node; +import org.w3c.dom.xpath.XPathNSResolver; + +/** + * Generic XPath namespace resolver using a DOM Node. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class DomXPathNSResolver +implements XPathNSResolver +{ + + Node node; + + DomXPathNSResolver (Node node) + { + this.node = node; + } + + public String lookupNamespaceURI (String prefix) + { + return node.lookupNamespaceURI (prefix); + } + +} diff --git a/libjava/gnu/xml/dom/DomXPathResult.java b/libjava/gnu/xml/dom/DomXPathResult.java new file mode 100644 index 0000000..1cc49d8 --- /dev/null +++ b/libjava/gnu/xml/dom/DomXPathResult.java @@ -0,0 +1,235 @@ +/* DomXPathResult.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import java.util.Collection; +import java.util.Iterator; +import org.w3c.dom.DOMException; +import org.w3c.dom.Node; +import org.w3c.dom.xpath.XPathException; +import org.w3c.dom.xpath.XPathNSResolver; +import org.w3c.dom.xpath.XPathResult; + +/** + * An XPath result object. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class DomXPathResult +implements XPathResult +{ + + final Object value; + final short type; + Iterator iterator; + + DomXPathResult (Object value, short requestedType) + { + this.value = value; + if (value instanceof Boolean) + { + type = XPathResult.BOOLEAN_TYPE; + } + else if (value instanceof Double) + { + type = XPathResult.NUMBER_TYPE; + } + else if (value instanceof String) + { + type = XPathResult.STRING_TYPE; + } + else if (value instanceof Collection) + { + Collection ns = (Collection) value; + switch (requestedType) + { + case XPathResult.ANY_TYPE: + case XPathResult.ANY_UNORDERED_NODE_TYPE: + type = (ns.size () == 1) ? XPathResult.FIRST_ORDERED_NODE_TYPE : + XPathResult.ORDERED_NODE_ITERATOR_TYPE; + break; + default: + type = requestedType; + } + iterator = ns.iterator (); + } + else + { + throw new IllegalArgumentException (); + } + } + + public boolean getBooleanValue() + { + if (type == XPathResult.BOOLEAN_TYPE) + { + return ((Boolean) value).booleanValue (); + } + throw new XPathException (XPathException.TYPE_ERR, value.toString ()); + } + + public boolean getInvalidIteratorState() + { + return iterator == null; + } + + public double getNumberValue() + { + if (type == XPathResult.NUMBER_TYPE) + { + return ((Double) value).doubleValue (); + } + throw new XPathException (XPathException.TYPE_ERR, value.toString ()); + } + + public short getResultType() + { + return type; + } + + public Node getSingleNodeValue() + { + switch (type) + { + case XPathResult.FIRST_ORDERED_NODE_TYPE: + case XPathResult.ORDERED_NODE_ITERATOR_TYPE: + case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE: + case XPathResult.UNORDERED_NODE_ITERATOR_TYPE: + case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE: + Collection ns = (Collection) value; + if (ns.isEmpty ()) + { + return null; + } + else + { + return (Node) ns.iterator ().next (); + } + } + throw new XPathException (XPathException.TYPE_ERR, value.toString ()); + } + + public int getSnapshotLength() + { + switch (type) + { + case XPathResult.FIRST_ORDERED_NODE_TYPE: + case XPathResult.ORDERED_NODE_ITERATOR_TYPE: + case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE: + case XPathResult.UNORDERED_NODE_ITERATOR_TYPE: + case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE: + return ((Collection) value).size (); + } + throw new XPathException (XPathException.TYPE_ERR, value.toString ()); + } + + public String getStringValue() + { + if (type == XPathResult.STRING_TYPE) + { + return (String) value; + } + throw new XPathException (XPathException.TYPE_ERR, value.toString ()); + } + + public Node iterateNext() + { + if (iterator != null) + { + if (iterator.hasNext ()) + { + return (Node) iterator.next (); + } + else + { + iterator = null; + return null; + } + } + throw new XPathException (XPathException.TYPE_ERR, value.toString ()); + } + + public Node snapshotItem(int index) + { + switch (type) + { + case XPathResult.FIRST_ORDERED_NODE_TYPE: + case XPathResult.ORDERED_NODE_ITERATOR_TYPE: + case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE: + case XPathResult.UNORDERED_NODE_ITERATOR_TYPE: + case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE: + Collection ns = (Collection) value; + Node[] nodes = new Node[ns.size ()]; + ns.toArray (nodes); + return nodes[index]; + } + throw new XPathException (XPathException.TYPE_ERR, value.toString ()); + } + + public String toString () + { + return getClass ().getName () + "[type=" + typeName (type) + ",value=" + + value + ']'; + } + + private String typeName (short type) + { + switch (type) + { + case XPathResult.BOOLEAN_TYPE: + return "BOOLEAN_TYPE"; + case XPathResult.NUMBER_TYPE: + return "NUMBER_TYPE"; + case XPathResult.STRING_TYPE: + return "STRING_TYPE"; + case XPathResult.FIRST_ORDERED_NODE_TYPE: + return "FIRST_ORDERED_NODE_TYPE"; + case XPathResult.ORDERED_NODE_ITERATOR_TYPE: + return "ORDERED_NODE_ITERATOR_TYPE"; + case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE: + return "ORDERED_NODE_SNAPSHOT_TYPE"; + case XPathResult.UNORDERED_NODE_ITERATOR_TYPE: + return "UNORDERED_NODE_ITERATOR_TYPE"; + case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE: + return "UNORDERED_NODE_SNAPSHOT_TYPE"; + default: + return "(unknown)"; + } + } + +} diff --git a/libjava/gnu/xml/dom/ImplementationList.java b/libjava/gnu/xml/dom/ImplementationList.java new file mode 100644 index 0000000..0f996ba --- /dev/null +++ b/libjava/gnu/xml/dom/ImplementationList.java @@ -0,0 +1,70 @@ +/* ImplementationList.java -- + Copyright (C) 2004 Free Software Foundation, Inc.. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ +package gnu.xml.dom; + +import java.util.List; + +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.DOMImplementationList; + +/** + * Implementation list for GNU JAXP. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class ImplementationList + implements DOMImplementationList +{ + + private List list; + + ImplementationList(List list) + { + this.list = list; + } + + public int getLength() + { + return list.size(); + } + + public DOMImplementation item(int index) + { + return (DOMImplementation) list.get(index); + } + +} diff --git a/libjava/gnu/xml/dom/ImplementationSource.java b/libjava/gnu/xml/dom/ImplementationSource.java new file mode 100644 index 0000000..f0c4bb5 --- /dev/null +++ b/libjava/gnu/xml/dom/ImplementationSource.java @@ -0,0 +1,167 @@ +/* ImplementationSource.java -- + Copyright (C) 2004 Free Software Foundation, Inc.. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ +package gnu.xml.dom; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.DOMImplementationList; +import org.w3c.dom.DOMImplementationSource; + +/** + * Implementation source for GNU JAXP. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class ImplementationSource + implements DOMImplementationSource +{ + + private static final String DIGITS = "1234567890"; + + /* + * GNU DOM implementations. + */ + private static final DOMImplementation[] implementations; + + static + { + List acc = new ArrayList(); + acc.add(new gnu.xml.dom.DomImpl()); + try + { + Class t = Class.forName("gnu.xml.libxmlj.dom.GnomeDocumentBuilder"); + acc.add(t.newInstance()); + } + catch (Exception e) + { + // libxmlj not available + } + catch (UnsatisfiedLinkError e) + { + // libxmlj not available + } + implementations = new DOMImplementation[acc.size()]; + acc.toArray(implementations); + } + + public DOMImplementation getDOMImplementation(String features) + { + List available = getImplementations(features); + if (available.isEmpty()) + { + return null; + } + return (DOMImplementation) available.get(0); + } + + public DOMImplementationList getDOMImplementationList(String features) + { + List available = getImplementations(features); + return new ImplementationList(available); + } + + /** + * Returns a list of the implementations that support the specified + * features. + */ + private final List getImplementations(String features) + { + List available = new ArrayList(Arrays.asList(implementations)); + for (Iterator i = parseFeatures(features).iterator(); i.hasNext(); ) + { + String feature = (String) i.next(); + String version = null; + int si = feature.indexOf(' '); + if (si != -1) + { + version = feature.substring(si + 1); + feature = feature.substring(0, si); + } + for (Iterator j = available.iterator(); j.hasNext(); ) + { + DOMImplementation impl = (DOMImplementation) j.next(); + if (!impl.hasFeature(feature, version)) + { + j.remove(); + } + } + } + return available; + } + + /** + * Parses the feature list into feature tokens. + */ + final List parseFeatures(String features) + { + List list = new ArrayList(); + int pos = 0, start = 0; + int len = features.length(); + for (; pos < len; pos++) + { + char c = features.charAt(pos); + if (c == ' ') + { + if (pos + 1 < len && + DIGITS.indexOf(features.charAt(pos + 1)) == -1) + { + list.add(getFeature(features, start, pos)); + start = pos + 1; + } + } + } + if (pos > start) + { + list.add(getFeature(features, start, len)); + } + return list; + } + + final String getFeature(String features, int start, int end) + { + if (features.length() > 0 && features.charAt(start) == '+') + { + return features.substring(start + 1, end); + } + return features.substring(start, end); + } + +} diff --git a/libjava/gnu/xml/dom/JAXPFactory.java b/libjava/gnu/xml/dom/JAXPFactory.java new file mode 100644 index 0000000..4271208 --- /dev/null +++ b/libjava/gnu/xml/dom/JAXPFactory.java @@ -0,0 +1,287 @@ +/* JAXPFactory.java -- + Copyright (C) 2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom; + +import java.io.IOException; + +import org.w3c.dom.Document; +import org.w3c.dom.DocumentType; +import org.w3c.dom.DOMImplementation; + +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParserFactory; + + +/** + * DOM bootstrapping API, for use with JAXP. + * + * @see Consumer + * + * @author David Brownell + */ +public final class JAXPFactory + extends DocumentBuilderFactory +{ + + private static final String PROPERTY = "http://xml.org/sax/properties/"; + private static final String FEATURE = "http://xml.org/sax/features/"; + + private SAXParserFactory pf; + + /** + * Default constructor. + */ + public JAXPFactory() + { + } + + /** + * Constructs a JAXP document builder which uses the default + * JAXP SAX2 parser and the DOM implementation in this package. + */ + public DocumentBuilder newDocumentBuilder() + throws ParserConfigurationException + { + if (pf == null) + { + // Force use of AElfred2 since not all JAXP parsers + // conform very well to the SAX2 API spec ... + pf = new gnu.xml.aelfred2.JAXPFactory(); + // pf = SAXParserFactory.newInstance (); + } + + // JAXP default: false + pf.setValidating(isValidating()); + + // FIXME: this namespace setup may cause errors in some + // conformant SAX2 parsers, which we CAN patch up by + // splicing a "NSFilter" stage up front ... + + // JAXP default: false + pf.setNamespaceAware(isNamespaceAware()); + + try + { + // undo rude "namespace-prefixes=false" default + pf.setFeature(FEATURE + "namespace-prefixes", true); + + return new JAXPBuilder(pf.newSAXParser().getXMLReader(), this); + } + catch (SAXException e) + { + String msg = "can't create JAXP DocumentBuilder: " + e.getMessage(); + throw new ParserConfigurationException(msg); + } + } + + /** There seems to be no useful specification for attribute names */ + public void setAttribute(String name, Object value) + throws IllegalArgumentException + { + if ("http://java.sun.com/xml/jaxp/properties/schemaLanguage".equals(name)) + { + // TODO + } + else + { + throw new IllegalArgumentException(name); + } + } + + /** There seems to be no useful specification for attribute names */ + public Object getAttribute(String name) + throws IllegalArgumentException + { + throw new IllegalArgumentException(name); + } + + static final class JAXPBuilder + extends DocumentBuilder + implements ErrorHandler + { + + private Consumer consumer; + private XMLReader producer; + private DomImpl impl; + + JAXPBuilder(XMLReader parser, JAXPFactory factory) + throws ParserConfigurationException + { + impl = new DomImpl(); + + // set up consumer side + try + { + consumer = new Consumer(); + } + catch (SAXException e) + { + throw new ParserConfigurationException(e.getMessage()); + } + + // JAXP defaults: true, noise nodes are good (bleech) + consumer.setHidingReferences(factory.isExpandEntityReferences()); + consumer.setHidingComments(factory.isIgnoringComments()); + consumer.setHidingWhitespace(factory.isIgnoringElementContentWhitespace()); + consumer.setHidingCDATA(factory.isCoalescing()); + + // set up producer side + producer = parser; + producer.setContentHandler(consumer.getContentHandler()); + producer.setDTDHandler(consumer.getDTDHandler()); + + try + { + String id; + + // if validating, report validity errors, and default + // to treating them as fatal + if (factory.isValidating ()) + { + producer.setFeature(FEATURE + "validation", true); + producer.setErrorHandler(this); + } + + // always save prefix info, maybe do namespace processing + producer.setFeature(FEATURE + "namespace-prefixes", true); + producer.setFeature(FEATURE + "namespaces", + factory.isNamespaceAware()); + + // set important handlers + id = PROPERTY + "lexical-handler"; + producer.setProperty(id, consumer.getProperty(id)); + + id = PROPERTY + "declaration-handler"; + producer.setProperty(id, consumer.getProperty(id)); + + } + catch (SAXException e) + { + throw new ParserConfigurationException(e.getMessage()); + } + } + + public Document parse(InputSource source) + throws SAXException, IOException + { + producer.parse(source); + Document doc = consumer.getDocument(); + // TODO inputEncoding + doc.setDocumentURI(source.getSystemId()); + return doc; + } + + public boolean isNamespaceAware() + { + try + { + return producer.getFeature(FEATURE + "namespaces"); + } + catch (SAXException e) + { + // "can't happen" + throw new RuntimeException(e.getMessage()); + } + } + + public boolean isValidating() + { + try + { + return producer.getFeature(FEATURE + "validation"); + } + catch (SAXException e) + { + // "can't happen" + throw new RuntimeException(e.getMessage()); + } + } + + public void setEntityResolver(EntityResolver resolver) + { + producer.setEntityResolver(resolver); + } + + public void setErrorHandler(ErrorHandler handler) + { + producer.setErrorHandler(handler); + consumer.setErrorHandler(handler); + } + + public DOMImplementation getDOMImplementation() + { + return impl; + } + + public Document newDocument() + { + return new DomDocument(); + } + + // implementation of error handler that's used when validating + public void fatalError(SAXParseException e) + throws SAXException + { + throw e; + } + + public void error(SAXParseException e) + throws SAXException + { + throw e; + } + + public void warning(SAXParseException e) + throws SAXException + { + /* ignore */ + } + + } + +} + diff --git a/libjava/gnu/xml/dom/ls/DomLSEx.java b/libjava/gnu/xml/dom/ls/DomLSEx.java new file mode 100644 index 0000000..5bf1595 --- /dev/null +++ b/libjava/gnu/xml/dom/ls/DomLSEx.java @@ -0,0 +1,75 @@ +/* DomLSEx.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom.ls; + +import java.io.PrintStream; +import java.io.PrintWriter; +import org.w3c.dom.ls.LSException; + +/** + * A DOM LS exception incorporating a cause. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class DomLSEx + extends LSException +{ + + private final Exception cause2; + + public DomLSEx(short type, Exception cause) + { + super(type, cause.getMessage()); + cause2 = cause; + } + + public void printStackTrace(PrintStream out) + { + super.printStackTrace(out); + out.print("caused by "); + cause2.printStackTrace(out); + } + + public void printStackTrace(PrintWriter out) + { + super.printStackTrace(out); + out.print("caused by "); + cause2.printStackTrace(out); + } + +} diff --git a/libjava/gnu/xml/dom/ls/DomLSInput.java b/libjava/gnu/xml/dom/ls/DomLSInput.java new file mode 100644 index 0000000..9b30b4f --- /dev/null +++ b/libjava/gnu/xml/dom/ls/DomLSInput.java @@ -0,0 +1,158 @@ +/* DomLSInput.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom.ls; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import org.w3c.dom.ls.LSInput; + +/** + * Specification of XML input to parse. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class DomLSInput + implements LSInput +{ + + private InputStream in; + private String systemId; + private String publicId; + private String baseURI; + private String encoding; + private boolean certifiedText; + + public Reader getCharacterStream() + { + return new InputStreamReader(in); + } + + public void setCharacterStream(Reader characterStream) + { + in = new ReaderInputStream(characterStream); + } + + public InputStream getByteStream() + { + return in; + } + + public void setByteStream(InputStream byteStream) + { + in = byteStream; + } + + public String getStringData() + { + StringBuffer acc = new StringBuffer(); + Reader reader = getCharacterStream(); + try + { + char[] buf = new char[4096]; + for (int len = reader.read(buf); len != -1; len = reader.read(buf)) + { + acc.append(buf, 0, len); + } + } + catch (IOException e) + { + return null; // ? + } + return acc.toString(); + } + + public void setStringData(String stringData) + { + in = new ReaderInputStream(new StringReader(stringData)); + } + + public String getSystemId() + { + return systemId; + } + + public void setSystemId(String systemId) + { + this.systemId = systemId; + } + + public String getPublicId() + { + return publicId; + } + + public void setPublicId(String publicId) + { + this.publicId = publicId; + } + + public String getBaseURI() + { + return baseURI; + } + + public void setBaseURI(String baseURI) + { + this.baseURI = baseURI; + } + + public String getEncoding() + { + return encoding; + } + + public void setEncoding(String encoding) + { + this.encoding = encoding; + } + + public boolean getCertifiedText() + { + return certifiedText; + } + + public void setCertifiedText(boolean certifiedText) + { + this.certifiedText = certifiedText; + } + +} + diff --git a/libjava/gnu/xml/dom/ls/DomLSOutput.java b/libjava/gnu/xml/dom/ls/DomLSOutput.java new file mode 100644 index 0000000..643ae7d --- /dev/null +++ b/libjava/gnu/xml/dom/ls/DomLSOutput.java @@ -0,0 +1,98 @@ +/* DomLSOutput.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom.ls; + +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import org.w3c.dom.ls.LSOutput; + +/** + * Specification of XML output to produce. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class DomLSOutput + implements LSOutput +{ + + private OutputStream out; + private String systemId; + private String encoding; + + public Writer getCharacterStream() + { + return new OutputStreamWriter(out); + } + + public void setCharacterStream(Writer characterStream) + { + out = new WriterOutputStream(characterStream); + } + + public OutputStream getByteStream() + { + return out; + } + + public void setByteStream(OutputStream out) + { + this.out = out; + } + + public String getSystemId() + { + return systemId; + } + + public void setSystemId(String systemId) + { + this.systemId = systemId; + } + + public String getEncoding() + { + return encoding; + } + + public void setEncoding(String encoding) + { + this.encoding = encoding; + } + +} diff --git a/libjava/gnu/xml/dom/ls/DomLSParser.java b/libjava/gnu/xml/dom/ls/DomLSParser.java new file mode 100644 index 0000000..b050b99 --- /dev/null +++ b/libjava/gnu/xml/dom/ls/DomLSParser.java @@ -0,0 +1,574 @@ +/* DomLSParser.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom.ls; + +import java.io.File; +import java.io.InputStream; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.w3c.dom.Document; +import org.w3c.dom.DOMConfiguration; +import org.w3c.dom.DOMException; +import org.w3c.dom.DOMStringList; +import org.w3c.dom.Node; +import org.w3c.dom.ls.DOMImplementationLS; +import org.w3c.dom.ls.LSException; +import org.w3c.dom.ls.LSInput; +import org.w3c.dom.ls.LSParser; +import org.w3c.dom.ls.LSParserFilter; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; +import gnu.xml.dom.DomDocument; +import gnu.xml.dom.DomEx; + +/** + * Parser implementation for GNU DOM. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class DomLSParser + implements LSParser, DOMConfiguration, DOMStringList, ErrorHandler +{ + + private static final List SUPPORTED_PARAMETERS + = Arrays.asList(new String[] { "cdata-sections", + "comments", + "element-content-whitespace", + "namespaces", + "expand-entity-references", + "coalescing", + "validating", + "xinclude-aware", + "entity-resolver", + "error-handler" }); + + private LSParserFilter filter; + private final boolean async; + private String schemaType; + private SAXEventSink eventSink; + private SAXParserFactory factory; + private XMLReader reader; + + private boolean namespaceAware = true; + private boolean ignoreWhitespace; + private boolean expandEntityReferences; + private boolean ignoreComments; + private boolean coalescing; + private boolean validating; + private boolean xIncludeAware; + private EntityResolver entityResolver; + private ErrorHandler errorHandler; + + public DomLSParser(short mode, String schemaType) + throws DOMException + { + switch (mode) + { + case DOMImplementationLS.MODE_ASYNCHRONOUS: + async = true; + break; + case DOMImplementationLS.MODE_SYNCHRONOUS: + async = false; + break; + default: + throw new DomEx(DOMException.NOT_SUPPORTED_ERR); + } + // TODO schemaType + this.schemaType = schemaType; + factory = SAXParserFactory.newInstance(); + } + + // -- LSParser -- + + public DOMConfiguration getDomConfig() + { + return this; + } + + public LSParserFilter getFilter() + { + return filter; + } + + public void setFilter(LSParserFilter filter) + { + this.filter = filter; + } + + public boolean getAsync() + { + return async; + } + + public boolean getBusy() + { + return eventSink != null; + } + + public Document parse(LSInput input) + throws DOMException, LSException + { + if (async) + { + return doParse(input); + } + else + { + synchronized (this) + { + return doParse(input); + } + } + } + + public Document parseURI(String uri) + throws DOMException, LSException + { + LSInput input = new DomLSInput(); + input.setSystemId(uri); + return parse(input); + } + + public Node parseWithContext(LSInput input, Node context, short action) + throws DOMException, LSException + { + Document doc = (context.getNodeType() == Node.DOCUMENT_NODE) ? + (Document) context : context.getOwnerDocument(); + input.setBaseURI(doc.getDocumentURI()); + // TODO use namespaces defined on context node + Document ret = parse(input); + Node root = ret.getDocumentElement(); + root = doc.adoptNode(root); + switch (action) + { + case ACTION_APPEND_AS_CHILDREN: + context.appendChild(root); + break; + case ACTION_REPLACE_CHILDREN: + Node c1 = context.getFirstChild(); + while (c1 != null) + { + Node next = c1.getNextSibling(); + context.removeChild(c1); + c1 = next; + } + context.appendChild(root); + break; + case ACTION_INSERT_BEFORE: + Node p1 = context.getParentNode(); + p1.insertBefore(root, context); + break; + case ACTION_INSERT_AFTER: + Node p2 = context.getParentNode(); + Node r1 = context.getNextSibling(); + if (r1 == null) + { + p2.appendChild(root); + } + else + { + p2.insertBefore(root, r1); + } + break; + case ACTION_REPLACE: + Node p3 = context.getParentNode(); + Node r2 = context.getNextSibling(); + p3.removeChild(context); + if (r2 == null) + { + p3.appendChild(root); + } + else + { + p3.insertBefore(root, r2); + } + break; + } + return root; + } + + public void abort() + { + if (eventSink != null) + { + eventSink.interrupt(); + } + } + + private Document doParse(LSInput input) + throws DOMException, LSException + { + // create event sink + if (eventSink != null) + { + throw new LSException(LSException.PARSE_ERR, "parse in progress"); + } + InputSource source = getInputSource(input); + eventSink = (filter == null) ? new SAXEventSink() : + new FilteredSAXEventSink(filter); + // configure sink + eventSink.namespaceAware = namespaceAware; + eventSink.ignoreWhitespace = ignoreWhitespace; + eventSink.expandEntityReferences = expandEntityReferences; + eventSink.ignoreComments = ignoreComments; + eventSink.coalescing = coalescing; + // get and configure reader + XMLReader reader = getXMLReader(); + try + { + reader.setContentHandler(eventSink); + reader.setDTDHandler(eventSink); + reader.setProperty("http://xml.org/sax/properties/lexical-handler", + eventSink); + reader.setProperty("http://xml.org/sax/properties/declaration-handler", + eventSink); + reader.setFeature("http://xml.org/sax/features/namespaces", + namespaceAware); + reader.setFeature("http://xml.org/sax/features/namespace-prefixes", + true); + reader.setFeature("http://xml.org/sax/features/validation", + validating); + try + { + reader.setFeature("http://xml.org/sax/features/use-attributes2", + true); + } + catch (SAXNotRecognizedException e) + { + // ignore + } + try + { + reader.setFeature("http://xml.org/sax/features/external-general-entities", + true); + } + catch (SAXNotRecognizedException e) + { + // ignore + } + reader.setEntityResolver(entityResolver); + reader.setErrorHandler(errorHandler); + // parse + reader.parse(source); + } + catch (DOMException e) + { + reader = null; + eventSink = null; + throw e; + } + catch (SAXException e) + { + reader = null; + eventSink = null; + throw new DomLSEx(LSException.PARSE_ERR, e); + } + catch (IOException e) + { + reader = null; + eventSink = null; + throw new DomLSEx(LSException.PARSE_ERR, e); + } + // return document + Document ret = eventSink.doc; + String systemId = input.getSystemId(); + if (systemId != null && ret instanceof DomDocument) + { + ((DomDocument) ret).setDocumentURI(systemId); + } + eventSink = null; + return ret; + } + + private XMLReader getXMLReader() + throws LSException + { + if (reader == null) + { + factory.setNamespaceAware(namespaceAware); + factory.setValidating(validating); + factory.setXIncludeAware(xIncludeAware); + try + { + SAXParser parser = factory.newSAXParser(); + reader = parser.getXMLReader(); + } + catch (ParserConfigurationException e) + { + throw new DomLSEx(LSException.PARSE_ERR, e); + } + catch (SAXException e) + { + throw new DomLSEx(LSException.PARSE_ERR, e); + } + } + return reader; + } + + private InputSource getInputSource(LSInput input) + throws LSException + { + InputSource source = null; + String systemId = input.getSystemId(); + InputStream in = input.getByteStream(); + if (in != null) + { + source = new InputSource(in); + source.setSystemId(systemId); + } + if (source == null && entityResolver != null) + { + String publicId = input.getPublicId(); + try + { + source = entityResolver.resolveEntity(publicId, systemId); + } + catch (SAXException e) + { + throw new DomLSEx(LSException.PARSE_ERR, e); + } + catch (IOException e) + { + throw new DomLSEx(LSException.PARSE_ERR, e); + } + } + if (source == null) + { + URL url = null; + String base = input.getBaseURI(); + try + { + try + { + URL baseURL = (base == null) ? null : new URL(base); + url = (baseURL == null) ? new URL(systemId) : + new URL(baseURL, systemId); + } + catch (MalformedURLException e) + { + File baseFile = (base == null) ? null : new File(base); + url = (baseFile == null) ? new File(systemId).toURL() : + new File(baseFile, systemId).toURL(); + } + in = url.openStream(); + systemId = url.toString(); + source = new InputSource(in); + source.setSystemId(systemId); + } + catch (IOException e) + { + throw new DomLSEx(LSException.PARSE_ERR, e); + } + } + return source; + } + + // -- DOMConfiguration -- + + public void setParameter(String name, Object value) + throws DOMException + { + name = name.toLowerCase(); + if ("cdata-sections".equals(name)) + { + coalescing = !((Boolean) value).booleanValue(); + } + else if ("comments".equals(name)) + { + ignoreComments = !((Boolean) value).booleanValue(); + } + else if ("element-content-whitespace".equals(name)) + { + ignoreWhitespace = !((Boolean) value).booleanValue(); + } + else if ("namespaces".equals(name)) + { + namespaceAware = ((Boolean) value).booleanValue(); + } + else if ("expand-entity-references".equals(name)) + { + expandEntityReferences = ((Boolean) value).booleanValue(); + } + else if ("coalescing".equals(name)) + { + coalescing = ((Boolean) value).booleanValue(); + } + else if ("validating".equals(name)) + { + validating = ((Boolean) value).booleanValue(); + } + else if ("xinclude-aware".equals(name)) + { + xIncludeAware = ((Boolean) value).booleanValue(); + } + else if ("entity-resolver".equals(name)) + { + entityResolver = (EntityResolver) value; + } + else if ("error-handler".equals(name)) + { + errorHandler = (ErrorHandler) value; + } + else + { + throw new DomEx(DomEx.NOT_SUPPORTED_ERR); + } + // invalidate reader, a new one will be created + reader = null; + } + + public Object getParameter(String name) + throws DOMException + { + name = name.toLowerCase(); + if ("cdata-sections".equals(name)) + { + return coalescing ? Boolean.FALSE : Boolean.TRUE; + } + else if ("comments".equals(name)) + { + return ignoreComments ? Boolean.FALSE : Boolean.TRUE; + } + else if ("element-content-whitespace".equals(name)) + { + return ignoreWhitespace ? Boolean.FALSE : Boolean.TRUE; + } + else if ("namespaces".equals(name)) + { + return namespaceAware ? Boolean.TRUE : Boolean.FALSE; + } + else if ("expand-entity-references".equals(name)) + { + return expandEntityReferences ? Boolean.TRUE : Boolean.FALSE; + } + else if ("coalescing".equals(name)) + { + return coalescing ? Boolean.TRUE : Boolean.FALSE; + } + else if ("validating".equals(name)) + { + return validating ? Boolean.TRUE : Boolean.FALSE; + } + else if ("xinclude-aware".equals(name)) + { + return xIncludeAware ? Boolean.TRUE : Boolean.FALSE; + } + else if ("entity-resolver".equals(name)) + { + return entityResolver; + } + else if ("error-handler".equals(name)) + { + return errorHandler; + } + else + { + throw new DomEx(DomEx.NOT_SUPPORTED_ERR); + } + } + + public boolean canSetParameter(String name, Object value) + { + return contains(name); + } + + public DOMStringList getParameterNames() + { + return this; + } + + // -- DOMStringList -- + + public String item(int i) + { + return (String) SUPPORTED_PARAMETERS.get(i); + } + + public int getLength() + { + return SUPPORTED_PARAMETERS.size(); + } + + public boolean contains(String str) + { + return SUPPORTED_PARAMETERS.contains(str); + } + + // -- ErrorHandler -- + + public void warning(SAXParseException e) + throws SAXException + { + if (errorHandler != null) + { + errorHandler.warning(e); + } + } + + public void error(SAXParseException e) + throws SAXException + { + if (errorHandler != null) + { + errorHandler.error(e); + } + } + + public void fatalError(SAXParseException e) + throws SAXException + { + if (errorHandler != null) + { + errorHandler.fatalError(e); + } + abort(); + } + +} + diff --git a/libjava/gnu/xml/dom/ls/DomLSSerializer.java b/libjava/gnu/xml/dom/ls/DomLSSerializer.java new file mode 100644 index 0000000..91a01c9 --- /dev/null +++ b/libjava/gnu/xml/dom/ls/DomLSSerializer.java @@ -0,0 +1,354 @@ +/* DomLSSerializer.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom.ls; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.StringWriter; +import java.io.Writer; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.Arrays; +import java.util.List; +import org.w3c.dom.DOMConfiguration; +import org.w3c.dom.DOMException; +import org.w3c.dom.DOMStringList; +import org.w3c.dom.Node; +import org.w3c.dom.ls.LSException; +import org.w3c.dom.ls.LSOutput; +import org.w3c.dom.ls.LSSerializer; +import org.w3c.dom.ls.LSSerializerFilter; +import org.w3c.dom.traversal.NodeFilter; +import gnu.xml.dom.DomEx; +import gnu.xml.transform.StreamSerializer; + +/** + * Serialize a DOM node to a stream. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class DomLSSerializer + extends StreamSerializer + implements LSSerializer, DOMConfiguration, DOMStringList +{ + + private static final List SUPPORTED_PARAMETERS = + Arrays.asList(new String[] {"discard-default-content", + "xml-declaration"}); + + private LSSerializerFilter filter; + private StreamSerializer serializer; + + public DomLSSerializer() + { + super(); + discardDefaultContent = true; + } + + // -- LSSerializer -- + + public DOMConfiguration getDomConfig() + { + return this; + } + + public String getNewLine() + { + return eol; + } + + public void setNewLine(String newLine) + { + if (newLine == null) + { + newLine = System.getProperty("line.separator"); + } + eol = newLine; + } + + public LSSerializerFilter getFilter() + { + return filter; + } + + public void setFilter(LSSerializerFilter filter) + { + this.filter = filter; + } + + public boolean write(Node node, LSOutput output) + throws LSException + { + OutputStream out = output.getByteStream(); + try + { + if (out == null) + { + String systemId = output.getSystemId(); + try + { + URL url = new URL(systemId); + URLConnection connection = url.openConnection(); + connection.setDoOutput(true); + if (connection instanceof HttpURLConnection) + { + ((HttpURLConnection) connection).setRequestMethod("PUT"); + } + out = connection.getOutputStream(); + } + catch (MalformedURLException e) + { + File file = new File(systemId); + out = new FileOutputStream(file); + } + } + serialize(node, out); + out.flush(); + return true; + } + catch (IOException e) + { + throw new DomLSEx(LSException.SERIALIZE_ERR, e); + } + } + + public boolean writeToURI(Node node, String uri) + throws LSException + { + LSOutput output = new DomLSOutput(); + output.setSystemId(uri); + return write(node, output); + } + + public String writeToString(Node node) + throws DOMException, LSException + { + Writer writer = new StringWriter(); + LSOutput output = new DomLSOutput(); + output.setCharacterStream(writer); + write(node, output); + return writer.toString(); + } + + public void serialize(Node node, OutputStream out) + throws IOException + { + if (filter == null) + { + super.serialize(node, out); + } + else + { + int wts = filter.getWhatToShow(); + if (wts != NodeFilter.SHOW_ALL) + { + switch (node.getNodeType()) + { + case Node.ATTRIBUTE_NODE: + if ((wts & NodeFilter.SHOW_ATTRIBUTE) == 0) + { + super.serialize(node, out); + return; + } + break; + case Node.TEXT_NODE: + if ((wts & NodeFilter.SHOW_TEXT) == 0) + { + super.serialize(node, out); + return; + } + break; + case Node.ELEMENT_NODE: + if ((wts & NodeFilter.SHOW_ELEMENT) == 0) + { + super.serialize(node, out); + return; + } + break; + case Node.CDATA_SECTION_NODE: + if ((wts & NodeFilter.SHOW_CDATA_SECTION) == 0) + { + super.serialize(node, out); + return; + } + break; + case Node.COMMENT_NODE: + if ((wts & NodeFilter.SHOW_COMMENT) == 0) + { + super.serialize(node, out); + return; + } + break; + case Node.DOCUMENT_NODE: + if ((wts & NodeFilter.SHOW_DOCUMENT) == 0) + { + super.serialize(node, out); + return; + } + break; + case Node.DOCUMENT_TYPE_NODE: + if ((wts & NodeFilter.SHOW_DOCUMENT_TYPE) == 0) + { + super.serialize(node, out); + return; + } + break; + case Node.PROCESSING_INSTRUCTION_NODE: + if ((wts & NodeFilter.SHOW_PROCESSING_INSTRUCTION) == 0) + { + super.serialize(node, out); + return; + } + break; + case Node.DOCUMENT_FRAGMENT_NODE: + if ((wts & NodeFilter.SHOW_DOCUMENT_FRAGMENT) == 0) + { + super.serialize(node, out); + return; + } + break; + case Node.ENTITY_NODE: + if ((wts & NodeFilter.SHOW_ENTITY) == 0) + { + super.serialize(node, out); + return; + } + break; + case Node.ENTITY_REFERENCE_NODE: + if ((wts & NodeFilter.SHOW_ENTITY_REFERENCE) == 0) + { + super.serialize(node, out); + return; + } + break; + case Node.NOTATION_NODE: + if ((wts & NodeFilter.SHOW_NOTATION) == 0) + { + super.serialize(node, out); + return; + } + break; + } + } + switch (filter.acceptNode(node)) + { + case NodeFilter.FILTER_ACCEPT: + super.serialize(node, out); + break; + case NodeFilter.FILTER_REJECT: + break; + case NodeFilter.FILTER_SKIP: + Node first = node.getFirstChild(); + if (first != null) + { + serialize(first, out); + } + break; + } + } + } + + // -- DOMConfiguration -- + + public void setParameter(String name, Object value) + throws DOMException + { + if ("discard-default-content".equals(name)) + { + discardDefaultContent = "true".equals(value.toString()); + } + else if ("xml-declaration".equals(name)) + { + xmlDeclaration = "false".equals(value.toString()); + } + else + { + throw new DomEx(DomEx.NOT_SUPPORTED_ERR); + } + } + + public Object getParameter(String name) + throws DOMException + { + if ("discard-default-content".equals(name)) + { + return discardDefaultContent ? "true" : "false"; + } + else if ("xml-declaration".equals(name)) + { + return xmlDeclaration ? "true" : "false"; + } + else + { + throw new DomEx(DomEx.NOT_SUPPORTED_ERR); + } + } + + public boolean canSetParameter(String name, Object value) + { + return contains(name); + } + + public DOMStringList getParameterNames() + { + return this; + } + + // -- DOMStringList -- + + public String item(int i) + { + return (String) SUPPORTED_PARAMETERS.get(i); + } + + public int getLength() + { + return SUPPORTED_PARAMETERS.size(); + } + + public boolean contains(String str) + { + return SUPPORTED_PARAMETERS.contains(str); + } + +} + diff --git a/libjava/gnu/xml/dom/ls/FilteredSAXEventSink.java b/libjava/gnu/xml/dom/ls/FilteredSAXEventSink.java new file mode 100644 index 0000000..63f32a8 --- /dev/null +++ b/libjava/gnu/xml/dom/ls/FilteredSAXEventSink.java @@ -0,0 +1,354 @@ +/* FilteredSAXEventSink.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom.ls; + +import java.util.LinkedList; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.Text; +import org.w3c.dom.ls.LSParserFilter; +import org.w3c.dom.traversal.NodeFilter; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +/** + * A SAX event sink that calls out to a parser filter in order to decide + * whether to insert nodes into the tree. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class FilteredSAXEventSink + extends SAXEventSink +{ + + final LSParserFilter filter; + final int whatToShow; + + /** + * Stack of elements to insert. + */ + LinkedList nodes; + + /** + * Corresponding stack of filter decisions about the nodes. + */ + LinkedList decisions; + + /** + * True when rejecting child nodes. + */ + boolean rejecting; + + FilteredSAXEventSink(LSParserFilter filter) + { + this.filter = filter; + whatToShow = filter.getWhatToShow(); + } + + public void startDocument() + throws SAXException + { + if (interrupted) + { + return; + } + nodes = new LinkedList(); + decisions = new LinkedList(); + + super.startDocument(); + } + + public void endDocument() + throws SAXException + { + if (interrupted) + { + return; + } + super.endDocument(); + + switch (getDecision(ctx, false)) + { + case LSParserFilter.FILTER_REJECT: + ctx = null; + doc = null; + break; + } + + nodes = null; + decisions = null; + } + + public void startElement(String uri, String localName, String qName, + Attributes atts) + throws SAXException + { + if (rejecting || interrupted) + { + return; + } + Element element = createElement(uri, localName, qName, atts); + ctx = element; + + short decision = getDecision(element, true); + nodes.addLast(element); + decisions.addLast(new Short(decision)); + + switch (decision) + { + case LSParserFilter.FILTER_REJECT: + rejecting = true; + break; + case LSParserFilter.FILTER_INTERRUPT: + interrupted = true; + break; + } + } + + protected Attr createAttr(Attributes atts, int index) + { + Attr attr = super.createAttr(atts, index); + short decision = getDecision(attr, false); + switch (decision) + { + case LSParserFilter.FILTER_REJECT: + return null; + case LSParserFilter.FILTER_INTERRUPT: + interrupted = true; + return null; + } + return attr; + } + + public void endElement(String uri, String localName, String qName) + throws SAXException + { + if (rejecting || interrupted) + { + return; + } + super.endElement(uri, localName, qName); + + Element element = (Element) nodes.removeLast(); + Node parent = nodes.isEmpty() ? doc : (Node) nodes.getLast(); + ctx = parent; + short decision = ((Short) decisions.removeLast()).shortValue(); + switch (decision) + { + case LSParserFilter.FILTER_SKIP: + // Add all children of element to parent + for (Node child = element.getFirstChild(); child != null; + child = child.getNextSibling()) + { + parent.insertBefore(child, element); + } + return; + case LSParserFilter.FILTER_REJECT: + rejecting = false; + break; + } + decision = getDecision(element, false); + switch (decision) + { + case LSParserFilter.FILTER_ACCEPT: + parent.appendChild(element); + break; + case LSParserFilter.FILTER_INTERRUPT: + interrupted = true; + break; + } + } + + public void characters(char[] c, int off, int len) + throws SAXException + { + if (rejecting || interrupted) + { + return; + } + Text text = createText(c, off, len); + short decision = getDecision(text, false); + switch (decision) + { + case LSParserFilter.FILTER_ACCEPT: + ctx.appendChild(text); + break; + case LSParserFilter.FILTER_INTERRUPT: + interrupted = true; + break; + } + } + + public void processingInstruction(String target, String data) + throws SAXException + { + if (rejecting || interrupted || inDTD) + { + return; + } + Node pi = createProcessingInstruction(target, data); + short decision = getDecision(pi, false); + switch (decision) + { + case LSParserFilter.FILTER_ACCEPT: + ctx.appendChild(pi); + break; + case LSParserFilter.FILTER_INTERRUPT: + interrupted = true; + break; + } + } + + public void startDTD(String name, String publicId, String systemId) + throws SAXException + { + if (interrupted) + { + return; + } + Node doctype = createDocumentType(name, publicId, systemId); + ctx = doctype; + inDTD = true; + nodes.addLast(doctype); + decisions.addLast(new Short(LSParserFilter.FILTER_ACCEPT)); + } + + public void endDTD() + throws SAXException + { + if (interrupted) + { + return; + } + Node doctype = (Node) nodes.removeLast(); + decisions.removeLast(); + inDTD = false; + ctx = doc; + short decision = getDecision(doctype, false); + switch (decision) + { + case LSParserFilter.FILTER_ACCEPT: + ctx.appendChild(doctype); + break; + case LSParserFilter.FILTER_INTERRUPT: + interrupted = true; + break; + } + } + + public void comment(char[] c, int off, int len) + throws SAXException + { + if (rejecting || interrupted || inDTD) + { + return; + } + Node comment = createComment(c, off, len); + short decision = getDecision(comment, false); + switch (decision) + { + case LSParserFilter.FILTER_ACCEPT: + ctx.appendChild(comment); + break; + case LSParserFilter.FILTER_INTERRUPT: + interrupted = true; + break; + } + } + + // TODO declarations + + short getDecision(Node node, boolean start) + { + boolean show = (whatToShow == NodeFilter.SHOW_ALL); + if (!show) + { + switch (node.getNodeType()) + { + case Node.ATTRIBUTE_NODE: + show = ((whatToShow & NodeFilter.SHOW_ATTRIBUTE) != 0); + break; + case Node.TEXT_NODE: + show = ((whatToShow & NodeFilter.SHOW_TEXT) != 0); + break; + case Node.CDATA_SECTION_NODE: + show = ((whatToShow & NodeFilter.SHOW_CDATA_SECTION) != 0); + break; + case Node.ELEMENT_NODE: + show = ((whatToShow & NodeFilter.SHOW_ELEMENT) != 0); + break; + case Node.COMMENT_NODE: + show = ((whatToShow & NodeFilter.SHOW_COMMENT) != 0); + break; + case Node.DOCUMENT_NODE: + show = ((whatToShow & NodeFilter.SHOW_DOCUMENT) != 0); + break; + case Node.PROCESSING_INSTRUCTION_NODE: + show = ((whatToShow & NodeFilter.SHOW_PROCESSING_INSTRUCTION) != 0); + break; + case Node.DOCUMENT_FRAGMENT_NODE: + show = ((whatToShow & NodeFilter.SHOW_DOCUMENT_FRAGMENT) != 0); + break; + case Node.DOCUMENT_TYPE_NODE: + show = ((whatToShow & NodeFilter.SHOW_DOCUMENT_TYPE) != 0); + break; + case Node.ENTITY_REFERENCE_NODE: + show = ((whatToShow & NodeFilter.SHOW_ENTITY_REFERENCE) != 0); + break; + case Node.ENTITY_NODE: + show = ((whatToShow & NodeFilter.SHOW_ENTITY) != 0); + break; + case Node.NOTATION_NODE: + show = ((whatToShow & NodeFilter.SHOW_NOTATION) != 0); + break; + } + } + if (!show) + { + return LSParserFilter.FILTER_ACCEPT; + } + if (start) + { + return filter.startElement((Element) node); + } + return filter.acceptNode(node); + } + +} + diff --git a/libjava/gnu/xml/dom/ls/ReaderInputStream.java b/libjava/gnu/xml/dom/ls/ReaderInputStream.java new file mode 100644 index 0000000..633b968 --- /dev/null +++ b/libjava/gnu/xml/dom/ls/ReaderInputStream.java @@ -0,0 +1,237 @@ +/* ReaderInputStream.java -- + Copyright (C) 1999, 2000, 2001, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom.ls; + +import java.io.InputStream; +import java.io.IOException; +import java.io.Reader; + +/** + * Character stream wrapper. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + * @author <a href='mailto:mark@klomp.org'>Mark Wielaard</a> + */ +public class ReaderInputStream + extends InputStream +{ + + private Reader reader; + private String encoding; + + // Holds extra spillover data if necessary + private byte extra[]; + private int pos; + + private byte extra_marked[]; + private int pos_marked; + + public ReaderInputStream(Reader reader) + { + this.reader = reader; + this.encoding = "UTF-8"; + } + + void setEncoding(String encoding) + { + this.encoding = encoding; + } + + public int read() + throws IOException + { + if (extra != null) + { + int result = extra[pos]; + pos++; + if (pos >= extra.length) + { + extra = null; + } + return result; + } + return reader.read(); + } + + public int read(byte[] b) + throws IOException + { + return read(b, 0, b.length); + } + + public int read(byte[] b, int off, int len) + throws IOException + { + if (len == 0) + { + return 0; + } + + if (extra != null) + { + int available = extra.length - pos; + int l = available < len ? available : len; + System.arraycopy(extra, 0, b, off, l); + pos += l; + if (pos >= extra.length) + { + extra = null; + } + return l; + } + + char[] c = new char[len]; + int l = reader.read(c, 0, len); + if (l == -1) + { + return -1; + } + + String s = new String(c, 0, l); + byte[] d = s.getBytes(encoding); + + int available = d.length; + int more = d.length - len; + if (more > 0) + { + extra = new byte[more]; + pos = 0; + System.arraycopy(d, len, extra, 0, more); + available -= more; + } + + System.arraycopy(d, 0, b, off, available); + return available; + } + + public void close() + throws IOException + { + reader.close(); + } + + public boolean markSupported() + { + return reader.markSupported(); + } + + public void mark(int limit) + { + if (extra != null) + { + extra_marked = new byte[extra.length]; + System.arraycopy(extra, 0, extra_marked, 0, extra.length); + pos_marked = pos; + } + else + { + extra_marked = null; + } + + try + { + // Note that this might be a bit more than asked for. + // Because we might also have the extra_marked bytes. + // That is fine (and necessary for reset() to work). + reader.mark(limit); + } + catch (IOException ioe) + { + throw new RuntimeException(ioe); + } + } + + public void reset() + throws IOException + { + extra = extra_marked; + pos = pos_marked; + extra_marked = null; + + reader.reset(); + } + + public long skip(long n) + throws IOException + { + long done = 0; + if (extra != null) + { + int available = extra.length - pos; + done = available < n ? available : n; + pos += done; + if (pos >= extra.length) + { + extra = null; + } + } + + n -= done; + if (n > 0) + { + return reader.skip(n) + done; + } + else + { + return done; + } + } + + /** + * Returns conservative number of bytes available without blocking. + * Actual number of bytes that can be read without blocking might + * be (much) bigger. + */ + public int available() + throws IOException + { + if (extra != null) + { + return pos - extra.length; + } + + return reader.ready() ? 1 : 0; + } + + public String toString() + { + return getClass().getName() + "[" + reader + ", " + encoding + "]"; + } + +} + diff --git a/libjava/gnu/xml/dom/ls/SAXEventSink.java b/libjava/gnu/xml/dom/ls/SAXEventSink.java new file mode 100644 index 0000000..24f6ccc --- /dev/null +++ b/libjava/gnu/xml/dom/ls/SAXEventSink.java @@ -0,0 +1,533 @@ +/* SAXEventSink.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom.ls; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import javax.xml.XMLConstants; +import org.w3c.dom.Attr; +import org.w3c.dom.DocumentType; +import org.w3c.dom.Element; +import org.w3c.dom.Entity; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.Text; +import org.xml.sax.Attributes; +import org.xml.sax.DTDHandler; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.ext.Attributes2; +import org.xml.sax.ext.DeclHandler; +import org.xml.sax.ext.LexicalHandler; +import gnu.xml.aelfred2.ContentHandler2; +import gnu.xml.dom.DomAttr; +import gnu.xml.dom.DomDocument; +import gnu.xml.dom.DomDoctype; + +/** + * A SAX content and lexical handler used to construct a DOM document. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class SAXEventSink + implements ContentHandler2, LexicalHandler, DTDHandler, DeclHandler +{ + + private static final String XMLNS_URI = XMLConstants.XMLNS_ATTRIBUTE_NS_URI; + private static final String XMLNS_PREFIX = XMLConstants.XMLNS_ATTRIBUTE; + + boolean namespaceAware; + boolean ignoreWhitespace; + boolean expandEntityReferences; + boolean ignoreComments; + boolean coalescing; + + DomDocument doc; // document being constructed + Node ctx; // current context (parent node) + LinkedList entityCtx; // entity context + List pending; // namespace nodes waiting for a declaring element + Locator locator; + boolean inCDATA; + boolean inDTD; + boolean interrupted; + + void interrupt() + { + interrupted = true; + } + + // -- ContentHandler2 -- + + public void setDocumentLocator(Locator locator) + { + this.locator = locator; + } + + public void startDocument() + throws SAXException + { + if (namespaceAware) + { + pending = new LinkedList(); + } + doc = new DomDocument(); + doc.setStrictErrorChecking(false); + doc.setBuilding(true); + ctx = doc; + } + + public void xmlDecl(String version, String encoding, boolean standalone, + String inputEncoding) + throws SAXException + { + if (interrupted) + { + return; + } + doc.setXmlVersion(version); + doc.setXmlEncoding(encoding); + doc.setXmlStandalone(standalone); + doc.setInputEncoding(inputEncoding); + } + + public void endDocument() + throws SAXException + { + doc.setStrictErrorChecking(true); + doc.setBuilding(false); + DomDoctype doctype = (DomDoctype) doc.getDoctype(); + if (doctype != null) + { + doctype.makeReadonly(); + } + ctx = null; + locator = null; + } + + public void startPrefixMapping(String prefix, String uri) + throws SAXException + { + if (namespaceAware) + { + String nsName = (prefix != null && prefix.length() > 0) ? + XMLNS_PREFIX + ":" + prefix : XMLNS_PREFIX; + DomAttr ns = (DomAttr) doc.createAttributeNS(XMLNS_URI, nsName); + ns.setNodeValue(uri); + if (ctx.getNodeType() == Node.ATTRIBUTE_NODE) + { + // Add to owner element + Node target = ((Attr) ctx).getOwnerElement(); + target.getAttributes().setNamedItemNS(ns); + } + else + { + // Add to pending list; namespace node will be inserted when + // element is seen + pending.add(ns); + } + } + } + + public void endPrefixMapping(String prefix) + throws SAXException + { + } + + public void startElement(String uri, String localName, String qName, + Attributes atts) + throws SAXException + { + if (interrupted) + { + return; + } + Element element = createElement(uri, localName, qName, atts); + // add element to context + ctx.appendChild(element); + ctx = element; + } + + protected Element createElement(String uri, String localName, String qName, + Attributes atts) + throws SAXException + { + // create element node + Element element = namespaceAware ? + doc.createElementNS(uri, qName) : + doc.createElement(qName); + NamedNodeMap attrs = element.getAttributes(); + if (namespaceAware && !pending.isEmpty()) + { + // add pending namespace nodes + for (Iterator i = pending.iterator(); i.hasNext(); ) + { + Node ns = (Node) i.next(); + attrs.setNamedItemNS(ns); + } + pending.clear(); + } + // add attributes + int len = atts.getLength(); + for (int i = 0; i < len; i++) + { + // create attribute + Attr attr = createAttr(atts, i); + if (attr != null) + { + // add attribute to element + if (namespaceAware) + { + attrs.setNamedItemNS(attr); + } + else + { + attrs.setNamedItem(attr); + } + } + } + return element; + } + + protected Attr createAttr(Attributes atts, int index) + { + DomAttr attr; + if (namespaceAware) + { + String a_uri = atts.getURI(index); + String a_qName = atts.getQName(index); + attr = (DomAttr) doc.createAttributeNS(a_uri, a_qName); + } + else + { + String a_qName = atts.getQName(index); + attr = (DomAttr) doc.createAttribute(a_qName); + } + attr.setNodeValue(atts.getValue(index)); + if (atts instanceof Attributes2) + { + Attributes2 atts2 = (Attributes2) atts; + // TODO attr.setDeclared(atts2.isDeclared(index)); + attr.setSpecified(atts2.isSpecified(index)); + } + return attr; + } + + public void endElement(String uri, String localName, String qName) + throws SAXException + { + if (interrupted) + { + return; + } + if (namespaceAware) + { + pending.clear(); + } + ctx = ctx.getParentNode(); + } + + public void characters(char[] c, int off, int len) + throws SAXException + { + if (interrupted) + { + return; + } + ctx.appendChild(createText(c, off, len)); + } + + protected Text createText(char[] c, int off, int len) + throws SAXException + { + Text text = (inCDATA && !coalescing) ? + doc.createCDATASection(new String(c, off, len)) : + doc.createTextNode(new String(c, off, len)); + return text; + } + + public void ignorableWhitespace(char[] c, int off, int len) + throws SAXException + { + if (interrupted) + { + return; + } + if (!ignoreWhitespace) + { + characters(c, off, len); + } + } + + public void processingInstruction(String target, String data) + throws SAXException + { + if (interrupted) + { + return; + } + if (!inDTD) + { + Node pi = createProcessingInstruction(target, data); + ctx.appendChild(pi); + } + } + + protected Node createProcessingInstruction(String target, String data) + { + return doc.createProcessingInstruction(target, data); + } + + public void skippedEntity(String name) + throws SAXException + { + // This callback is totally pointless + } + + // -- LexicalHandler -- + + public void startDTD(String name, String publicId, String systemId) + throws SAXException + { + if (interrupted) + { + return; + } + Node doctype = createDocumentType(name, publicId, systemId); + doc.appendChild(doctype); + ctx = doctype; + inDTD = true; + } + + protected Node createDocumentType(String name, String publicId, + String systemId) + { + return new DomDoctype(doc, name, publicId, systemId); + } + + public void endDTD() + throws SAXException + { + if (interrupted) + { + return; + } + inDTD = false; + ctx = ctx.getParentNode(); + } + + public void startEntity(String name) + throws SAXException + { + DocumentType doctype = doc.getDoctype(); + if (doctype == null) + { + throw new SAXException("SAX parser error: " + + "reference to entity in undeclared doctype"); + } + if ("[dtd]".equals(name) || name.charAt(0) == '%') + { + // Ignore DTD and parameter entities + ctx = doctype; + return; + } + // Get entity + NamedNodeMap entities = doctype.getEntities(); + Entity entity = (Entity) entities.getNamedItem(name); + if (entity == null) + { + throw new SAXException("SAX parser error: " + + "reference to undeclared entity: " + name); + } + pushEntity(entity); + } + + public void endEntity(String name) + throws SAXException + { + if ("[dtd]".equals(name) || name.charAt(0) == '%') + { + // Ignore DTD and parameter entities + return; + } + Entity entity = popEntity(); + // TODO resolve external entities to ensure that entity has content + if (expandEntityReferences) + { + // Get entity content + for (Node child = entity.getFirstChild(); child != null; + child = child.getNextSibling()) + { + ctx.appendChild(child); + } + } + else + { + Node entityReference = doc.createEntityReference(name); + ctx.appendChild(entityReference); + } + } + + void pushEntity(Node entity) + { + if (entityCtx == null) + { + entityCtx = new LinkedList(); + } + entityCtx.addLast(ctx); + ctx = entity; + } + + Entity popEntity() + { + Entity ret = (Entity) ctx; + ctx = (Node) entityCtx.removeLast(); + return ret; + } + + public void startCDATA() + throws SAXException + { + inCDATA = true; + } + + public void endCDATA() + throws SAXException + { + inCDATA = false; + } + + public void comment(char[] c, int off, int len) + throws SAXException + { + if (interrupted) + { + return; + } + if (!inDTD) + { + Node comment = createComment(c, off, len); + ctx.appendChild(comment); + } + } + + protected Node createComment(char[] c, int off, int len) + { + return doc.createComment(new String(c, off, len)); + } + + // -- DTDHandler -- + + public void notationDecl(String name, String publicId, String systemId) + throws SAXException + { + if (interrupted) + { + return; + } + DomDoctype doctype = (DomDoctype) ctx; + doctype.declareNotation(name, publicId, systemId); + } + + public void unparsedEntityDecl(String name, String publicId, String systemId, + String notationName) + throws SAXException + { + if (interrupted) + { + return; + } + DomDoctype doctype = (DomDoctype) ctx; + Entity entity = doctype.declareEntity(name, publicId, systemId, + notationName); + } + + // -- DeclHandler -- + + public void elementDecl(String name, String model) + throws SAXException + { + if (interrupted) + { + return; + } + DomDoctype doctype = (DomDoctype) ctx; + doctype.elementDecl(name, model); + } + + public void attributeDecl(String eName, String aName, String type, + String mode, String value) + throws SAXException + { + if (interrupted) + { + return; + } + DomDoctype doctype = (DomDoctype) ctx; + doctype.attributeDecl(eName, aName, type, mode, value); + } + + public void internalEntityDecl(String name, String value) + throws SAXException + { + if (interrupted) + { + return; + } + DomDoctype doctype = (DomDoctype) ctx; + Entity entity = doctype.declareEntity(name, null, null, null); + if (entity != null) + { + Node text = doc.createTextNode(value); + entity.appendChild(text); + } + } + + public void externalEntityDecl(String name, String publicId, String systemId) + throws SAXException + { + if (interrupted) + { + return; + } + DomDoctype doctype = (DomDoctype) ctx; + Entity entity = doctype.declareEntity(name, publicId, systemId, null); + } + +} + diff --git a/libjava/gnu/xml/dom/ls/WriterOutputStream.java b/libjava/gnu/xml/dom/ls/WriterOutputStream.java new file mode 100644 index 0000000..7eddb2e --- /dev/null +++ b/libjava/gnu/xml/dom/ls/WriterOutputStream.java @@ -0,0 +1,98 @@ +/* WriterOutputStream.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.dom.ls; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Writer; + +/** + * Character stream wrapper. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class WriterOutputStream + extends OutputStream +{ + + private Writer writer; + private String encoding; + + public WriterOutputStream(Writer writer) + { + this.writer = writer; + this.encoding = "UTF-8"; + } + + void setEncoding(String encoding) + { + this.encoding = encoding; + } + + public void write(int c) + throws IOException + { + writer.write(c); + } + + public void write(byte[] b) + throws IOException + { + write(b, 0, b.length); + } + + public void write(byte[] b, int off, int len) + throws IOException + { + writer.write(new String(b, off, len, encoding)); + } + + public void close() + throws IOException + { + writer.close(); + } + + public void flush() + throws IOException + { + writer.flush(); + } + +} + diff --git a/libjava/gnu/xml/dom/package.html b/libjava/gnu/xml/dom/package.html new file mode 100644 index 0000000..fbc864a --- /dev/null +++ b/libjava/gnu/xml/dom/package.html @@ -0,0 +1,273 @@ +<html> +<body> + +<p> +This is a Free Software DOM Level 3 implementation, supporting these features: +<ul> +<li>"XML"</li> +<li>"Events"</li> +<li>"MutationEvents"</li> +<li>"HTMLEvents" (won't generate them though)</li> +<li>"UIEvents" (also won't generate them)</li> +<li>"USER-Events" (a conformant extension)</li> +<li>"Traversal" (optional)</li> +<li>"XPath"</li> +<li>"LS" and "LS-Async"</li> +</ul> +It is intended to be a reasonable base both for +experimentation and supporting additional DOM modules as clean layers. +</p> + +<p> +Note that while DOM does not specify its behavior in the +face of concurrent access, this implementation does. +Specifically: +<ul> +<li>If only one thread at a time accesses a Document, +of if several threads cooperate for read-only access, +then no concurrency conflicts will occur.</li> +<li>If several threads mutate a given document +(or send events using it) at the same time, +there is currently no guarantee that +they won't interfere with each other.</li> +</ul> +</p> + +<h3>Design Goals</h3> + +<p> +A number of DOM implementations are available in Java, including +commercial ones from Sun, IBM, Oracle, and DataChannel as well as +noncommercial ones from Docuverse, OpenXML, and Silfide. Why have +another? Some of the goals of this version: +</p> + +<ul> +<li>Advanced DOM support. This was the first generally available +implementation of DOM Level 2 in Java, and one of the first Level 3 +and XPath implementations.</li> + +<li> Free Software. This one is distributed under the GPL (with +"library exception") so it can be used with a different class of +application.</li> + +<li>Second implementation syndrome. I can do it simpler this time +around ... and heck, writing it only takes a bit over a day once you +know your way around.</li> + +<li>Sanity check the then-current Last Call DOM draft. Best to find +bugs early, when they're relatively fixable. Yes, bugs were found.</li> + +<li>Modularity. Most of the implementations mentioned above are part +of huge packages; take all (including bugs, of which some have far +too many), or take nothing. I prefer a menu approach, when possible. +This code is standalone, not beholden to any particular parser or XSL +or XPath code.</li> + +<li>OK, I'm a hacker, I like to write code.</li> +</ul> + +<p> +This also works with the GNU Compiler for Java (GCJ). GCJ promises +to be quite the environment for programming Java, both directly and from +C++ using the new CNI interfaces (which really use C++, unlike JNI). </p> + + +<h3>Open Issues</h3> + +<p>At this writing:</p> +<ul> +<li>See below for some restrictions on the mutation event +support ... some events aren't reported (and likely won't be).</li> + +<li>More testing and conformance work is needed.</li> + +<li>We need an XML Schema validator (actually we need validation in the DOM +full stop).</li> +</ul> + +<p> +I ran a profiler a few times and remove some of the performance hotspots, +but it's not tuned. Reporting mutation events, in particular, is +rather costly -- it started at about a 40% penalty for appendNode calls, +I've got it down around 12%, but it'll be hard to shrink it much further. +The overall code size is relatively small, though you may want to be rid of +many of the unused DOM interface classes (HTML, CSS, and so on). +</p> + + +<h2><a name="features">Features of this Package</a></h2> + +<p> Starting with DOM Level 2, you can really see that DOM is constructed +as a bunch of optional modules around a core of either XML or HTML +functionality. Different implementations will support different optional +modules. This implementation provides a set of features that should be +useful if you're not depending on the HTML functionality (lots of convenience +functions that mostly don't buy much except API surface area) and user +interface support. That is, browsers will want more -- but what they +need should be cleanly layered over what's already here. </p> + +<h3> Core Feature Set: "XML" </h3> + +<p> This DOM implementation supports the "XML" feature set, which basically +gets you four things over the bare core (which you're officially not supposed +to implement except in conjunction with the "XML" or "HTML" feature). In +order of decreasing utility, those four things are: </p> <ol> + + <li> ProcessingInstruction nodes. These are probably the most + valuable thing. Handy little buggers, in part because all the APIs + you need to use them are provided, and they're designed to let you + escape XML document structure rules in controlled ways.</li> + + <li> CDATASection nodes. These are of of limited utility since CDATA + is just text that prints funny. These are of use to some sorts of + applications, though I encourage folk to not use them. </li> + + <li> DocumentType nodes, and associated Notation and Entity nodes. + These appear to be useless. Briefly, these "Type" nodes expose no + typing information. They're only really usable to expose some lexical + structure that almost every application needs to ignore. (XML editors + might like to see them, but they need true typing information much more.) + I strongly encourage people not to use these. </li> + + <li> EntityReference nodes can show up. These are actively annoying, + since they add an extra level of hierarchy, are the cause of most of + the complexity in attribute values, and their contents are immutable. + Avoid these.</li> + + </ol> + +<h3> Optional Feature Sets: "Events", and friends </h3> + +<p> Events may be one of the more interesting new features in Level 2. +This package provides the core feature set and exposes mutation events. +No gooey events though; if you want that, write a layered implementation! </p> + +<p> Three mutation events aren't currently generated:</p> <ul> + + <li> <em>DOMSubtreeModified</em> is poorly specified. Think of this + as generating one such event around the time of finalization, which + is a fully conformant implementation. This implementation is exactly + as useful as that one. </li> + + <li> <em>DOMNodeRemovedFromDocument</em> and + <em>DOMNodeInsertedIntoDocument</em> are supposed to get sent to + every node in a subtree that gets removed or inserted (respectively). + This can be <em>extremely costly</em>, and the removal and insertion + processing is already significantly slower due to event reporting. + It's much easier, and more efficient, to have a listener higher in the + tree watch removal and insertion events through the bubbling or capture + mechanisms, than it is to watch for these two events.</li> + + </ul> + +<p> In addition, certain kinds of attribute modification aren't reported. +A fix is known, but it couldn't report the previous value of the attribute. +More work could fix all of this (as well as reduce the generally high cost +of childful attributes), but that's not been done yet. </p> + +<p> Also, note that it is a <em>Bad Thing™</em> to have the listener +for a mutation event change the ancestry for the target of that event. +Or to prevent mutation events from bubbling to where they're needed. +Just don't do those, OK? </p> + +<p> As an experimental feature (named "USER-Events"), you can provide +your own "user" events. Just name them anything starting with "USER-" +and you're set. Dispatch them through, bubbling, capturing, or what +ever takes your fancy. One important thing you can't currently do is +pass any data (like an object) with those events. Maybe later there +will be a "UserEvent" interface letting you get some substantial use +out of this mechanism even if you're not "inside" of a DOM package.</p> + +<p> You can create and send HTML events. Ditto UIEvents. Since DOM +doesn't require a UI, it's the UI's job to send them; perhaps that's +part of your application. </p> + +<p><em>This package may be built without the ability to report mutation +events, gaining a significant speedup in DOM construction time. However, +if that is done then certain other features -- notably node iterators +and getElementsByTagname -- will not be available.</em> + + +<h3> Optional Feature: "Traversal" </h3> + +<p> Each DOM node has all you need to walk to everything connected +to that node. Lightweight, efficient utilities are easily layered on +top of just the core APIs. </p> + +<p> Traversal APIs are an optional part of DOM Level 2, providing +a not-so-lightweight way to walk over DOM trees, if your application +didn't already have such utilities for use with data represented via +DOM. Implementing this helped debug the (optional) event and mutation +event subsystems, so it's provided here. </p> + +<p> At this writing, the "TreeWalker" interface isn't implemented. </p> + + + +<h2><a name='avoid'>DOM Functionality to Avoid</a></h2> + +<p> For what appear to be a combination of historical and "committee +logic" reasons, DOM has a number of <em>features which I strongly advise +you to avoid using</em> in your library and application code. These +include the following types of DOM nodes; see the documentation for the +implementation class for more information: <ul> + + <li> CDATASection + (<a href='DomCDATA.html'>DomCDATA</a> class) + ... use normal Text nodes instead, so you don't have to make + every algorithm recognize multiple types of character data + + <li> DocumentType + (<a href='DomDoctype.html'>DomDocType</a> class) + ... if this held actual typing information, it might be useful + + <li> Entity + (<a href='DomEntity.html'>DomEntity</a> class) + ... neither parsed nor unparsed entities work well in DOM; it + won't even tell you which attributes identify unparsed entities + + <li> EntityReference + (<a href='DomEntityReference.html'>DomEntityReference</a> class) + ... permitted implementation variances are extreme, all children + are readonly, and these can interact poorly with namespaces + + <li> Notation + (<a href='DomNotation.html'>DomNotation</a> class) + ... only really usable with unparsed entities (which aren't well + supported; see above) or perhaps with PIs after the DTD, not with + NOTATION attributes + + </ul> + +<p> If you really need to use unparsed entities or notations, use SAX; +it offers better support for all DTD-related functionality. +It also exposes actual +document typing information (such as element content models).</p> + +<p> Also, when accessing attribute values, use methods that provide their +values as single strings, rather than those which expose value substructure +(Text and EntityReference nodes). (See the <a href='DomAttr.html'>DomAttr</a> +documentation for more information.) </p> + +<p> Note that many of these features were provided as partial support for +editor functionality (including the incomplete DTD access). Full editor +functionality requires access to potentially malformed lexical structure, +at the level of unparsed tokens and below. Access at such levels is so +complex that using it in non-editor applications sacrifices all the +benefits of XML; editor aplications need extremely specialized APIs. </p> + +<p> (This isn't a slam against DTDs, note; only against the broken support +for them in DOM. Even despite inclusion of some dubious SGML legacy features +such as notations and unparsed entities, +and the ongoing proliferation of alternative schema and validation tools, +DTDs are still the most widely adopted tool +to constrain XML document structure. +Alternative schemes generally focus on data transfer style +applications; open document architectures comparable to +DocBook 4.0 don't yet exist in the schema world. +Feel free to use DTDs; just don't expect DOM to help you.) </p> + +</body> +</html> + diff --git a/libjava/gnu/xml/pipeline/CallFilter.java b/libjava/gnu/xml/pipeline/CallFilter.java new file mode 100644 index 0000000..0d858599 --- /dev/null +++ b/libjava/gnu/xml/pipeline/CallFilter.java @@ -0,0 +1,250 @@ +/* CallFilter.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.pipeline; + +import java.io.*; +import java.net.*; + +import org.xml.sax.*; +import org.xml.sax.ext.*; +import org.xml.sax.helpers.XMLReaderFactory; + +import gnu.xml.util.Resolver; +import gnu.xml.util.XMLWriter; + + +/** + * Input is sent as an XML request to given URI, and the output of this + * filter is the parsed response to that request. + * A connection is opened to the remote URI when the startDocument call is + * issued through this filter, and the request is finished when the + * endDocument call is issued. Events should be written quickly enough to + * prevent the remote HTTP server from aborting the connection due to + * inactivity; you may want to buffer text in an earlier pipeline stage. + * If your application requires validity checking of such + * outputs, have the output pipeline include a validation stage. + * + * <p>In effect, this makes a remote procedure call to the URI, with the + * request and response document syntax as chosen by the application. + * <em>Note that all the input events must be seen, and sent to the URI, + * before the first output event can be seen. </em> Clients are delayed + * at least by waiting for the server to respond, constraining concurrency. + * Services can thus be used to synchronize concurrent activities, and + * even to prioritize service among different clients. + * + * <p> You are advised to avoid restricting yourself to an "RPC" model + * for distributed computation. With a World Wide Web, network latencies + * and failures (e.g. non-availability) + * are significant; adopting a "procedure" model, rather than a workflow + * model where bulk requests are sent and worked on asynchronously, is not + * generally an optimal system-wide architecture. When the messages may + * need authentication, such as with an OpenPGP signature, or when server + * loads don't argue in favor of immediate responses, non-RPC models can + * be advantageous. (So-called "peer to peer" computing models are one + * additional type of model, though too often that term is applied to + * systems that still have a centralized control structure.) + * + * <p> <em>Be strict in what you send, liberal in what you accept,</em> as + * the Internet tradition goes. Strictly conformant data should never cause + * problems to its receiver; make your request pipeline be very strict, and + * don't compromise on that. Make your response pipeline strict as well, + * but be ready to tolerate specific mild, temporary, and well-documented + * variations from specific communications peers. + * + * @see XmlServlet + * + * @author David Brownell + */ +final public class CallFilter implements EventConsumer +{ + private Requestor req; + private EventConsumer next; + private URL target; + private URLConnection conn; + private ErrorHandler errHandler; + + + /** + * Initializes a call filter so that its inputs are sent to the + * specified URI, and its outputs are sent to the next consumer + * provided. + * + * @exception IOException if the URI isn't accepted as a URL + */ + // constructor used by PipelineFactory + public CallFilter (String uri, EventConsumer next) + throws IOException + { + this.next = next; + req = new Requestor (); + setCallTarget (uri); + } + + /** + * Assigns the URI of the call target to be used. + * Does not affect calls currently being made. + */ + final public void setCallTarget (String uri) + throws IOException + { + target = new URL (uri); + } + + /** + * Assigns the error handler to be used to present most fatal + * errors. + */ + public void setErrorHandler (ErrorHandler handler) + { + req.setErrorHandler (handler); + } + + + /** + * Returns the call target's URI. + */ + final public String getCallTarget () + { + return target.toString (); + } + + /** Returns the content handler currently in use. */ + final public org.xml.sax.ContentHandler getContentHandler () + { + return req; + } + + /** Returns the DTD handler currently in use. */ + final public DTDHandler getDTDHandler () + { + return req; + } + + + /** + * Returns the declaration or lexical handler currently in + * use, or throws an exception for other properties. + */ + final public Object getProperty (String id) + throws SAXNotRecognizedException + { + if (EventFilter.DECL_HANDLER.equals (id)) + return req; + if (EventFilter.LEXICAL_HANDLER.equals (id)) + return req; + throw new SAXNotRecognizedException (id); + } + + + // JDK 1.1 seems to need it to be done this way, sigh + ErrorHandler getErrorHandler () { return errHandler; } + + // + // Takes input and echoes to server as POST input. + // Then sends the POST reply to the next pipeline element. + // + final class Requestor extends XMLWriter + { + Requestor () + { + super ((Writer)null); + } + + public synchronized void startDocument () throws SAXException + { + // Connect to remote object and set up to send it XML text + try { + if (conn != null) + throw new IllegalStateException ("call is being made"); + + conn = target.openConnection (); + conn.setDoOutput (true); + conn.setRequestProperty ("Content-Type", + "application/xml;charset=UTF-8"); + + setWriter (new OutputStreamWriter ( + conn.getOutputStream (), + "UTF8"), "UTF-8"); + + } catch (IOException e) { + fatal ("can't write (POST) to URI: " + target, e); + } + + // NOW base class can safely write that text! + super.startDocument (); + } + + public void endDocument () throws SAXException + { + // + // Finish writing the request (for HTTP, a POST); + // this closes the output stream. + // + super.endDocument (); + + // + // Receive the response. + // Produce events for the next stage. + // + InputSource source; + XMLReader producer; + String encoding; + + try { + + source = new InputSource (conn.getInputStream ()); + +// FIXME if status is anything but success, report it!! It'd be good to +// save the request data just in case we need to deal with a forward. + + encoding = Resolver.getEncoding (conn.getContentType ()); + if (encoding != null) + source.setEncoding (encoding); + + producer = XMLReaderFactory.createXMLReader (); + producer.setErrorHandler (getErrorHandler ()); + EventFilter.bind (producer, next); + producer.parse (source); + conn = null; + + } catch (IOException e) { + fatal ("I/O Exception reading response, " + e.getMessage (), e); + } + } + } +} diff --git a/libjava/gnu/xml/pipeline/DomConsumer.java b/libjava/gnu/xml/pipeline/DomConsumer.java new file mode 100644 index 0000000..17fdeeb --- /dev/null +++ b/libjava/gnu/xml/pipeline/DomConsumer.java @@ -0,0 +1,969 @@ +/* DomConsumer.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.pipeline; + +import java.util.Hashtable; + +import org.w3c.dom.*; +import org.xml.sax.*; +import org.xml.sax.ext.DeclHandler; +import org.xml.sax.ext.LexicalHandler; +import org.xml.sax.helpers.AttributesImpl; + +import gnu.xml.aelfred2.ContentHandler2; +import gnu.xml.util.DomParser; + + +/** + * This consumer builds a DOM Document from its input, acting either as a + * pipeline terminus or as an intermediate buffer. When a document's worth + * of events has been delivered to this consumer, that document is read with + * a {@link DomParser} and sent to the next consumer. It is also available + * as a read-once property. + * + * <p>The DOM tree is constructed as faithfully as possible. There are some + * complications since a DOM should expose behaviors that can't be implemented + * without API backdoors into that DOM, and because some SAX parsers don't + * report all the information that DOM permits to be exposed. The general + * problem areas involve information from the Document Type Declaration (DTD). + * DOM only represents a limited subset, but has some behaviors that depend + * on much deeper knowledge of a document's DTD. You shouldn't have much to + * worry about unless you change handling of "noise" nodes from its default + * setting (which ignores them all); note if you use JAXP to populate your + * DOM trees, it wants to save "noise" nodes by default. (Such nodes include + * ignorable whitespace, comments, entity references and CDATA boundaries.) + * Otherwise, your + * main worry will be if you use a SAX parser that doesn't flag ignorable + * whitespace unless it's validating (few don't). + * + * <p> The SAX2 events used as input must contain XML Names for elements + * and attributes, with original prefixes. In SAX2, + * this is optional unless the "namespace-prefixes" parser feature is set. + * Moreover, many application components won't provide completely correct + * structures anyway. <em>Before you convert a DOM to an output document, + * you should plan to postprocess it to create or repair such namespace + * information.</em> The {@link NSFilter} pipeline stage does such work. + * + * <p> <em>Note: changes late in DOM L2 process made it impractical to + * attempt to create the DocumentType node in any implementation-neutral way, + * much less to populate it (L1 didn't support even creating such nodes). + * To create and populate such a node, subclass the inner + * {@link DomConsumer.Handler} class and teach it about the backdoors into + * whatever DOM implementation you want. It's possible that some revised + * DOM API (L3?) will make this problem solvable again. </em> + * + * @see DomParser + * + * @author David Brownell + */ +public class DomConsumer implements EventConsumer +{ + private Class domImpl; + + private boolean hidingCDATA = true; + private boolean hidingComments = true; + private boolean hidingWhitespace = true; + private boolean hidingReferences = true; + + private Handler handler; + private ErrorHandler errHandler; + + private EventConsumer next; + + // FIXME: this can't be a generic pipeline stage just now, + // since its input became a Class not a String (to be turned + // into a class, using the right class loader) + + + /** + * Configures this pipeline terminus to use the specified implementation + * of DOM when constructing its result value. + * + * @param impl class implementing {@link org.w3c.dom.Document Document} + * which publicly exposes a default constructor + * + * @exception SAXException when there is a problem creating an + * empty DOM document using the specified implementation + */ + public DomConsumer (Class impl) + throws SAXException + { + domImpl = impl; + handler = new Handler (this); + } + + /** + * This is the hook through which a subclass provides a handler + * which knows how to access DOM extensions, specific to some + * implementation, to record additional data in a DOM. + * Treat this as part of construction; don't call it except + * before (or between) parses. + */ + protected void setHandler (Handler h) + { + handler = h; + } + + + private Document emptyDocument () + throws SAXException + { + try { + return (Document) domImpl.newInstance (); + } catch (IllegalAccessException e) { + throw new SAXException ("can't access constructor: " + + e.getMessage ()); + } catch (InstantiationException e) { + throw new SAXException ("can't instantiate Document: " + + e.getMessage ()); + } + } + + + /** + * Configures this consumer as a buffer/filter, using the specified + * DOM implementation when constructing its result value. + * + * <p> This event consumer acts as a buffer and filter, in that it + * builds a DOM tree and then writes it out when <em>endDocument</em> + * is invoked. Because of the limitations of DOM, much information + * will as a rule not be seen in that replay. To get a full fidelity + * copy of the input event stream, use a {@link TeeConsumer}. + * + * @param impl class implementing {@link org.w3c.dom.Document Document} + * which publicly exposes a default constructor + * @param next receives a "replayed" sequence of parse events when + * the <em>endDocument</em> method is invoked. + * + * @exception SAXException when there is a problem creating an + * empty DOM document using the specified DOM implementation + */ + public DomConsumer (Class impl, EventConsumer n) + throws SAXException + { + this (impl); + next = n; + } + + + /** + * Returns the document constructed from the preceding + * sequence of events. This method should not be + * used again until another sequence of events has been + * given to this EventConsumer. + */ + final public Document getDocument () + { + return handler.clearDocument (); + } + + public void setErrorHandler (ErrorHandler handler) + { + errHandler = handler; + } + + + /** + * Returns true if the consumer is hiding entity references nodes + * (the default), and false if EntityReference nodes should + * instead be created. Such EntityReference nodes will normally be + * empty, unless an implementation arranges to populate them and then + * turn them back into readonly objects. + * + * @see #setHidingReferences + */ + final public boolean isHidingReferences () + { return hidingReferences; } + + /** + * Controls whether the consumer will hide entity expansions, + * or will instead mark them with entity reference nodes. + * + * @see #isHidingReferences + * @param flag False if entity reference nodes will appear + */ + final public void setHidingReferences (boolean flag) + { hidingReferences = flag; } + + + /** + * Returns true if the consumer is hiding comments (the default), + * and false if they should be placed into the output document. + * + * @see #setHidingComments + */ + public final boolean isHidingComments () + { return hidingComments; } + + /** + * Controls whether the consumer is hiding comments. + * + * @see #isHidingComments + */ + public final void setHidingComments (boolean flag) + { hidingComments = flag; } + + + /** + * Returns true if the consumer is hiding ignorable whitespace + * (the default), and false if such whitespace should be placed + * into the output document as children of element nodes. + * + * @see #setHidingWhitespace + */ + public final boolean isHidingWhitespace () + { return hidingWhitespace; } + + /** + * Controls whether the consumer hides ignorable whitespace + * + * @see #isHidingComments + */ + public final void setHidingWhitespace (boolean flag) + { hidingWhitespace = flag; } + + + /** + * Returns true if the consumer is saving CDATA boundaries, or + * false (the default) otherwise. + * + * @see #setHidingCDATA + */ + final public boolean isHidingCDATA () + { return hidingCDATA; } + + /** + * Controls whether the consumer will save CDATA boundaries. + * + * @see #isHidingCDATA + * @param flag True to treat CDATA text differently from other + * text nodes + */ + final public void setHidingCDATA (boolean flag) + { hidingCDATA = flag; } + + + + /** Returns the document handler being used. */ + final public ContentHandler getContentHandler () + { return handler; } + + /** Returns the DTD handler being used. */ + final public DTDHandler getDTDHandler () + { return handler; } + + /** + * Returns the lexical handler being used. + * (DOM construction can't really use declaration handlers.) + */ + final public Object getProperty (String id) + throws SAXNotRecognizedException + { + if ("http://xml.org/sax/properties/lexical-handler".equals (id)) + return handler; + if ("http://xml.org/sax/properties/declaration-handler".equals (id)) + return handler; + throw new SAXNotRecognizedException (id); + } + + EventConsumer getNext () { return next; } + + ErrorHandler getErrorHandler () { return errHandler; } + + /** + * Class used to intercept various parsing events and use them to + * populate a DOM document. Subclasses would typically know and use + * backdoors into specific DOM implementations, used to implement + * DTD-related functionality. + * + * <p> Note that if this ever throws a DOMException (runtime exception) + * that will indicate a bug in the DOM (e.g. doesn't support something + * per specification) or the parser (e.g. emitted an illegal name, or + * accepted illegal input data). </p> + */ + public static class Handler + implements ContentHandler2, LexicalHandler, + DTDHandler, DeclHandler + { + protected DomConsumer consumer; + + private DOMImplementation impl; + private Document document; + private boolean isL2; + + private Locator locator; + private Node top; + private boolean inCDATA; + private boolean mergeCDATA; + private boolean inDTD; + private String currentEntity; + + private boolean recreatedAttrs; + private AttributesImpl attributes = new AttributesImpl (); + + /** + * Subclasses may use SAX2 events to provide additional + * behaviors in the resulting DOM. + */ + protected Handler (DomConsumer consumer) + throws SAXException + { + this.consumer = consumer; + document = consumer.emptyDocument (); + impl = document.getImplementation (); + isL2 = impl.hasFeature ("XML", "2.0"); + } + + private void fatal (String message, Exception x) + throws SAXException + { + SAXParseException e; + ErrorHandler errHandler = consumer.getErrorHandler ();; + + if (locator == null) + e = new SAXParseException (message, null, null, -1, -1, x); + else + e = new SAXParseException (message, locator, x); + if (errHandler != null) + errHandler.fatalError (e); + throw e; + } + + /** + * Returns and forgets the document produced. If the handler is + * reused, a new document may be created. + */ + Document clearDocument () + { + Document retval = document; + document = null; + locator = null; + return retval; + } + + /** + * Returns the document under construction. + */ + protected Document getDocument () + { return document; } + + /** + * Returns the current node being populated. This is usually + * an Element or Document, but it might be an EntityReference + * node if some implementation-specific code knows how to put + * those into the result tree and later mark them as readonly. + */ + protected Node getTop () + { return top; } + + + // SAX1 + public void setDocumentLocator (Locator locator) + { + this.locator = locator; + } + + // SAX1 + public void startDocument () + throws SAXException + { + if (document == null) + try { + if (isL2) { + // couple to original implementation + document = impl.createDocument (null, "foo", null); + document.removeChild (document.getFirstChild ()); + } else { + document = consumer.emptyDocument (); + } + } catch (Exception e) { + fatal ("DOM create document", e); + } + top = document; + } + + // ContentHandler2 + public void xmlDecl(String version, + String encoding, + boolean standalone, + String inputEncoding) + throws SAXException + { + if (document != null) + { + document.setXmlVersion(version); + document.setXmlStandalone(standalone); + } + } + + // SAX1 + public void endDocument () + throws SAXException + { + try { + if (consumer.getNext () != null && document != null) { + DomParser parser = new DomParser (document); + + EventFilter.bind (parser, consumer.getNext ()); + parser.parse ("ignored"); + } + } finally { + top = null; + } + } + + // SAX1 + public void processingInstruction (String target, String data) + throws SAXException + { + // we can't create populated entity ref nodes using + // only public DOM APIs (they've got to be readonly) + if (currentEntity != null) + return; + + ProcessingInstruction pi; + + if (isL2 + // && consumer.isUsingNamespaces () + && target.indexOf (':') != -1) + namespaceError ( + "PI target name is namespace nonconformant: " + + target); + if (inDTD) + return; + pi = document.createProcessingInstruction (target, data); + top.appendChild (pi); + } + + /** + * Subclasses may overrride this method to provide a more efficient + * way to construct text nodes. + * Typically, copying the text into a single character array will + * be more efficient than doing that as well as allocating other + * needed for a String, including an internal StringBuffer. + * Those additional memory and CPU costs can be incurred later, + * if ever needed. + * Unfortunately the standard DOM factory APIs encourage those costs + * to be incurred early. + */ + protected Text createText ( + boolean isCDATA, + char ch [], + int start, + int length + ) { + String value = new String (ch, start, length); + + if (isCDATA) + return document.createCDATASection (value); + else + return document.createTextNode (value); + } + + // SAX1 + public void characters (char ch [], int start, int length) + throws SAXException + { + // we can't create populated entity ref nodes using + // only public DOM APIs (they've got to be readonly + // at creation time) + if (currentEntity != null) + return; + + Node lastChild = top.getLastChild (); + + // merge consecutive text or CDATA nodes if appropriate. + if (lastChild instanceof Text) { + if (consumer.isHidingCDATA () + // consecutive Text content ... always merge + || (!inCDATA + && !(lastChild instanceof CDATASection)) + // consecutive CDATASection content ... don't + // merge between sections, only within them + || (inCDATA && mergeCDATA + && lastChild instanceof CDATASection) + ) { + CharacterData last = (CharacterData) lastChild; + String value = new String (ch, start, length); + + last.appendData (value); + return; + } + } + if (inCDATA && !consumer.isHidingCDATA ()) { + top.appendChild (createText (true, ch, start, length)); + mergeCDATA = true; + } else + top.appendChild (createText (false, ch, start, length)); + } + + // SAX2 + public void skippedEntity (String name) + throws SAXException + { + // this callback is useless except to report errors, since + // we can't know if the ref was in content, within an + // attribute, within a declaration ... only one of those + // cases supports more intelligent action than a panic. + fatal ("skipped entity: " + name, null); + } + + // SAX2 + public void startPrefixMapping (String prefix, String uri) + throws SAXException + { + // reconstruct "xmlns" attributes deleted by all + // SAX2 parsers without "namespace-prefixes" = true + if ("".equals (prefix)) + attributes.addAttribute ("", "", "xmlns", + "CDATA", uri); + else + attributes.addAttribute ("", "", "xmlns:" + prefix, + "CDATA", uri); + recreatedAttrs = true; + } + + // SAX2 + public void endPrefixMapping (String prefix) + throws SAXException + { } + + // SAX2 + public void startElement ( + String uri, + String localName, + String qName, + Attributes atts + ) throws SAXException + { + // we can't create populated entity ref nodes using + // only public DOM APIs (they've got to be readonly) + if (currentEntity != null) + return; + + // parser discarded basic information; DOM tree isn't writable + // without massaging to assign prefixes to all nodes. + // the "NSFilter" class does that massaging. + if (qName.length () == 0) + qName = localName; + + + Element element; + int length = atts.getLength (); + + if (!isL2) { + element = document.createElement (qName); + + // first the explicit attributes ... + length = atts.getLength (); + for (int i = 0; i < length; i++) + element.setAttribute (atts.getQName (i), + atts.getValue (i)); + // ... then any recreated ones (DOM deletes duplicates) + if (recreatedAttrs) { + recreatedAttrs = false; + length = attributes.getLength (); + for (int i = 0; i < length; i++) + element.setAttribute (attributes.getQName (i), + attributes.getValue (i)); + attributes.clear (); + } + + top.appendChild (element); + top = element; + return; + } + + // For an L2 DOM when namespace use is enabled, use + // createElementNS/createAttributeNS except when + // (a) it's an element in the default namespace, or + // (b) it's an attribute with no prefix + String namespace; + + if (localName.length () != 0) + namespace = (uri.length () == 0) ? null : uri; + else + namespace = getNamespace (getPrefix (qName), atts); + + if (namespace == null) + element = document.createElement (qName); + else + element = document.createElementNS (namespace, qName); + + populateAttributes (element, atts); + if (recreatedAttrs) { + recreatedAttrs = false; + // ... DOM deletes any duplicates + populateAttributes (element, attributes); + attributes.clear (); + } + + top.appendChild (element); + top = element; + } + + final static String xmlnsURI = "http://www.w3.org/2000/xmlns/"; + + private void populateAttributes (Element element, Attributes attrs) + throws SAXParseException + { + int length = attrs.getLength (); + + for (int i = 0; i < length; i++) { + String type = attrs.getType (i); + String value = attrs.getValue (i); + String name = attrs.getQName (i); + String local = attrs.getLocalName (i); + String uri = attrs.getURI (i); + + // parser discarded basic information, DOM tree isn't writable + if (name.length () == 0) + name = local; + + // all attribute types other than these three may not + // contain scoped names... enumerated attributes get + // reported as NMTOKEN, except for NOTATION values + if (!("CDATA".equals (type) + || "NMTOKEN".equals (type) + || "NMTOKENS".equals (type))) { + if (value.indexOf (':') != -1) { + namespaceError ( + "namespace nonconformant attribute value: " + + "<" + element.getNodeName () + + " " + name + "='" + value + "' ...>"); + } + } + + // xmlns="" is legal (undoes default NS) + // xmlns:foo="" is illegal + String prefix = getPrefix (name); + String namespace; + + if ("xmlns".equals (prefix)) { + if ("".equals (value)) + namespaceError ("illegal null namespace decl, " + name); + namespace = xmlnsURI; + } else if ("xmlns".equals (name)) + namespace = xmlnsURI; + + else if (prefix == null) + namespace = null; + else if (!"".equals(uri) && uri.length () != 0) + namespace = uri; + else + namespace = getNamespace (prefix, attrs); + + if (namespace == null) + element.setAttribute (name, value); + else + element.setAttributeNS (namespace, name, value); + } + } + + private String getPrefix (String name) + { + int temp; + + if ((temp = name.indexOf (':')) > 0) + return name.substring (0, temp); + return null; + } + + // used with SAX1-level parser output + private String getNamespace (String prefix, Attributes attrs) + throws SAXParseException + { + String namespace; + String decl; + + // defaulting + if (prefix == null) { + decl = "xmlns"; + namespace = attrs.getValue (decl); + if ("".equals (namespace)) + return null; + else if (namespace != null) + return namespace; + + // "xmlns" is like a keyword + // ... according to the Namespace REC, but DOM L2 CR2+ + // and Infoset violate that by assigning a namespace. + // that conflict is resolved elsewhere. + } else if ("xmlns".equals (prefix)) + return null; + + // "xml" prefix is fixed + else if ("xml".equals (prefix)) + return "http://www.w3.org/XML/1998/namespace"; + + // otherwise, expect a declaration + else { + decl = "xmlns:" + prefix; + namespace = attrs.getValue (decl); + } + + // if we found a local declaration, great + if (namespace != null) + return namespace; + + + // ELSE ... search up the tree we've been building + for (Node n = top; + n != null && n.getNodeType () != Node.DOCUMENT_NODE; + n = (Node) n.getParentNode ()) { + if (n.getNodeType () == Node.ENTITY_REFERENCE_NODE) + continue; + Element e = (Element) n; + Attr attr = e.getAttributeNode (decl); + if (attr != null) + return attr.getNodeValue (); + } + // see above re "xmlns" as keyword + if ("xmlns".equals (decl)) + return null; + + namespaceError ("Undeclared namespace prefix: " + prefix); + return null; + } + + // SAX2 + public void endElement (String uri, String localName, String qName) + throws SAXException + { + // we can't create populated entity ref nodes using + // only public DOM APIs (they've got to be readonly) + if (currentEntity != null) + return; + + top = top.getParentNode (); + } + + // SAX1 (mandatory reporting if validating) + public void ignorableWhitespace (char ch [], int start, int length) + throws SAXException + { + if (consumer.isHidingWhitespace ()) + return; + characters (ch, start, length); + } + + // SAX2 lexical event + public void startCDATA () + throws SAXException + { + inCDATA = true; + // true except for the first fragment of a cdata section + mergeCDATA = false; + } + + // SAX2 lexical event + public void endCDATA () + throws SAXException + { + inCDATA = false; + } + + // SAX2 lexical event + // + // this SAX2 callback merges two unrelated things: + // - Declaration of the root element type ... belongs with + // the other DTD declaration methods, NOT HERE. + // - IDs for the optional external subset ... belongs here + // with other lexical information. + // + // ...and it doesn't include the internal DTD subset, desired + // both to support DOM L2 and to enable "pass through" processing + // + public void startDTD (String name, String publicId, String SystemId) + throws SAXException + { + // need to filter out comments and PIs within the DTD + inDTD = true; + } + + // SAX2 lexical event + public void endDTD () + throws SAXException + { + inDTD = false; + } + + // SAX2 lexical event + public void comment (char ch [], int start, int length) + throws SAXException + { + Node comment; + + // we can't create populated entity ref nodes using + // only public DOM APIs (they've got to be readonly) + if (consumer.isHidingComments () + || inDTD + || currentEntity != null) + return; + comment = document.createComment (new String (ch, start, length)); + top.appendChild (comment); + } + + /** + * May be overridden by subclasses to return true, indicating + * that entity reference nodes can be populated and then made + * read-only. + */ + public boolean canPopulateEntityRefs () + { return false; } + + // SAX2 lexical event + public void startEntity (String name) + throws SAXException + { + // are we ignoring what would be contents of an + // entity ref, since we can't populate it? + if (currentEntity != null) + return; + + // Are we hiding all entity boundaries? + if (consumer.isHidingReferences ()) + return; + + // SAX2 shows parameter entities; DOM hides them + if (name.charAt (0) == '%' || "[dtd]".equals (name)) + return; + + // Since we can't create a populated entity ref node in any + // standard way, we create an unpopulated one. + EntityReference ref = document.createEntityReference (name); + top.appendChild (ref); + top = ref; + + // ... allowing subclasses to populate them + if (!canPopulateEntityRefs ()) + currentEntity = name; + } + + // SAX2 lexical event + public void endEntity (String name) + throws SAXException + { + if (name.charAt (0) == '%' || "[dtd]".equals (name)) + return; + if (name.equals (currentEntity)) + currentEntity = null; + if (!consumer.isHidingReferences ()) + top = top.getParentNode (); + } + + + // SAX1 DTD event + public void notationDecl ( + String name, + String publicId, String SystemId + ) throws SAXException + { + /* IGNORE -- no public DOM API lets us store these + * into the doctype node + */ + } + + // SAX1 DTD event + public void unparsedEntityDecl ( + String name, + String publicId, String SystemId, + String notationName + ) throws SAXException + { + /* IGNORE -- no public DOM API lets us store these + * into the doctype node + */ + } + + // SAX2 declaration event + public void elementDecl (String name, String model) + throws SAXException + { + /* IGNORE -- no content model support in DOM L2 */ + } + + // SAX2 declaration event + public void attributeDecl ( + String eName, + String aName, + String type, + String mode, + String value + ) throws SAXException + { + /* IGNORE -- no attribute model support in DOM L2 */ + } + + // SAX2 declaration event + public void internalEntityDecl (String name, String value) + throws SAXException + { + /* IGNORE -- no public DOM API lets us store these + * into the doctype node + */ + } + + // SAX2 declaration event + public void externalEntityDecl ( + String name, + String publicId, + String SystemId + ) throws SAXException + { + /* IGNORE -- no public DOM API lets us store these + * into the doctype node + */ + } + + // + // These really should offer the option of nonfatal handling, + // like other validity errors, though that would cause major + // chaos in the DOM data structures. DOM is already spec'd + // to treat many of these as fatal, so this is consistent. + // + private void namespaceError (String description) + throws SAXParseException + { + SAXParseException err; + + err = new SAXParseException (description, locator); + throw err; + } + } +} diff --git a/libjava/gnu/xml/pipeline/EventConsumer.java b/libjava/gnu/xml/pipeline/EventConsumer.java new file mode 100644 index 0000000..5f97373 --- /dev/null +++ b/libjava/gnu/xml/pipeline/EventConsumer.java @@ -0,0 +1,95 @@ +/* EventConsumer.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.pipeline; + +import org.xml.sax.*; + + +/** + * Collects the event consumption apparatus of a SAX pipeline stage. + * Consumers which permit some handlers or other characteristics to be + * configured will provide methods to support that configuration. + * + * <p> Two important categories of consumers include <em>filters</em>, which + * process events and pass them on to other consumers, and <em>terminus</em> + * (or <em>terminal</em>) stages, which don't pass events on. Filters are not + * necessarily derived from the {@link EventFilter} class, although that + * class can substantially simplify their construction by automating the + * most common activities. + * + * <p> Event consumers which follow certain conventions for the signatures + * of their constructors can be automatically assembled into pipelines + * by the {@link PipelineFactory} class. + * + * @author David Brownell + */ +public interface EventConsumer +{ + /** Most stages process these core SAX callbacks. */ + public ContentHandler getContentHandler (); + + /** Few stages will use unparsed entities. */ + public DTDHandler getDTDHandler (); + + /** + * This method works like the SAX2 XMLReader method of the same name, + * and is used to retrieve the optional lexical and declaration handlers + * in a pipeline. + * + * @param id This is a URI identifying the type of property desired. + * @return The value of that property, if it is defined. + * + * @exception SAXNotRecognizedException Thrown if the particular + * pipeline stage does not understand the specified identifier. + */ + public Object getProperty (String id) + throws SAXNotRecognizedException; + + /** + * This method provides a filter stage with a handler that abstracts + * presentation of warnings and both recoverable and fatal errors. + * Most pipeline stages should share a single policy and mechanism + * for such reports, since application components require consistency + * in such activities. Accordingly, typical responses to this method + * invocation involve saving the handler for use; filters will pass + * it on to any other consumers they use. + * + * @param handler encapsulates error handling policy for this stage + */ + public void setErrorHandler (ErrorHandler handler); +} diff --git a/libjava/gnu/xml/pipeline/EventFilter.java b/libjava/gnu/xml/pipeline/EventFilter.java new file mode 100644 index 0000000..8587808 --- /dev/null +++ b/libjava/gnu/xml/pipeline/EventFilter.java @@ -0,0 +1,809 @@ +/* EventFilter.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.pipeline; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.xml.sax.*; +import org.xml.sax.ext.*; +import org.xml.sax.helpers.XMLFilterImpl; + +import gnu.xml.aelfred2.ContentHandler2; + +/** + * A customizable event consumer, used to assemble various kinds of filters + * using SAX handlers and an optional second consumer. It can be constructed + * in two ways: <ul> + * + * <li> To serve as a passthrough, sending all events to a second consumer. + * The second consumer may be identified through {@link #getNext}. + * + * <li> To serve as a dead end, with all handlers null; + * {@link #getNext} returns null. + * + * </ul> + * + * <p> Additionally, SAX handlers may be assigned, which completely replace + * the "upstream" view (through {@link EventConsumer}) of handlers, initially + * null or the "next" consumer provided to the constructor. To make + * it easier to build specialized filter classes, this class implements + * all the standard SAX consumer handlers, and those implementations + * delegate "downstream" to the consumer accessed by {@link #getNext}. + * + * <p> The simplest way to create a custom a filter class is to create a + * subclass which overrides one or more handler interface methods. The + * constructor for that subclass then registers itself as a handler for + * those interfaces using a call such as <em>setContentHandler(this)</em>, + * so the "upstream" view of event delivery is modified from the state + * established in the base class constructor. That way, + * the overridden methods intercept those event callbacks + * as they go "downstream", and + * all other event callbacks will pass events to any next consumer. + * Overridden methods may invoke superclass methods (perhaps after modifying + * parameters) if they wish to delegate such calls. Such subclasses + * should use {@link #getErrorHandler} to report errors using the + * common error reporting mechanism. + * + * <p> Another important technique is to construct a filter consisting + * of only a few specific types of handler. For example, one could easily + * prune out lexical events or various declarations by providing handlers + * which don't pass those events downstream, or by providing null handlers. + * + * <hr /> + * + * <p> This may be viewed as the consumer oriented analogue of the SAX2 + * {@link org.xml.sax.helpers.XMLFilterImpl XMLFilterImpl} class. + * Key differences include: <ul> + * + * <li> This fully separates consumer and producer roles: it + * does not implement the producer side <em>XMLReader</em> or + * <em>EntityResolver</em> interfaces, so it can only be used + * in "push" mode (it has no <em>parse()</em> methods). + * + * <li> "Extension" handlers are fully supported, enabling a + * richer set of application requirements. + * And it implements {@link EventConsumer}, which groups related + * consumer methods together, rather than leaving them separated. + * + * <li> The chaining which is visible is "downstream" to the next + * consumer, not "upstream" to the preceding producer. + * It supports "fan-in", where + * a consumer can be fed by several producers. (For "fan-out", + * see the {@link TeeConsumer} class.) + * + * <li> Event chaining is set up differently. It is intended to + * work "upstream" from terminus towards producer, during filter + * construction, as described above. + * This is part of an early binding model: + * events don't need to pass through stages which ignore them. + * + * <li> ErrorHandler support is separated, on the grounds that + * pipeline stages need to share the same error handling policy. + * For the same reason, error handler setup goes "downstream": + * when error handlers get set, they are passed to subsequent + * consumers. + * + * </ul> + * + * <p> The {@link #chainTo chainTo()} convenience routine supports chaining to + * an XMLFilterImpl, in its role as a limited functionality event + * consumer. Its event producer role ({@link XMLFilter}) is ignored. + * + * <hr /> + * + * <p> The {@link #bind bind()} routine may be used associate event pipelines + * with any kind of {@link XMLReader} that will produce the events. + * Such pipelines don't necessarily need to have any members which are + * implemented using this class. That routine has some intelligence + * which supports automatic changes to parser feature flags, letting + * event piplines become largely independent of the particular feature + * sets of parsers. + * + * @author David Brownell + */ +public class EventFilter + implements EventConsumer, ContentHandler2, DTDHandler, + LexicalHandler, DeclHandler +{ + // SAX handlers + private ContentHandler docHandler, docNext; + private DTDHandler dtdHandler, dtdNext; + private LexicalHandler lexHandler, lexNext; + private DeclHandler declHandler, declNext; + // and ideally, one more for the stuff SAX2 doesn't show + + private Locator locator; + private EventConsumer next; + private ErrorHandler errHandler; + + + /** SAX2 URI prefix for standard feature flags. */ + public static final String FEATURE_URI + = "http://xml.org/sax/features/"; + /** SAX2 URI prefix for standard properties (mostly for handlers). */ + public static final String PROPERTY_URI + = "http://xml.org/sax/properties/"; + + /** SAX2 property identifier for {@link DeclHandler} events */ + public static final String DECL_HANDLER + = PROPERTY_URI + "declaration-handler"; + /** SAX2 property identifier for {@link LexicalHandler} events */ + public static final String LEXICAL_HANDLER + = PROPERTY_URI + "lexical-handler"; + + // + // These class objects will be null if the relevant class isn't linked. + // Small configurations (pJava and some kinds of embedded systems) need + // to facilitate smaller executables. So "instanceof" is undesirable + // when bind() sees if it can remove some stages. + // + // SECURITY NOTE: assuming all these classes are part of the same sealed + // package, there's no problem saving these in the instance of this class + // that's associated with "this" class loader. But that wouldn't be true + // for classes in another package. + // + private static boolean loaded; + private static Class nsClass; + private static Class validClass; + private static Class wfClass; + private static Class xincClass; + + static ClassLoader getClassLoader () + { + Method m = null; + + try { + m = Thread.class.getMethod("getContextClassLoader", null); + } catch (NoSuchMethodException e) { + // Assume that we are running JDK 1.1, use the current ClassLoader + return EventFilter.class.getClassLoader(); + } + + try { + return (ClassLoader) m.invoke(Thread.currentThread(), null); + } catch (IllegalAccessException e) { + // assert(false) + throw new UnknownError(e.getMessage()); + } catch (InvocationTargetException e) { + // assert(e.getTargetException() instanceof SecurityException) + throw new UnknownError(e.getMessage()); + } + } + + static Class loadClass (ClassLoader classLoader, String className) + { + try { + if (classLoader == null) + return Class.forName(className); + else + return classLoader.loadClass(className); + } catch (Exception e) { + return null; + } + } + + static private void loadClasses () + { + ClassLoader loader = getClassLoader (); + + nsClass = loadClass (loader, "gnu.xml.pipeline.NSFilter"); + validClass = loadClass (loader, "gnu.xml.pipeline.ValidationConsumer"); + wfClass = loadClass (loader, "gnu.xml.pipeline.WellFormednessFilter"); + xincClass = loadClass (loader, "gnu.xml.pipeline.XIncludeFilter"); + loaded = true; + } + + + /** + * Binds the standard SAX2 handlers from the specified consumer + * pipeline to the specified producer. These handlers include the core + * {@link ContentHandler} and {@link DTDHandler}, plus the extension + * {@link DeclHandler} and {@link LexicalHandler}. Any additional + * application-specific handlers need to be bound separately. + * The {@link ErrorHandler} is handled differently: the producer's + * error handler is passed through to the consumer pipeline. + * The producer is told to include namespace prefix information if it + * can, since many pipeline stages need that Infoset information to + * work well. + * + * <p> At the head of the pipeline, certain standard event filters are + * recognized and handled specially. This facilitates construction + * of processing pipelines that work regardless of the capabilities + * of the XMLReader implementation in use; for example, it permits + * validating output of a {@link gnu.xml.util.DomParser}. <ul> + * + * <li> {@link NSFilter} will be removed if the producer can be + * told not to discard namespace data, using the "namespace-prefixes" + * feature flag. + * + * <li> {@link ValidationConsumer} will be removed if the producer + * can be told to validate, using the "validation" feature flag. + * + * <li> {@link WellFormednessFilter} is always removed, on the + * grounds that no XMLReader is permitted to producee malformed + * event streams and this would just be processing overhead. + * + * <li> {@link XIncludeFilter} stops the special handling, except + * that it's told about the "namespace-prefixes" feature of the + * event producer so that the event stream is internally consistent. + * + * <li> The first consumer which is not one of those classes stops + * such special handling. This means that if you want to force + * one of those filters to be used, you could just precede it with + * an instance of {@link EventFilter} configured as a pass-through. + * You might need to do that if you are using an {@link NSFilter} + * subclass to fix names found in attributes or character data. + * + * </ul> + * + * <p> Other than that, this method works with any kind of event consumer, + * not just event filters. Note that in all cases, the standard handlers + * are assigned; any previous handler assignments for the handler will + * be overridden. + * + * @param producer will deliver events to the specified consumer + * @param consumer pipeline supplying event handlers to be associated + * with the producer (may not be null) + */ + public static void bind (XMLReader producer, EventConsumer consumer) + { + Class klass = null; + boolean prefixes; + + if (!loaded) + loadClasses (); + + // DOM building, printing, layered validation, and other + // things don't work well when prefix info is discarded. + // Include it by default, whenever possible. + try { + producer.setFeature (FEATURE_URI + "namespace-prefixes", + true); + prefixes = true; + } catch (SAXException e) { + prefixes = false; + } + + // NOTE: This loop doesn't use "instanceof", since that + // would prevent compiling/linking without those classes + // being present. + while (consumer != null) { + klass = consumer.getClass (); + + // we might have already changed this problematic SAX2 default. + if (nsClass != null && nsClass.isAssignableFrom (klass)) { + if (!prefixes) + break; + consumer = ((EventFilter)consumer).getNext (); + + // the parser _might_ do DTD validation by default ... + // if not, maybe we can change this setting. + } else if (validClass != null + && validClass.isAssignableFrom (klass)) { + try { + producer.setFeature (FEATURE_URI + "validation", + true); + consumer = ((ValidationConsumer)consumer).getNext (); + } catch (SAXException e) { + break; + } + + // parsers are required not to have such bugs + } else if (wfClass != null && wfClass.isAssignableFrom (klass)) { + consumer = ((WellFormednessFilter)consumer).getNext (); + + // stop on the first pipeline stage we can't remove + } else + break; + + if (consumer == null) + klass = null; + } + + // the actual setting here doesn't matter as much + // as that producer and consumer agree + if (xincClass != null && klass != null + && xincClass.isAssignableFrom (klass)) + ((XIncludeFilter)consumer).setSavingPrefixes (prefixes); + + // Some SAX parsers can't handle null handlers -- bleech + DefaultHandler2 h = new DefaultHandler2 (); + + if (consumer != null && consumer.getContentHandler () != null) + producer.setContentHandler (consumer.getContentHandler ()); + else + producer.setContentHandler (h); + if (consumer != null && consumer.getDTDHandler () != null) + producer.setDTDHandler (consumer.getDTDHandler ()); + else + producer.setDTDHandler (h); + + try { + Object dh; + + if (consumer != null) + dh = consumer.getProperty (DECL_HANDLER); + else + dh = null; + if (dh == null) + dh = h; + producer.setProperty (DECL_HANDLER, dh); + } catch (Exception e) { /* ignore */ } + try { + Object lh; + + if (consumer != null) + lh = consumer.getProperty (LEXICAL_HANDLER); + else + lh = null; + if (lh == null) + lh = h; + producer.setProperty (LEXICAL_HANDLER, lh); + } catch (Exception e) { /* ignore */ } + + // this binding goes the other way around + if (producer.getErrorHandler () == null) + producer.setErrorHandler (h); + if (consumer != null) + consumer.setErrorHandler (producer.getErrorHandler ()); + } + + /** + * Initializes all handlers to null. + */ + // constructor used by PipelineFactory + public EventFilter () { } + + + /** + * Handlers that are not otherwise set will default to those from + * the specified consumer, making it easy to pass events through. + * If the consumer is null, all handlers are initialzed to null. + */ + // constructor used by PipelineFactory + public EventFilter (EventConsumer consumer) + { + if (consumer == null) + return; + + next = consumer; + + // We delegate through the "xxNext" handlers, and + // report the "xxHandler" ones on our input side. + + // Normally a subclass would both override handler + // methods and register itself as the "xxHandler". + + docHandler = docNext = consumer.getContentHandler (); + dtdHandler = dtdNext = consumer.getDTDHandler (); + try { + declHandler = declNext = (DeclHandler) + consumer.getProperty (DECL_HANDLER); + } catch (SAXException e) { /* leave value null */ } + try { + lexHandler = lexNext = (LexicalHandler) + consumer.getProperty (LEXICAL_HANDLER); + } catch (SAXException e) { /* leave value null */ } + } + + /** + * Treats the XMLFilterImpl as a limited functionality event consumer, + * by arranging to deliver events to it; this lets such classes be + * "wrapped" as pipeline stages. + * + * <p> <em>Upstream Event Setup:</em> + * If no handlers have been assigned to this EventFilter, then the + * handlers from specified XMLFilterImpl are returned from this + * {@link EventConsumer}: the XMLFilterImpl is just "wrapped". + * Otherwise the specified handlers will be returned. + * + * <p> <em>Downstream Event Setup:</em> + * Subclasses may chain event delivery to the specified XMLFilterImpl + * by invoking the appropiate superclass methods, + * as if their constructor passed a "next" EventConsumer to the + * constructor for this class. + * If this EventFilter has an ErrorHandler, it is assigned as + * the error handler for the XMLFilterImpl, just as would be + * done for a next stage implementing {@link EventConsumer}. + * + * @param next the next downstream component of the pipeline. + * @exception IllegalStateException if the "next" consumer has + * already been set through the constructor. + */ + public void chainTo (XMLFilterImpl next) + { + if (this.next != null) + throw new IllegalStateException (); + + docNext = next.getContentHandler (); + if (docHandler == null) + docHandler = docNext; + dtdNext = next.getDTDHandler (); + if (dtdHandler == null) + dtdHandler = dtdNext; + + try { + declNext = (DeclHandler) next.getProperty (DECL_HANDLER); + if (declHandler == null) + declHandler = declNext; + } catch (SAXException e) { /* leave value null */ } + try { + lexNext = (LexicalHandler) next.getProperty (LEXICAL_HANDLER); + if (lexHandler == null) + lexHandler = lexNext; + } catch (SAXException e) { /* leave value null */ } + + if (errHandler != null) + next.setErrorHandler (errHandler); + } + + /** + * Records the error handler that should be used by this stage, and + * passes it "downstream" to any subsequent stage. + */ + final public void setErrorHandler (ErrorHandler handler) + { + errHandler = handler; + if (next != null) + next.setErrorHandler (handler); + } + + /** + * Returns the error handler assigned this filter stage, or null + * if no such assigment has been made. + */ + final public ErrorHandler getErrorHandler () + { + return errHandler; + } + + + /** + * Returns the next event consumer in sequence; or null if there + * is no such handler. + */ + final public EventConsumer getNext () + { return next; } + + + /** + * Assigns the content handler to use; a null handler indicates + * that these events will not be forwarded. + * This overrides the previous settting for this handler, which was + * probably pointed to the next consumer by the base class constructor. + */ + final public void setContentHandler (ContentHandler h) + { + docHandler = h; + } + + /** Returns the content handler being used. */ + final public ContentHandler getContentHandler () + { + return docHandler; + } + + /** + * Assigns the DTD handler to use; a null handler indicates + * that these events will not be forwarded. + * This overrides the previous settting for this handler, which was + * probably pointed to the next consumer by the base class constructor. + */ + final public void setDTDHandler (DTDHandler h) + { dtdHandler = h; } + + /** Returns the dtd handler being used. */ + final public DTDHandler getDTDHandler () + { + return dtdHandler; + } + + /** + * Stores the property, normally a handler; a null handler indicates + * that these events will not be forwarded. + * This overrides the previous handler settting, which was probably + * pointed to the next consumer by the base class constructor. + */ + final public void setProperty (String id, Object o) + throws SAXNotRecognizedException, SAXNotSupportedException + { + try { + Object value = getProperty (id); + + if (value == o) + return; + if (DECL_HANDLER.equals (id)) { + declHandler = (DeclHandler) o; + return; + } + if (LEXICAL_HANDLER.equals (id)) { + lexHandler = (LexicalHandler) o; + return; + } + throw new SAXNotSupportedException (id); + + } catch (ClassCastException e) { + throw new SAXNotSupportedException (id); + } + } + + /** Retrieves a property of unknown intent (usually a handler) */ + final public Object getProperty (String id) + throws SAXNotRecognizedException + { + if (DECL_HANDLER.equals (id)) + return declHandler; + if (LEXICAL_HANDLER.equals (id)) + return lexHandler; + + throw new SAXNotRecognizedException (id); + } + + /** + * Returns any locator provided to the next consumer, if this class + * (or a subclass) is handling {@link ContentHandler } events. + */ + public Locator getDocumentLocator () + { return locator; } + + + // CONTENT HANDLER DELEGATIONS + + /** <b>SAX2:</b> passes this callback to the next consumer, if any */ + public void setDocumentLocator (Locator locator) + { + this.locator = locator; + if (docNext != null) + docNext.setDocumentLocator (locator); + } + + /** <b>SAX2:</b> passes this callback to the next consumer, if any */ + public void startDocument () throws SAXException + { + if (docNext != null) + docNext.startDocument (); + } + + public void xmlDecl(String version, String encoding, boolean standalone, + String inputEncoding) + throws SAXException + { + if (docNext != null && docNext instanceof ContentHandler2) + { + ((ContentHandler2) docNext).xmlDecl(version, encoding, standalone, + inputEncoding); + } + } + + /** <b>SAX2:</b> passes this callback to the next consumer, if any */ + public void skippedEntity (String name) throws SAXException + { + if (docNext != null) + docNext.skippedEntity (name); + } + + /** <b>SAX2:</b> passes this callback to the next consumer, if any */ + public void processingInstruction (String target, String data) + throws SAXException + { + if (docNext != null) + docNext.processingInstruction (target, data); + } + + /** <b>SAX2:</b> passes this callback to the next consumer, if any */ + public void characters (char ch [], int start, int length) + throws SAXException + { + if (docNext != null) + docNext.characters (ch, start, length); + } + + /** <b>SAX2:</b> passes this callback to the next consumer, if any */ + public void ignorableWhitespace (char ch [], int start, int length) + throws SAXException + { + if (docNext != null) + docNext.ignorableWhitespace (ch, start, length); + } + + /** <b>SAX2:</b> passes this callback to the next consumer, if any */ + public void startPrefixMapping (String prefix, String uri) + throws SAXException + { + if (docNext != null) + docNext.startPrefixMapping (prefix, uri); + } + + /** <b>SAX2:</b> passes this callback to the next consumer, if any */ + public void startElement ( + String uri, String localName, + String qName, Attributes atts + ) throws SAXException + { + if (docNext != null) + docNext.startElement (uri, localName, qName, atts); + } + + /** <b>SAX2:</b> passes this callback to the next consumer, if any */ + public void endElement (String uri, String localName, String qName) + throws SAXException + { + if (docNext != null) + docNext.endElement (uri, localName, qName); + } + + /** <b>SAX2:</b> passes this callback to the next consumer, if any */ + public void endPrefixMapping (String prefix) throws SAXException + { + if (docNext != null) + docNext.endPrefixMapping (prefix); + } + + /** <b>SAX2:</b> passes this callback to the next consumer, if any */ + public void endDocument () throws SAXException + { + if (docNext != null) + docNext.endDocument (); + locator = null; + } + + + // DTD HANDLER DELEGATIONS + + /** <b>SAX1:</b> passes this callback to the next consumer, if any */ + public void unparsedEntityDecl ( + String name, + String publicId, + String systemId, + String notationName + ) throws SAXException + { + if (dtdNext != null) + dtdNext.unparsedEntityDecl (name, publicId, systemId, notationName); + } + + /** <b>SAX1:</b> passes this callback to the next consumer, if any */ + public void notationDecl (String name, String publicId, String systemId) + throws SAXException + { + if (dtdNext != null) + dtdNext.notationDecl (name, publicId, systemId); + } + + + // LEXICAL HANDLER DELEGATIONS + + /** <b>SAX2:</b> passes this callback to the next consumer, if any */ + public void startDTD (String name, String publicId, String systemId) + throws SAXException + { + if (lexNext != null) + lexNext.startDTD (name, publicId, systemId); + } + + /** <b>SAX2:</b> passes this callback to the next consumer, if any */ + public void endDTD () + throws SAXException + { + if (lexNext != null) + lexNext.endDTD (); + } + + /** <b>SAX2:</b> passes this callback to the next consumer, if any */ + public void comment (char ch [], int start, int length) + throws SAXException + { + if (lexNext != null) + lexNext.comment (ch, start, length); + } + + /** <b>SAX2:</b> passes this callback to the next consumer, if any */ + public void startCDATA () + throws SAXException + { + if (lexNext != null) + lexNext.startCDATA (); + } + + /** <b>SAX2:</b> passes this callback to the next consumer, if any */ + public void endCDATA () + throws SAXException + { + if (lexNext != null) + lexNext.endCDATA (); + } + + /** + * <b>SAX2:</b> passes this callback to the next consumer, if any. + */ + public void startEntity (String name) + throws SAXException + { + if (lexNext != null) + lexNext.startEntity (name); + } + + /** + * <b>SAX2:</b> passes this callback to the next consumer, if any. + */ + public void endEntity (String name) + throws SAXException + { + if (lexNext != null) + lexNext.endEntity (name); + } + + + // DECLARATION HANDLER DELEGATIONS + + + /** <b>SAX2:</b> passes this callback to the next consumer, if any */ + public void elementDecl (String name, String model) + throws SAXException + { + if (declNext != null) + declNext.elementDecl (name, model); + } + + /** <b>SAX2:</b> passes this callback to the next consumer, if any */ + public void attributeDecl (String eName, String aName, + String type, String mode, String value) + throws SAXException + { + if (declNext != null) + declNext.attributeDecl (eName, aName, type, mode, value); + } + + /** <b>SAX2:</b> passes this callback to the next consumer, if any */ + public void externalEntityDecl (String name, + String publicId, String systemId) + throws SAXException + { + if (declNext != null) + declNext.externalEntityDecl (name, publicId, systemId); + } + + /** <b>SAX2:</b> passes this callback to the next consumer, if any */ + public void internalEntityDecl (String name, String value) + throws SAXException + { + if (declNext != null) + declNext.internalEntityDecl (name, value); + } +} diff --git a/libjava/gnu/xml/pipeline/LinkFilter.java b/libjava/gnu/xml/pipeline/LinkFilter.java new file mode 100644 index 0000000..28a4501 --- /dev/null +++ b/libjava/gnu/xml/pipeline/LinkFilter.java @@ -0,0 +1,243 @@ +/* LinkFilter.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.pipeline; + +import java.io.IOException; +import java.net.URL; +import java.util.Enumeration; +import java.util.Vector; + +import org.xml.sax.Attributes; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; + + +/** + * Pipeline filter to remember XHTML links found in a document, + * so they can later be crawled. Fragments are not counted, and duplicates + * are ignored. Callers are responsible for filtering out URLs they aren't + * interested in. Events are passed through unmodified. + * + * <p> Input MUST include a setDocumentLocator() call, as it's used to + * resolve relative links in the absence of a "base" element. Input MUST + * also include namespace identifiers, since it is the XHTML namespace + * identifier which is used to identify the relevant elements. + * + * <p><em>FIXME:</em> handle xml:base attribute ... in association with + * a stack of base URIs. Similarly, recognize/support XLink data. + * + * @author David Brownell + */ +public class LinkFilter extends EventFilter +{ + // for storing URIs + private Vector vector = new Vector (); + + // struct for "full" link record (tbd) + // these for troubleshooting original source: + // original uri + // uri as resolved (base, relative, etc) + // URI of originating doc + // line # + // original element + attrs (img src, desc, etc) + + // XLink model of the link ... for inter-site pairups ? + + private String baseURI; + + private boolean siteRestricted = false; + + // + // XXX leverage blacklist info (like robots.txt) + // + // XXX constructor w/param ... pipeline for sending link data + // probably XHTML --> XLink, providing info as sketched above + // + + + /** + * Constructs a new event filter, which collects links in private data + * structure for later enumeration. + */ + // constructor used by PipelineFactory + public LinkFilter () + { + super.setContentHandler (this); + } + + + /** + * Constructs a new event filter, which collects links in private data + * structure for later enumeration and passes all events, unmodified, + * to the next consumer. + */ + // constructor used by PipelineFactory + public LinkFilter (EventConsumer next) + { + super (next); + super.setContentHandler (this); + } + + + /** + * Returns an enumeration of the links found since the filter + * was constructed, or since removeAllLinks() was called. + * + * @return enumeration of strings. + */ + public Enumeration getLinks () + { + return vector.elements (); + } + + /** + * Removes records about all links reported to the event + * stream, as if the filter were newly created. + */ + public void removeAllLinks () + { + vector = new Vector (); + } + + + /** + * Collects URIs for (X)HTML content from elements which hold them. + */ + public void startElement ( + String uri, + String localName, + String qName, + Attributes atts + ) throws SAXException + { + String link; + + // Recognize XHTML links. + if ("http://www.w3.org/1999/xhtml".equals (uri)) { + + if ("a".equals (localName) || "base".equals (localName) + || "area".equals (localName)) + link = atts.getValue ("href"); + else if ("iframe".equals (localName) || "frame".equals (localName)) + link = atts.getValue ("src"); + else if ("blockquote".equals (localName) || "q".equals (localName) + || "ins".equals (localName) || "del".equals (localName)) + link = atts.getValue ("cite"); + else + link = null; + link = maybeAddLink (link); + + // "base" modifies designated baseURI + if ("base".equals (localName) && link != null) + baseURI = link; + + if ("iframe".equals (localName) || "img".equals (localName)) + maybeAddLink (atts.getValue ("longdesc")); + } + + super.startElement (uri, localName, qName, atts); + } + + private String maybeAddLink (String link) + { + int index; + + // ignore empty links and fragments inside docs + if (link == null) + return null; + if ((index = link.indexOf ("#")) >= 0) + link = link.substring (0, index); + if (link.equals ("")) + return null; + + try { + // get the real URI + URL base = new URL ((baseURI != null) + ? baseURI + : getDocumentLocator ().getSystemId ()); + URL url = new URL (base, link); + + link = url.toString (); + + // ignore duplicates + if (vector.contains (link)) + return link; + + // other than what "base" does, stick to original site: + if (siteRestricted) { + // don't switch protocols + if (!base.getProtocol ().equals (url.getProtocol ())) + return link; + // don't switch servers + if (base.getHost () != null + && !base.getHost ().equals (url.getHost ())) + return link; + } + + vector.addElement (link); + + return link; + + } catch (IOException e) { + // bad URLs we don't want + } + return null; + } + + /** + * Reports an error if no Locator has been made available. + */ + public void startDocument () + throws SAXException + { + if (getDocumentLocator () == null) + throw new SAXException ("no Locator!"); + } + + /** + * Forgets about any base URI information that may be recorded. + * Applications will often want to call removeAllLinks(), likely + * after examining the links which were reported. + */ + public void endDocument () + throws SAXException + { + baseURI = null; + super.endDocument (); + } +} diff --git a/libjava/gnu/xml/pipeline/NSFilter.java b/libjava/gnu/xml/pipeline/NSFilter.java new file mode 100644 index 0000000..9e8a643 --- /dev/null +++ b/libjava/gnu/xml/pipeline/NSFilter.java @@ -0,0 +1,340 @@ +/* NSFilter.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.pipeline; + +import java.util.EmptyStackException; +import java.util.Enumeration; +import java.util.Stack; + +import org.xml.sax.*; +import org.xml.sax.ext.*; +import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.helpers.NamespaceSupport; + + +/** + * This filter ensures that element and attribute names are properly prefixed, + * and that such prefixes are declared. Such data is critical for operations + * like writing XML text, and validating against DTDs: names or their prefixes + * may have been discarded, although they are essential to the exchange of + * information using XML. There are various common ways that such data + * gets discarded: <ul> + * + * <li> By default, SAX2 parsers must discard the "xmlns*" + * attributes, and may also choose not to report properly prefixed + * names for elements or attributes. (Some parsers may support + * changing the <em>namespace-prefixes</em> value from the default + * to <em>true</em>, effectively eliminating the need to use this + * filter on their output.) + * + * <li> When event streams are generated from a DOM tree, they may + * have never have had prefixes or declarations for namespaces; or + * the existing prefixes or declarations may have been invalidated + * by structural modifications to that DOM tree. + * + * <li> Other software writing SAX event streams won't necessarily + * be worrying about prefix management, and so they will need to + * have a transparent solution for managing them. + * + * </ul> + * + * <p> This filter uses a heuristic to choose the prefix to assign to any + * particular name which wasn't already corectly prefixed. The associated + * namespace will be correct, and the prefix will be declared. Original + * structures facilitating text editing, such as conventions about use of + * mnemonic prefix names or the scoping of prefixes, can't always be + * reconstructed after they are discarded, as strongly encouraged by the + * current SAX2 defaults. + * + * <p> Note that this can't possibly know whether values inside attribute + * value or document content involve prefixed names. If your application + * requires using prefixed names in such locations you'll need to add some + * appropriate logic (perhaps adding additional heuristics in a subclass). + * + * @author David Brownell + */ +public class NSFilter extends EventFilter +{ + private NamespaceSupport nsStack = new NamespaceSupport (); + private Stack elementStack = new Stack (); + + private boolean pushedContext; + private String nsTemp [] = new String [3]; + private AttributesImpl attributes = new AttributesImpl (); + private boolean usedDefault; + + // gensymmed prefixes use this root name + private static final String prefixRoot = "prefix-"; + + + /** + * Passes events through to the specified consumer, after first + * processing them. + * + * @param next the next event consumer to receive events. + */ + // constructor used by PipelineFactory + public NSFilter (EventConsumer next) + { + super (next); + + setContentHandler (this); + } + + private void fatalError (String message) + throws SAXException + { + SAXParseException e; + ErrorHandler handler = getErrorHandler (); + Locator locator = getDocumentLocator (); + + if (locator == null) + e = new SAXParseException (message, null, null, -1, -1); + else + e = new SAXParseException (message, locator); + if (handler != null) + handler.fatalError (e); + throw e; + } + + + public void startDocument () throws SAXException + { + elementStack.removeAllElements (); + nsStack.reset (); + pushedContext = false; + super.startDocument (); + } + + /** + * This call is not passed to the next consumer in the chain. + * Prefix declarations and scopes are only exposed in the form + * of attributes; this callback just records a declaration that + * will be exposed as an attribute. + */ + public void startPrefixMapping (String prefix, String uri) + throws SAXException + { + if (pushedContext == false) { + nsStack.pushContext (); + pushedContext = true; + } + + // this check is awkward, but the paranoia prevents big trouble + for (Enumeration e = nsStack.getDeclaredPrefixes (); + e.hasMoreElements (); + /* NOP */ ) { + String declared = (String) e.nextElement (); + + if (!declared.equals (prefix)) + continue; + if (uri.equals (nsStack.getURI (prefix))) + return; + fatalError ("inconsistent binding for prefix '" + prefix + + "' ... " + uri + " (was " + nsStack.getURI (prefix) + ")"); + } + + if (!nsStack.declarePrefix (prefix, uri)) + fatalError ("illegal prefix declared: " + prefix); + } + + private String fixName (String ns, String l, String name, boolean isAttr) + throws SAXException + { + if ("".equals (name) || name == null) { + name = l; + if ("".equals (name) || name == null) + fatalError ("empty/null name"); + } + + // can we correctly process the name as-is? + // handles "element scope" attribute names here. + if (nsStack.processName (name, nsTemp, isAttr) != null + && nsTemp [0].equals (ns) + ) { + return nsTemp [2]; + } + + // nope, gotta modify the name or declare a default mapping + int temp; + + // get rid of any current prefix + if ((temp = name.indexOf (':')) >= 0) { + name = name.substring (temp + 1); + + // ... maybe that's enough (use/prefer default namespace) ... + if (!isAttr && nsStack.processName (name, nsTemp, false) != null + && nsTemp [0].equals (ns) + ) { + return nsTemp [2]; + } + } + + // must we define and use the default/undefined prefix? + if ("".equals (ns)) { + if (isAttr) + fatalError ("processName bug"); + if (attributes.getIndex ("xmlns") != -1) + fatalError ("need to undefine default NS, but it's bound: " + + attributes.getValue ("xmlns")); + + nsStack.declarePrefix ("", ""); + attributes.addAttribute ("", "", "xmlns", "CDATA", ""); + return name; + } + + // is there at least one non-null prefix we can use? + for (Enumeration e = nsStack.getDeclaredPrefixes (); + e.hasMoreElements (); + /* NOP */) { + String prefix = (String) e.nextElement (); + String uri = nsStack.getURI (prefix); + + if (uri == null || !uri.equals (ns)) + continue; + return prefix + ":" + name; + } + + // no such luck. create a prefix name, declare it, use it. + for (temp = 0; temp >= 0; temp++) { + String prefix = prefixRoot + temp; + + if (nsStack.getURI (prefix) == null) { + nsStack.declarePrefix (prefix, ns); + attributes.addAttribute ("", "", "xmlns:" + prefix, + "CDATA", ns); + return prefix + ":" + name; + } + } + fatalError ("too many prefixes genned"); + // NOTREACHED + return null; + } + + public void startElement ( + String uri, String localName, + String qName, Attributes atts + ) throws SAXException + { + if (!pushedContext) + nsStack.pushContext (); + pushedContext = false; + + // make sure we have all NS declarations handy before we start + int length = atts.getLength (); + + for (int i = 0; i < length; i++) { + String aName = atts.getQName (i); + + if (!aName.startsWith ("xmlns")) + continue; + + String prefix; + + if ("xmlns".equals (aName)) + prefix = ""; + else if (aName.indexOf (':') == 5) + prefix = aName.substring (6); + else // "xmlnsfoo" etc. + continue; + startPrefixMapping (prefix, atts.getValue (i)); + } + + // put namespace decls at the start of our regenned attlist + attributes.clear (); + for (Enumeration e = nsStack.getDeclaredPrefixes (); + e.hasMoreElements (); + /* NOP */) { + String prefix = (String) e.nextElement (); + + attributes.addAttribute ("", "", + ("".equals (prefix) + ? "xmlns" + : "xmlns:" + prefix), + "CDATA", + nsStack.getURI (prefix)); + } + + // name fixups: element, then attributes. + // fixName may declare a new prefix or, for the element, + // redeclare the default (if element name needs it). + qName = fixName (uri, localName, qName, false); + + for (int i = 0; i < length; i++) { + String aName = atts.getQName (i); + String aNS = atts.getURI (i); + String aLocal = atts.getLocalName (i); + String aType = atts.getType (i); + String aValue = atts.getValue (i); + + if (aName.startsWith ("xmlns")) + continue; + aName = fixName (aNS, aLocal, aName, true); + attributes.addAttribute (aNS, aLocal, aName, aType, aValue); + } + + elementStack.push (qName); + + // pass event along, with cleaned-up names and decls. + super.startElement (uri, localName, qName, attributes); + } + + public void endElement (String uri, String localName, String qName) + throws SAXException + { + nsStack.popContext (); + qName = (String) elementStack.pop (); + super.endElement (uri, localName, qName); + } + + /** + * This call is not passed to the next consumer in the chain. + * Prefix declarations and scopes are only exposed in their + * attribute form. + */ + public void endPrefixMapping (String prefix) + throws SAXException + { } + + public void endDocument () throws SAXException + { + elementStack.removeAllElements (); + nsStack.reset (); + super.endDocument (); + } +} diff --git a/libjava/gnu/xml/pipeline/PipelineFactory.java b/libjava/gnu/xml/pipeline/PipelineFactory.java new file mode 100644 index 0000000..5edca73 --- /dev/null +++ b/libjava/gnu/xml/pipeline/PipelineFactory.java @@ -0,0 +1,723 @@ +/* PipelineFactory.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.pipeline; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.lang.reflect.Constructor; +import java.util.StringTokenizer; + +import org.xml.sax.*; +import org.xml.sax.ext.*; + + +/** + * This provides static factory methods for creating simple event pipelines. + * These pipelines are specified by strings, suitable for passing on + * command lines or embedding in element attributes. For example, one way + * to write a pipeline that restores namespace syntax, validates (stopping + * the pipeline on validity errors) and then writes valid data to standard + * output is this: <pre> + * nsfix | validate | write ( stdout )</pre> + * + * <p> In this syntax, the tokens are always separated by whitespace, and each + * stage of the pipeline may optionally have a parameter (which can be a + * pipeline) in parentheses. Interior stages are called filters, and the + * rightmost end of a pipeline is called a terminus. + * + * <p> Stages are usually implemented by a single class, which may not be + * able to act as both a filter and a terminus; but any terminus can be + * automatically turned into a filter, through use of a {@link TeeConsumer}. + * The stage identifiers are either class names, or are one of the following + * short identifiers built into this class. (Most of these identifiers are + * no more than aliases for classes.) The built-in identifiers include:</p> + + <table border="1" cellpadding="3" cellspacing="0"> + <tr bgcolor="#ccccff" class="TableHeadingColor"> + <th align="center" width="5%">Stage</th> + <th align="center" width="9%">Parameter</th> + <th align="center" width="1%">Terminus</th> + <th align="center">Description</th> + </tr> + + <tr valign="top" align="center"> + <td><a href="../dom/Consumer.html">dom</a></td> + <td><em>none</em></td> + <td> yes </td> + <td align="left"> Applications code can access a DOM Document built + from the input event stream. When used as a filter, this buffers + data up to an <em>endDocument</em> call, and then uses a DOM parser + to report everything that has been recorded (which can easily be + less than what was reported to it). </td> + </tr> + <tr valign="top" align="center"> + <td><a href="NSFilter.html">nsfix</a></td> + <td><em>none</em></td> + <td>no</td> + <td align="left">This stage ensures that the XML element and attribute + names in its output use namespace prefixes and declarations correctly. + That is, so that they match the "Namespace plus LocalName" naming data + with which each XML element and attribute is already associated. </td> + </tr> + <tr valign="top" align="center"> + <td><a href="EventFilter.html">null</a></td> + <td><em>none</em></td> + <td>yes</td> + <td align="left">This stage ignores all input event data.</td> + </tr> + <tr valign="top" align="center"> + <td><a href="CallFilter.html">server</a></td> + <td><em>required</em><br> server URL </td> + <td>no</td> + <td align="left">Sends its input as XML request to a remote server, + normally a web application server using the HTTP or HTTPS protocols. + The output of this stage is the parsed response from that server.</td> + </tr> + <tr valign="top" align="center"> + <td><a href="TeeConsumer.html">tee</a></td> + <td><em>required</em><br> first pipeline</td> + <td>no</td> + <td align="left">This sends its events down two paths; its parameter + is a pipeline descriptor for the first path, and the second path + is the output of this stage.</td> + </tr> + + <tr valign="top" align="center"> + <td><a href="ValidationConsumer.html">validate</a></td> + <td><em>none</em></td> + <td>yes</td> + <td align="left">This checks for validity errors, and reports them + through its error handler. The input must include declaration events + and some lexical events. </td> + </tr> + <tr valign="top" align="center"> + <td><a href="WellFormednessFilter.html">wf</a></td> + <td><em>none</em></td> + <td>yes</td> + <td align="left"> This class provides some basic "well formedness" + tests on the input event stream, and reports a fatal error if any + of them fail. One example: start/end calls for elements must match. + No SAX parser is permitted to produce malformed output, but other + components can easily do so.</td> + </tr> + <tr valign="top" align="center"> + <td>write</td> + <td><em>required</em><br> "stdout", "stderr", or filename</td> + <td>yes</td> + <td align="left"> Writes its input to the specified output, as pretty + printed XML text encoded using UTF-8. Input events must be well + formed and "namespace fixed", else the output won't be XML (or possibly + namespace) conformant. The symbolic names represent + <em>System.out</em> and <em>System.err</em> respectively; names must + correspond to files which don't yet exist.</td> + </tr> + <tr valign="top" align="center"> + <td>xhtml</td> + <td><em>required</em><br> "stdout", "stderr", or filename</td> + <td>yes</td> + <td align="left"> Like <em>write</em> (above), except that XHTML rules + are followed. The XHTML 1.0 Transitional document type is declared, + and only ASCII characters are written (for interoperability). Other + characters are written as entity or character references; the text is + pretty printed.</td> + </tr> + <tr valign="top" align="center"> + <td><a href="XIncludeFilter.html">xinclude</a></td> + <td><em>none</em></td> + <td>no</td> + <td align="left">This stage handles XInclude processing. + This is like entity inclusion, except that the included content + is declared in-line rather than in the DTD at the beginning of + a document. + </td> + </tr> + <tr valign="top" align="center"> + <td><a href="XsltFilter.html">xslt</a></td> + <td><em>required</em><br> XSLT stylesheet URI</td> + <td>no</td> + <td align="left">This stage handles XSLT transformation + according to a stylesheet. + The implementation of the transformation may not actually + stream data, although if such an XSLT engine is in use + then that can happen. + </td> + </tr> + + </table> + + * <p> Note that {@link EventFilter#bind} can automatically eliminate + * some filters by setting SAX2 parser features appropriately. This means + * that you can routinely put filters like "nsfix", "validate", or "wf" at the + * front of a pipeline (for components that need inputs conditioned to match + * that level of correctness), and know that it won't actually be used unless + * it's absolutely necessary. + * + * @author David Brownell + */ +public class PipelineFactory +{ + /** + * Creates a simple pipeline according to the description string passed in. + */ + public static EventConsumer createPipeline (String description) + throws IOException + { + return createPipeline (description, null); + } + + /** + * Extends an existing pipeline by prepending the filter pipeline to the + * specified consumer. Some pipelines need more customization than can + * be done through this simplified syntax. When they are set up with + * direct API calls, use this method to merge more complex pipeline + * segments with easily configured ones. + */ + public static EventConsumer createPipeline ( + String description, + EventConsumer next + ) throws IOException + { + // tokens are (for now) what's separated by whitespace; + // very easy to parse, but IDs never have spaces. + + StringTokenizer tokenizer; + String tokens []; + + tokenizer = new StringTokenizer (description); + tokens = new String [tokenizer.countTokens ()]; + for (int i = 0; i < tokens.length; i++) + tokens [i] = tokenizer.nextToken (); + + PipelineFactory factory = new PipelineFactory (); + Pipeline pipeline = factory.parsePipeline (tokens, next); + + return pipeline.createPipeline (); + } + + + private PipelineFactory () { /* NYET */ } + + + /** + * Extends an existing pipeline by prepending a pre-tokenized filter + * pipeline to the specified consumer. Tokens are class names (or the + * predefined aliases) left and right parenthesis, and the vertical bar. + */ + public static EventConsumer createPipeline ( + String tokens [], + EventConsumer next + ) throws IOException + { + PipelineFactory factory = new PipelineFactory (); + Pipeline pipeline = factory.parsePipeline (tokens, next); + + return pipeline.createPipeline (); + } + + + private String tokens []; + private int index; + + private Pipeline parsePipeline (String toks [], EventConsumer next) + { + tokens = toks; + index = 0; + + Pipeline retval = parsePipeline (next); + + if (index != toks.length) + throw new ArrayIndexOutOfBoundsException ( + "extra token: " + tokens [index]); + return retval; + } + + // pipeline ::= stage | stage '|' pipeline + private Pipeline parsePipeline (EventConsumer next) + { + Pipeline retval = new Pipeline (parseStage ()); + + // minimal pipelines: "stage" and "... | id" + if (index > (tokens.length - 2) + || !"|".equals (tokens [index]) + ) { + retval.next = next; + return retval; + } + index++; + retval.rest = parsePipeline (next); + return retval; + } + + // stage ::= id | id '(' pipeline ')' + private Stage parseStage () + { + Stage retval = new Stage (tokens [index++]); + + // minimal stages: "id" and "id ( id )" + if (index > (tokens.length - 2) + || !"(".equals (tokens [index]) /*)*/ + ) + return retval; + + index++; + retval.param = parsePipeline (null); + if (index >= tokens.length) + throw new ArrayIndexOutOfBoundsException ( + "missing right paren"); + if (/*(*/ !")".equals (tokens [index++])) + throw new ArrayIndexOutOfBoundsException ( + "required right paren, not: " + tokens [index - 1]); + return retval; + } + + + // + // these classes obey the conventions for constructors, so they're + // only built in to this table of shortnames + // + // - filter (one or two types of arglist) + // * last constructor is 'next' element + // * optional (first) string parameter + // + // - terminus (one or types of arglist) + // * optional (only) string parameter + // + // terminus stages are transformed into filters if needed, by + // creating a "tee". filter stages aren't turned to terminus + // stages though; either eliminate such stages, or add some + // terminus explicitly. + // + private static final String builtinStages [][] = { + { "dom", "gnu.xml.dom.Consumer" }, + { "nsfix", "gnu.xml.pipeline.NSFilter" }, + { "null", "gnu.xml.pipeline.EventFilter" }, + { "server", "gnu.xml.pipeline.CallFilter" }, + { "tee", "gnu.xml.pipeline.TeeConsumer" }, + { "validate", "gnu.xml.pipeline.ValidationConsumer" }, + { "wf", "gnu.xml.pipeline.WellFormednessFilter" }, + { "xinclude", "gnu.xml.pipeline.XIncludeFilter" }, + { "xslt", "gnu.xml.pipeline.XsltFilter" }, + +// XXX want: option for validate, to preload external part of a DTD + + // xhtml, write ... nyet generic-ready + }; + + private static class Stage + { + String id; + Pipeline param; + + Stage (String name) + { id = name; } + + public String toString () + { + if (param == null) + return id; + return id + " ( " + param + " )"; + } + + private void fail (String message) + throws IOException + { + throw new IOException ("in '" + id + + "' stage of pipeline, " + message); + } + + EventConsumer createStage (EventConsumer next) + throws IOException + { + String name = id; + + // most builtins are just class aliases + for (int i = 0; i < builtinStages.length; i++) { + if (id.equals (builtinStages [i][0])) { + name = builtinStages [i][1]; + break; + } + } + + // Save output as XML or XHTML text + if ("write".equals (name) || "xhtml".equals (name)) { + String filename; + boolean isXhtml = "xhtml".equals (name); + OutputStream out = null; + TextConsumer consumer; + + if (param == null) + fail ("parameter is required"); + + filename = param.toString (); + if ("stdout".equals (filename)) + out = System.out; + else if ("stderr".equals (filename)) + out = System.err; + else { + File f = new File (filename); + +/* + if (!f.isAbsolute ()) + fail ("require absolute file paths"); + */ + if (f.exists ()) + fail ("file already exists: " + f.getName ()); + +// XXX this races against the existence test + out = new FileOutputStream (f); + } + + if (!isXhtml) + consumer = new TextConsumer (out); + else + consumer = new TextConsumer ( + new OutputStreamWriter (out, "8859_1"), + true); + + consumer.setPrettyPrinting (true); + if (next == null) + return consumer; + return new TeeConsumer (consumer, next); + + } else { + // + // Here go all the builtins that are just aliases for + // classes, and all stage IDs that started out as such + // class names. The following logic relies on several + // documented conventions for constructor invocation. + // + String msg = null; + + try { + Class klass = Class.forName (name); + Class argTypes [] = null; + Constructor constructor = null; + boolean filter = false; + Object params [] = null; + Object obj = null; + + // do we need a filter stage? + if (next != null) { + // "next" consumer is always passed, with + // or without the optional string param + if (param == null) { + argTypes = new Class [1]; + argTypes [0] = EventConsumer.class; + + params = new Object [1]; + params [0] = next; + + msg = "no-param filter"; + } else { + argTypes = new Class [2]; + argTypes [0] = String.class; + argTypes [1] = EventConsumer.class; + + params = new Object [2]; + params [0] = param.toString (); + params [1] = next; + + msg = "one-param filter"; + } + + + try { + constructor = klass.getConstructor (argTypes); + } catch (NoSuchMethodException e) { + // try creating a filter from a + // terminus and a tee + filter = true; + msg += " built from "; + } + } + + // build from a terminus stage, with or + // without the optional string param + if (constructor == null) { + String tmp; + + if (param == null) { + argTypes = new Class [0]; + params = new Object [0]; + + tmp = "no-param terminus"; + } else { + argTypes = new Class [1]; + argTypes [0] = String.class; + + params = new Object [1]; + params [0] = param.toString (); + + tmp = "one-param terminus"; + } + if (msg == null) + msg = tmp; + else + msg += tmp; + constructor = klass.getConstructor (argTypes); + // NOT creating terminus by dead-ending + // filters ... users should think about + // that one, something's likely wrong + } + + obj = constructor.newInstance (params); + + // return EventConsumers directly, perhaps after + // turning them into a filter + if (obj instanceof EventConsumer) { + if (filter) + return new TeeConsumer ((EventConsumer) obj, next); + return (EventConsumer) obj; + } + + // if it's not a handler, it's an error + // we can wrap handlers in a filter + EventFilter retval = new EventFilter (); + boolean updated = false; + + if (obj instanceof ContentHandler) { + retval.setContentHandler ((ContentHandler) obj); + updated = true; + } + if (obj instanceof DTDHandler) { + retval.setDTDHandler ((DTDHandler) obj); + updated = true; + } + if (obj instanceof LexicalHandler) { + retval.setProperty ( + EventFilter.PROPERTY_URI + "lexical-handler", + obj); + updated = true; + } + if (obj instanceof DeclHandler) { + retval.setProperty ( + EventFilter.PROPERTY_URI + "declaration-handler", + obj); + updated = true; + } + + if (!updated) + fail ("class is neither Consumer nor Handler"); + + if (filter) + return new TeeConsumer (retval, next); + return retval; + + } catch (IOException e) { + throw e; + + } catch (NoSuchMethodException e) { + fail (name + " constructor missing -- " + msg); + + } catch (ClassNotFoundException e) { + fail (name + " class not found"); + + } catch (Exception e) { + // e.printStackTrace (); + fail ("stage not available: " + e.getMessage ()); + } + } + // NOTREACHED + return null; + } + } + + private static class Pipeline + { + Stage stage; + + // rest may be null + Pipeline rest; + EventConsumer next; + + Pipeline (Stage s) + { stage = s; } + + public String toString () + { + if (rest == null && next == null) + return stage.toString (); + if (rest != null) + return stage + " | " + rest; + throw new IllegalArgumentException ("next"); + } + + EventConsumer createPipeline () + throws IOException + { + if (next == null) { + if (rest == null) + next = stage.createStage (null); + else + next = stage.createStage (rest.createPipeline ()); + } + return next; + } + } + +/* + public static void main (String argv []) + { + try { + // three basic terminus cases + createPipeline ("null"); + createPipeline ("validate"); + createPipeline ("write ( stdout )"); + + // four basic filters + createPipeline ("nsfix | write ( stderr )"); + createPipeline ("wf | null"); + createPipeline ("null | null"); + createPipeline ( +"call ( http://www.example.com/services/xml-1a ) | xhtml ( stdout )"); + + // tee junctions + createPipeline ("tee ( validate ) | write ( stdout )"); + createPipeline ("tee ( nsfix | write ( stdout ) ) | validate"); + + // longer pipeline + createPipeline ("nsfix | tee ( validate ) | write ( stdout )"); + createPipeline ( + "null | wf | nsfix | tee ( validate ) | write ( stdout )"); + + // try some parsing error cases + try { + createPipeline ("null ("); // extra token '(' + System.err.println ("** didn't report error"); + } catch (Exception e) { + System.err.println ("== err: " + e.getMessage ()); } + + try { + createPipeline ("nsfix |"); // extra token '|' + System.err.println ("** didn't report error"); + } catch (Exception e) { + System.err.println ("== err: " + e.getMessage ()); } + + try { + createPipeline ("xhtml ( foo"); // missing right paren + System.err.println ("** didn't report error"); + } catch (Exception e) { + System.err.println ("== err: " + e.getMessage ()); } + + try { + createPipeline ("xhtml ( foo bar"); // required right paren + System.err.println ("** didn't report error"); + } catch (Exception e) { + System.err.println ("== err: " + e.getMessage ()); } + + try { + createPipeline ("tee ( nsfix | validate");// missing right paren + System.err.println ("** didn't report error"); + } catch (Exception e) { + System.err.println ("== err: " + e.getMessage ()); } + + // try some construction error cases + + try { + createPipeline ("call"); // missing param + System.err.println ("** didn't report error"); + } catch (Exception e) { + System.err.println ("== err: " + e.getMessage ()); } + try { + createPipeline ("call ( foobar )"); // broken param + System.err.println ("** didn't report error"); + } catch (Exception e) { + System.err.println ("== err: " + e.getMessage ()); } + try { + createPipeline ("nsfix ( foobar )"); // illegal param + System.err.println ("** didn't report error"); + } catch (Exception e) { + System.err.println ("== err: " + e.getMessage ()); } + try { + createPipeline ("null ( foobar )"); // illegal param + System.err.println ("** didn't report error"); + } catch (Exception e) { + System.err.println ("== err: " + e.getMessage ()); } + try { + createPipeline ("wf ( foobar )"); // illegal param + System.err.println ("** didn't report error"); + } catch (Exception e) { + System.err.println ("== err: " + e.getMessage ()); } + try { + createPipeline ("xhtml ( foobar.html )"); + new File ("foobar.html").delete (); + // now supported + } catch (Exception e) { + System.err.println ("** err: " + e.getMessage ()); } + try { + createPipeline ("xhtml"); // missing param + System.err.println ("** didn't report error"); + } catch (Exception e) { + System.err.println ("== err: " + e.getMessage ()); } + try { + createPipeline ("write ( stdout ) | null"); // nonterminal + System.err.println ("** didn't report error"); + } catch (Exception e) { + System.err.println ("== err: " + e.getMessage ()); } + try { + createPipeline ("validate | null"); + // now supported + } catch (Exception e) { + System.err.println ("** err: " + e.getMessage ()); } + try { + createPipeline ("validate ( foo )"); // illegal param + System.err.println ("** didn't report error"); + } catch (Exception e) { + System.err.println ("== err: " + e.getMessage ()); } + try { + createPipeline ("tee"); // missing param + System.err.println ("** didn't report error"); + } catch (Exception e) { + System.err.println ("== err: " + e.getMessage ()); } + try { + // only builtins so far + createPipeline ("com.example.xml.FilterClass"); + System.err.println ("** didn't report error"); + } catch (Exception e) { + System.err.println ("== err: " + e.getMessage ()); } + + } catch (Exception e) { + e.printStackTrace (); + } + } +/**/ + +} diff --git a/libjava/gnu/xml/pipeline/TeeConsumer.java b/libjava/gnu/xml/pipeline/TeeConsumer.java new file mode 100644 index 0000000..6d3227e --- /dev/null +++ b/libjava/gnu/xml/pipeline/TeeConsumer.java @@ -0,0 +1,413 @@ +/* TeeConsumer.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.pipeline; + +import java.io.IOException; +import org.xml.sax.*; +import org.xml.sax.ext.*; +//import gnu.xml.util; + + +/** + * Fans its events out to two other consumers, a "tee" filter stage in an + * event pipeline. Networks can be assembled with multiple output points. + * + * <p> Error handling should be simple if you remember that exceptions + * you throw will cancel later stages in that callback's pipeline, and + * generally the producer will stop if it sees such an exception. You + * may want to protect your pipeline against such backflows, making a + * kind of reverse filter (or valve?) so that certain exceptions thrown by + * your pipeline will caught and handled before the producer sees them. + * Just use a "try/catch" block, rememebering that really important + * cleanup tasks should be in "finally" clauses. + * + * <p> That issue isn't unique to "tee" consumers, but tee consumers have + * the additional twist that exceptions thrown by the first consumer + * will cause the second consumer not to see the callback (except for + * the endDocument callback, which signals state cleanup). + * + * @author David Brownell + */ +final public class TeeConsumer + implements EventConsumer, + ContentHandler, DTDHandler, + LexicalHandler,DeclHandler +{ + private EventConsumer first, rest; + + // cached to minimize time overhead + private ContentHandler docFirst, docRest; + private DeclHandler declFirst, declRest; + private LexicalHandler lexFirst, lexRest; + + + /** + * Constructs a consumer which sends all its events to the first + * consumer, and then the second one. If the first consumer throws + * an exception, the second one will not see the event which + * caused that exception to be reported. + * + * @param car The first consumer to get the events + * @param cdr The second consumer to get the events + */ + public TeeConsumer (EventConsumer car, EventConsumer cdr) + { + if (car == null || cdr == null) + throw new NullPointerException (); + first = car; + rest = cdr; + + // + // Cache the handlers. + // + docFirst = first.getContentHandler (); + docRest = rest.getContentHandler (); + // DTD handler isn't cached (rarely needed) + + try { + declFirst = null; + declFirst = (DeclHandler) first.getProperty ( + EventFilter.DECL_HANDLER); + } catch (SAXException e) {} + try { + declRest = null; + declRest = (DeclHandler) rest.getProperty ( + EventFilter.DECL_HANDLER); + } catch (SAXException e) {} + + try { + lexFirst = null; + lexFirst = (LexicalHandler) first.getProperty ( + EventFilter.LEXICAL_HANDLER); + } catch (SAXException e) {} + try { + lexRest = null; + lexRest = (LexicalHandler) rest.getProperty ( + EventFilter.LEXICAL_HANDLER); + } catch (SAXException e) {} + } + +/* FIXME + /** + * Constructs a pipeline, and is otherwise a shorthand for the + * two-consumer constructor for this class. + * + * @param first Description of the first pipeline to get events, + * which will be passed to {@link PipelineFactory#createPipeline} + * @param rest The second pipeline to get the events + * / + // constructor used by PipelineFactory + public TeeConsumer (String first, EventConsumer rest) + throws IOException + { + this (PipelineFactory.createPipeline (first), rest); + } +*/ + + /** Returns the first pipeline to get event calls. */ + public EventConsumer getFirst () + { return first; } + + /** Returns the second pipeline to get event calls. */ + public EventConsumer getRest () + { return rest; } + + /** Returns the content handler being used. */ + final public ContentHandler getContentHandler () + { + if (docRest == null) + return docFirst; + if (docFirst == null) + return docRest; + return this; + } + + /** Returns the dtd handler being used. */ + final public DTDHandler getDTDHandler () + { + // not cached (hardly used) + if (rest.getDTDHandler () == null) + return first.getDTDHandler (); + if (first.getDTDHandler () == null) + return rest.getDTDHandler (); + return this; + } + + /** Returns the declaration or lexical handler being used. */ + final public Object getProperty (String id) + throws SAXNotRecognizedException + { + // + // in degenerate cases, we have no work to do. + // + Object firstProp = null, restProp = null; + + try { firstProp = first.getProperty (id); } + catch (SAXNotRecognizedException e) { /* ignore */ } + try { restProp = rest.getProperty (id); } + catch (SAXNotRecognizedException e) { /* ignore */ } + + if (restProp == null) + return firstProp; + if (firstProp == null) + return restProp; + + // + // we've got work to do; handle two builtin cases. + // + if (EventFilter.DECL_HANDLER.equals (id)) + return this; + if (EventFilter.LEXICAL_HANDLER.equals (id)) + return this; + + // + // non-degenerate, handled by both consumers, but we don't know + // how to handle this. + // + throw new SAXNotRecognizedException ("can't tee: " + id); + } + + /** + * Provides the error handler to both subsequent nodes of + * this filter stage. + */ + public void setErrorHandler (ErrorHandler handler) + { + first.setErrorHandler (handler); + rest.setErrorHandler (handler); + } + + + // + // ContentHandler + // + public void setDocumentLocator (Locator locator) + { + // this call is not made by all parsers + docFirst.setDocumentLocator (locator); + docRest.setDocumentLocator (locator); + } + + public void startDocument () + throws SAXException + { + docFirst.startDocument (); + docRest.startDocument (); + } + + public void endDocument () + throws SAXException + { + try { + docFirst.endDocument (); + } finally { + docRest.endDocument (); + } + } + + public void startPrefixMapping (String prefix, String uri) + throws SAXException + { + docFirst.startPrefixMapping (prefix, uri); + docRest.startPrefixMapping (prefix, uri); + } + + public void endPrefixMapping (String prefix) + throws SAXException + { + docFirst.endPrefixMapping (prefix); + docRest.endPrefixMapping (prefix); + } + + public void skippedEntity (String name) + throws SAXException + { + docFirst.skippedEntity (name); + docRest.skippedEntity (name); + } + + public void startElement (String uri, String localName, + String qName, Attributes atts) + throws SAXException + { + docFirst.startElement (uri, localName, qName, atts); + docRest.startElement (uri, localName, qName, atts); + } + + public void endElement (String uri, String localName, String qName) + throws SAXException + { + docFirst.endElement (uri, localName, qName); + docRest.endElement (uri, localName, qName); + } + + public void processingInstruction (String target, String data) + throws SAXException + { + docFirst.processingInstruction (target, data); + docRest.processingInstruction (target, data); + } + + public void characters (char ch [], int start, int length) + throws SAXException + { + docFirst.characters (ch, start, length); + docRest.characters (ch, start, length); + } + + public void ignorableWhitespace (char ch [], int start, int length) + throws SAXException + { + docFirst.ignorableWhitespace (ch, start, length); + docRest.ignorableWhitespace (ch, start, length); + } + + + // + // DTDHandler + // + public void notationDecl (String name, String publicId, String systemId) + throws SAXException + { + DTDHandler l1 = first.getDTDHandler (); + DTDHandler l2 = rest.getDTDHandler (); + + l1.notationDecl (name, publicId, systemId); + l2.notationDecl (name, publicId, systemId); + } + + public void unparsedEntityDecl (String name, + String publicId, String systemId, + String notationName + ) throws SAXException + { + DTDHandler l1 = first.getDTDHandler (); + DTDHandler l2 = rest.getDTDHandler (); + + l1.unparsedEntityDecl (name, publicId, systemId, notationName); + l2.unparsedEntityDecl (name, publicId, systemId, notationName); + } + + + // + // DeclHandler + // + public void attributeDecl (String eName, String aName, + String type, + String mode, String value) + throws SAXException + { + declFirst.attributeDecl (eName, aName, type, mode, value); + declRest.attributeDecl (eName, aName, type, mode, value); + } + + public void elementDecl (String name, String model) + throws SAXException + { + declFirst.elementDecl (name, model); + declRest.elementDecl (name, model); + } + + public void externalEntityDecl (String name, + String publicId, String systemId) + throws SAXException + { + declFirst.externalEntityDecl (name, publicId, systemId); + declRest.externalEntityDecl (name, publicId, systemId); + } + + public void internalEntityDecl (String name, String value) + throws SAXException + { + declFirst.internalEntityDecl (name, value); + declRest.internalEntityDecl (name, value); + } + + + // + // LexicalHandler + // + public void comment (char ch [], int start, int length) + throws SAXException + { + lexFirst.comment (ch, start, length); + lexRest.comment (ch, start, length); + } + + public void startCDATA () + throws SAXException + { + lexFirst.startCDATA (); + lexRest.startCDATA (); + } + + public void endCDATA () + throws SAXException + { + lexFirst.endCDATA (); + lexRest.endCDATA (); + } + + public void startEntity (String name) + throws SAXException + { + lexFirst.startEntity (name); + lexRest.startEntity (name); + } + + public void endEntity (String name) + throws SAXException + { + lexFirst.endEntity (name); + lexRest.endEntity (name); + } + + public void startDTD (String name, String publicId, String systemId) + throws SAXException + { + lexFirst.startDTD (name, publicId, systemId); + lexRest.startDTD (name, publicId, systemId); + } + + public void endDTD () + throws SAXException + { + lexFirst.endDTD (); + lexRest.endDTD (); + } +} diff --git a/libjava/gnu/xml/pipeline/TextConsumer.java b/libjava/gnu/xml/pipeline/TextConsumer.java new file mode 100644 index 0000000..1039b3b --- /dev/null +++ b/libjava/gnu/xml/pipeline/TextConsumer.java @@ -0,0 +1,117 @@ +/* TextConsumer.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.pipeline; + +import java.io.*; + +import org.xml.sax.*; + +import gnu.xml.util.XMLWriter; + + +/** + * Terminates a pipeline, consuming events to print them as well formed + * XML (or XHTML) text. + * + * <p> Input must be well formed, and must include XML names (e.g. the + * prefixes and prefix declarations must be present), or the output of + * this class is undefined. + * + * @see NSFilter + * @see WellFormednessFilter + * + * @author David Brownell + */ +public class TextConsumer extends XMLWriter implements EventConsumer +{ + /** + * Constructs an event consumer which echoes its input as text, + * optionally adhering to some basic XHTML formatting options + * which increase interoperability with old (v3) browsers. + * + * <p> For the best interoperability, when writing as XHTML only + * ASCII characters are emitted; other characters are turned to + * entity or character references as needed, and no XML declaration + * is provided in the document. + */ + public TextConsumer (Writer w, boolean isXhtml) + throws IOException + { + super (w, isXhtml ? "US-ASCII" : null); + setXhtml (isXhtml); + } + + /** + * Constructs a consumer that writes its input as XML text. + * XHTML rules are not followed. + */ + public TextConsumer (Writer w) + throws IOException + { + this (w, false); + } + + /** + * Constructs a consumer that writes its input as XML text, + * encoded in UTF-8. XHTML rules are not followed. + */ + public TextConsumer (OutputStream out) + throws IOException + { + this (new OutputStreamWriter (out, "UTF8"), false); + } + + /** <b>EventConsumer</b> Returns the document handler being used. */ + public ContentHandler getContentHandler () + { return this; } + + /** <b>EventConsumer</b> Returns the dtd handler being used. */ + public DTDHandler getDTDHandler () + { return this; } + + /** <b>XMLReader</b>Retrieves a property (lexical and decl handlers) */ + public Object getProperty (String propertyId) + throws SAXNotRecognizedException + { + if (EventFilter.LEXICAL_HANDLER.equals (propertyId)) + return this; + if (EventFilter.DECL_HANDLER.equals (propertyId)) + return this; + throw new SAXNotRecognizedException (propertyId); + } +} diff --git a/libjava/gnu/xml/pipeline/ValidationConsumer.java b/libjava/gnu/xml/pipeline/ValidationConsumer.java new file mode 100644 index 0000000..e73c0ff --- /dev/null +++ b/libjava/gnu/xml/pipeline/ValidationConsumer.java @@ -0,0 +1,1922 @@ +/* ValidationConsumer.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.pipeline; + +import java.io.*; + +import java.util.EmptyStackException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Stack; +import java.util.StringTokenizer; +import java.util.Vector; + +import org.xml.sax.*; +import org.xml.sax.ext.*; +import org.xml.sax.helpers.XMLReaderFactory; + + +/** + * This class checks SAX2 events to report validity errors; it works as + * both a filter and a terminus on an event pipeline. It relies on the + * producer of SAX events to: </p> <ol> + * + * <li> Conform to the specification of a non-validating XML parser that + * reads all external entities, reported using SAX2 events. </li> + * + * <li> Report ignorable whitespace as such (through the ContentHandler + * interface). This is, strictly speaking, optional for nonvalidating + * XML processors. </li> + * + * <li> Make SAX2 DeclHandler callbacks, with default + * attribute values already normalized (and without "<").</li> + * + * <li> Make SAX2 LexicalHandler startDTD() and endDTD () + * callbacks. </li> + * + * <li> Act as if the <em>(URI)/namespace-prefixes</em> property were + * set to true, by providing XML 1.0 names and all <code>xmlns*</code> + * attributes (rather than omitting either or both). </li> + * + * </ol> + * + * <p> At this writing, the major SAX2 parsers (such as Ælfred2, + * Crimson, and Xerces) meet these requirements, and this validation + * module is used by the optional Ælfred2 validation support. + * </p> + * + * <p> Note that because this is a layered validator, it has to duplicate some + * work that the parser is doing; there are also other cost to layering. + * However, <em>because of layering it doesn't need a parser</em> in order + * to work! You can use it with anything that generates SAX events, such + * as an application component that wants to detect invalid content in + * a changed area without validating an entire document, or which wants to + * ensure that it doesn't write invalid data to a communications partner.</p> + * + * <p> Also, note that because this is a layered validator, the line numbers + * reported for some errors may seem strange. For example, if an element does + * not permit character content, the validator + * will use the locator provided to it. + * That might reflect the last character of a <em>characters</em> event + * callback, rather than the first non-whitespace character. </p> + * + * <hr /> + * + * <!-- + * <p> Of interest is the fact that unlike most currently known XML validators, + * this one can report some cases of non-determinism in element content models. + * It is a compile-time option, enabled by default. This will only report + * such XML errors if they relate to content actually appearing in a document; + * content models aren't aggressively scanned for non-deterministic structure. + * Documents which trigger such non-deterministic transitions may be handled + * differently by different validating parsers, without losing conformance + * to the XML specification. </p> + * --> + * + * <p> Current limitations of the validation performed are in roughly three + * categories. </p> + * + * <p> The first category represents constraints which demand violations + * of software layering: exposing lexical details, one of the first things + * that <em>application</em> programming interfaces (APIs) hide. These + * invariably relate to XML entity handling, and to historical oddities + * of the XML validation semantics. Curiously, + * recent (Autumn 1999) conformance testing showed that these constraints are + * among those handled worst by existing XML validating parsers. Arguments + * have been made that each of these VCs should be turned into WFCs (most + * of them) or discarded (popular for the standalone declaration); in short, + * that these are bugs in the XML specification (not all via SGML): </p><ul> + * + * <li> The <em>Proper Declaration/PE Nesting</em> and + * <em>Proper Group/PE Nesting</em> VCs can't be tested because they + * require access to particularly low level lexical level information. + * In essence, the reason XML isn't a simple thing to parse is that + * it's not a context free grammar, and these constraints elevate that + * SGML-derived context sensitivity to the level of a semantic rule. + * + * <li> The <em>Standalone Document Declaration</em> VC can't be + * tested. This is for two reasons. First, this flag isn't made + * available through SAX2. Second, it also requires breaking that + * lexical layering boundary. (If you ever wondered why classes + * in compiler construction or language design barely mention the + * existence of context-sensitive grammars, it's because of messy + * issues like these.) + * + * <li> The <em>Entity Declared</em> VC can't be tested, because it + * also requires breaking that lexical layering boundary! There's also + * another issue: the VC wording (and seemingly intent) is ambiguous. + * (This is still true in the "Second edition" XML spec.) + * Since there is a WFC of the same name, everyone's life would be + * easier if references to undeclared parsed entities were always well + * formedness errors, regardless of whether they're parameter entities + * or not. (Note that nonvalidating parsers are not required + * to report all such well formedness errors if they don't read external + * parameter entities, although currently most XML parsers read them + * in an attempt to avoid problems from inconsistent parser behavior.) + * + * </ul> + * + * <p> The second category of limitations on this validation represent + * constraints associated with information that is not guaranteed to be + * available (or in one case, <em>is guaranteed not to be available</em>, + * through the SAX2 API: </p><ul> + * + * <li> The <em>Unique Element Type Declaration</em> VC may not be + * reportable, if the underlying parser happens not to expose + * multiple declarations. (Ælfred2 reports these validity + * errors directly.)</li> + * + * <li> Similarly, the <em>Unique Notation Name</em> VC, added in the + * 14-January-2000 XML spec errata to restrict typing models used by + * elements, may not be reportable. (Ælfred reports these + * validity errors directly.) </li> + * + * </ul> + * + * <p> A third category relates to ease of implementation. (Think of this + * as "bugs".) The most notable issue here is character handling. Rather + * than attempting to implement the voluminous character tables in the XML + * specification (Appendix B), Unicode rules are used directly from + * the java.lang.Character class. Recent JVMs have begun to diverge from + * the original specification for that class (Unicode 2.0), meaning that + * different JVMs may handle that aspect of conformance differently. + * </p> + * + * <p> Note that for some of the validity errors that SAX2 does not + * expose, a nonvalidating parser is permitted (by the XML specification) + * to report validity errors. When used with a parser that does so for + * the validity constraints mentioned above (or any other SAX2 event + * stream producer that does the same thing), overall conformance is + * substantially improved. + * + * @see gnu.xml.aelfred2.SAXDriver + * @see gnu.xml.aelfred2.XmlReader + * + * @author David Brownell + */ +public final class ValidationConsumer extends EventFilter +{ + // report error if we happen to notice a non-deterministic choice? + // we won't report buggy content models; just buggy instances + private static final boolean warnNonDeterministic = false; + + // for tracking active content models + private String rootName; + private Stack contentStack = new Stack (); + + // flags for "saved DTD" processing + private boolean disableDeclarations; + private boolean disableReset; + + // + // most VCs get tested when we see element start tags. the per-element + // info (including attributes) recorded here duplicates that found inside + // many nonvalidating parsers, hence dual lookups etc ... that's why a + // layered validator isn't going to be as fast as a non-layered one. + // + + // key = element name; value = ElementInfo + private Hashtable elements = new Hashtable (); + + // some VCs relate to ID/IDREF/IDREFS attributes + // key = id; value = boolean true (defd) or false (refd) + private Hashtable ids = new Hashtable (); + + // we just record declared notation and unparsed entity names. + // the implementation here is simple/slow; these features + // are seldom used, one hopes they'll wither away soon + private Vector notations = new Vector (5, 5); + private Vector nDeferred = new Vector (5, 5); + private Vector unparsed = new Vector (5, 5); + private Vector uDeferred = new Vector (5, 5); + + // note: DocBk 3.1.7 XML defines over 2 dozen notations, + // used when defining unparsed entities for graphics + // (and maybe in other places) + + + + /** + * Creates a pipeline terminus which consumes all events passed to + * it; this will report validity errors as if they were fatal errors, + * unless an error handler is assigned. + * + * @see #setErrorHandler + */ + // constructor used by PipelineFactory + // ... and want one taking system ID of an external subset + public ValidationConsumer () + { + this (null); + } + + /** + * Creates a pipeline filter which reports validity errors and then + * passes events on to the next consumer if they were not fatal. + * + * @see #setErrorHandler + */ + // constructor used by PipelineFactory + // ... and want one taking system ID of an external subset + // (which won't send declaration events) + public ValidationConsumer (EventConsumer next) + { + super (next); + + setContentHandler (this); + setDTDHandler (this); + try { setProperty (DECL_HANDLER, this); } + catch (Exception e) { /* "can't happen" */ } + try { setProperty (LEXICAL_HANDLER, this); } + catch (Exception e) { /* "can't happen" */ } + } + + + private static final String fakeRootName + = ":Nobody:in:their_Right.Mind_would:use:this-name:1x:"; + + /** + * Creates a validation consumer which is preloaded with the DTD provided. + * It does this by constructing a document with that DTD, then parsing + * that document and recording its DTD declarations. Then it arranges + * not to modify that information. + * + * <p> The resulting validation consumer will only validate against + * the specified DTD, regardless of whether some other DTD is found + * in a document being parsed. + * + * @param rootName The name of the required root element; if this is + * null, any root element name will be accepted. + * @param publicId If non-null and there is a non-null systemId, this + * identifier provides an alternate access identifier for the DTD's + * external subset. + * @param systemId If non-null, this is a URI (normally URL) that + * may be used to access the DTD's external subset. + * @param internalSubset If non-null, holds literal markup declarations + * comprising the DTD's internal subset. + * @param resolver If non-null, this will be provided to the parser for + * use when resolving parameter entities (including any external subset). + * @param resolver If non-null, this will be provided to the parser for + * use when resolving parameter entities (including any external subset). + * @param minimalElement If non-null, a minimal valid document. + * + * @exception SAXNotSupportedException If the default SAX parser does + * not support the standard lexical or declaration handlers. + * @exception SAXParseException If the specified DTD has either + * well-formedness or validity errors + * @exception IOException If the specified DTD can't be read for + * some reason + */ + public ValidationConsumer ( + String rootName, + String publicId, + String systemId, + String internalSubset, + EntityResolver resolver, + String minimalDocument + ) throws SAXException, IOException + { + this (null); + + disableReset = true; + if (rootName == null) + rootName = fakeRootName; + + // + // Synthesize document with that DTD; is it possible to do + // better for the declaration of the root element? + // + // NOTE: can't use SAX2 to write internal subsets. + // + StringWriter writer = new StringWriter (); + + writer.write ("<!DOCTYPE "); + writer.write (rootName); + if (systemId != null) { + writer.write ("\n "); + if (publicId != null) { + writer.write ("PUBLIC '"); + writer.write (publicId); + writer.write ("'\n\t'"); + } else + writer.write ("SYSTEM '"); + writer.write (systemId); + writer.write ("'"); + } + writer.write (" [ "); + if (rootName == fakeRootName) { + writer.write ("\n<!ELEMENT "); + writer.write (rootName); + writer.write (" EMPTY>"); + } + if (internalSubset != null) + writer.write (internalSubset); + writer.write ("\n ]>"); + + if (minimalDocument != null) { + writer.write ("\n"); + writer.write (minimalDocument); + writer.write ("\n"); + } else { + writer.write (" <"); + writer.write (rootName); + writer.write ("/>\n"); + } + minimalDocument = writer.toString (); + + // + // OK, load it + // + XMLReader producer; + + producer = XMLReaderFactory.createXMLReader (); + bind (producer, this); + + if (resolver != null) + producer.setEntityResolver (resolver); + + InputSource in; + + in = new InputSource (new StringReader (minimalDocument)); + producer.parse (in); + + disableDeclarations = true; + if (rootName == fakeRootName) + this.rootName = null; + } + + private void resetState () + { + if (!disableReset) { + rootName = null; + contentStack.removeAllElements (); + elements.clear (); + ids.clear (); + + notations.removeAllElements (); + nDeferred.removeAllElements (); + unparsed.removeAllElements (); + uDeferred.removeAllElements (); + } + } + + + private void warning (String description) + throws SAXException + { + ErrorHandler errHandler = getErrorHandler (); + Locator locator = getDocumentLocator (); + SAXParseException err; + + if (errHandler == null) + return; + + if (locator == null) + err = new SAXParseException (description, null, null, -1, -1); + else + err = new SAXParseException (description, locator); + errHandler.warning (err); + } + + // package private (for ChildrenRecognizer) + private void error (String description) + throws SAXException + { + ErrorHandler errHandler = getErrorHandler (); + Locator locator = getDocumentLocator (); + SAXParseException err; + + if (locator == null) + err = new SAXParseException (description, null, null, -1, -1); + else + err = new SAXParseException (description, locator); + if (errHandler != null) + errHandler.error (err); + else // else we always treat it as fatal! + throw err; + } + + private void fatalError (String description) + throws SAXException + { + ErrorHandler errHandler = getErrorHandler (); + Locator locator = getDocumentLocator (); + SAXParseException err; + + if (locator != null) + err = new SAXParseException (description, locator); + else + err = new SAXParseException (description, null, null, -1, -1); + if (errHandler != null) + errHandler.fatalError (err); + // we always treat this as fatal, regardless of the handler + throw err; + } + + + private static boolean isExtender (char c) + { + // [88] Extender ::= ... + return c == 0x00b7 || c == 0x02d0 || c == 0x02d1 || c == 0x0387 + || c == 0x0640 || c == 0x0e46 || c == 0x0ec6 || c == 0x3005 + || (c >= 0x3031 && c <= 0x3035) + || (c >= 0x309d && c <= 0x309e) + || (c >= 0x30fc && c <= 0x30fe); + } + + + // use augmented Unicode rules, not full XML rules + private boolean isName (String name, String context, String id) + throws SAXException + { + char buf [] = name.toCharArray (); + boolean pass = true; + + if (!Character.isUnicodeIdentifierStart (buf [0]) + && ":_".indexOf (buf [0]) == -1) + pass = false; + else { + int max = buf.length; + for (int i = 1; pass && i < max; i++) { + char c = buf [i]; + if (!Character.isUnicodeIdentifierPart (c) + && ":-_.".indexOf (c) == -1 + && !isExtender (c)) + pass = false; + } + } + + if (!pass) + error ("In " + context + " for " + id + + ", '" + name + "' is not a name"); + return pass; // true == OK + } + + // use augmented Unicode rules, not full XML rules + private boolean isNmtoken (String nmtoken, String context, String id) + throws SAXException + { + char buf [] = nmtoken.toCharArray (); + boolean pass = true; + int max = buf.length; + + // XXX make this share code with isName + + for (int i = 0; pass && i < max; i++) { + char c = buf [i]; + if (!Character.isUnicodeIdentifierPart (c) + && ":-_.".indexOf (c) == -1 + && !isExtender (c)) + pass = false; + } + + if (!pass) + error ("In " + context + " for " + id + + ", '" + nmtoken + "' is not a name token"); + return pass; // true == OK + } + + private void checkEnumeration (String value, String type, String name) + throws SAXException + { + if (!hasMatch (value, type)) + // VC: Enumeration + error ("Value '" + value + + "' for attribute '" + name + + "' is not permitted: " + type); + } + + // used to test enumerated attributes and mixed content models + // package private + static boolean hasMatch (String value, String orList) + { + int len = value.length (); + int max = orList.length () - len; + + for (int start = 0; + (start = orList.indexOf (value, start)) != -1; + start++) { + char c; + + if (start > max) + break; + c = orList.charAt (start - 1); + if (c != '|' && c != '('/*)*/) + continue; + c = orList.charAt (start + len); + if (c != '|' && /*(*/ c != ')') + continue; + return true; + } + return false; + } + + /** + * <b>LexicalHandler</b> Records the declaration of the root + * element, so it can be verified later. + * Passed to the next consumer, unless this one was + * preloaded with a particular DTD. + */ + public void startDTD (String name, String publicId, String systemId) + throws SAXException + { + if (disableDeclarations) + return; + + rootName = name; + super.startDTD (name, publicId, systemId); + } + + /** + * <b>LexicalHandler</b> Verifies that all referenced notations + * and unparsed entities have been declared. + * Passed to the next consumer, unless this one was + * preloaded with a particular DTD. + */ + public void endDTD () + throws SAXException + { + if (disableDeclarations) + return; + + // this is a convenient hook for end-of-dtd checks, but we + // could also trigger it in the first startElement call. + // locator info is more appropriate here though. + + // VC: Notation Declared (NDATA can refer to them before decls, + // as can NOTATION attribute enumerations and defaults) + int length = nDeferred.size (); + for (int i = 0; i < length; i++) { + String notation = (String) nDeferred.elementAt (i); + if (!notations.contains (notation)) { + error ("A declaration referred to notation '" + notation + + "' which was never declared"); + } + } + nDeferred.removeAllElements (); + + // VC: Entity Name (attribute values can refer to them + // before they're declared); VC Attribute Default Legal + length = uDeferred.size (); + for (int i = 0; i < length; i++) { + String entity = (String) uDeferred.elementAt (i); + if (!unparsed.contains (entity)) { + error ("An attribute default referred to entity '" + entity + + "' which was never declared"); + } + } + uDeferred.removeAllElements (); + super.endDTD (); + } + + + // These are interned, so we can rely on "==" to find the type of + // all attributes except enumerations ... + // "(this|or|that|...)" and "NOTATION (this|or|that|...)" + static final String types [] = { + "CDATA", + "ID", "IDREF", "IDREFS", + "NMTOKEN", "NMTOKENS", + "ENTITY", "ENTITIES" + }; + + + /** + * <b>DecllHandler</b> Records attribute declaration for later use + * in validating document content, and checks validity constraints + * that are applicable to attribute declarations. + * Passed to the next consumer, unless this one was + * preloaded with a particular DTD. + */ + public void attributeDecl ( + String eName, + String aName, + String type, + String mode, + String value + ) throws SAXException + { + if (disableDeclarations) + return; + + ElementInfo info = (ElementInfo) elements.get (eName); + AttributeInfo ainfo = new AttributeInfo (); + boolean checkOne = false; + boolean interned = false; + + // cheap interning of type names and #FIXED, #REQUIRED + // for faster startElement (we can use "==") + for (int i = 0; i < types.length; i++) { + if (types [i].equals (type)) { + type = types [i]; + interned = true; + break; + } + } + if ("#FIXED".equals (mode)) + mode = "#FIXED"; + else if ("#REQUIRED".equals (mode)) + mode = "#REQUIRED"; + + ainfo.type = type; + ainfo.mode = mode; + ainfo.value = value; + + // we might not have seen the content model yet + if (info == null) { + info = new ElementInfo (eName); + elements.put (eName, info); + } + if ("ID" == type) { + checkOne = true; + if (!("#REQUIRED" == mode || "#IMPLIED".equals (mode))) { + // VC: ID Attribute Default + error ("ID attribute '" + aName + + "' must be #IMPLIED or #REQUIRED"); + } + + } else if (!interned && type.startsWith ("NOTATION ")) { + checkOne = true; + + // VC: Notation Attributes (notations must be declared) + StringTokenizer tokens = new StringTokenizer ( + type.substring (10, type.lastIndexOf (')')), + "|"); + while (tokens.hasMoreTokens ()) { + String token = tokens.nextToken (); + if (!notations.contains (token)) + nDeferred.addElement (token); + } + } + if (checkOne) { + for (Enumeration e = info.attributes.keys (); + e.hasMoreElements (); + /* NOP */) { + String name; + AttributeInfo ainfo2; + + name = (String) e.nextElement (); + ainfo2 = (AttributeInfo) info.attributes.get (name); + if (type == ainfo2.type || !interned /* NOTATION */) { + // VC: One ID per Element Type + // VC: One Notation per Element TYpe + error ("Element '" + eName + + "' already has an attribute of type " + + (interned ? "NOTATION" : type) + + " ('" + name + + "') so '" + aName + + "' is a validity error"); + } + } + } + + // VC: Attribute Default Legal + if (value != null) { + + if ("CDATA" == type) { + // event source rejected '<' + + } else if ("NMTOKEN" == type) { + // VC: Name Token (is a nmtoken) + isNmtoken (value, "attribute default", aName); + + } else if ("NMTOKENS" == type) { + // VC: Name Token (is a nmtoken; at least one value) + StringTokenizer tokens = new StringTokenizer (value); + if (!tokens.hasMoreTokens ()) + error ("Default for attribute '" + aName + + "' must have at least one name token."); + else do { + String token = tokens.nextToken (); + isNmtoken (token, "attribute default", aName); + } while (tokens.hasMoreTokens ()); + + } else if ("IDREF" == type || "ENTITY" == type) { + // VC: Entity Name (is a name) + // VC: IDREF (is a name) (is declared) + isName (value, "attribute default", aName); + if ("ENTITY" == type && !unparsed.contains (value)) + uDeferred.addElement (value); + + } else if ("IDREFS" == type || "ENTITIES" == type) { + // VC: Entity Name (is a name; at least one value) + // VC: IDREF (is a name; at least one value) + StringTokenizer names = new StringTokenizer (value); + if (!names.hasMoreTokens ()) + error ("Default for attribute '" + aName + + "' must have at least one name."); + else do { + String name = names.nextToken (); + isName (name, "attribute default", aName); + if ("ENTITIES" == type && !unparsed.contains (name)) + uDeferred.addElement (value); + } while (names.hasMoreTokens ()); + + } else if (type.charAt (0) == '(' /*)*/ ) { + // VC: Enumeration (must match) + checkEnumeration (value, type, aName); + + } else if (!interned && checkOne) { /* NOTATION */ + // VC: Notation attributes (must be names) + isName (value, "attribute default", aName); + + // VC: Notation attributes (must be declared) + if (!notations.contains (value)) + nDeferred.addElement (value); + + // VC: Enumeration (must match) + checkEnumeration (value, type, aName); + + } else if ("ID" != type) + throw new RuntimeException ("illegal attribute type: " + type); + } + + if (info.attributes.get (aName) == null) + info.attributes.put (aName, ainfo); + /* + else + warning ("Element '" + eName + + "' already has an attribute named '" + aName + "'"); + */ + + if ("xml:space".equals (aName)) { + if (!("(default|preserve)".equals (type) + || "(preserve|default)".equals (type) + // these next two are arguable; XHTML's DTD doesn't + // deserve errors. After all, it's not like any + // illegal _value_ could pass ... + || "(preserve)".equals (type) + || "(default)".equals (type) + )) + error ( + "xml:space attribute type must be like '(default|preserve)'" + + " not '" + type + "'" + ); + + } + super.attributeDecl (eName, aName, type, mode, value); + } + + /** + * <b>DecllHandler</b> Records the element declaration for later use + * when checking document content, and checks validity constraints that + * apply to element declarations. Passed to the next consumer, unless + * this one was preloaded with a particular DTD. + */ + public void elementDecl (String name, String model) + throws SAXException + { + if (disableDeclarations) + return; + + ElementInfo info = (ElementInfo) elements.get (name); + + // we might have seen an attribute decl already + if (info == null) { + info = new ElementInfo (name); + elements.put (name, info); + } + if (info.model != null) { + // NOTE: not all parsers can report such duplicates. + // VC: Unique Element Type Declaration + error ("Element type '" + name + + "' was already declared."); + } else { + info.model = model; + + // VC: No Duplicate Types (in mixed content models) + if (model.charAt (1) == '#') // (#PCDATA... + info.getRecognizer (this); + } + super.elementDecl (name, model); + } + + /** + * <b>DecllHandler</b> passed to the next consumer, unless this + * one was preloaded with a particular DTD + */ + public void internalEntityDecl (String name, String value) + throws SAXException + { + if (!disableDeclarations) + super.internalEntityDecl (name, value); + } + + /** + * <b>DecllHandler</b> passed to the next consumer, unless this + * one was preloaded with a particular DTD + */ + public void externalEntityDecl (String name, + String publicId, String systemId) + throws SAXException + { + if (!disableDeclarations) + super.externalEntityDecl (name, publicId, systemId); + } + + + /** + * <b>DTDHandler</b> Records the notation name, for checking + * NOTATIONS attribute values and declararations of unparsed + * entities. Passed to the next consumer, unless this one was + * preloaded with a particular DTD. + */ + public void notationDecl (String name, String publicId, String systemId) + throws SAXException + { + if (disableDeclarations) + return; + + notations.addElement (name); + super.notationDecl (name, publicId, systemId); + } + + /** + * <b>DTDHandler</b> Records the entity name, for checking + * ENTITY and ENTITIES attribute values; records the notation + * name if it hasn't yet been declared. Passed to the next consumer, + * unless this one was preloaded with a particular DTD. + */ + public void unparsedEntityDecl ( + String name, + String publicId, + String systemId, + String notationName + ) throws SAXException + { + if (disableDeclarations) + return; + + unparsed.addElement (name); + if (!notations.contains (notationName)) + nDeferred.addElement (notationName); + super.unparsedEntityDecl (name, publicId, systemId, notationName); + } + + + /** + * <b>ContentHandler</b> Ensures that state from any previous parse + * has been deleted. + * Passed to the next consumer. + */ + public void startDocument () + throws SAXException + { + resetState (); + super.startDocument (); + } + + + private static boolean isAsciiLetter (char c) + { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + } + + + /** + * <b>ContentHandler</b> Reports a fatal exception. Validating + * XML processors may not skip any entities. + */ + public void skippedEntity (String name) + throws SAXException + { + fatalError ("may not skip entities"); + } + + /* + * SAX2 doesn't expand non-PE refs in attribute defaults... + */ + private String expandDefaultRefs (String s) + throws SAXException + { + if (s.indexOf ('&') < 0) + return s; + +// FIXME: handle &#nn; &#xnn; &name; + String message = "Can't expand refs in attribute default: " + s; + warning (message); + + return s; + } + + /** + * <b>ContentHandler</b> Performs validity checks against element + * (and document) content models, and attribute values. + * Passed to the next consumer. + */ + public void startElement ( + String uri, + String localName, + String qName, + Attributes atts + ) throws SAXException + { + // + // First check content model for the enclosing scope. + // + if (contentStack.isEmpty ()) { + // VC: Root Element Type + if (!qName.equals (rootName)) { + if (rootName == null) + warning ("This document has no DTD, can't be valid"); + else + error ("Root element type '" + qName + + "' was declared to be '" + rootName + "'"); + } + } else { + Recognizer state = (Recognizer) contentStack.peek (); + + if (state != null) { + Recognizer newstate = state.acceptElement (qName); + + if (newstate == null) + error ("Element type '" + qName + + "' in element '" + state.type.name + + "' violates content model " + state.type.model + ); + if (newstate != state) { + contentStack.pop (); + contentStack.push (newstate); + } + } + } + + // + // Then check that this element was declared, and push the + // object used to validate its content model onto our stack. + // + // This is where the recognizer gets created, if needed; if + // it's a "children" (elements) content model, an NDFA is + // created. (One recognizer is used per content type, no + // matter how complex that recognizer is.) + // + ElementInfo info; + + info = (ElementInfo) elements.get (qName); + if (info == null || info.model == null) { + // VC: Element Valid (base clause) + error ("Element type '" + qName + "' was not declared"); + contentStack.push (null); + + // for less diagnostic noise, fake a declaration. + elementDecl (qName, "ANY"); + } else + contentStack.push (info.getRecognizer (this)); + + // + // Then check each attribute present + // + int len; + String aname; + AttributeInfo ainfo; + + if (atts != null) + len = atts.getLength (); + else + len = 0; + + for (int i = 0; i < len; i++) { + aname = atts.getQName (i); + + if (info == null + || (ainfo = (AttributeInfo) info.attributes.get (aname)) + == null) { + // VC: Attribute Value Type + error ("Attribute '" + aname + + "' was not declared for element type " + qName); + continue; + } + + String value = atts.getValue (i); + + // note that "==" for type names and "#FIXED" is correct + // (and fast) since we've interned those literals. + + if ("#FIXED" == ainfo.mode) { + String expanded = expandDefaultRefs (ainfo.value); + + // VC: Fixed Attribute Default + if (!value.equals (expanded)) { + error ("Attribute '" + aname + + "' must match " + expanded + ); + continue; + } + } + + if ("CDATA" == ainfo.type) + continue; + + // + // For all other attribute types, there are various + // rules to follow. + // + + if ("ID" == ainfo.type) { + // VC: ID (must be a name) + if (isName (value, "ID attribute", aname)) { + if (Boolean.TRUE == ids.get (value)) + // VC: ID (appears once) + error ("ID attribute " + aname + + " uses an ID value '" + value + + "' which was already declared."); + else + // any forward refs are no longer problems + ids.put (value, Boolean.TRUE); + } + continue; + } + + if ("IDREF" == ainfo.type) { + // VC: IDREF (value must be a name) + if (isName (value, "IDREF attribute", aname)) { + // VC: IDREF (must match some ID attribute) + if (ids.get (value) == null) + // new -- assume it's a forward ref + ids.put (value, Boolean.FALSE); + } + continue; + } + + if ("IDREFS" == ainfo.type) { + StringTokenizer tokens = new StringTokenizer (value, " "); + + if (!tokens.hasMoreTokens ()) { + // VC: IDREF (one or more values) + error ("IDREFS attribute " + aname + + " must have at least one ID ref"); + } else do { + String id = tokens.nextToken (); + + // VC: IDREF (value must be a name) + if (isName (id, "IDREFS attribute", aname)) { + // VC: IDREF (must match some ID attribute) + if (ids.get (id) == null) + // new -- assume it's a forward ref + ids.put (id, Boolean.FALSE); + } + } while (tokens.hasMoreTokens ()); + continue; + } + + if ("NMTOKEN" == ainfo.type) { + // VC: Name Token (is a name token) + isNmtoken (value, "NMTOKEN attribute", aname); + continue; + } + + if ("NMTOKENS" == ainfo.type) { + StringTokenizer tokens = new StringTokenizer (value, " "); + + if (!tokens.hasMoreTokens ()) { + // VC: Name Token (one or more values) + error ("NMTOKENS attribute " + aname + + " must have at least one name token"); + } else do { + String token = tokens.nextToken (); + + // VC: Name Token (is a name token) + isNmtoken (token, "NMTOKENS attribute", aname); + } while (tokens.hasMoreTokens ()); + continue; + } + + if ("ENTITY" == ainfo.type) { + if (!unparsed.contains (value)) + // VC: Entity Name + error ("Value of attribute '" + aname + + "' refers to unparsed entity '" + value + + "' which was not declared."); + continue; + } + + if ("ENTITIES" == ainfo.type) { + StringTokenizer tokens = new StringTokenizer (value, " "); + + if (!tokens.hasMoreTokens ()) { + // VC: Entity Name (one or more values) + error ("ENTITIES attribute " + aname + + " must have at least one name token"); + } else do { + String entity = tokens.nextToken (); + + if (!unparsed.contains (entity)) + // VC: Entity Name + error ("Value of attribute '" + aname + + "' refers to unparsed entity '" + entity + + "' which was not declared."); + } while (tokens.hasMoreTokens ()); + continue; + } + + // + // check for enumerations last; more expensive + // + if (ainfo.type.charAt (0) == '(' /*)*/ + || ainfo.type.startsWith ("NOTATION ") + ) { + // VC: Enumeration (value must be defined) + checkEnumeration (value, ainfo.type, aname); + continue; + } + } + + // + // Last, check that all #REQUIRED attributes were provided + // + if (info != null) { + Hashtable table = info.attributes; + + if (table.size () != 0) { + Enumeration e = table.keys (); + + // XXX table.keys uses the heap, bleech -- slows things + + while (e.hasMoreElements ()) { + aname = (String) e.nextElement (); + ainfo = (AttributeInfo) table.get (aname); + + // "#REQUIRED" mode was interned in attributeDecl + if ("#REQUIRED" == ainfo.mode + && atts.getValue (aname) == null) { + // VC: Required Attribute + error ("Attribute '" + aname + "' must be specified " + + "for element type " + qName); + } + } + } + } + super.startElement (uri, localName, qName, atts); + } + + /** + * <b>ContentHandler</b> Reports a validity error if the element's content + * model does not permit character data. + * Passed to the next consumer. + */ + public void characters (char ch [], int start, int length) + throws SAXException + { + Recognizer state; + + if (contentStack.empty ()) + state = null; + else + state = (Recognizer) contentStack.peek (); + + // NOTE: if this ever supports with SAX parsers that don't + // report ignorable whitespace as such (only XP?), this class + // needs to morph it into ignorableWhitespace() as needed ... + + if (state != null && !state.acceptCharacters ()) + // VC: Element Valid (clauses three, four -- see recognizer) + error ("Character content not allowed in element " + + state.type.name); + + super.characters (ch, start, length); + } + + + /** + * <b>ContentHandler</b> Reports a validity error if the element's content + * model does not permit end-of-element yet, or a well formedness error + * if there was no matching startElement call. + * Passed to the next consumer. + */ + public void endElement (String uri, String localName, String qName) + throws SAXException + { + try { + Recognizer state = (Recognizer) contentStack.pop (); + + if (state != null && !state.completed ()) + // VC: Element valid (clauses two, three, four; see Recognizer) + error ("Premature end for element '" + + state.type.name + + "', content model " + + state.type.model); + + // could insist on match of start element, but that's + // something the input stream must to guarantee. + + } catch (EmptyStackException e) { + fatalError ("endElement without startElement: " + qName + + ((uri == null) + ? "" + : ( " { '" + uri + "', " + localName + " }"))); + } + super.endElement (uri, localName, qName); + } + + /** + * <b>ContentHandler</b> Checks whether all ID values that were + * referenced have been declared, and releases all resources. + * Passed to the next consumer. + * + * @see #setDocumentLocator + */ + public void endDocument () + throws SAXException + { + for (Enumeration idNames = ids.keys (); + idNames.hasMoreElements (); + /* NOP */) { + String id = (String) idNames.nextElement (); + + if (Boolean.FALSE == ids.get (id)) { + // VC: IDREF (must match ID) + error ("Undeclared ID value '" + id + + "' was referred to by an IDREF/IDREFS attribute"); + } + } + + resetState (); + super.endDocument (); + } + + + /** Holds per-element declarations */ + static private final class ElementInfo + { + String name; + String model; + + // key = attribute name; value = AttributeInfo + Hashtable attributes = new Hashtable (11); + + ElementInfo (String n) { name = n; } + + private Recognizer recognizer; + + // for validating content models: one per type, shared, + // and constructed only on demand ... so unused elements do + // not need to consume resources. + Recognizer getRecognizer (ValidationConsumer consumer) + throws SAXException + { + if (recognizer == null) { + if ("ANY".equals (model)) + recognizer = ANY; + else if ("EMPTY".equals (model)) + recognizer = new EmptyRecognizer (this); + else if ('#' == model.charAt (1)) + // n.b. this constructor does a validity check + recognizer = new MixedRecognizer (this, consumer); + else + recognizer = new ChildrenRecognizer (this, consumer); + } + return recognizer; + } + } + + /** Holds per-attribute declarations */ + static private final class AttributeInfo + { + String type; + String mode; // #REQUIRED, etc (or null) + String value; // or null + } + + + // + // Content model validation + // + + static private final Recognizer ANY = new Recognizer (null); + + + // Base class defines the calls used to validate content, + // and supports the "ANY" content model + static private class Recognizer + { + final ElementInfo type; + + Recognizer (ElementInfo t) { type = t; } + + // return true iff character data is legal here + boolean acceptCharacters () + throws SAXException + // VC: Element Valid (third and fourth clauses) + { return true; } + + // null return = failure + // otherwise, next state (like an FSM) + // prerequisite: tested that name was declared + Recognizer acceptElement (String name) + throws SAXException + // VC: Element Valid (fourth clause) + { return this; } + + // return true iff model is completed, can finish + boolean completed () + throws SAXException + // VC: Element Valid (fourth clause) + { return true; } + + public String toString () + // n.b. "children" is the interesting case! + { return (type == null) ? "ANY" : type.model; } + } + + // "EMPTY" content model -- no characters or elements + private static final class EmptyRecognizer extends Recognizer + { + public EmptyRecognizer (ElementInfo type) + { super (type); } + + // VC: Element Valid (first clause) + boolean acceptCharacters () + { return false; } + + // VC: Element Valid (first clause) + Recognizer acceptElement (String name) + { return null; } + } + + // "Mixed" content model -- ANY, but restricts elements + private static final class MixedRecognizer extends Recognizer + { + private String permitted []; + + // N.B. constructor tests for duplicated element names (VC) + public MixedRecognizer (ElementInfo t, ValidationConsumer v) + throws SAXException + { + super (t); + + // (#PCDATA...)* or (#PCDATA) ==> ... or empty + // with the "..." being "|elname|..." + StringTokenizer tokens = new StringTokenizer ( + t.model.substring (8, t.model.lastIndexOf (')')), + "|"); + Vector vec = new Vector (); + + while (tokens.hasMoreTokens ()) { + String token = tokens.nextToken (); + + if (vec.contains (token)) + v.error ("element " + token + + " is repeated in mixed content model: " + + t.model); + else + vec.addElement (token.intern ()); + } + permitted = new String [vec.size ()]; + for (int i = 0; i < permitted.length; i++) + permitted [i] = (String) vec.elementAt (i); + + // in one large machine-derived DTD sample, most of about + // 250 mixed content models were empty, and 25 had ten or + // more entries. 2 had over a hundred elements. Linear + // search isn't obviously wrong. + } + + // VC: Element Valid (third clause) + Recognizer acceptElement (String name) + { + int length = permitted.length; + + // first pass -- optimistic w.r.t. event source interning + // (and document validity) + for (int i = 0; i < length; i++) + if (permitted [i] == name) + return this; + // second pass -- pessimistic w.r.t. event source interning + for (int i = 0; i < length; i++) + if (permitted [i].equals (name)) + return this; + return null; + } + } + + + // recognizer loop flags, see later + private static final int F_LOOPHEAD = 0x01; + private static final int F_LOOPNEXT = 0x02; + + // for debugging -- used to label/count nodes in toString() + private static int nodeCount; + + /** + * "Children" content model -- these are nodes in NDFA state graphs. + * They work in fixed space. Note that these graphs commonly have + * cycles, handling features such as zero-or-more and one-or-more. + * + * <p>It's readonly, so only one copy is ever needed. The content model + * stack may have any number of pointers into each graph, when a model + * happens to be needed more than once due to element nesting. Since + * traversing the graph just moves to another node, and never changes + * it, traversals never interfere with each other. + * + * <p>There is an option to report non-deterministic models. These are + * always XML errors, but ones which are not often reported despite the + * fact that they can lead to different validating parsers giving + * different results for the same input. (The XML spec doesn't require + * them to be reported.) + * + * <p><b>FIXME</b> There's currently at least one known bug here, in that + * it's not actually detecting the non-determinism it tries to detect. + * (Of the "optional.xml" test, the once-or-twice-2* tests are all non-D; + * maybe some others.) This may relate to the issue flagged below as + * "should not" happen (but it was), which showed up when patching the + * graph to have one exit node (or more EMPTY nodes). + */ + private static final class ChildrenRecognizer extends Recognizer + implements Cloneable + { + // for reporting non-deterministic content models + // ... a waste of space if we're not reporting those! + // ... along with the 'model' member (in base class) + private ValidationConsumer consumer; + + // for CHOICE nodes -- each component is an arc that + // accepts a different NAME (or is EMPTY indicating + // NDFA termination). + private Recognizer components []; + + // for NAME/SEQUENCE nodes -- accepts that NAME and + // then goes to the next node (CHOICE, NAME, EMPTY). + private String name; + private Recognizer next; + + // loops always point back to a CHOICE node. we mark such choice + // nodes (F_LOOPHEAD) for diagnostics and faster deep cloning. + // We also mark nodes before back pointers (F_LOOPNEXT), to ensure + // termination when we patch sequences and loops. + private int flags; + + + // prevent a needless indirection between 'this' and 'node' + private void copyIn (ChildrenRecognizer node) + { + // model & consumer are already set + components = node.components; + name = node.name; + next = node.next; + flags = node.flags; + } + + // used to construct top level "children" content models, + public ChildrenRecognizer (ElementInfo type, ValidationConsumer vc) + { + this (vc, type); + populate (type.model.toCharArray (), 0); + patchNext (new EmptyRecognizer (type), null); + } + + // used internally; populating is separate + private ChildrenRecognizer (ValidationConsumer vc, ElementInfo type) + { + super (type); + consumer = vc; + } + + + // + // When rewriting some graph nodes we need deep clones in one case; + // mostly shallow clones (what the JVM handles for us) are fine. + // + private ChildrenRecognizer shallowClone () + { + try { + return (ChildrenRecognizer) clone (); + } catch (CloneNotSupportedException e) { + throw new Error ("clone"); + } + } + + private ChildrenRecognizer deepClone () + { + return deepClone (new Hashtable (37)); + } + + private ChildrenRecognizer deepClone (Hashtable table) + { + ChildrenRecognizer retval; + + if ((flags & F_LOOPHEAD) != 0) { + retval = (ChildrenRecognizer) table.get (this); + if (retval != null) + return this; + + retval = shallowClone (); + table.put (this, retval); + } else + retval = shallowClone (); + + if (next != null) { + if (next instanceof ChildrenRecognizer) + retval.next = ((ChildrenRecognizer)next) + .deepClone (table); + else if (!(next instanceof EmptyRecognizer)) + throw new RuntimeException ("deepClone"); + } + + if (components != null) { + retval.components = new Recognizer [components.length]; + for (int i = 0; i < components.length; i++) { + Recognizer temp = components [i]; + + if (temp == null) + retval.components [i] = null; + else if (temp instanceof ChildrenRecognizer) + retval.components [i] = ((ChildrenRecognizer)temp) + .deepClone (table); + else if (!(temp instanceof EmptyRecognizer)) + throw new RuntimeException ("deepClone"); + } + } + + return retval; + } + + // connect subgraphs, first to next (sequencing) + private void patchNext (Recognizer theNext, Hashtable table) + { + // backpointers must not be repatched or followed + if ((flags & F_LOOPNEXT) != 0) + return; + + // XXX this table "shouldn't" be needed, right? + // but some choice nodes looped if it isn't there. + if (table != null && table.get (this) != null) + return; + if (table == null) + table = new Hashtable (); + + // NAME/SEQUENCE + if (name != null) { + if (next == null) + next = theNext; + else if (next instanceof ChildrenRecognizer) { + ((ChildrenRecognizer)next).patchNext (theNext, table); + } else if (!(next instanceof EmptyRecognizer)) + throw new RuntimeException ("patchNext"); + return; + } + + // CHOICE + for (int i = 0; i < components.length; i++) { + if (components [i] == null) + components [i] = theNext; + else if (components [i] instanceof ChildrenRecognizer) { + ((ChildrenRecognizer)components [i]) + .patchNext (theNext, table); + } else if (!(components [i] instanceof EmptyRecognizer)) + throw new RuntimeException ("patchNext"); + } + + if (table != null && (flags | F_LOOPHEAD) != 0) + table.put (this, this); + } + + /** + * Parses a 'children' spec (or recursively 'cp') and makes this + * become a regular graph node. + * + * @return index after this particle + */ + private int populate (char parseBuf [], int startPos) + { + int nextPos = startPos + 1; + char c; + + if (nextPos < 0 || nextPos >= parseBuf.length) + throw new IndexOutOfBoundsException (); + + // Grammar of the string is from the XML spec, but + // with whitespace removed by the SAX parser. + + // children ::= (choice | seq) ('?' | '*' | '+')? + // cp ::= (Name | choice | seq) ('?' | '*' | '+')? + // choice ::= '(' cp ('|' choice)* ')' + // seq ::= '(' cp (',' choice)* ')' + + // interior nodes only + // cp ::= name ... + if (parseBuf [startPos] != '('/*)*/) { + boolean done = false; + do { + switch (c = parseBuf [nextPos]) { + case '?': case '*': case '+': + case '|': case ',': + case /*(*/ ')': + done = true; + continue; + default: + nextPos++; + continue; + } + } while (!done); + name = new String (parseBuf, startPos, nextPos - startPos); + + // interior OR toplevel nodes + // cp ::= choice .. + // cp ::= seq .. + } else { + // collect everything as a separate list, and merge it + // into "this" later if we can (SEQUENCE or singleton) + ChildrenRecognizer first; + + first = new ChildrenRecognizer (consumer, type); + nextPos = first.populate (parseBuf, nextPos); + c = parseBuf [nextPos++]; + + if (c == ',' || c == '|') { + ChildrenRecognizer current = first; + char separator = c; + Vector v = null; + + if (separator == '|') { + v = new Vector (); + v.addElement (first); + } + + do { + ChildrenRecognizer link; + + link = new ChildrenRecognizer (consumer, type); + nextPos = link.populate (parseBuf, nextPos); + + if (separator == ',') { + current.patchNext (link, null); + current = link; + } else + v.addElement (link); + + c = parseBuf [nextPos++]; + } while (c == separator); + + // choice ... collect everything into one array. + if (separator == '|') { + // assert v.size() > 1 + components = new Recognizer [v.size ()]; + for (int i = 0; i < components.length; i++) { + components [i] = (Recognizer) + v.elementAt (i); + } + // assert flags == 0 + + // sequence ... merge into "this" to be smaller. + } else + copyIn (first); + + // treat singletons like one-node sequences. + } else + copyIn (first); + + if (c != /*(*/ ')') + throw new RuntimeException ("corrupt content model"); + } + + // + // Arity is optional, and the root of all fun. We keep the + // FSM state graph simple by only having NAME/SEQUENCE and + // CHOICE nodes (or EMPTY to terminate a model), easily + // evaluated. So we rewrite each node that has arity, using + // those primitives. We create loops here, if needed. + // + if (nextPos < parseBuf.length) { + c = parseBuf [nextPos]; + if (c == '?' || c == '*' || c == '+') { + nextPos++; + + // Rewrite 'zero-or-one' "?" arity to a CHOICE: + // - SEQUENCE (clone, what's next) + // - or, what's next + // Size cost: N --> N + 1 + if (c == '?') { + Recognizer once = shallowClone (); + + components = new Recognizer [2]; + components [0] = once; + // components [1] initted to null + name = null; + next = null; + flags = 0; + + + // Rewrite 'zero-or-more' "*" arity to a CHOICE. + // - LOOP (clone, back to this CHOICE) + // - or, what's next + // Size cost: N --> N + 1 + } else if (c == '*') { + ChildrenRecognizer loop = shallowClone (); + + loop.patchNext (this, null); + loop.flags |= F_LOOPNEXT; + flags = F_LOOPHEAD; + + components = new Recognizer [2]; + components [0] = loop; + // components [1] initted to null + name = null; + next = null; + + + // Rewrite 'one-or-more' "+" arity to a SEQUENCE. + // Basically (a)+ --> ((a),(a)*). + // - this + // - CHOICE + // * LOOP (clone, back to the CHOICE) + // * or, whatever's next + // Size cost: N --> 2N + 1 + } else if (c == '+') { + ChildrenRecognizer loop = deepClone (); + ChildrenRecognizer choice; + + choice = new ChildrenRecognizer (consumer, type); + loop.patchNext (choice, null); + loop.flags |= F_LOOPNEXT; + choice.flags = F_LOOPHEAD; + + choice.components = new Recognizer [2]; + choice.components [0] = loop; + // choice.components [1] initted to null + // choice.name, choice.next initted to null + + patchNext (choice, null); + } + } + } + + return nextPos; + } + + // VC: Element Valid (second clause) + boolean acceptCharacters () + { return false; } + + // VC: Element Valid (second clause) + Recognizer acceptElement (String type) + throws SAXException + { + // NAME/SEQUENCE + if (name != null) { + if (name.equals (type)) + return next; + return null; + } + + // CHOICE ... optionally reporting nondeterminism we + // run across. we won't check out every transition + // for nondeterminism; only the ones we follow. + Recognizer retval = null; + + for (int i = 0; i < components.length; i++) { + Recognizer temp = components [i].acceptElement (type); + + if (temp == null) + continue; + else if (!warnNonDeterministic) + return temp; + else if (retval == null) + retval = temp; + else if (retval != temp) + consumer.error ("Content model " + this.type.model + + " is non-deterministic for " + type); + } + return retval; + } + + // VC: Element Valid (second clause) + boolean completed () + throws SAXException + { + // expecting a specific element + if (name != null) + return false; + + // choice, some sequences + for (int i = 0; i < components.length; i++) { + if (components [i].completed ()) + return true; + } + + return false; + } + +/** / + // FOR DEBUGGING ... flattens the graph for printing. + + public String toString () + { + StringBuffer buf = new StringBuffer (); + + // only one set of loop labels can be generated + // at a time... + synchronized (ANY) { + nodeCount = 0; + + toString (buf, new Hashtable ()); + return buf.toString (); + } + } + + private void toString (StringBuffer buf, Hashtable table) + { + // When we visit a node, label and count it. + // Nodes are never visited/counted more than once. + // For small models labels waste space, but if arity + // mappings were used the savings are substantial. + // (Plus, the output can be more readily understood.) + String temp = (String) table.get (this); + + if (temp != null) { + buf.append ('{'); + buf.append (temp); + buf.append ('}'); + return; + } else { + StringBuffer scratch = new StringBuffer (15); + + if ((flags & F_LOOPHEAD) != 0) + scratch.append ("loop"); + else + scratch.append ("node"); + scratch.append ('-'); + scratch.append (++nodeCount); + temp = scratch.toString (); + + table.put (this, temp); + buf.append ('['); + buf.append (temp); + buf.append (']'); + buf.append (':'); + } + + // NAME/SEQUENCE + if (name != null) { + // n.b. some output encodings turn some name chars into '?' + // e.g. with Japanese names and ASCII output + buf.append (name); + if (components != null) // bug! + buf.append ('$'); + if (next == null) + buf.append (",*"); + else if (next instanceof EmptyRecognizer) // patch-to-next + buf.append (",{}"); + else if (next instanceof ChildrenRecognizer) { + buf.append (','); + ((ChildrenRecognizer)next).toString (buf, table); + } else // bug! + buf.append (",+"); + return; + } + + // CHOICE + buf.append ("<"); + for (int i = 0; i < components.length; i++) { + if (i != 0) + buf.append ("|"); + if (components [i] instanceof EmptyRecognizer) { + buf.append ("{}"); + } else if (components [i] == null) { // patch-to-next + buf.append ('*'); + } else { + ChildrenRecognizer r; + + r = (ChildrenRecognizer) components [i]; + r.toString (buf, table); + } + } + buf.append (">"); + } +/**/ + } +} diff --git a/libjava/gnu/xml/pipeline/WellFormednessFilter.java b/libjava/gnu/xml/pipeline/WellFormednessFilter.java new file mode 100644 index 0000000..3047ae3 --- /dev/null +++ b/libjava/gnu/xml/pipeline/WellFormednessFilter.java @@ -0,0 +1,362 @@ +/* WellFormednessFilter.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.pipeline; + +import java.util.EmptyStackException; +import java.util.Stack; + +import gnu.xml.aelfred2.SAXDriver; +import org.xml.sax.*; +import org.xml.sax.ext.*; + + +/** + * This filter reports fatal exceptions in the case of event streams that + * are not well formed. The rules currently tested include: <ul> + * + * <li>setDocumentLocator ... may be called only before startDocument + * + * <li>startDocument/endDocument ... must be paired, and all other + * calls (except setDocumentLocator) must be nested within these. + * + * <li>startElement/endElement ... must be correctly paired, and + * may never appear within CDATA sections. + * + * <li>comment ... can't contain "--" + * + * <li>character data ... can't contain "]]>" + * + * <li>whitespace ... can't contain CR + * + * <li>whitespace and character data must be within an element + * + * <li>processing instruction ... can't contain "?>" or CR + * + * <li>startCDATA/endCDATA ... must be correctly paired. + * + * </ul> + * + * <p> Other checks for event stream correctness may be provided in + * the future. For example, insisting that + * entity boundaries nest correctly, + * namespace scopes nest correctly, + * namespace values never contain relative URIs, + * attributes don't have "<" characters; + * and more. + * + * @author David Brownell + */ +public final class WellFormednessFilter extends EventFilter +{ + private boolean startedDoc; + private Stack elementStack = new Stack (); + private boolean startedCDATA; + private String dtdState = "before"; + + + /** + * Swallows all events after performing well formedness checks. + */ + // constructor used by PipelineFactory + public WellFormednessFilter () + { this (null); } + + + /** + * Passes events through to the specified consumer, after first + * processing them. + */ + // constructor used by PipelineFactory + public WellFormednessFilter (EventConsumer consumer) + { + super (consumer); + + setContentHandler (this); + setDTDHandler (this); + + try { + setProperty (LEXICAL_HANDLER, this); + } catch (SAXException e) { /* can't happen */ } + } + + /** + * Resets state as if any preceding event stream was well formed. + * Particularly useful if it ended through some sort of error, + * and the endDocument call wasn't made. + */ + public void reset () + { + startedDoc = false; + startedCDATA = false; + elementStack.removeAllElements (); + } + + + private SAXParseException getException (String message) + { + SAXParseException e; + Locator locator = getDocumentLocator (); + + if (locator == null) + return new SAXParseException (message, null, null, -1, -1); + else + return new SAXParseException (message, locator); + } + + private void fatalError (String message) + throws SAXException + { + SAXParseException e = getException (message); + ErrorHandler handler = getErrorHandler (); + + if (handler != null) + handler.fatalError (e); + throw e; + } + + /** + * Throws an exception when called after startDocument. + * + * @param locator the locator, to be used in error reporting or relative + * URI resolution. + * + * @exception IllegalStateException when called after the document + * has already been started + */ + public void setDocumentLocator (Locator locator) + { + if (startedDoc) + throw new IllegalStateException ( + "setDocumentLocator called after startDocument"); + super.setDocumentLocator (locator); + } + + public void startDocument () throws SAXException + { + if (startedDoc) + fatalError ("startDocument called more than once"); + startedDoc = true; + startedCDATA = false; + elementStack.removeAllElements (); + super.startDocument (); + } + + public void startElement ( + String uri, String localName, + String qName, Attributes atts + ) throws SAXException + { + if (!startedDoc) + fatalError ("callback outside of document?"); + if ("inside".equals (dtdState)) + fatalError ("element inside DTD?"); + else + dtdState = "after"; + if (startedCDATA) + fatalError ("element inside CDATA section"); + if (qName == null || "".equals (qName)) + fatalError ("startElement name missing"); + elementStack.push (qName); + super.startElement (uri, localName, qName, atts); + } + + public void endElement (String uri, String localName, String qName) + throws SAXException + { + if (!startedDoc) + fatalError ("callback outside of document?"); + if (startedCDATA) + fatalError ("element inside CDATA section"); + if (qName == null || "".equals (qName)) + fatalError ("endElement name missing"); + + try { + String top = (String) elementStack.pop (); + + if (!qName.equals (top)) + fatalError ("<" + top + " ...>...</" + qName + ">"); + // XXX could record/test namespace info + } catch (EmptyStackException e) { + fatalError ("endElement without startElement: </" + qName + ">"); + } + super.endElement (uri, localName, qName); + } + + public void endDocument () throws SAXException + { + if (!startedDoc) + fatalError ("callback outside of document?"); + dtdState = "before"; + startedDoc = false; + super.endDocument (); + } + + + public void startDTD (String root, String publicId, String systemId) + throws SAXException + { + if (!startedDoc) + fatalError ("callback outside of document?"); + if ("before" != dtdState) + fatalError ("two DTDs?"); + if (!elementStack.empty ()) + fatalError ("DTD must precede root element"); + dtdState = "inside"; + super.startDTD (root, publicId, systemId); + } + + public void notationDecl (String name, String publicId, String systemId) + throws SAXException + { +// FIXME: not all parsers will report startDTD() ... +// we'd rather insist we're "inside". + if ("after" == dtdState) + fatalError ("not inside DTD"); + super.notationDecl (name, publicId, systemId); + } + + public void unparsedEntityDecl (String name, + String publicId, String systemId, String notationName) + throws SAXException + { +// FIXME: not all parsers will report startDTD() ... +// we'd rather insist we're "inside". + if ("after" == dtdState) + fatalError ("not inside DTD"); + super.unparsedEntityDecl (name, publicId, systemId, notationName); + } + + // FIXME: add the four DeclHandler calls too + + public void endDTD () + throws SAXException + { + if (!startedDoc) + fatalError ("callback outside of document?"); + if ("inside" != dtdState) + fatalError ("DTD ends without start?"); + dtdState = "after"; + super.endDTD (); + } + + public void characters (char ch [], int start, int length) + throws SAXException + { + int here = start, end = start + length; + if (elementStack.empty ()) + fatalError ("characters must be in an element"); + while (here < end) { + if (ch [here++] != ']') + continue; + if (here == end) // potential problem ... + continue; + if (ch [here++] != ']') + continue; + if (here == end) // potential problem ... + continue; + if (ch [here++] == '>') + fatalError ("character data can't contain \"]]>\""); + } + super.characters (ch, start, length); + } + + public void ignorableWhitespace (char ch [], int start, int length) + throws SAXException + { + int here = start, end = start + length; + if (elementStack.empty ()) + fatalError ("characters must be in an element"); + while (here < end) { + if (ch [here++] == '\r') + fatalError ("whitespace can't contain CR"); + } + super.ignorableWhitespace (ch, start, length); + } + + public void processingInstruction (String target, String data) + throws SAXException + { + if (data.indexOf ('\r') > 0) + fatalError ("PIs can't contain CR"); + if (data.indexOf ("?>") > 0) + fatalError ("PIs can't contain \"?>\""); + } + + public void comment (char ch [], int start, int length) + throws SAXException + { + if (!startedDoc) + fatalError ("callback outside of document?"); + if (startedCDATA) + fatalError ("comments can't nest in CDATA"); + int here = start, end = start + length; + while (here < end) { + if (ch [here] == '\r') + fatalError ("comments can't contain CR"); + if (ch [here++] != '-') + continue; + if (here == end) + fatalError ("comments can't end with \"--->\""); + if (ch [here++] == '-') + fatalError ("comments can't contain \"--\""); + } + super.comment (ch, start, length); + } + + public void startCDATA () + throws SAXException + { + if (!startedDoc) + fatalError ("callback outside of document?"); + if (startedCDATA) + fatalError ("CDATA starts can't nest"); + startedCDATA = true; + super.startCDATA (); + } + + public void endCDATA () + throws SAXException + { + if (!startedDoc) + fatalError ("callback outside of document?"); + if (!startedCDATA) + fatalError ("CDATA end without start?"); + startedCDATA = false; + super.endCDATA (); + } +} diff --git a/libjava/gnu/xml/pipeline/XIncludeFilter.java b/libjava/gnu/xml/pipeline/XIncludeFilter.java new file mode 100644 index 0000000..efa05d9 --- /dev/null +++ b/libjava/gnu/xml/pipeline/XIncludeFilter.java @@ -0,0 +1,580 @@ +/* XIncludeFilter.java -- + Copyright (C) 2001,2002 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.pipeline; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLConnection; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Stack; +import java.util.Vector; + +import org.xml.sax.Attributes; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; + +import gnu.xml.util.Resolver; + + + +/** + * Filter to process an XPointer-free subset of + * <a href="http://www.w3.org/TR/xinclude">XInclude</a>, supporting its + * use as a kind of replacement for parsed general entities. + * XInclude works much like the <code>#include</code> of C/C++ but + * works for XML documents as well as unparsed text files. + * Restrictions from the 17-Sept-2002 CR draft of XInclude are as follows: + * + * <ul> + * + * <li> URIs must not include fragment identifiers. + * The CR specifies support for XPointer <em>element()</em> fragment IDs, + * which is not currently implemented here. + * + * <li> <em>xi:fallback</em> handling of resource errors is not + * currently supported. + * + * <li> DTDs are not supported in included files, since the SAX DTD events + * must have completely preceded any included file. + * The CR explicitly allows the DTD related portions of the infoset to + * grow as an effect of including XML documents. + * + * <li> <em>xml:base</em> fixup isn't done. + * + * </ul> + * + * <p> XML documents that are included will normally be processed using + * the default SAX namespace rules, meaning that prefix information may + * be discarded. This may be changed with {@link #setSavingPrefixes + * setSavingPrefixes()}. <em>You are strongly advised to do this.</em> + * + * <p> Note that XInclude allows highly incompatible implementations, which + * are specialized to handle application-specific infoset extensions. Some + * such implementations can be implemented by subclassing this one, but + * they may only be substituted in applications at "user option". + * + * <p>TBD: "IURI" handling. + * + * @author David Brownell + */ +public class XIncludeFilter extends EventFilter implements Locator +{ + private Hashtable extEntities = new Hashtable (5, 5); + private int ignoreCount; + private Stack uris = new Stack (); + private Locator locator; + private Vector inclusions = new Vector (5, 5); + private boolean savingPrefixes; + + /** + */ + public XIncludeFilter (EventConsumer next) + throws SAXException + { + super (next); + setContentHandler (this); + // DTDHandler callbacks pass straight through + setProperty (DECL_HANDLER, this); + setProperty (LEXICAL_HANDLER, this); + } + + private void fatal (SAXParseException e) throws SAXException + { + ErrorHandler eh; + + eh = getErrorHandler (); + if (eh != null) + eh.fatalError (e); + throw e; + } + + /** + * Passes "this" down the filter chain as a proxy locator. + */ + public void setDocumentLocator (Locator locator) + { + this.locator = locator; + super.setDocumentLocator (this); + } + + /** Used for proxy locator; do not call directly. */ + public String getSystemId () + { return (locator == null) ? null : locator.getSystemId (); } + /** Used for proxy locator; do not call directly. */ + public String getPublicId () + { return (locator == null) ? null : locator.getPublicId (); } + /** Used for proxy locator; do not call directly. */ + public int getLineNumber () + { return (locator == null) ? -1 : locator.getLineNumber (); } + /** Used for proxy locator; do not call directly. */ + public int getColumnNumber () + { return (locator == null) ? -1 : locator.getColumnNumber (); } + + /** + * Assigns the flag controlling the setting of the SAX2 + * <em>namespace-prefixes</em> flag. + */ + public void setSavingPrefixes (boolean flag) + { savingPrefixes = flag; } + + /** + * Returns the flag controlling the setting of the SAX2 + * <em>namespace-prefixes</em> flag when parsing included documents. + * The default value is the SAX2 default (false), which discards + * information that can be useful. + */ + public boolean isSavingPrefixes () + { return savingPrefixes; } + + // + // Two mechanisms are interacting here. + // + // - XML Base implies a stack of base URIs, updated both by + // "real entity" boundaries and element boundaries. + // + // - Active "Real Entities" (for document and general entities, + // and by xincluded files) are tracked to prevent circular + // inclusions. + // + private String addMarker (String uri) + throws SAXException + { + if (locator != null && locator.getSystemId () != null) + uri = locator.getSystemId (); + + // guard against InputSource objects without system IDs + if (uri == null) + fatal (new SAXParseException ("Entity URI is unknown", locator)); + + try { + URL url = new URL (uri); + + uri = url.toString (); + if (inclusions.contains (uri)) + fatal (new SAXParseException ( + "XInclude, circular inclusion", locator)); + inclusions.addElement (uri); + uris.push (url); + } catch (IOException e) { + // guard against illegal relative URIs (Xerces) + fatal (new SAXParseException ("parser bug: relative URI", + locator, e)); + } + return uri; + } + + private void pop (String uri) + { + inclusions.removeElement (uri); + uris.pop (); + } + + // + // Document entity boundaries get both treatments. + // + public void startDocument () throws SAXException + { + ignoreCount = 0; + addMarker (null); + super.startDocument (); + } + + public void endDocument () throws SAXException + { + inclusions.setSize (0); + extEntities.clear (); + uris.setSize (0); + super.endDocument (); + } + + // + // External general entity boundaries get both treatments. + // + public void externalEntityDecl (String name, + String publicId, String systemId) + throws SAXException + { + if (name.charAt (0) == '%') + return; + try { + URL url = new URL (locator.getSystemId ()); + systemId = new URL (url, systemId).toString (); + } catch (IOException e) { + // what could we do? + } + extEntities.put (name, systemId); + } + + public void startEntity (String name) + throws SAXException + { + if (ignoreCount != 0) { + ignoreCount++; + return; + } + + String uri = (String) extEntities.get (name); + if (uri != null) + addMarker (uri); + super.startEntity (name); + } + + public void endEntity (String name) + throws SAXException + { + if (ignoreCount != 0) { + if (--ignoreCount != 0) + return; + } + + String uri = (String) extEntities.get (name); + + if (uri != null) + pop (uri); + super.endEntity (name); + } + + // + // element boundaries only affect the base URI stack, + // unless they're XInclude elements. + // + public void + startElement (String uri, String localName, String qName, Attributes atts) + throws SAXException + { + if (ignoreCount != 0) { + ignoreCount++; + return; + } + + URL baseURI = (URL) uris.peek (); + String base; + + base = atts.getValue ("http://www.w3.org/XML/1998/namespace", "base"); + if (base == null) + uris.push (baseURI); + else { + URL url; + + if (base.indexOf ('#') != -1) + fatal (new SAXParseException ( + "xml:base with fragment: " + base, + locator)); + + try { + baseURI = new URL (baseURI, base); + uris.push (baseURI); + } catch (Exception e) { + fatal (new SAXParseException ( + "xml:base with illegal uri: " + base, + locator, e)); + } + } + + if (!"http://www.w3.org/2001/XInclude".equals (uri)) { + super.startElement (uri, localName, qName, atts); + return; + } + + if ("include".equals (localName)) { + String href = atts.getValue ("href"); + String parse = atts.getValue ("parse"); + String encoding = atts.getValue ("encoding"); + URL url = (URL) uris.peek (); + SAXParseException x = null; + + if (href == null) + fatal (new SAXParseException ( + "XInclude missing href", + locator)); + if (href.indexOf ('#') != -1) + fatal (new SAXParseException ( + "XInclude with fragment: " + href, + locator)); + + if (parse == null || "xml".equals (parse)) + x = xinclude (url, href); + else if ("text".equals (parse)) + x = readText (url, href, encoding); + else + fatal (new SAXParseException ( + "unknown XInclude parsing mode: " + parse, + locator)); + if (x == null) { + // strip out all child content + ignoreCount++; + return; + } + + // FIXME the 17-Sept-2002 CR of XInclude says we "must" + // use xi:fallback elements to handle resource errors, + // if they exist. + fatal (x); + + } else if ("fallback".equals (localName)) { + fatal (new SAXParseException ( + "illegal top level XInclude 'fallback' element", + locator)); + } else { + ErrorHandler eh = getErrorHandler (); + + // CR doesn't say this is an error + if (eh != null) + eh.warning (new SAXParseException ( + "unrecognized toplevel XInclude element: " + localName, + locator)); + super.startElement (uri, localName, qName, atts); + } + } + + public void endElement (String uri, String localName, String qName) + throws SAXException + { + if (ignoreCount != 0) { + if (--ignoreCount != 0) + return; + } + + uris.pop (); + if (!("http://www.w3.org/2001/XInclude".equals (uri) + && "include".equals (localName))) + super.endElement (uri, localName, qName); + } + + // + // ignore all content within non-empty xi:include elements + // + public void characters (char ch [], int start, int length) + throws SAXException + { + if (ignoreCount == 0) + super.characters (ch, start, length); + } + + public void processingInstruction (String target, String value) + throws SAXException + { + if (ignoreCount == 0) + super.processingInstruction (target, value); + } + + public void ignorableWhitespace (char ch [], int start, int length) + throws SAXException + { + if (ignoreCount == 0) + super.ignorableWhitespace (ch, start, length); + } + + public void comment (char ch [], int start, int length) + throws SAXException + { + if (ignoreCount == 0) + super.comment (ch, start, length); + } + + public void startCDATA () throws SAXException + { + if (ignoreCount == 0) + super.startCDATA (); + } + + public void endCDATA () throws SAXException + { + if (ignoreCount == 0) + super.endCDATA (); + } + + public void startPrefixMapping (String prefix, String uri) + throws SAXException + { + if (ignoreCount == 0) + super.startPrefixMapping (prefix, uri); + } + + public void endPrefixMapping (String prefix) throws SAXException + { + if (ignoreCount == 0) + super.endPrefixMapping (prefix); + } + + public void skippedEntity (String name) throws SAXException + { + if (ignoreCount == 0) + super.skippedEntity (name); + } + + // JDK 1.1 seems to need it to be done this way, sigh + void setLocator (Locator l) { locator = l; } + Locator getLocator () { return locator; } + + + // + // for XIncluded entities, manage the current locator and + // filter out events that would be incorrect to report + // + private class Scrubber extends EventFilter + { + Scrubber (EventFilter f) + throws SAXException + { + // delegation passes to next in chain + super (f); + + // process all content events + super.setContentHandler (this); + super.setProperty (LEXICAL_HANDLER, this); + + // drop all DTD events + super.setDTDHandler (null); + super.setProperty (DECL_HANDLER, null); + } + + // maintain proxy locator + // only one startDocument()/endDocument() pair per event stream + public void setDocumentLocator (Locator l) + { setLocator (l); } + public void startDocument () + { } + public void endDocument () + { } + + private void reject (String message) throws SAXException + { fatal (new SAXParseException (message, getLocator ())); } + + // only the DTD from the "base document" gets reported + public void startDTD (String root, String publicId, String systemId) + throws SAXException + { reject ("XIncluded DTD: " + systemId); } + public void endDTD () + throws SAXException + { reject ("XIncluded DTD"); } + // ... so this should never happen + public void skippedEntity (String name) throws SAXException + { reject ("XInclude skipped entity: " + name); } + + // since we rejected DTDs, only builtin entities can be reported + } + + // <xi:include parse='xml' ...> + // relative to the base URI passed + private SAXParseException xinclude (URL url, String href) + throws SAXException + { + XMLReader helper; + Scrubber scrubber; + Locator savedLocator = locator; + + // start with a parser acting just like our input + // modulo DTD-ish stuff (validation flag, entity resolver) + helper = XMLReaderFactory.createXMLReader (); + helper.setErrorHandler (getErrorHandler ()); + helper.setFeature (FEATURE_URI + "namespace-prefixes", true); + + // Set up the proxy locator and event filter. + scrubber = new Scrubber (this); + locator = null; + bind (helper, scrubber); + + // Merge the included document, except its DTD + try { + url = new URL (url, href); + href = url.toString (); + + if (inclusions.contains (href)) + fatal (new SAXParseException ( + "XInclude, circular inclusion", locator)); + + inclusions.addElement (href); + uris.push (url); + helper.parse (new InputSource (href)); + return null; + } catch (java.io.IOException e) { + return new SAXParseException (href, locator, e); + } finally { + pop (href); + locator = savedLocator; + } + } + + // <xi:include parse='text' ...> + // relative to the base URI passed + private SAXParseException readText (URL url, String href, String encoding) + throws SAXException + { + InputStream in = null; + + try { + URLConnection conn; + InputStreamReader reader; + char buf [] = new char [4096]; + int count; + + url = new URL (url, href); + conn = url.openConnection (); + in = conn.getInputStream (); + if (encoding == null) + encoding = Resolver.getEncoding (conn.getContentType ()); + if (encoding == null) { + ErrorHandler eh = getErrorHandler (); + if (eh != null) + eh.warning (new SAXParseException ( + "guessing text encoding for URL: " + url, + locator)); + reader = new InputStreamReader (in); + } else + reader = new InputStreamReader (in, encoding); + + while ((count = reader.read (buf, 0, buf.length)) != -1) + super.characters (buf, 0, count); + in.close (); + return null; + } catch (IOException e) { + return new SAXParseException ( + "can't XInclude text", + locator, e); + } + } +} diff --git a/libjava/gnu/xml/pipeline/XsltFilter.java b/libjava/gnu/xml/pipeline/XsltFilter.java new file mode 100644 index 0000000..b1bebbe --- /dev/null +++ b/libjava/gnu/xml/pipeline/XsltFilter.java @@ -0,0 +1,131 @@ +/* XsltFilter.java -- + Copyright (C) 2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.pipeline; + +import java.io.IOException; + +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.sax.*; +import javax.xml.transform.stream.StreamSource; + +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.ext.LexicalHandler; + + +/** + * Packages an XSLT transform as a pipeline component. + * Note that all DTD events (callbacks to DeclHandler and DTDHandler + * interfaces) are discarded, although XSLT transforms may be set up to + * use the LexicalHandler to write DTDs with only an external subset. + * Not every XSLT engine will necessarily be usable with this filter, + * but current versions of + * <a href="http://saxon.sourceforge.net">SAXON</a> and + * <a href="http://xml.apache.org/xalan-j">Xalan</a> should work well. + * + * @see TransformerFactory + * + * @author David Brownell + */ +final public class XsltFilter extends EventFilter +{ + /** + * Creates a filter that performs the specified transform. + * Uses the JAXP 1.1 interfaces to access the default XSLT + * engine configured for in the current execution context, + * and parses the stylesheet without custom EntityResolver + * or ErrorHandler support. + * + * @param stylesheet URI for the stylesheet specifying the + * XSLT transform + * @param next provides the ContentHandler and LexicalHandler + * to receive XSLT output. + * @exception SAXException if the stylesheet can't be parsed + * @exception IOException if there are difficulties + * bootstrapping the XSLT engine, such as it not supporting + * SAX well enough to use this way. + */ + public XsltFilter (String stylesheet, EventConsumer next) + throws SAXException, IOException + { + // First, get a transformer with the stylesheet preloaded + TransformerFactory tf = null; + TransformerHandler th; + + try { + SAXTransformerFactory stf; + + tf = TransformerFactory.newInstance (); + if (!tf.getFeature (SAXTransformerFactory.FEATURE) // sax inputs + || !tf.getFeature (SAXResult.FEATURE) // sax outputs + || !tf.getFeature (StreamSource.FEATURE) // stylesheet + ) + throw new IOException ("XSLT factory (" + + tf.getClass ().getName () + + ") does not support SAX"); + stf = (SAXTransformerFactory) tf; + th = stf.newTransformerHandler (new StreamSource (stylesheet)); + } catch (TransformerConfigurationException e) { + throw new IOException ("XSLT factory (" + + (tf == null + ? "none available" + : tf.getClass ().getName ()) + + ") configuration error, " + + e.getMessage () + ); + } + + // Hook its outputs up to the pipeline ... + SAXResult out = new SAXResult (); + + out.setHandler (next.getContentHandler ()); + try { + LexicalHandler lh; + lh = (LexicalHandler) next.getProperty (LEXICAL_HANDLER); + out.setLexicalHandler (lh); + } catch (Exception e) { + // ignore + } + th.setResult (out); + + // ... and make sure its inputs look like ours. + setContentHandler (th); + setProperty (LEXICAL_HANDLER, th); + } +} diff --git a/libjava/gnu/xml/pipeline/package.html b/libjava/gnu/xml/pipeline/package.html new file mode 100644 index 0000000..352f4c8 --- /dev/null +++ b/libjava/gnu/xml/pipeline/package.html @@ -0,0 +1,255 @@ +<html><head><title> +blah +<!-- +/* + * Copyright (C) 1999-2001 The Free Software Foundation, Inc. + */ +--> +</title></head><body> + +<p>This package exposes a kind of XML processing pipeline, based on sending +SAX events, which can be used as components of application architectures. +Pipelines are used to convey streams of processing events from a producer +to one or more consumers, and to let each consumer control the data seen by +later consumers. + +<p> There is a <a href="PipelineFactory.html">PipelineFactory</a> class which +accepts a syntax describing how to construct some simple pipelines. Strings +describing such pipelines can be used in command line tools (see the +<a href="../util/DoParse.html">DoParse</a> class) +and in other places that it is +useful to let processing be easily reconfigured. Pipelines can of course +be constructed programmatically, providing access to options that the +factory won't. + +<p> Web applications are supported by making it easy for servlets (or +non-Java web application components) to be part of a pipeline. They can +originate XML (or XHTML) data through an <em>InputSource</em> or in +response to XML messages sent from clients using <em>CallFilter</em> +pipeline stages. Such facilities are available using the simple syntax +for pipeline construction. + + +<h2> Programming Models </h2> + +<p> Pipelines should be simple to understand. + +<ul> + <li> XML content, typically entire documents, + is pushed through consumers by producers. + + <li> Pipelines are basically about consuming SAX2 callback events, + where the events encapsulate XML infoset-level data.<ul> + + <li> Pipelines are constructed by taking one or more consumer + stages and combining them to produce a composite consumer. + + <li> A pipeline is presumed to have pending tasks and state from + the beginning of its ContentHandler.startDocument() callback until + it's returned from its ContentHandler.doneDocument() callback. + + <li> Pipelines may have multiple output stages ("fan-out") + or multiple input stages ("fan-in") when appropriate. + + <li> Pipelines may be long-lived, but need not be. + + </ul> + + <li> There is flexibility about event production. <ul> + + <li> SAX2 XMLReader objects are producers, which + provide a high level "pull" model: documents (text or DOM) are parsed, + and the parser pushes individual events through the pipeline. + + <li> Events can be pushed directly to event consumer components + by application modules, if they invoke SAX2 callbacks directly. + That is, application modules use the XML Infoset as exposed + through SAX2 event callbacks. + + </ul> + + <li> Multiple producer threads may concurrently access a pipeline, + if they coordinate appropriately. + + <li> Pipeline processing is not the only framework applications + will use. + + </ul> + + +<h3> Producers: XMLReader or Custom </h3> + +<p> Many producers will be SAX2 XMLReader objects, and +will read (pull) data which is then written (pushed) as events. +Typically these will parse XML text (acquired from +<code>org.xml.sax.helpers.XMLReaderFactory</code>) or a DOM tree +(using a <code><a href="../util/DomParser.html">DomParser</a></code>) +These may be bound to event consumer using a convenience routine, +<em><a href="EventFilter.html">EventFilter</a>.bind()</em>. +Once bound, these producers may be given additional documents to +sent through its pipeline. + +<p> In other cases, you will write producers yourself. For example, some +data structures might know how to write themselves out using one or +more XML models, expressed as sequences of SAX2 event callbacks. +An application module might +itself be a producer, issuing startDocument and endDocument events +and then asking those data structures to write themselves out to a +given EventConsumer, or walking data structures (such as JDBC query +results) and applying its own conversion rules. WAP format XML +(WBMXL) can be directly converted to producer output. + +<p> SAX2 introduced an "XMLFilter" interface, which is a kind of XMLReader. +It is most useful in conjunction with its XMLFilterImpl helper class; +see the <em><a href="EventFilter.html">EventFilter</a></em> javadoc +for information contrasting that XMLFilterImpl approach with the +relevant parts of this pipeline framework. Briefly, such XMLFilterImpl +children can be either producers or consumers, and are more limited in +configuration flexibility. In this framework, the focus of filters is +on the EventConsumer side; see the section on +<a href="#fitting">pipe fitting</a> below. + + +<h3> Consume to Standard or Custom Data Representations </h3> + +<p> Many consumers will be used to create standard representations of XML +data. The <a href="TextConsumer.html">TextConsumer</a> takes its events +and writes them as text for a single XML document, +using an internal <a href="../util/XMLWriter.html">XMLWriter</a>. +The <a href="DomConsumer.html">DomConsumer</a> takes its events and uses +them to create and populate a DOM Document. + +<p> In other cases, you will write consumers yourself. For example, +you might use a particular unmarshaling filter to produce objects +that fit your application's requirements, instead of using DOM. +Such consumers work at the level of XML data models, rather than with +specific representations such as XML text or a DOM tree. You could +convert your output directly to WAP format data (WBXML). + + +<h3><a name="fitting">Pipe Fitting</a></h3> + +<p> Pipelines are composite event consumers, with each stage having +the opportunity to transform the data before delivering it to any +subsequent stages. + +<p> The <a href="PipelineFactory.html">PipelineFactory</a> class +provides access to much of this functionality through a simple syntax. +See the table in that class's javadoc describing a number of standard +components. Direct API calls are still needed for many of the most +interesting pipeline configurations, including ones leveraging actual +or logical concurrency. + +<p> Four basic types of pipe fitting are directly supported. These may +be used to construct complex pipeline networks. <ul> + + <li> <a href="TeeConsumer.html">TeeConsumer</a> objects split event + flow so it goes to two two different consumers, one before the other. + This is a basic form of event fan-out; you can use this class to + copy events to any number of output pipelines. + + <li> Clients can call remote components through HTTP or HTTPS using + the <a href="CallFilter.html">CallFilter</a> component, and Servlets + can implement such components by extending the + <a href="XmlServlet.html">XmlServlet</a> component. Java is not + required on either end, and transport protocols other than HTTP may + also be used. + + <li> <a href="EventFilter.html">EventFilter</a> objects selectively + provide handling for callbacks, and can pass unhandled ones to a + subsequent stage. They are often subclassed, since much of the + basic filtering machinery is already in place in the base class. + + <li> Applications can merge two event flows by just using the same + consumer in each one. If multiple threads are in use, synchronization + needs to be addressed by the appropriate application level policy. + + </ul> + +<p> Note that filters can be as complex as +<a href="XsltFilter.html">XSLT transforms</a> +available) on input data, or as simple as removing simple syntax data +such as ignorable whitespace, comments, and CDATA delimiters. +Some simple "built-in" filters are part of this package. + + +<h3> Coding Conventions: Filter and Terminus Stages</h3> + +<p> If you follow these coding conventions, your classes may be used +directly (give the full class name) in pipeline descriptions as understood +by the PipelineFactory. There are four constructors the factory may +try to use; in order of decreasing numbers of parameters, these are: <ul> + + <li> Filters that need a single String setup parameter should have + a public constructor with two parameters: that string, then the + EventConsumer holding the "next" consumer to get events. + + <li> Filters that don't need setup parameters should have a public + constructor that accepts a single EventConsumer holding the "next" + consumer to get events when they are done. + + <li> Terminus stages may have a public constructor taking a single + paramter: the string value of that parameter. + + <li> Terminus stages may have a public no-parameters constructor. + + </ul> + +<p> Of course, classes may support more than one such usage convention; +if they do, they can automatically be used in multiple modes. If you +try to use a terminus class as a filter, and that terminus has a constructor +with the appropriate number of arguments, it is automatically wrapped in +a "tee" filter. + + +<h2> Debugging Tip: "Tee" Joints can Snapshot Data</h2> + +<p> It can sometimes be hard to see what's happening, when something +goes wrong. Easily fixed: just snapshot the data. Then you can find +out where things start to go wrong. + +<p> If you're using pipeline descriptors so that they're easily +administered, just stick a <em>write ( filename )</em> +filter into the pipeline at an appropriate point. + +<p> Inside your programs, you can do the same thing directly: perhaps +by saving a Writer (perhaps a StringWriter) in a variable, using that +to create a TextConsumer, and making that the first part of a tee -- +splicing that into your pipeline at a convenient location. + +<p> You can also use a DomConsumer to buffer the data, but remember +that DOM doesn't save all the information that XML provides, so that DOM +snapshots are relatively low fidelity. They also are substantially more +expensive in terms of memory than a StringWriter holding similar data. + +<h2> Debugging Tip: Non-XML Producers</h2> + +<p> Producers in pipelines don't need to start from XML +data structures, such as text in XML syntax (likely coming +from some <em>XMLReader</em> that parses XML) or a +DOM representation (perhaps with a +<a href="../util/DomParser.html">DomParser</a>). + +<p> One common type of event producer will instead make +direct calls to SAX event handlers returned from an +<a href="EventConsumer.html">EventConsumer</a>. +For example, making <em>ContentHandler.startElement</em> +calls and matching <em>ContentHandler.endElement</em> calls. + +<p> Applications making such calls can catch certain +common "syntax errors" by using a +<a href="WellFormednessFilter.html">WellFormednessFilter</a>. +That filter will detect (and report) erroneous input data +such as mismatched document, element, or CDATA start/end calls. +Use such a filter near the head of the pipeline that your +producer feeds, at least while debugging, to help ensure that +you're providing legal XML Infoset data. + +<p> You can also arrange to validate data on the fly. +For DTD validation, you can configure a +<a href="ValidationConsumer.html">ValidationConsumer</a> +to work as a filter, using any DTD you choose. +Other validation schemes can be handled with other +validation filters. + +</body></html> diff --git a/libjava/gnu/xml/transform/AbstractNumberNode.java b/libjava/gnu/xml/transform/AbstractNumberNode.java new file mode 100644 index 0000000..097637e --- /dev/null +++ b/libjava/gnu/xml/transform/AbstractNumberNode.java @@ -0,0 +1,321 @@ +/* AbstractNumberNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; +import org.w3c.dom.Node; +import org.w3c.dom.Text; +import gnu.xml.xpath.Expr; + +/** + * A template node representing the XSL <code>number</code> instruction. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +abstract class AbstractNumberNode + extends TemplateNode +{ + + static final int ALPHABETIC = 0; + static final int TRADITIONAL = 1; + + final TemplateNode format; + final String lang; + final int letterValue; + final String groupingSeparator; + final int groupingSize; + + AbstractNumberNode(TemplateNode children, TemplateNode next, + TemplateNode format, String lang, + int letterValue, String groupingSeparator, + int groupingSize) + { + super(children, next); + this.format = format; + this.lang = lang; + this.letterValue = letterValue; + this.groupingSeparator = groupingSeparator; + this.groupingSize = groupingSize; + } + + void doApply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + Document doc = (parent instanceof Document) ? (Document) parent : + parent.getOwnerDocument(); + DocumentFragment fragment = doc.createDocumentFragment(); + format.apply(stylesheet, mode, context, pos, len, fragment, null); + String f = Expr._string(context, Collections.singleton(fragment)); + String value = format(f, compute(stylesheet, context, pos, len)); + Text text = doc.createTextNode(value); + if (nextSibling != null) + { + parent.insertBefore(text, nextSibling); + } + else + { + parent.appendChild(text); + } + // xsl:number doesn't process children + if (next != null) + { + next.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + } + + String format(String format, int[] number) + { + if (number.length == 0) + { + return ""; + } + int start = 0, end = 0, len = format.length(); // region of format + // Tokenize + List tokens = new ArrayList((number.length * 2) + 1); + List types = new ArrayList(tokens.size()); + while (end < len) + { + while (end < len && !isAlphanumeric(format.charAt(end))) + { + end++; + } + if (end > start) + { + tokens.add(format.substring(start, end)); + types.add(Boolean.FALSE); + } + start = end; + while (end < len && isAlphanumeric(format.charAt(end))) + { + end++; + } + if (end > start) + { + tokens.add(format.substring(start, end)); + types.add(Boolean.TRUE); + } + start = end; + } + // Process tokens + StringBuffer buf = new StringBuffer(); + len = tokens.size(); + int pos = 0; + for (int i = 0; i < len; i++) + { + String token = (i < 0) ? "." : (String) tokens.get(i); + boolean alpha = (i < 0) ? true : + ((Boolean) types.get(i)).booleanValue(); + if (!alpha) + { + buf.append(token); + } + else + { + if (pos < number.length) + { + format(buf, number[pos++], token); + if (((i + 1 == len) || (i + 2 == len)) && + (pos < number.length)) + { + // More numbers than tokens, reuse last token + i -= 2; + } + } + if (pos == number.length && i < (len - 2)) + { + // No more numbers. Skip to the end... + i = len - 2; + if (((Boolean) types.get(i + 1)).booleanValue()) + { + // number formatting token, ignore + i++; + } + } + } + } + //System.err.println("format: '"+format+"' "+asList(number)+" = '"+buf.toString()+"'"); + return buf.toString(); + } + + /*List asList(int[] number) + { + List l = new ArrayList(); + for (int i = 0; i < number.length; i++) + l.add(new Integer(number[i])); + return l; + }*/ + + void format(StringBuffer buf, int number, String formatToken) + { + int len = formatToken.length(); + char c = formatToken.charAt(len - 1); + if (Character.digit(c, 10) == 1) + { + // Check preceding characters + for (int i = len - 2; i >= 0; i--) + { + if (formatToken.charAt(i) != (c - 1)) + { + format(buf, number, "1"); + return; + } + } + // Decimal representation + String val = Integer.toString(number); + for (int d = len - val.length(); d > 0; d--) + { + buf.append('0'); + } + buf.append(val); + } + else if ("A".equals(formatToken)) + { + buf.append(alphabetic('@', number)); + } + else if ("a".equals(formatToken)) + { + buf.append(alphabetic('`', number)); + } + else if ("i".equals(formatToken)) + { + buf.append(roman(false, number)); + } + else if ("I".equals(formatToken)) + { + buf.append(roman(true, number)); + } + else + { + // Unknown numbering sequence + format(buf, number, "1"); + } + } + + static final boolean isAlphanumeric(char c) + { + switch (Character.getType(c)) + { + case Character.DECIMAL_DIGIT_NUMBER: // Nd + case Character.LETTER_NUMBER: // Nl + case Character.OTHER_NUMBER: // No + case Character.UPPERCASE_LETTER: // Lu + case Character.LOWERCASE_LETTER: // Ll + case Character.TITLECASE_LETTER: // Lt + case Character.MODIFIER_LETTER: // Lm + case Character.OTHER_LETTER: // Lo + return true; + default: + return false; + } + } + + static final String alphabetic(char offset, int number) + { + StringBuffer buf = new StringBuffer(); + while (number > 0) + { + int r = number % 26; + number = number / 26; + buf.insert(0, (char) (offset + r)); + } + return buf.toString(); + } + + static final int[] roman_numbers = {1, 5, 10, 50, 100, 500, 1000}; + static final char[] roman_chars = {'i', 'v', 'x', 'l', 'c', 'd', 'm'}; + + static final String roman(boolean upper, int number) + { + StringBuffer buf = new StringBuffer(); + for (int pos = roman_numbers.length - 1; pos >= 0; pos -= 2) + { + int f = number / roman_numbers[pos]; + if (f != 0) + { + number = number % (f * roman_numbers[pos]); + } + if (f > 4 && f < 9) + { + buf.append(roman_chars[pos + 1]); + f -= 5; + } + if (f == 4) + { + buf.append(roman_chars[pos]); + buf.append(roman_chars[pos + 1]); + } + else if (f == 9) + { + buf.append(roman_chars[pos]); + buf.append(roman_chars[pos + 2]); + } + else + { + for (; f > 0; f--) + { + buf.append(roman_chars[pos]); + } + } + } + return upper ? buf.toString().toUpperCase() : buf.toString(); + } + + abstract int[] compute(Stylesheet stylesheet, Node context, int pos, int len) + throws TransformerException; + + public String toString() + { + StringBuffer buf = new StringBuffer(getClass().getName()); + buf.append('['); + buf.append("format="); + buf.append(format); + buf.append(']'); + return buf.toString(); + } + +} diff --git a/libjava/gnu/xml/transform/ApplyImportsNode.java b/libjava/gnu/xml/transform/ApplyImportsNode.java new file mode 100644 index 0000000..2b28655 --- /dev/null +++ b/libjava/gnu/xml/transform/ApplyImportsNode.java @@ -0,0 +1,91 @@ +/* ApplyImportsNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.Collection; +import java.util.Iterator; +import java.text.DecimalFormat; +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.Text; +import gnu.xml.xpath.Expr; + +/** + * A template node representing an XSLT <code>apply-imports</code> instruction. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class ApplyImportsNode + extends TemplateNode +{ + + ApplyImportsNode(TemplateNode children, TemplateNode next) + { + super(children, next); + } + + TemplateNode clone(Stylesheet stylesheet) + { + return new ApplyImportsNode((children == null) ? null : + children.clone(stylesheet), + (next == null) ? null : + next.clone(stylesheet)); + } + + void doApply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + TemplateNode t = stylesheet.getTemplate(mode, context, true); + if (t != null) + { + t.apply(stylesheet, mode, context, pos, len, + parent, nextSibling); + } + if (next != null) + { + next.apply(stylesheet, mode, context, pos, len, + parent, nextSibling); + } + } + +} + diff --git a/libjava/gnu/xml/transform/ApplyTemplatesNode.java b/libjava/gnu/xml/transform/ApplyTemplatesNode.java new file mode 100644 index 0000000..83e7c55 --- /dev/null +++ b/libjava/gnu/xml/transform/ApplyTemplatesNode.java @@ -0,0 +1,193 @@ +/* ApplyTemplatesNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; + +/** + * A template node representing the XSL <code>apply-templates</code> + * instruction. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class ApplyTemplatesNode + extends TemplateNode +{ + + final Expr select; + final QName mode; + final List sortKeys; + final List withParams; + final boolean isDefault; + + ApplyTemplatesNode(TemplateNode children, TemplateNode next, + Expr select, QName mode, + List sortKeys, List withParams, boolean isDefault) + { + super(children, next); + this.select = select; + this.mode = mode; + this.sortKeys = sortKeys; + this.withParams = withParams; + this.isDefault = isDefault; + } + + TemplateNode clone(Stylesheet stylesheet) + { + int len = sortKeys.size(); + List sortKeys2 = new ArrayList(len); + for (int i = 0; i < len; i++) + { + sortKeys2.add(((Key) sortKeys.get(i)).clone(stylesheet)); + } + len = withParams.size(); + List withParams2 = new ArrayList(len); + for (int i = 0; i < len; i++) + { + withParams2.add(((WithParam) withParams.get(i)).clone(stylesheet)); + } + return new ApplyTemplatesNode((children == null) ? null : + children.clone(stylesheet), + (next == null) ? null : + next.clone(stylesheet), + select.clone(stylesheet), + mode, sortKeys2, withParams2, isDefault); + } + + void doApply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + Object ret = select.evaluate(context, pos, len); + if (ret != null && ret instanceof Collection) + { + if (withParams != null) + { + // push the parameter context + stylesheet.bindings.push(false); + // set the parameters + for (Iterator i = withParams.iterator(); i.hasNext(); ) + { + WithParam p = (WithParam) i.next(); + Object value = p.getValue(stylesheet, mode, context, pos, len); + stylesheet.bindings.set(p.name, value, false); + } + } + Collection ns = (Collection) ret; + List nodes = new ArrayList(ns); + if (sortKeys != null) + { + for (Iterator i = sortKeys.iterator(); i.hasNext(); ) + { + SortKey sortKey = (SortKey) i.next(); + sortKey.init(stylesheet, mode, context, pos, len, parent, + nextSibling); + } + Collections.sort(nodes, new XSLComparator(sortKeys)); + } + else + { + Collections.sort(nodes, documentOrderComparator); + } + int l = nodes.size(); + QName effectiveMode = isDefault ? mode : this.mode; + for (int i = 0; i < l; i++) + { + Node node = (Node) nodes.get(i); + TemplateNode t = stylesheet.getTemplate(effectiveMode, node, + false); + if (t != null) + { + if (stylesheet.debug) + { + System.err.println("Applying " + t); + } + stylesheet.current = node; + t.apply(stylesheet, effectiveMode, node, i + 1, l, + parent, nextSibling); + } + } + if (withParams != null) + { + // pop the variable context + stylesheet.bindings.pop(false); + } + } + // apply-templates doesn't have processable children + if (next != null) + { + next.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + } + + public String toString() + { + StringBuffer buf = new StringBuffer(getClass().getName()); + buf.append('['); + boolean o = false; + if (select != null) + { + buf.append("select="); + buf.append(select); + o = true; + } + if (mode != null) + { + if (o) + { + buf.append(','); + } + buf.append("mode="); + buf.append(mode); + } + buf.append(']'); + return buf.toString(); + } + +} diff --git a/libjava/gnu/xml/transform/AttributeNode.java b/libjava/gnu/xml/transform/AttributeNode.java new file mode 100644 index 0000000..c4409db --- /dev/null +++ b/libjava/gnu/xml/transform/AttributeNode.java @@ -0,0 +1,246 @@ +/* AttributeNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import javax.xml.XMLConstants; +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; +import org.w3c.dom.Attr; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; + +/** + * A template node representing an XSL <code>attribute</code> instruction. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class AttributeNode + extends TemplateNode +{ + + final TemplateNode name; + final TemplateNode namespace; + final Node source; + + AttributeNode(TemplateNode children, TemplateNode next, TemplateNode name, + TemplateNode namespace, Node source) + { + super(children, next); + this.name = name; + this.namespace = namespace; + this.source = source; + } + + TemplateNode clone(Stylesheet stylesheet) + { + return new AttributeNode((children == null) ? null : + children.clone(stylesheet), + (next == null) ? null : next.clone(stylesheet), + name.clone(stylesheet), + (namespace == null) ? null : + namespace.clone(stylesheet), + source); + } + + void doApply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + Document doc = (parent instanceof Document) ? (Document) parent : + parent.getOwnerDocument(); + // Create a document fragment to hold the name + DocumentFragment fragment = doc.createDocumentFragment(); + // Apply name to the fragment + name.apply(stylesheet, mode, + context, pos, len, + fragment, null); + // Use XPath string-value of fragment + String nameValue = Expr.stringValue(fragment); + + String namespaceValue = null; + if (namespace != null) + { + // Create a document fragment to hold the namespace + fragment = doc.createDocumentFragment(); + // Apply namespace to the fragment + namespace.apply(stylesheet, mode, + context, pos, len, + fragment, null); + // Use XPath string-value of fragment + namespaceValue = Expr.stringValue(fragment); + if (namespaceValue.length() == 0) + { + namespaceValue = null; + } + } + + String prefix = getPrefix(nameValue); + if (namespaceValue == null) + { + if (prefix != null) + { + if (XMLConstants.XML_NS_PREFIX.equals(prefix)) + { + namespaceValue = XMLConstants.XML_NS_URI; + } + else + { + // Resolve namespace for this prefix + namespaceValue = source.lookupNamespaceURI(prefix); + } + } + } + else + { + if (prefix != null) + { + String ns2 = source.lookupNamespaceURI(prefix); + if (ns2 != null && !ns2.equals(namespaceValue)) + { + // prefix clashes, reset it + prefix = null; + int ci = nameValue.indexOf(':'); + nameValue = nameValue.substring(ci + 1); + } + } + } + if (prefix == null) + { + // Resolve prefix for this namespace + prefix = source.lookupPrefix(namespaceValue); + if (prefix != null) + { + nameValue = prefix + ":" + nameValue; + } + else + { + if (namespaceValue != null) + { + // Must invent a prefix + prefix = inventPrefix(parent); + nameValue = prefix + ":" + nameValue; + } + } + } + NamedNodeMap attrs = parent.getAttributes(); + boolean insert = true; + if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceValue) || + XMLConstants.XMLNS_ATTRIBUTE.equals(nameValue) || + nameValue.startsWith("xmlns:")) + { + // Namespace declaration, do not output + insert = false; + } + if (prefix != null && namespaceValue == null) + { + // Not a QName + insert = false; + } + if (parent.getNodeType() == Node.ELEMENT_NODE && + parent.getFirstChild() != null) + { + // XSLT 7.1.3 Adding an attribute to an element after children have + // been added to it is an error + insert = false; + } + if (insert) + { + // Insert attribute + Attr attr = (namespaceValue != null) ? + doc.createAttributeNS(namespaceValue, nameValue) : + doc.createAttribute(nameValue); + if (attrs != null) + { + if (namespace != null) + { + attrs.setNamedItemNS(attr); + } + else + { + attrs.setNamedItem(attr); + } + } + if (children != null) + { + children.apply(stylesheet, mode, + context, pos, len, + attr, null); + } + } + if (next != null) + { + next.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + } + + final String getPrefix(String name) + { + int ci = name.indexOf(':'); + return (ci == -1) ? null : name.substring(0, ci); + } + + final String inventPrefix(Node parent) + { + String base = "ns"; + int count = 0; + String ret = base + Integer.toString(count); + while (parent.lookupNamespaceURI(ret) != null) + { + count++; + ret = base + Integer.toString(count); + } + return ret; + } + + public String toString() + { + StringBuffer buf = new StringBuffer(getClass().getName()); + buf.append('['); + buf.append("name="); + buf.append(name); + buf.append(']'); + return buf.toString(); + } + +} diff --git a/libjava/gnu/xml/transform/AttributeSet.java b/libjava/gnu/xml/transform/AttributeSet.java new file mode 100644 index 0000000..92869d1 --- /dev/null +++ b/libjava/gnu/xml/transform/AttributeSet.java @@ -0,0 +1,67 @@ +/* AttributeSet.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +/** + * An attribute-set entry in a stylesheet. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class AttributeSet +{ + + final TemplateNode children; + final String name; + final String uas; + + AttributeSet(TemplateNode children, String name, String uas) + { + this.children = children; + this.name = name; + this.uas = uas; + } + + AttributeSet clone(Stylesheet stylesheet) + { + return new AttributeSet((children == null) ? null : + children.clone(stylesheet), + name, uas); + } + +} + diff --git a/libjava/gnu/xml/transform/Bindings.java b/libjava/gnu/xml/transform/Bindings.java new file mode 100644 index 0000000..f898a72 --- /dev/null +++ b/libjava/gnu/xml/transform/Bindings.java @@ -0,0 +1,246 @@ +/* Bindings.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import javax.xml.xpath.XPathVariableResolver; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; + +/** + * The set of variable bindings in effect for a stylesheet. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class Bindings + implements XPathVariableResolver, Cloneable +{ + + final Stylesheet stylesheet; + + /** + * Global variables. + */ + final LinkedList variables; + + /** + * Parameter value stack. + */ + final LinkedList parameters; + + Bindings(Stylesheet stylesheet) + { + this.stylesheet = stylesheet; + variables = new LinkedList(); + parameters = new LinkedList(); + push(true); + push(false); + } + + public Object clone() + { + try + { + return (Bindings) super.clone(); + } + catch (CloneNotSupportedException e) + { + throw new Error(e.getMessage()); + } + } + + void push(boolean global) + { + if (global) + { + variables.addFirst(new HashMap()); + } + else + { + parameters.addFirst(new HashMap()); + } + } + + void pop(boolean global) + { + if (global) + { + variables.removeFirst(); + } + else + { + parameters.removeFirst(); + } + } + + public boolean containsKey(String name, boolean global) + { + Iterator i = global ? variables.iterator() : parameters.iterator(); + while (i.hasNext()) + { + Map ctx = (Map) i.next(); + if (ctx.containsKey(name)) + { + return true; + } + } + return false; + } + + public Object get(String name, Node context, int pos, int len) + { + //System.err.println("bindings.get: "+name); + //System.err.println("\t"+toString()); + Object ret = null; + for (Iterator i = variables.iterator(); i.hasNext() && ret == null; ) + { + Map vctx = (Map) i.next(); + ret = vctx.get(name); + } + if (ret == null) + { + for (Iterator i = parameters.iterator(); i.hasNext() && ret == null; ) + { + Map pctx = (Map) i.next(); + ret = pctx.get(name); + } + } + /*if (ret instanceof Expr && context != null) + { + Expr expr = (Expr) ret; + ret = expr.evaluate(context, 1, 1); + }*/ + if (ret instanceof Node) + { + ret = Collections.singleton(ret); + } + if (ret == null) + { + ret = ""; + } + //System.err.println("\tret="+ret); + return ret; + } + + void set(String name, Object value, boolean global) + { + if (global) + { + Map context = (Map) variables.getFirst(); + context.put(name, value); + } + else + { + Map context = (Map) parameters.getFirst(); + context.put(name, value); + } + } + + public Object resolveVariable(QName qName) + { + return get(qName.toString(), null, 1, 1); + } + + public String toString() + { + StringBuffer buf = new StringBuffer(); + boolean next = false; + Collection seen = new HashSet(); + buf.append('{'); + for (Iterator i = variables.iterator(); i.hasNext(); ) + { + Map ctx = (Map) i.next(); + for (Iterator j = ctx.entrySet().iterator(); j.hasNext(); ) + { + if (next) + { + buf.append(','); + } + else + { + next = true; + } + Map.Entry entry = (Map.Entry) j.next(); + Object key = entry.getKey(); + if (!seen.contains(key)) + { + buf.append(key); + buf.append('='); + buf.append(entry.getValue()); + seen.add(key); + } + } + } + for (Iterator i = parameters.iterator(); i.hasNext(); ) + { + Map ctx = (Map) i.next(); + for (Iterator j = ctx.entrySet().iterator(); j.hasNext(); ) + { + if (next) + { + buf.append(','); + } + else + { + next = true; + } + Map.Entry entry = (Map.Entry) j.next(); + Object key = entry.getKey(); + if (!seen.contains(key)) + { + buf.append(key); + buf.append('='); + buf.append(entry.getValue()); + seen.add(key); + } + } + } + buf.append('}'); + return buf.toString(); + } +} diff --git a/libjava/gnu/xml/transform/CallTemplateNode.java b/libjava/gnu/xml/transform/CallTemplateNode.java new file mode 100644 index 0000000..ee64e2e --- /dev/null +++ b/libjava/gnu/xml/transform/CallTemplateNode.java @@ -0,0 +1,130 @@ +/* CallTemplateNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Node; + +/** + * A template node representing the XSL <code>call-template</code> + * instruction. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class CallTemplateNode + extends TemplateNode +{ + + final QName name; + final List withParams; + + CallTemplateNode(TemplateNode children, TemplateNode next, + QName name, List withParams) + { + super(children, next); + this.name = name; + this.withParams = withParams; + } + + TemplateNode clone(Stylesheet stylesheet) + { + int len = withParams.size(); + List withParams2 = new ArrayList(len); + for (int i = 0; i < len; i++) + { + withParams2.add(((WithParam) withParams.get(i)).clone(stylesheet)); + } + return new CallTemplateNode((children == null) ? null : + children.clone(stylesheet), + (next == null) ? null : + next.clone(stylesheet), + name, withParams2); + } + + void doApply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + if (withParams != null) + { + // push the parameter context + stylesheet.bindings.push(false); + // set the parameters + for (Iterator i = withParams.iterator(); i.hasNext(); ) + { + WithParam p = (WithParam) i.next(); + Object value = p.getValue(stylesheet, mode, context, pos, len); + stylesheet.bindings.set(p.name, value, false); + } + } + TemplateNode t = stylesheet.getTemplate(mode, name); + if (t != null) + { + t.apply(stylesheet, mode, context, pos, len, + parent, nextSibling); + } + if (withParams != null) + { + // pop the variable context + stylesheet.bindings.pop(false); + } + // call-template doesn't have processable children + if (next != null) + { + next.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + } + + public String toString() + { + StringBuffer buf = new StringBuffer(getClass().getName()); + buf.append('['); + buf.append("name="); + buf.append(name); + buf.append(']'); + return buf.toString(); + } + +} diff --git a/libjava/gnu/xml/transform/ChooseNode.java b/libjava/gnu/xml/transform/ChooseNode.java new file mode 100644 index 0000000..76b0d8d --- /dev/null +++ b/libjava/gnu/xml/transform/ChooseNode.java @@ -0,0 +1,93 @@ +/* ChooseNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Node; + +/** + * A template node representing an XSL <code>choose</code> instruction. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class ChooseNode + extends TemplateNode +{ + + ChooseNode(TemplateNode children, TemplateNode next) + { + super(children, next); + } + + TemplateNode clone(Stylesheet stylesheet) + { + return new ChooseNode((children == null) ? null : + children.clone(stylesheet), + (next == null) ? null : + next.clone(stylesheet)); + } + + void doApply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + if (children != null) + { + children.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + if (next != null) + { + next.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + } + + public String toString() + { + StringBuffer buf = new StringBuffer(getClass().getName()); + buf.append('['); + buf.append(']'); + return buf.toString(); + } + +} diff --git a/libjava/gnu/xml/transform/CommentNode.java b/libjava/gnu/xml/transform/CommentNode.java new file mode 100644 index 0000000..99870cd --- /dev/null +++ b/libjava/gnu/xml/transform/CommentNode.java @@ -0,0 +1,115 @@ +/* CommentNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Comment; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; + +/** + * A template node representing the XSL <code>comment</code> instruction. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class CommentNode + extends TemplateNode +{ + + CommentNode(TemplateNode children, TemplateNode next) + { + super(children, next); + } + + TemplateNode clone(Stylesheet stylesheet) + { + return new CommentNode((children == null) ? null : + children.clone(stylesheet), + (next == null) ? null : + next.clone(stylesheet)); + } + + void doApply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + String value = ""; + Document doc = (parent instanceof Document) ? (Document) parent : + parent.getOwnerDocument(); + if (children != null) + { + // Create a document fragment to hold the text + DocumentFragment fragment = doc.createDocumentFragment(); + // Apply children to the fragment + children.apply(stylesheet, mode, + context, pos, len, + fragment, null); + // Use XPath string-value of fragment + value = Expr.stringValue(fragment); + } + Comment comment = doc.createComment(value); + // Insert into result tree + if (nextSibling != null) + { + parent.insertBefore(comment, nextSibling); + } + else + { + parent.appendChild(comment); + } + if (next != null) + { + next.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + } + + public String toString() + { + StringBuffer buf = new StringBuffer(getClass().getName()); + buf.append('['); + buf.append(']'); + return buf.toString(); + } + +} diff --git a/libjava/gnu/xml/transform/CopyNode.java b/libjava/gnu/xml/transform/CopyNode.java new file mode 100644 index 0000000..d06a0af --- /dev/null +++ b/libjava/gnu/xml/transform/CopyNode.java @@ -0,0 +1,179 @@ +/* CopyNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.Iterator; +import java.util.StringTokenizer; +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +/** + * A template node representing the XSL <code>copy</code> instruction. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class CopyNode + extends TemplateNode +{ + + final String uas; + + CopyNode(TemplateNode children, TemplateNode next, String uas) + { + super(children, next); + this.uas = uas; + } + + TemplateNode clone(Stylesheet stylesheet) + { + return new CopyNode((children == null) ? null : + children.clone(stylesheet), + (next == null) ? null : + next.clone(stylesheet), + uas); + } + + void doApply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + Node copy = parent; + switch (context.getNodeType()) + { + case Node.TEXT_NODE: + case Node.ATTRIBUTE_NODE: + case Node.ELEMENT_NODE: + case Node.PROCESSING_INSTRUCTION_NODE: + case Node.COMMENT_NODE: + Document doc = (parent instanceof Document) ? (Document) parent : + parent.getOwnerDocument(); + copy = context.cloneNode(false); + copy = doc.adoptNode(copy); + if (copy.getNodeType() == Node.ATTRIBUTE_NODE) + { + if (parent.getFirstChild() != null) + { + // Ignore attempt to add attribute after children + } + else + { + NamedNodeMap attrs = parent.getAttributes(); + if (attrs != null) + { + attrs.setNamedItemNS(copy); + } + } + } + else + { + if (nextSibling != null) + { + parent.insertBefore(copy, nextSibling); + } + else + { + parent.appendChild(copy); + } + } + } + if (uas != null) + { + StringTokenizer st = new StringTokenizer(uas, " "); + while (st.hasMoreTokens()) + { + addAttributeSet(stylesheet, mode, context, pos, len, + copy, null, st.nextToken()); + } + } + if (children != null) + { + children.apply(stylesheet, mode, + context, pos, len, + copy, null); + } + if (next != null) + { + next.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + } + + void addAttributeSet(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling, String attributeSet) + throws TransformerException + { + for (Iterator i = stylesheet.attributeSets.iterator(); i.hasNext(); ) + { + AttributeSet as = (AttributeSet) i.next(); + if (!as.name.equals(attributeSet)) + { + continue; + } + if (as.uas != null) + { + StringTokenizer st = new StringTokenizer(as.uas, " "); + while (st.hasMoreTokens()) + { + addAttributeSet(stylesheet, mode, context, pos, len, + parent, nextSibling, st.nextToken()); + } + } + if (as.children != null) + { + as.children.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + } + } + + public String toString() + { + StringBuffer buf = new StringBuffer(getClass().getName()); + buf.append('['); + buf.append(']'); + return buf.toString(); + } + +} diff --git a/libjava/gnu/xml/transform/CopyOfNode.java b/libjava/gnu/xml/transform/CopyOfNode.java new file mode 100644 index 0000000..7ca1006 --- /dev/null +++ b/libjava/gnu/xml/transform/CopyOfNode.java @@ -0,0 +1,179 @@ +/* CopyOfNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.Text; +import gnu.xml.xpath.Expr; + +/** + * A template node representing an XSLT <code>copy-of</code> instruction. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class CopyOfNode + extends TemplateNode +{ + + final Expr select; + + CopyOfNode(TemplateNode children, TemplateNode next, Expr select) + { + super(children, next); + this.select = select; + } + + TemplateNode clone(Stylesheet stylesheet) + { + return new CopyOfNode((children == null) ? null : + children.clone(stylesheet), + (next == null) ? null : + next.clone(stylesheet), + select.clone(stylesheet)); + } + + void doApply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + Object ret = select.evaluate(context, pos, len); + Document doc = (parent instanceof Document) ? (Document) parent : + parent.getOwnerDocument(); + if (ret instanceof Collection) + { + Collection ns = (Collection) ret; + List list = new ArrayList(ns); + Collections.sort(list, documentOrderComparator); + for (Iterator i = list.iterator(); i.hasNext(); ) + { + Node src = (Node) i.next(); + short nodeType = src.getNodeType(); + if (nodeType == Node.DOCUMENT_NODE) + { + // Use document element + src = ((Document) src).getDocumentElement(); + if (src == null) + { + continue; + } + nodeType = Node.ELEMENT_NODE; + } + else if (nodeType == Node.ATTRIBUTE_NODE) + { + if (parent.getFirstChild() != null) + { + // Ignore attempt to add attribute after children + continue; + } + } + if (parent.getNodeType() == Node.ATTRIBUTE_NODE && + nodeType != Node.TEXT_NODE && + nodeType != Node.ENTITY_REFERENCE_NODE) + { + // Ignore + continue; + } + Node node = src.cloneNode(true); + node = doc.adoptNode(node); + if (nodeType == Node.ATTRIBUTE_NODE) + { + NamedNodeMap attrs = parent.getAttributes(); + if (attrs != null) + { + attrs.setNamedItemNS(node); + } + } + else + { + if (nextSibling != null) + { + parent.insertBefore(node, nextSibling); + } + else + { + parent.appendChild(node); + } + } + } + } + else + { + String value = Expr._string(context, ret); + if (value != null && value.length() > 0) + { + Text textNode = doc.createTextNode(value); + if (nextSibling != null) + { + parent.insertBefore(textNode, nextSibling); + } + else + { + parent.appendChild(textNode); + } + } + } + // copy-of doesn't process children + if (next != null) + { + next.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + } + + public String toString() + { + StringBuffer buf = new StringBuffer(getClass().getName()); + buf.append('['); + buf.append("select="); + buf.append(select); + buf.append(']'); + return buf.toString(); + } + +} diff --git a/libjava/gnu/xml/transform/CurrentFunction.java b/libjava/gnu/xml/transform/CurrentFunction.java new file mode 100644 index 0000000..0c901be --- /dev/null +++ b/libjava/gnu/xml/transform/CurrentFunction.java @@ -0,0 +1,98 @@ +/* CurrentFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.Collections; +import java.util.List; +import javax.xml.xpath.XPathFunction; +import javax.xml.xpath.XPathFunctionException; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; +import gnu.xml.xpath.Function; + +/** + * The XSLT <code>current()</code>function. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class CurrentFunction + extends Expr + implements Function, XPathFunction +{ + + final Stylesheet stylesheet; + + CurrentFunction(Stylesheet stylesheet) + { + this.stylesheet = stylesheet; + } + + public Object evaluate(List args) + throws XPathFunctionException + { + // We can't do anything useful here. + // So much for the JAXP API... + return Collections.EMPTY_SET; + } + + public void setArguments(List args) + { + } + + public Object evaluate(Node context, int pos, int len) + { + return Collections.singleton(stylesheet.current); + } + + public Expr clone(Object context) + { + Stylesheet s = stylesheet; + if (context instanceof Stylesheet) + { + s = (Stylesheet) context; + } + return new CurrentFunction(s); + } + + public String toString() + { + return "current()"; + } + +} + diff --git a/libjava/gnu/xml/transform/DOMSourceLocator.java b/libjava/gnu/xml/transform/DOMSourceLocator.java new file mode 100644 index 0000000..b9ef705 --- /dev/null +++ b/libjava/gnu/xml/transform/DOMSourceLocator.java @@ -0,0 +1,84 @@ +/* DOMSourceLocator.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import javax.xml.transform.dom.DOMLocator; +import org.w3c.dom.Node; + +/** + * Simple DOMLocator implementation. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class DOMSourceLocator + implements DOMLocator +{ + + final Node node; + + DOMSourceLocator(Node node) + { + this.node = node; + } + + public Node getOriginatingNode() + { + return node; + } + + public String getPublicId() + { + return null; + } + + public String getSystemId() + { + return null; + } + + public int getLineNumber() + { + return -1; + } + + public int getColumnNumber() + { + return -1; + } + +} diff --git a/libjava/gnu/xml/transform/DocumentFunction.java b/libjava/gnu/xml/transform/DocumentFunction.java new file mode 100644 index 0000000..03f22b4 --- /dev/null +++ b/libjava/gnu/xml/transform/DocumentFunction.java @@ -0,0 +1,249 @@ +/* DocumentFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.io.InputStream; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.TreeSet; +import javax.xml.transform.ErrorListener; +import javax.xml.transform.TransformerException; +import javax.xml.transform.URIResolver; +import javax.xml.transform.dom.DOMSource; +import javax.xml.xpath.XPathFunction; +import javax.xml.xpath.XPathFunctionException; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import gnu.xml.xpath.Constant; +import gnu.xml.xpath.Expr; +import gnu.xml.xpath.Function; +import gnu.xml.xpath.IdFunction; + +/** + * The XSLT <code>document()</code>function. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class DocumentFunction + extends Expr + implements Function, XPathFunction +{ + + final Stylesheet stylesheet; + final Node base; + List args; + List values; + + DocumentFunction(Stylesheet stylesheet, Node base) + { + this.stylesheet = stylesheet; + this.base = base; + } + + public Object evaluate(List args) + throws XPathFunctionException + { + values = args; + return evaluate(null, 1, 1); + } + + public void setArguments(List args) + { + this.args = args; + } + + public Object evaluate(Node context, int pos, int len) + { + int arity = args.size(); + if (values == null) + { + values = new ArrayList(arity); + for (int i = 0; i < arity; i++) + { + Expr arg = (Expr) args.get(i); + values.add(arg.evaluate(context, pos, len)); + } + } + Object ret; + switch (arity) + { + case 1: + Object arg = values.get(0); + if (arg instanceof Collection) + { + Collection ns = (Collection) arg; + Collection acc = new TreeSet(); + for (Iterator i = ns.iterator(); i.hasNext(); ) + { + Node node = (Node) i.next(); + String s = Expr.stringValue(node); + acc.addAll(document(s, node.getBaseURI())); + } + ret = acc; + } + else + { + String s = Expr._string(context, arg); + ret = document(s, base.getBaseURI()); + } + break; + case 2: + Object arg1 = values.get(0); + Object arg2 = values.get(1); + if (!(arg2 instanceof Collection)) + { + throw new RuntimeException("second argument is not a node-set"); + } + Collection arg2ns = (Collection) arg2; + String base2 = arg2ns.isEmpty() ? null : + ((Node) arg2ns.iterator().next()).getBaseURI(); + if (arg1 instanceof Collection) + { + Collection arg1ns = (Collection) arg1; + Collection acc = new TreeSet(); + for (Iterator i = arg1ns.iterator(); i.hasNext(); ) + { + Node node = (Node) i.next(); + String s = Expr.stringValue(node); + acc.addAll(document(s, base2)); + } + ret = acc; + } + else + { + String s = Expr._string(context, arg1); + ret = document(s, base2); + } + break; + default: + throw new RuntimeException("invalid arity"); + } + values = null; + return ret; + } + + /** + * The XSL <code>document</code> function. + * @see XSLT 12.1 + * @param uri the URI from which to retrieve nodes + * @param base the base URI for relative URIs + */ + Collection document(String uri, String base) + { + if ("".equals(uri) || uri == null) + { + uri = this.base.getBaseURI(); + } + + // Get fragment + Expr fragment = null; + int hi = uri.indexOf('#'); + if (hi != -1) + { + String f = uri.substring(hi + 1); + uri = uri.substring(0, hi); + // TODO handle xpointer() here + // this only handles IDs + fragment = new IdFunction(new Constant(f)); + } + + // Get document source + try + { + DOMSource source; + XSLURIResolver resolver = stylesheet.factory.resolver; + synchronized (resolver) + { + if (stylesheet.transformer != null) + { + resolver.setUserResolver(stylesheet.transformer.uriResolver); + resolver.setUserListener(stylesheet.transformer.errorListener); + } + source = resolver.resolveDOM(null, base, uri); + } + Node node = source.getNode(); + if (fragment == null) + { + return Collections.singleton(node); + } + else + { + Object ret = fragment.evaluate(node, 1, 1); + if (!(ret instanceof Collection)) + { + // XXX Report error? + return Collections.EMPTY_SET; + } + return (Collection) ret; + } + } + catch (TransformerException e) + { + String msg = "can't open " + uri; + if (base != null) + { + msg += " with base " + base; + } + throw new RuntimeException(msg); + } + } + + public Expr clone(Object context) + { + Stylesheet s = stylesheet; + if (context instanceof Stylesheet) + { + s = (Stylesheet) context; + } + DocumentFunction f = new DocumentFunction(s, base); + int len = args.size(); + List args2 = new ArrayList(len); + for (int i = 0; i < len; i++) + { + args2.add(((Expr) args.get(i)).clone(context)); + } + f.setArguments(args2); + return f; + } + +} diff --git a/libjava/gnu/xml/transform/ElementAvailableFunction.java b/libjava/gnu/xml/transform/ElementAvailableFunction.java new file mode 100644 index 0000000..0c9ce44 --- /dev/null +++ b/libjava/gnu/xml/transform/ElementAvailableFunction.java @@ -0,0 +1,169 @@ +/* ElementAvailableFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.TreeSet; +import javax.xml.namespace.NamespaceContext; +import javax.xml.xpath.XPathFunction; +import javax.xml.xpath.XPathFunctionException; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; +import gnu.xml.xpath.Function; + +/** + * The XSLT <code>element-available</code> function. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class ElementAvailableFunction + extends Expr + implements Function, XPathFunction +{ + + static final Collection elements; + static + { + TreeSet acc = new TreeSet(); + acc.add("stylesheet"); + acc.add("template"); + acc.add("param"); + acc.add("variable"); + acc.add("include"); + acc.add("import"); + acc.add("output"); + acc.add("preserve-space"); + acc.add("strip-space"); + acc.add("key"); + acc.add("decimal-format"); + acc.add("namespace-alias"); + acc.add("attribute-set"); + acc.add("apply-templates"); + acc.add("call-template"); + acc.add("value-of"); + acc.add("for-each"); + acc.add("if"); + acc.add("choose"); + acc.add("when"); + acc.add("otherwise"); + acc.add("element"); + acc.add("attribute"); + acc.add("text"); + acc.add("copy"); + acc.add("processing-instruction"); + acc.add("comment"); + acc.add("number"); + acc.add("copy-of"); + acc.add("message"); + acc.add("sort"); + acc.add("with-param"); + acc.add("fallback"); + acc.add("apply-imports"); + elements = Collections.unmodifiableSet(acc); + } + + final NamespaceContext nsctx; + List args; + + ElementAvailableFunction(NamespaceContext nsctx) + { + this.nsctx = nsctx; + } + + public Object evaluate(List args) + throws XPathFunctionException + { + // Useless... + return Collections.EMPTY_SET; + } + + public void setArguments(List args) + { + this.args = args; + } + + public Object evaluate(Node context, int pos, int len) + { + Expr arg = (Expr) args.get(0); + Object val = arg.evaluate(context, pos, len); + String name = _string(context, val); + String prefix, localName, uri; + int ci = name.indexOf(':'); + if (ci == -1) + { + prefix = null; + localName = name; + } + else + { + prefix = name.substring(0, ci); + localName = name.substring(ci + 1); + } + uri = nsctx.getNamespaceURI(prefix); + if (Stylesheet.XSL_NS.equals(uri)) + { + return elements.contains(localName) ? + Boolean.TRUE : Boolean.FALSE; + // TODO extension elements + } + return Boolean.FALSE; + } + + public Expr clone(Object context) + { + NamespaceContext n = nsctx; + if (context instanceof NamespaceContext) + { + n = (NamespaceContext) context; + } + ElementAvailableFunction f = new ElementAvailableFunction(n); + int len = args.size(); + List args2 = new ArrayList(len); + for (int i = 0; i < len; i++) + { + args2.add(((Expr) args.get(i)).clone(context)); + } + f.setArguments(args2); + return f; + } + +} + diff --git a/libjava/gnu/xml/transform/ElementNode.java b/libjava/gnu/xml/transform/ElementNode.java new file mode 100644 index 0000000..305989c0 --- /dev/null +++ b/libjava/gnu/xml/transform/ElementNode.java @@ -0,0 +1,279 @@ +/* ElementNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.StringTokenizer; +import javax.xml.XMLConstants; +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; + +/** + * A template node representing an XSL <code>element</code> instruction. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class ElementNode + extends TemplateNode +{ + + final TemplateNode name; + final TemplateNode namespace; + final String uas; + final Node source; + final Collection elementExcludeResultPrefixes; + + ElementNode(TemplateNode children, TemplateNode next, TemplateNode name, + TemplateNode namespace, String uas, Node source) + { + super(children, next); + this.name = name; + this.namespace = namespace; + this.uas = uas; + this.source = source; + NamedNodeMap attrs = source.getAttributes(); + Node attr = attrs.getNamedItemNS(Stylesheet.XSL_NS, + "exclude-result-prefixes"); + if (attr != null) + { + elementExcludeResultPrefixes = new HashSet(); + StringTokenizer st = new StringTokenizer(attr.getNodeValue()); + while (st.hasMoreTokens()) + { + elementExcludeResultPrefixes.add(st.nextToken()); + } + } + else + { + elementExcludeResultPrefixes = Collections.EMPTY_SET; + } + } + + TemplateNode clone(Stylesheet stylesheet) + { + return new ElementNode((children == null) ? null : + children.clone(stylesheet), + (next == null) ? null : + next.clone(stylesheet), + name.clone(stylesheet), + (namespace == null) ? null : + namespace.clone(stylesheet), + uas, source); + } + + void doApply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + Document doc = (parent instanceof Document) ? (Document) parent : + parent.getOwnerDocument(); + // Create a document fragment to hold the name + DocumentFragment fragment = doc.createDocumentFragment(); + // Apply name to the fragment + name.apply(stylesheet, mode, + context, pos, len, + fragment, null); + // Use XPath string-value of fragment + String nameValue = Expr.stringValue(fragment); + + String namespaceValue = null; + if (namespace != null) + { + // Create a document fragment to hold the namespace + fragment = doc.createDocumentFragment(); + // Apply namespace to the fragment + namespace.apply(stylesheet, mode, + context, pos, len, + fragment, null); + // Use XPath string-value of fragment + namespaceValue = Expr.stringValue(fragment); + if (namespaceValue.length() == 0) + { + namespaceValue = null; + } + } + + String prefix = getPrefix(nameValue); + if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) + { + int ci = nameValue.indexOf(':'); + nameValue = nameValue.substring(ci + 1); + } + else + { + // Namespace aliasing + if (prefix == null) + { + prefix = "#default"; + } + String resultPrefix = + (String) stylesheet.namespaceAliases.get(prefix); + if (resultPrefix != null) + { + if ("#default".equals(resultPrefix)) + { + resultPrefix = null; + } + namespaceValue = source.lookupNamespaceURI(resultPrefix); + } + if (prefix == "#default") + { + prefix = null; + } + // Look up ordinary namespace for this prefix + if (namespaceValue == null) + { + if (XMLConstants.XML_NS_PREFIX.equals(prefix)) + { + namespaceValue = XMLConstants.XML_NS_URI; + } + else + { + // Resolve namespace for this prefix + namespaceValue = source.lookupNamespaceURI(prefix); + } + } + /*if (prefix == null) + { + // Resolve prefix for this namespace + prefix = parent.lookupPrefix(namespaceValue); + if (prefix != null) + { + nameValue = prefix + ":" + nameValue; + } + }*/ + } + // Create element + Element element = (namespaceValue != null) ? + doc.createElementNS(namespaceValue, nameValue) : + doc.createElement(nameValue); + if (nextSibling != null) + { + parent.insertBefore(element, nextSibling); + } + else + { + parent.appendChild(element); + } + stylesheet.addNamespaceNodes(source, element, doc, + elementExcludeResultPrefixes); + if (uas != null) + { + StringTokenizer st = new StringTokenizer(uas, " "); + while (st.hasMoreTokens()) + { + addAttributeSet(stylesheet, mode, context, pos, len, + element, null, st.nextToken()); + } + } + if (children != null) + { + children.apply(stylesheet, mode, + context, pos, len, + element, null); + } + if (next != null) + { + next.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + } + + final String getPrefix(String name) + { + int ci = name.indexOf(':'); + return (ci == -1) ? null : name.substring(0, ci); + } + + void addAttributeSet(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling, String attributeSet) + throws TransformerException + { + for (Iterator i = stylesheet.attributeSets.iterator(); i.hasNext(); ) + { + AttributeSet as = (AttributeSet) i.next(); + if (!as.name.equals(attributeSet)) + { + continue; + } + if (as.uas != null) + { + StringTokenizer st = new StringTokenizer(as.uas, " "); + while (st.hasMoreTokens()) + { + addAttributeSet(stylesheet, mode, context, pos, len, + parent, nextSibling, st.nextToken()); + } + } + if (as.children != null) + { + as.children.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + } + } + + public String toString() + { + StringBuffer buf = new StringBuffer(getClass().getName()); + buf.append('['); + buf.append("name="); + buf.append(name); + if (uas != null) + { + buf.append(",uas="); + buf.append(uas); + } + buf.append(']'); + return buf.toString(); + } + +} diff --git a/libjava/gnu/xml/transform/ErrorListenerErrorHandler.java b/libjava/gnu/xml/transform/ErrorListenerErrorHandler.java new file mode 100644 index 0000000..929f7f0 --- /dev/null +++ b/libjava/gnu/xml/transform/ErrorListenerErrorHandler.java @@ -0,0 +1,101 @@ +/* ErrorListenerErrorHandler.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import javax.xml.transform.ErrorListener; +import javax.xml.transform.TransformerException; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +/** + * An ErrorHandler that wraps an ErrorListener. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class ErrorListenerErrorHandler + implements ErrorHandler +{ + + final ErrorListener listener; + + ErrorListenerErrorHandler(ErrorListener listener) + { + this.listener = listener; + } + + public void warning(SAXParseException e) + throws SAXException + { + try + { + listener.warning(new TransformerException(e)); + } + catch (TransformerException e2) + { + throw new SAXException(e2); + } + } + + public void error(SAXParseException e) + throws SAXException + { + try + { + listener.error(new TransformerException(e)); + } + catch (TransformerException e2) + { + throw new SAXException(e2); + } + } + + public void fatalError(SAXParseException e) + throws SAXException + { + try + { + listener.fatalError(new TransformerException(e)); + } + catch (TransformerException e2) + { + throw new SAXException(e2); + } + } + +} diff --git a/libjava/gnu/xml/transform/ForEachNode.java b/libjava/gnu/xml/transform/ForEachNode.java new file mode 100644 index 0000000..af96628 --- /dev/null +++ b/libjava/gnu/xml/transform/ForEachNode.java @@ -0,0 +1,149 @@ +/* ForEachNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; + +/** + * A template node representing an XSLT <code>for-each</code> instruction. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class ForEachNode + extends TemplateNode +{ + + final Expr select; + final List sortKeys; + + ForEachNode(TemplateNode children, TemplateNode next, Expr select, + List sortKeys) + { + super(children, next); + this.select = select; + this.sortKeys = sortKeys; + } + + TemplateNode clone(Stylesheet stylesheet) + { + int len = sortKeys.size(); + List sortKeys2 = new ArrayList(len); + for (int i = 0; i < len; i++) + { + sortKeys2.add(((Key) sortKeys.get(i)).clone(stylesheet)); + } + return new ForEachNode((children == null) ? null : + children.clone(stylesheet), + (next == null) ? null : + next.clone(stylesheet), + select.clone(stylesheet), + sortKeys2); + } + + void doApply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + if (children != null) + { + // Set current template to null + Template saved = stylesheet.currentTemplate; + stylesheet.currentTemplate = null; + Object ret = select.evaluate(context, pos, len); + //System.err.println(toString() + ": " + context+" -> "+ret); + if (ret instanceof Collection) + { + Collection ns = (Collection) ret; + List list = new ArrayList(ns); + if (sortKeys != null) + { + for (Iterator i = sortKeys.iterator(); i.hasNext(); ) + { + SortKey sortKey = (SortKey) i.next(); + sortKey.init(stylesheet, mode, context, pos, len, parent, + nextSibling); + } + Collections.sort(list, new XSLComparator(sortKeys)); + } + else + { + Collections.sort(list, documentOrderComparator); + } + // Perform children for each node + int l = list.size(); + int p = 1; + for (Iterator i = list.iterator(); i.hasNext(); ) + { + Node node = (Node) i.next(); + stylesheet.current = node; + children.apply(stylesheet, mode, + node, p++, l, + parent, nextSibling); + } + } + // Restore current template + stylesheet.currentTemplate = saved; + } + if (next != null) + { + next.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + } + + public String toString() + { + StringBuffer buf = new StringBuffer(getClass().getName()); + buf.append('['); + buf.append("select="); + buf.append(select); + buf.append(']'); + return buf.toString(); + } + +} diff --git a/libjava/gnu/xml/transform/FormatNumberFunction.java b/libjava/gnu/xml/transform/FormatNumberFunction.java new file mode 100644 index 0000000..73c5af4 --- /dev/null +++ b/libjava/gnu/xml/transform/FormatNumberFunction.java @@ -0,0 +1,133 @@ +/* FormatNumberFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.xml.xpath.XPathFunction; +import javax.xml.xpath.XPathFunctionException; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; +import gnu.xml.xpath.Function; + +/** + * The XSLT <code>format-number()</code>function. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class FormatNumberFunction + extends Expr + implements XPathFunction, Function +{ + + final Stylesheet stylesheet; + List args; + + FormatNumberFunction(Stylesheet stylesheet) + { + this.stylesheet = stylesheet; + } + + public Object evaluate(List args) + throws XPathFunctionException + { + // Useless... + return Collections.EMPTY_SET; + } + + public void setArguments(List args) + { + this.args = args; + } + + public Object evaluate(Node context, int pos, int len) + { + int arity = args.size(); + List values = new ArrayList(arity); + for (int i = 0; i < arity; i++) + { + Expr arg = (Expr) args.get(i); + values.add(arg.evaluate(context, pos, len)); + } + double number = _number(context, values.get(0)); + String pattern = _string(context, values.get(1)); + // Currency symbol ¤ is not supposed to be present + if (pattern.indexOf('\u00a4') != -1) + { + // Replace with $ (Xalan does this) + pattern = pattern.replace('\u00a4', '$'); + } + String dfName = null; + if (arity > 2) + { + dfName = _string(context, values.get(2)); + // otherwise the default decimal-format will be used + } + DecimalFormat df = (DecimalFormat) stylesheet.decimalFormats.get(dfName); + if (df == null) + { + throw new IllegalArgumentException("No such decimal-format: " + + dfName); + } + df.applyLocalizedPattern(pattern); + return df.format(number); + } + + public Expr clone(Object context) + { + Stylesheet s = stylesheet; + if (context instanceof Stylesheet) + { + s = (Stylesheet) context; + } + FormatNumberFunction f = new FormatNumberFunction(s); + int len = args.size(); + List args2 = new ArrayList(len); + for (int i = 0; i < len; i++) + { + args2.add(((Expr) args.get(i)).clone(context)); + } + f.setArguments(args2); + return f; + } + +} + diff --git a/libjava/gnu/xml/transform/FunctionAvailableFunction.java b/libjava/gnu/xml/transform/FunctionAvailableFunction.java new file mode 100644 index 0000000..2931082 --- /dev/null +++ b/libjava/gnu/xml/transform/FunctionAvailableFunction.java @@ -0,0 +1,175 @@ +/* FunctionAvailableFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.TreeSet; +import javax.xml.namespace.NamespaceContext; +import javax.xml.xpath.XPathFunction; +import javax.xml.xpath.XPathFunctionException; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; +import gnu.xml.xpath.Function; + +/** + * The XSLT <code>function-available</code> function. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class FunctionAvailableFunction + extends Expr + implements Function, XPathFunction +{ + + static final Collection xsltFunctions; + static final Collection xpathFunctions; + static + { + TreeSet acc = new TreeSet(); + acc.add("document"); + acc.add("key"); + acc.add("format-number"); + acc.add("current"); + acc.add("unparsed-entity-uri"); + acc.add("generate-id"); + acc.add("system-property"); + acc.add("element-available"); + acc.add("function-available"); + xsltFunctions = Collections.unmodifiableSet(acc); + acc = new TreeSet(); + acc.add("boolean"); + acc.add("ceiling"); + acc.add("concat"); + acc.add("contains"); + acc.add("count"); + acc.add("false"); + acc.add("floor"); + acc.add("id"); + acc.add("lang"); + acc.add("last"); + acc.add("local-name"); + acc.add("name"); + acc.add("namespace-uri"); + acc.add("normalize-space"); + acc.add("not"); + acc.add("number"); + acc.add("position"); + acc.add("round"); + acc.add("starts-with"); + acc.add("string"); + acc.add("string-length"); + acc.add("substring-after"); + acc.add("substring-before"); + acc.add("substring"); + acc.add("sum"); + acc.add("translate"); + acc.add("true"); + xpathFunctions = Collections.unmodifiableSet(acc); + } + + final NamespaceContext nsctx; + List args; + + FunctionAvailableFunction(NamespaceContext nsctx) + { + this.nsctx = nsctx; + } + + public Object evaluate(List args) + throws XPathFunctionException + { + // Useless... + return Collections.EMPTY_SET; + } + + public void setArguments(List args) + { + this.args = args; + } + + public Object evaluate(Node context, int pos, int len) + { + Expr arg = (Expr) args.get(0); + Object val = arg.evaluate(context, pos, len); + String name = _string(context, val); + String prefix, localName, uri; + int ci = name.indexOf(':'); + if (ci == -1) + { + prefix = null; + localName = name; + } + else + { + prefix = name.substring(0, ci); + localName = name.substring(ci + 1); + } + uri = nsctx.getNamespaceURI(prefix); + if (uri == null) + { + return xsltFunctions.contains(localName) || + xpathFunctions.contains(localName) ? + Boolean.TRUE : Boolean.FALSE; + // TODO extension functions + } + return Boolean.FALSE; + } + + public Expr clone(Object context) + { + NamespaceContext n = nsctx; + if (context instanceof NamespaceContext) + { + n = (NamespaceContext) context; + } + FunctionAvailableFunction f = new FunctionAvailableFunction(n); + int len = args.size(); + List args2 = new ArrayList(len); + for (int i = 0; i < len; i++) + { + args2.add(((Expr) args.get(i)).clone(context)); + } + f.setArguments(args2); + return f; + } + +} + diff --git a/libjava/gnu/xml/transform/GenerateIdFunction.java b/libjava/gnu/xml/transform/GenerateIdFunction.java new file mode 100644 index 0000000..9e48f79 --- /dev/null +++ b/libjava/gnu/xml/transform/GenerateIdFunction.java @@ -0,0 +1,126 @@ +/* GenerateIdFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import javax.xml.xpath.XPathFunction; +import javax.xml.xpath.XPathFunctionException; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; +import gnu.xml.xpath.Function; + +/** + * The XSLT <code>generate-id()</code>function. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class GenerateIdFunction + extends Expr + implements XPathFunction, Function +{ + + List args; + + public Object evaluate(List args) + throws XPathFunctionException + { + // Useless... + return Collections.EMPTY_SET; + } + + public void setArguments(List args) + { + this.args = args; + } + + public Object evaluate(Node context, int pos, int len) + { + int arity = args.size(); + List values = new ArrayList(arity); + for (int i = 0; i < arity; i++) + { + Expr arg = (Expr) args.get(i); + values.add(arg.evaluate(context, pos, len)); + } + Node node; + Collection ns = (arity == 0) ? Collections.EMPTY_SET : + (Collection) values.get(0); + if (ns.isEmpty()) + { + node = context; + } + else + { + List list = new ArrayList(ns); + Collections.sort(list, documentOrderComparator); + node = (Node) list.get(0); + } + + String name = node.getNodeName(); + int index = 0, depth = 0; + for (Node ctx = node.getPreviousSibling(); ctx != null; + ctx = ctx.getPreviousSibling()) + { + index++; + } + for (Node ctx = node.getParentNode(); ctx != null; + ctx = ctx.getParentNode()) + { + depth++; + } + return name + "-" + index + "-" + depth; + } + + public Expr clone(Object context) + { + GenerateIdFunction f = new GenerateIdFunction(); + int len = args.size(); + List args2 = new ArrayList(len); + for (int i = 0; i < len; i++) + { + args2.add(((Expr) args.get(i)).clone(context)); + } + f.setArguments(args2); + return f; + } + +} + diff --git a/libjava/gnu/xml/transform/IfNode.java b/libjava/gnu/xml/transform/IfNode.java new file mode 100644 index 0000000..c977348 --- /dev/null +++ b/libjava/gnu/xml/transform/IfNode.java @@ -0,0 +1,107 @@ +/* IfNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; + +/** + * A template node representing an XSL <code>if</code> instruction. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class IfNode + extends TemplateNode +{ + + final Expr test; + + IfNode(TemplateNode children, TemplateNode next, Expr test) + { + super(children, next); + this.test = test; + } + + TemplateNode clone(Stylesheet stylesheet) + { + return new IfNode((children == null) ? null : + children.clone(stylesheet), + (next == null) ? null : + next.clone(stylesheet), + test.clone(stylesheet)); + } + + void doApply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + Object ret = test.evaluate(context, pos, len); + boolean success = (ret instanceof Boolean) ? + ((Boolean) ret).booleanValue() : + Expr._boolean(context, ret); + if (success) + { + if (children != null) + { + children.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + } + if (next != null) + { + next.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + } + + public String toString() + { + StringBuffer buf = new StringBuffer(getClass().getName()); + buf.append('['); + buf.append("test="); + buf.append(test); + buf.append(']'); + return buf.toString(); + } + +} diff --git a/libjava/gnu/xml/transform/Key.java b/libjava/gnu/xml/transform/Key.java new file mode 100644 index 0000000..008400f --- /dev/null +++ b/libjava/gnu/xml/transform/Key.java @@ -0,0 +1,71 @@ +/* Key.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import javax.xml.namespace.QName; +import gnu.xml.xpath.Expr; +import gnu.xml.xpath.Pattern; + +/** + * An XSL key. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class Key +{ + + final QName name; + final Pattern match; + final Expr use; + + Key(QName name, Pattern match, Expr use) + { + this.name = name; + this.match = match; + this.use = use; + } + + Key clone(Stylesheet stylesheet) + { + return new Key(name, + (Pattern) match.clone(stylesheet), + use.clone(stylesheet)); + } + +} + diff --git a/libjava/gnu/xml/transform/KeyFunction.java b/libjava/gnu/xml/transform/KeyFunction.java new file mode 100644 index 0000000..233c12f --- /dev/null +++ b/libjava/gnu/xml/transform/KeyFunction.java @@ -0,0 +1,216 @@ +/* KeyFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import javax.xml.namespace.QName; +import javax.xml.xpath.XPathFunction; +import javax.xml.xpath.XPathFunctionException; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; +import gnu.xml.xpath.Function; +import gnu.xml.xpath.Pattern; + +/** + * The XSLT <code>key()</code>function. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class KeyFunction + extends Pattern + implements XPathFunction, Function +{ + + final Stylesheet stylesheet; + List args; + + KeyFunction(Stylesheet stylesheet) + { + this.stylesheet = stylesheet; + } + + public Object evaluate(List args) + throws XPathFunctionException + { + // Useless... + return Collections.EMPTY_SET; + } + + public void setArguments(List args) + { + this.args = args; + } + + public boolean matches(Node context) + { + Object ret = evaluate(context, 1, 1); + return !((Collection) ret).isEmpty(); + } + + public Object evaluate(Node context, int pos, int len) + { + // Evaluate arguments + int arity = args.size(); + List values = new ArrayList(arity); + for (int i = 0; i < arity; i++) + { + Expr arg = (Expr) args.get(i); + values.add(arg.evaluate(context, pos, len)); + } + // Get key name + QName keyName = QName.valueOf(_string(context, values.get(0))); + // Expand qualified name + String uri = keyName.getNamespaceURI(); + String prefix = keyName.getPrefix(); + if ((uri == null || uri.length() == 0) && + (prefix != null && prefix.length() > 0)) + { + uri = stylesheet.getNamespaceURI(prefix); + if (uri != null && uri.length() > 0) + { + String localName = keyName.getLocalPart(); + keyName = new QName(uri, localName, prefix); + } + } + // Compute matching key set + Collection keySet = new LinkedList(); + for (Iterator i = stylesheet.keys.iterator(); i.hasNext(); ) + { + Key key = (Key) i.next(); + if (key.name.equals(keyName)) + { + keySet.add(key); + } + } + // Get target + Object target = values.get(1); + Collection acc = new LinkedHashSet(); + Document doc = (context instanceof Document) ? (Document) context : + context.getOwnerDocument(); + if (target instanceof Collection) + { + for (Iterator i = ((Collection) target).iterator(); i.hasNext(); ) + { + String val = Expr.stringValue((Node) i.next()); + addKeyNodes(doc, keySet, val, acc); + } + } + else + { + String val = Expr._string(context, target); + addKeyNodes(doc, keySet, val, acc); + } + List ret = new ArrayList(acc); + Collections.sort(ret, documentOrderComparator); + return ret; + } + + final void addKeyNodes(Node node, Collection keySet, + String value, Collection acc) + { + addKeyNodeIfMatch(node, keySet, value, acc); + // Apply children + for (Node ctx = node.getFirstChild(); ctx != null; + ctx = ctx.getNextSibling()) + { + addKeyNodes(ctx, keySet, value, acc); + } + } + + final void addKeyNodeIfMatch(Node node, Collection keySet, + String value, Collection acc) + { + for (Iterator i = keySet.iterator(); i.hasNext(); ) + { + Key key = (Key) i.next(); + if (key.match.matches(node)) + { + Object eval = key.use.evaluate(node, 1, 1); + if (eval instanceof Collection) + { + for (Iterator j = ((Collection) eval).iterator(); + j.hasNext(); ) + { + String keyValue = Expr.stringValue((Node) j.next()); + if (value.equals(keyValue)) + { + acc.add(node); + return; + } + } + } + else + { + String keyValue = Expr._string(node, eval); + if (value.equals(keyValue)) + { + acc.add(node); + return; + } + } + } + } + } + + public Expr clone(Object context) + { + Stylesheet s = stylesheet; + if (context instanceof Stylesheet) + { + s = (Stylesheet) context; + } + KeyFunction f = new KeyFunction(s); + int len = args.size(); + List args2 = new ArrayList(len); + for (int i = 0; i < len; i++) + { + args2.add(((Expr) args.get(i)).clone(context)); + } + f.setArguments(args2); + return f; + } + +} + diff --git a/libjava/gnu/xml/transform/LiteralNode.java b/libjava/gnu/xml/transform/LiteralNode.java new file mode 100644 index 0000000..1b5af1a --- /dev/null +++ b/libjava/gnu/xml/transform/LiteralNode.java @@ -0,0 +1,225 @@ +/* LiteralNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.StringTokenizer; +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +/** + * A template node that copies a DOM node in the template to the result + * tree. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class LiteralNode + extends TemplateNode +{ + + /** + * The source node in the XSL template. + */ + final Node source; + + final Collection elementExcludeResultPrefixes; + + LiteralNode(TemplateNode children, TemplateNode next, Node source) + { + super(children, next); + this.source = source; + if (source.getNodeType() == Node.ELEMENT_NODE) + { + NamedNodeMap attrs = source.getAttributes(); + Node attr = attrs.getNamedItemNS(Stylesheet.XSL_NS, + "exclude-result-prefixes"); + if (attr != null) + { + elementExcludeResultPrefixes = new HashSet(); + StringTokenizer st = new StringTokenizer(attr.getNodeValue()); + while (st.hasMoreTokens()) + { + elementExcludeResultPrefixes.add(st.nextToken()); + } + } + else + { + elementExcludeResultPrefixes = Collections.EMPTY_SET; + } + } + else + { + elementExcludeResultPrefixes = null; + } + } + + TemplateNode clone(Stylesheet stylesheet) + { + return new LiteralNode((children == null) ? null : + children.clone(stylesheet), + (next == null) ? null : + next.clone(stylesheet), + source); + } + + void doApply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + Node result = null; + Document doc = (parent instanceof Document) ? (Document) parent : + parent.getOwnerDocument(); + short nodeType = source.getNodeType(); + if (nodeType == Node.ATTRIBUTE_NODE && + parent.getFirstChild() != null) + { + // Ignore attributes added after child elements + } + else + { + // Namespace aliasing + if (nodeType == Node.ELEMENT_NODE) + { + String prefix = source.getPrefix(); + if (prefix == null) + { + prefix = "#default"; + } + String resultPrefix = + (String) stylesheet.namespaceAliases.get(prefix); + if (resultPrefix != null) + { + if ("#default".equals(resultPrefix)) + { + resultPrefix = null; + } + String uri = source.lookupNamespaceURI(resultPrefix); + String name = source.getNodeName(); + // Create a new element node in the result document + result = doc.createElementNS(uri, name); + // copy attributes + NamedNodeMap srcAttrs = source.getAttributes(); + NamedNodeMap dstAttrs = result.getAttributes(); + int l = srcAttrs.getLength(); + for (int i = 0; i < l; i++) + { + Node attr = srcAttrs.item(i); + if (!Stylesheet.XSL_NS.equals(attr.getNamespaceURI())) + { + attr = attr.cloneNode(true); + attr = doc.adoptNode(attr); + dstAttrs.setNamedItemNS(attr); + } + } + } + } + if (result == null) + { + // Create result node + result = source.cloneNode(false); + // Remove any XSL attributes + NamedNodeMap attrs = result.getAttributes(); + if (attrs != null) + { + int l = attrs.getLength(); + for (int i = 0; i < l; i++) + { + Node attr = attrs.item(i); + if (Stylesheet.XSL_NS.equals(attr.getNamespaceURI())) + { + attrs.removeNamedItem(attr.getNodeName()); + i--; + l--; + } + } + } + result = doc.adoptNode(result); + if (result == null) + { + String msg = "Error adopting node to result tree"; + DOMSourceLocator l = new DOMSourceLocator(context); + throw new TransformerException(msg, l); + } + } + if (nextSibling != null) + { + parent.insertBefore(result, nextSibling); + } + else + { + parent.appendChild(result); + } + if (nodeType == Node.ELEMENT_NODE) + { + stylesheet.addNamespaceNodes(source, result, doc, + elementExcludeResultPrefixes); + } + // children + if (children != null) + { + children.apply(stylesheet, mode, + context, pos, len, + result, null); + } + } + // next sibling + if (next != null) + { + next.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + } + + public String toString() + { + StringBuffer buf = new StringBuffer(getClass().getName()); + buf.append('['); + buf.append("source="); + buf.append(source); + buf.append(']'); + return buf.toString(); + } + +} diff --git a/libjava/gnu/xml/transform/MessageNode.java b/libjava/gnu/xml/transform/MessageNode.java new file mode 100644 index 0000000..c79060a --- /dev/null +++ b/libjava/gnu/xml/transform/MessageNode.java @@ -0,0 +1,97 @@ +/* MessageNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; + +/** + * An XSL <code>message</code> instruction. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class MessageNode + extends TemplateNode +{ + + final boolean terminate; + + MessageNode(TemplateNode children, TemplateNode next, boolean terminate) + { + super(children, next); + this.terminate = terminate; + } + + TemplateNode clone(Stylesheet stylesheet) + { + return new MessageNode((children == null) ? null : + children.clone(stylesheet), + (next == null) ? null : + next.clone(stylesheet), + terminate); + } + + void doApply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + if (children != null) + { + Document doc = (parent instanceof Document) ? (Document) parent : + parent.getOwnerDocument(); + DocumentFragment fragment = doc.createDocumentFragment(); + children.apply(stylesheet, mode, context, pos, len, fragment, null); + String message = Expr.stringValue(fragment); + System.err.println(message); + if (terminate) + { + stylesheet.terminated = true; + } + } + if (next != null && !terminate) + { + next.apply(stylesheet, mode, context, pos, len, parent, nextSibling); + } + } + +} diff --git a/libjava/gnu/xml/transform/NodeNumberNode.java b/libjava/gnu/xml/transform/NodeNumberNode.java new file mode 100644 index 0000000..76f8d7e --- /dev/null +++ b/libjava/gnu/xml/transform/NodeNumberNode.java @@ -0,0 +1,261 @@ +/* NodeNumberNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; +import gnu.xml.xpath.NodeTypeTest; +import gnu.xml.xpath.Pattern; +import gnu.xml.xpath.Selector; +import gnu.xml.xpath.Test; +import gnu.xml.xpath.UnionExpr; + +/** + * A template node representing the XSL <code>number</code> instruction + * with no <code>value</code> expression, i.e. the value is computed from + * the document position of the context node. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class NodeNumberNode + extends AbstractNumberNode +{ + + static final int SINGLE = 0; + static final int MULTIPLE = 1; + static final int ANY = 2; + + final int level; + final Pattern count; + final Pattern from; + + NodeNumberNode(TemplateNode children, TemplateNode next, + int level, Pattern count, Pattern from, + TemplateNode format, String lang, + int letterValue, String groupingSeparator, int groupingSize) + { + super(children, next, format, lang, letterValue, groupingSeparator, + groupingSize); + this.level = level; + this.count = count; + this.from = from; + } + + TemplateNode clone(Stylesheet stylesheet) + { + return new NodeNumberNode((children == null) ? null : + children.clone(stylesheet), + (next == null) ? null : + next.clone(stylesheet), + level, + (count == null) ? null : + (Pattern) count.clone(stylesheet), + (from == null) ? from : + (Pattern) from.clone(stylesheet), + format, lang, letterValue, + groupingSeparator, groupingSize); + } + + int[] compute(Stylesheet stylesheet, Node context, int pos, int len) + throws TransformerException + { + /*if (from != null) + { + Object ret = from.evaluate(context, pos, len); + if (ret instanceof Collection) + { + Collection ns = (Collection) ret; + if (ns.size() > 0) + { + List list = new ArrayList(ns); + Collections.sort(list, documentOrderComparator); + context = (Node) list.get(0); + } + else + { + return new int[0]; + } + } + else + { + return new int[0]; + } + }*/ + Node current = context; + switch (level) + { + case SINGLE: + if (from == null) + { + while (context != null && !countMatches(current, context)) + { + context = context.getParentNode(); + } + } + else + { + while (context != null && !countMatches(current, context) && + !fromMatches(context)) + { + context = context.getParentNode(); + } + } + return (context == null) ? new int[0] : + new int[] { (context == current) ? pos : getIndex(current, context) }; + case MULTIPLE: + List ancestors = new ArrayList(); + while (context != null) + { + if (countMatches(current, context)) + { + if (from == null || fromMatches(context)) + { + ancestors.add(context); + } + } + context = context.getParentNode(); + } + Collections.sort(ancestors, documentOrderComparator); + int[] ret = new int[ancestors.size()]; + for (int i = 0; i < ret.length; i++) + { + ret[i] = getIndex(current, (Node) ancestors.get(i)); + } + return ret; + case ANY: + Expr preceding = new Selector(Selector.PRECEDING, + Collections.EMPTY_LIST); + Expr ancestorOrSelf = new Selector(Selector.ANCESTOR_OR_SELF, + Collections.EMPTY_LIST); + Expr any = new UnionExpr(preceding, ancestorOrSelf); + Object eval = any.evaluate(context, pos, len); + if (eval instanceof Collection) + { + Collection ns = (Collection) eval; + List candidates = new ArrayList(); + for (Iterator i = ns.iterator(); i.hasNext(); ) + { + Node candidate = (Node) i.next(); + if (countMatches(current, candidate)) + { + candidates.add(candidate); + if (from != null && from.matches(candidate)) + { + break; + } + } + } + return new int[] { candidates.size() }; + } + return new int[0]; + default: + throw new TransformerException("invalid level"); + } + } + + boolean countMatches(Node current, Node node) + { + if (count == null) + { + int cnt = current.getNodeType(); + int nnt = node.getNodeType(); + if (cnt != nnt) + { + return false; + } + if (nnt == Node.ELEMENT_NODE || nnt == Node.ATTRIBUTE_NODE) + { + String curi = current.getNamespaceURI(); + String nuri = node.getNamespaceURI(); + if ((curi == null && nuri != null) || + (curi != null && !curi.equals(nuri))) + { + return false; + } + String cn = current.getLocalName(); + String nn = current.getLocalName(); + if (!cn.equals(nn)) + { + return false; + } + } + return true; + } + else + { + return count.matches(node); + } + } + + boolean fromMatches(Node node) + { + for (Node ctx = node.getParentNode(); ctx != null; + ctx = ctx.getParentNode()) + { + if (from.matches(ctx)) + { + return true; + } + } + return false; + } + + int getIndex(Node current, Node node) + { + int index = 0; + do + { + do + { + node = node.getPreviousSibling(); + } + while (node != null && !countMatches(current, node)); + index++; + } + while (node != null); + return index; + } + +} diff --git a/libjava/gnu/xml/transform/NumberNode.java b/libjava/gnu/xml/transform/NumberNode.java new file mode 100644 index 0000000..0970ac9 --- /dev/null +++ b/libjava/gnu/xml/transform/NumberNode.java @@ -0,0 +1,86 @@ +/* NumberNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import javax.xml.transform.TransformerException; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; + +/** + * A template node representing the XSL <code>number</code> instruction + * with a <code>value</code> expression. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class NumberNode + extends AbstractNumberNode +{ + + final Expr value; + + NumberNode(TemplateNode children, TemplateNode next, + Expr value, TemplateNode format, String lang, + int letterValue, String groupingSeparator, int groupingSize) + { + super(children, next, format, lang, letterValue, groupingSeparator, + groupingSize); + this.value = value; + } + + TemplateNode clone(Stylesheet stylesheet) + { + return new NumberNode((children == null) ? null : + children.clone(stylesheet), + (next == null) ? null : + next.clone(stylesheet), + value.clone(stylesheet), + format, lang, letterValue, + groupingSeparator, groupingSize); + } + + int[] compute(Stylesheet stylesheet, Node context, int pos, int len) + throws TransformerException + { + Object ret = value.evaluate(context, pos, len); + Double d = (ret instanceof Double) ? ((Double) ret) : + new Double(Expr._number(context, ret)); + return new int[] { d.intValue() }; + } + +} diff --git a/libjava/gnu/xml/transform/OtherwiseNode.java b/libjava/gnu/xml/transform/OtherwiseNode.java new file mode 100644 index 0000000..1d9590e --- /dev/null +++ b/libjava/gnu/xml/transform/OtherwiseNode.java @@ -0,0 +1,93 @@ +/* OtherwiseNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Node; + +/** + * A template node representing an XSL <code>otherwise</code> instruction. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class OtherwiseNode + extends TemplateNode +{ + + OtherwiseNode(TemplateNode children, TemplateNode next) + { + super(children, next); + } + + TemplateNode clone(Stylesheet stylesheet) + { + return new OtherwiseNode((children == null) ? null : + children.clone(stylesheet), + (next == null) ? null : + next.clone(stylesheet)); + } + + void doApply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + if (children != null) + { + children.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + if (next != null) + { + next.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + } + + public String toString() + { + StringBuffer buf = new StringBuffer(getClass().getName()); + buf.append('['); + buf.append(']'); + return buf.toString(); + } + +} diff --git a/libjava/gnu/xml/transform/ParameterNode.java b/libjava/gnu/xml/transform/ParameterNode.java new file mode 100644 index 0000000..5b766d5 --- /dev/null +++ b/libjava/gnu/xml/transform/ParameterNode.java @@ -0,0 +1,156 @@ +/* ParameterNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.Collections; +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; + +/** + * A template node that sets a variable or parameter during template + * processing. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class ParameterNode + extends TemplateNode +{ + + final String name; + final Expr select; + final boolean global; + + ParameterNode(TemplateNode children, TemplateNode next, + String name, Expr select, boolean global) + { + super(children, next); + this.name = name; + this.select = select; + this.global = global; + } + + TemplateNode clone(Stylesheet stylesheet) + { + return new ParameterNode((children == null) ? null : + children.clone(stylesheet), + (next == null) ? null : + next.clone(stylesheet), + name, + select.clone(stylesheet), + global); + } + + void doApply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + boolean apply = global || !stylesheet.bindings.containsKey(name, global); + if (apply) + { + // push the variable context + stylesheet.bindings.push(global); + // set the variable + Object value = getValue(stylesheet, mode, context, pos, len); + if (value != null) + { + stylesheet.bindings.set(name, value, global); + } + } + // variable and param don't process children as such + // all subsequent instructions are processed with that variable context + if (next != null) + { + next.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + if (apply) + { + // pop the variable context + stylesheet.bindings.pop(global); + } + } + + Object getValue(Stylesheet stylesheet, QName mode, + Node context, int pos, int len) + throws TransformerException + { + if (select != null) + { + return select.evaluate(context, pos, len); + } + else if (children != null) + { + Document doc = (context instanceof Document) ? (Document) context : + context.getOwnerDocument(); + DocumentFragment fragment = doc.createDocumentFragment(); + children.apply(stylesheet, mode, context, pos, len, fragment, null); + return Collections.singleton(fragment); + } + else + { + return null; + } + } + + public String toString() + { + StringBuffer buf = new StringBuffer(getClass().getName()); + buf.append('['); + buf.append("name="); + buf.append(name); + if (select != null) + { + buf.append(",select="); + buf.append(select); + } + if (global) + { + buf.append(",global"); + } + buf.append(']'); + return buf.toString(); + } + +} + diff --git a/libjava/gnu/xml/transform/ProcessingInstructionNode.java b/libjava/gnu/xml/transform/ProcessingInstructionNode.java new file mode 100644 index 0000000..1eb2bd6 --- /dev/null +++ b/libjava/gnu/xml/transform/ProcessingInstructionNode.java @@ -0,0 +1,123 @@ +/* ProcessingInstructionNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; +import org.w3c.dom.Node; +import org.w3c.dom.ProcessingInstruction; +import gnu.xml.xpath.Expr; + +/** + * A template node representing the XSL <code>processing-instruction</code> + * instruction. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class ProcessingInstructionNode + extends TemplateNode +{ + + final String name; + + ProcessingInstructionNode(TemplateNode children, TemplateNode next, + String name) + { + super(children, next); + this.name = name; + } + + TemplateNode clone(Stylesheet stylesheet) + { + return new ProcessingInstructionNode((children == null) ? null : + children.clone(stylesheet), + (next == null) ? null : + next.clone(stylesheet), + name); + } + + void doApply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + String data = null; + Document doc = (parent instanceof Document) ? (Document) parent : + parent.getOwnerDocument(); + if (children != null) + { + // Create a document fragment to hold the text + DocumentFragment fragment = doc.createDocumentFragment(); + // Apply children to the fragment + children.apply(stylesheet, mode, + context, pos, len, + fragment, null); + // Use XPath string-value of fragment + data = Expr.stringValue(fragment); + } + ProcessingInstruction pi = doc.createProcessingInstruction(name, data); + // Insert into result tree + if (nextSibling != null) + { + parent.insertBefore(pi, nextSibling); + } + else + { + parent.appendChild(pi); + } + if (next != null) + { + next.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + } + + public String toString() + { + StringBuffer buf = new StringBuffer(getClass().getName()); + buf.append('['); + buf.append("name="); + buf.append(name); + buf.append(']'); + return buf.toString(); + } + +} diff --git a/libjava/gnu/xml/transform/SAXSerializer.java b/libjava/gnu/xml/transform/SAXSerializer.java new file mode 100644 index 0000000..c5e3820 --- /dev/null +++ b/libjava/gnu/xml/transform/SAXSerializer.java @@ -0,0 +1,305 @@ +/* SAXSerializer.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import org.w3c.dom.Attr; +import org.w3c.dom.DocumentType; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.ext.LexicalHandler; + +/** + * Serializes a DOM node to a sequence of SAX events. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class SAXSerializer + implements Attributes +{ + + transient NamedNodeMap attrs; + transient LinkedList namespaces = new LinkedList(); + + boolean isDefined(String prefix, String uri) + { + for (Iterator i = namespaces.iterator(); i.hasNext(); ) + { + HashMap ctx = (HashMap) i.next(); + if (uri.equals(ctx.get(prefix))) + { + return true; + } + } + return false; + } + + void define(String prefix, String uri) + { + for (Iterator i = namespaces.iterator(); i.hasNext(); ) + { + HashMap ctx = (HashMap) i.next(); + if (ctx.containsKey(prefix)) + { + HashMap newCtx = new HashMap(); + newCtx.put(prefix, uri); + namespaces.addFirst(newCtx); + return; + } + } + HashMap ctx; + if (namespaces.isEmpty()) + { + ctx = new HashMap(); + namespaces.add(ctx); + } + else + { + ctx = (HashMap) namespaces.getFirst(); + } + ctx.put(prefix, uri); + } + + void undefine(String prefix, String uri) + { + for (Iterator i = namespaces.iterator(); i.hasNext(); ) + { + HashMap ctx = (HashMap) i.next(); + if (uri.equals(ctx.get(prefix))) + { + ctx.remove(prefix); + if (ctx.isEmpty()) + { + namespaces.remove(ctx); + } + return; + } + } + } + + public int getLength() + { + return attrs.getLength(); + } + + public String getURI(int index) + { + return attrs.item(index).getNamespaceURI(); + } + + public String getLocalName(int index) + { + return attrs.item(index).getLocalName(); + } + + public String getQName(int index) + { + return attrs.item(index).getNodeName(); + } + + public String getType(int index) + { + Attr attr = (Attr) attrs.item(index); + return attr.isId() ? "ID" : "CDATA"; + } + + public String getValue(int index) + { + return attrs.item(index).getNodeValue(); + } + + public int getIndex(String uri, String localName) + { + int len = attrs.getLength(); + for (int i = 0; i < len; i++) + { + Node attr = attrs.item(i); + String a_uri = attr.getNamespaceURI(); + String a_localName = attr.getLocalName(); + if (((a_uri == null && uri == null) || + (a_uri != null && a_uri.equals(uri))) && + a_localName.equals(localName)) + { + return i; + } + } + return -1; + } + + public int getIndex(String qName) + { + int len = attrs.getLength(); + for (int i = 0; i < len; i++) + { + Node attr = attrs.item(i); + String a_name = attr.getNodeName(); + if (a_name.equals(qName)) + { + return i; + } + } + return -1; + } + + public String getType(String uri, String localName) + { + Attr attr = (Attr) attrs.getNamedItemNS(uri, localName); + return attr.isId() ? "ID" : "CDATA"; + } + + public String getType(String qName) + { + Attr attr = (Attr) attrs.getNamedItem(qName); + return attr.isId() ? "ID" : "CDATA"; + } + + public String getValue(String uri, String localName) + { + return attrs.getNamedItemNS(uri, localName).getNodeValue(); + } + + public String getValue(String qName) + { + return attrs.getNamedItem(qName).getNodeValue(); + } + + void serialize(Node node, ContentHandler ch, LexicalHandler lh) + throws SAXException + { + attrs = node.getAttributes(); + Node children; + Node next = node.getNextSibling(); + switch (node.getNodeType()) + { + case Node.ELEMENT_NODE: + String uri = node.getNamespaceURI(); + String prefix = node.getPrefix(); + boolean defined = isDefined(prefix, uri); + if (!defined) + { + define(prefix, uri); + ch.startPrefixMapping(prefix, uri); + } + String localName = node.getLocalName(); + String qName = node.getNodeName(); + ch.startElement(uri, localName, qName, this); + children = node.getFirstChild(); + if (children != null) + { + serialize(children, ch, lh); + } + ch.endElement(uri, localName, qName); + if (!defined) + { + ch.endPrefixMapping(prefix); + undefine(prefix, uri); + } + break; + case Node.TEXT_NODE: + char[] chars = node.getNodeValue().toCharArray(); + ch.characters(chars, 0, chars.length); + break; + case Node.CDATA_SECTION_NODE: + char[] cdata = node.getNodeValue().toCharArray(); + if (lh != null) + { + lh.startCDATA(); + ch.characters(cdata, 0, cdata.length); + lh.endCDATA(); + } + else + { + ch.characters(cdata, 0, cdata.length); + } + break; + case Node.COMMENT_NODE: + if (lh != null) + { + char[] comment = node.getNodeValue().toCharArray(); + lh.comment(comment, 0, comment.length); + } + break; + case Node.DOCUMENT_NODE: + case Node.DOCUMENT_FRAGMENT_NODE: + ch.startDocument(); + children = node.getFirstChild(); + if (children != null) + { + serialize(children, ch, lh); + } + ch.endDocument(); + break; + case Node.DOCUMENT_TYPE_NODE: + if (lh != null) + { + DocumentType doctype = (DocumentType) node; + String publicId = doctype.getPublicId(); + String systemId = doctype.getSystemId(); + lh.startDTD(node.getNodeName(), publicId, systemId); + NamedNodeMap entities = doctype.getEntities(); + int len = entities.getLength(); + for (int i = 0; i < len; i++) + { + Node entity = entities.item(i); + String entityName = entity.getNodeName(); + lh.startEntity(entityName); + lh.endEntity(entityName); + } + lh.endDTD(); + } + break; + case Node.PROCESSING_INSTRUCTION_NODE: + ch.processingInstruction(node.getNodeName(), node.getNodeValue()); + break; + case Node.ENTITY_REFERENCE_NODE: + ch.skippedEntity(node.getNodeName()); + break; + } + attrs = null; + if (next != null) + { + serialize(next, ch, lh); + } + } + +} diff --git a/libjava/gnu/xml/transform/SortKey.java b/libjava/gnu/xml/transform/SortKey.java new file mode 100644 index 0000000..9ecc919 --- /dev/null +++ b/libjava/gnu/xml/transform/SortKey.java @@ -0,0 +1,154 @@ +/* SortKey.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; + +/** + * An XSL sort key. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class SortKey +{ + + static final int DEFAULT = 0; + static final int UPPER_FIRST = 1; + static final int LOWER_FIRST = 2; + + final Expr select; + final TemplateNode langTemplate; + final TemplateNode dataTypeTemplate; + final TemplateNode orderTemplate; + final TemplateNode caseOrderTemplate; + + transient String lang; + transient String dataType; + transient boolean descending; + transient int caseOrder; + + SortKey(Expr select, TemplateNode lang, TemplateNode dataType, + TemplateNode order, TemplateNode caseOrder) + { + this.select = select; + this.langTemplate = lang; + this.dataTypeTemplate = dataType; + this.orderTemplate = order; + this.caseOrderTemplate = caseOrder; + } + + String key(Node node) + { + Object ret = select.evaluate(node, 1, 1); + if (ret instanceof String) + { + return (String) ret; + } + else + { + return Expr._string(node, ret); + } + } + + /** + * Prepare for a sort. + * This sets all transient variables from their AVTs. + */ + void init(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + Document doc = (context instanceof Document) ? (Document) context : + context.getOwnerDocument(); + if (langTemplate == null) + { + lang = null; + } + else + { + DocumentFragment fragment = doc.createDocumentFragment(); + langTemplate.apply(stylesheet, mode, context, pos, len, + fragment, null); + lang = Expr.stringValue(fragment); + } + if (dataTypeTemplate == null) + { + dataType = "text"; + } + else + { + DocumentFragment fragment = doc.createDocumentFragment(); + dataTypeTemplate.apply(stylesheet, mode, context, pos, len, + fragment, null); + dataType = Expr.stringValue(fragment); + } + if (orderTemplate == null) + { + descending = false; + } + else + { + DocumentFragment fragment = doc.createDocumentFragment(); + orderTemplate.apply(stylesheet, mode, context, pos, len, + fragment, null); + String order = Expr.stringValue(fragment); + descending = "descending".equals(order); + } + if (caseOrderTemplate == null) + { + caseOrder = DEFAULT; + } + else + { + DocumentFragment fragment = doc.createDocumentFragment(); + caseOrderTemplate.apply(stylesheet, mode, context, pos, len, + fragment, null); + String co = Expr.stringValue(fragment); + caseOrder = "upper-first".equals(co) ? UPPER_FIRST : + "lower-first".equals(co) ? LOWER_FIRST : + DEFAULT; + } + } + +} diff --git a/libjava/gnu/xml/transform/StreamSerializer.java b/libjava/gnu/xml/transform/StreamSerializer.java new file mode 100644 index 0000000..136105a --- /dev/null +++ b/libjava/gnu/xml/transform/StreamSerializer.java @@ -0,0 +1,632 @@ +/* StreamSerializer.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.xml.XMLConstants; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentType; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +/** + * Serializes a DOM node to an output stream. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class StreamSerializer +{ + + static final int SPACE = 0x20; + static final int BANG = 0x21; // ! + static final int APOS = 0x27; // ' + static final int SLASH = 0x2f; // / + static final int BRA = 0x3c; // < + static final int KET = 0x3e; // > + static final int EQ = 0x3d; // = + + protected String encoding; + boolean compatibilityMode; + final int mode; + final Map namespaces; + protected String eol; + Collection cdataSectionElements = Collections.EMPTY_SET; + + protected boolean discardDefaultContent; + protected boolean xmlDeclaration = true; + + public StreamSerializer() + { + this(Stylesheet.OUTPUT_XML, null, null); + } + + public StreamSerializer(String encoding) + { + this(Stylesheet.OUTPUT_XML, encoding, null); + } + + public StreamSerializer(int mode, String encoding, String eol) + { + this.mode = mode; + if (encoding == null) + { + encoding = "UTF-8"; + } + this.encoding = encoding.intern(); + compatibilityMode = true; + if (encoding.length() > 3) + { + String p = encoding.substring(0, 3); + if (p.equalsIgnoreCase("UTF") || + p.equalsIgnoreCase("UCS")) + { + compatibilityMode = false; + } + } + this.eol = (eol != null) ? eol : System.getProperty("line.separator"); + namespaces = new HashMap(); + } + + void setCdataSectionElements(Collection c) + { + cdataSectionElements = c; + } + + public void serialize(final Node node, final OutputStream out) + throws IOException + { + serialize(node, out, false); + } + + void serialize(final Node node, final OutputStream out, + boolean convertToCdata) + throws IOException + { + if (out == null) + { + throw new NullPointerException("no output stream"); + } + String value, prefix; + Node children; + Node next = node.getNextSibling(); + String uri = node.getNamespaceURI(); + boolean defined = false; + short nt = node.getNodeType(); + if (convertToCdata && nt == Node.TEXT_NODE) + { + nt = Node.CDATA_SECTION_NODE; + } + switch (nt) + { + case Node.ATTRIBUTE_NODE: + prefix = node.getPrefix(); + if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(uri) || + XMLConstants.XMLNS_ATTRIBUTE.equals(prefix) || + (prefix != null && prefix.startsWith("xmlns:"))) + { + String nsuri = node.getNodeValue(); + if (isDefined(nsuri)) + { + break; + } + define(nsuri, node.getLocalName()); + } + else if (uri != null && !isDefined(uri)) + { + prefix = define(uri, prefix); + String nsname = (prefix == null) ? "xmlns" : "xmlns:" + prefix; + out.write(SPACE); + out.write(encodeText(nsname)); + out.write(EQ); + String nsvalue = "'" + encode(uri, true, true) + "'"; + out.write(nsvalue.getBytes(encoding)); + defined = true; + } + out.write(SPACE); + String a_nodeName = node.getNodeName(); + out.write(encodeText(a_nodeName)); + String a_nodeValue = node.getNodeValue(); + if (mode == Stylesheet.OUTPUT_HTML && + a_nodeName.equals(a_nodeValue)) + { + break; + } + out.write(EQ); + value = "'" + encode(a_nodeValue, true, true) + "'"; + out.write(encodeText(value)); + break; + case Node.ELEMENT_NODE: + value = node.getNodeName(); + out.write(BRA); + out.write(encodeText(value)); + if (uri != null && !isDefined(uri)) + { + prefix = define(uri, node.getPrefix()); + String nsname = (prefix == null) ? "xmlns" : "xmlns:" + prefix; + out.write(SPACE); + out.write(encodeText(nsname)); + out.write(EQ); + String nsvalue = "'" + encode(uri, true, true) + "'"; + out.write(encodeText(nsvalue)); + defined = true; + } + NamedNodeMap attrs = node.getAttributes(); + if (attrs != null) + { + int len = attrs.getLength(); + for (int i = 0; i < len; i++) + { + Attr attr = (Attr) attrs.item(i); + if (discardDefaultContent && !attr.getSpecified()) + { + // NOOP + } + else + { + serialize(attr, out, false); + } + } + } + convertToCdata = cdataSectionElements.contains(value); + children = node.getFirstChild(); + if (children == null) + { + out.write(SLASH); + out.write(KET); + } + else + { + out.write(KET); + serialize(children, out, convertToCdata); + out.write(BRA); + out.write(SLASH); + out.write(encodeText(value)); + out.write(KET); + } + break; + case Node.TEXT_NODE: + value = node.getNodeValue(); + if (!"yes".equals(node.getUserData("disable-output-escaping"))) + { + value = encode(value, false, false); + } + out.write(encodeText(value)); + break; + case Node.CDATA_SECTION_NODE: + value = "<![CDATA[" + node.getNodeValue() + "]]>"; + out.write(encodeText(value)); + break; + case Node.COMMENT_NODE: + value = "<!--" + node.getNodeValue() + "-->"; + out.write(encodeText(value)); + Node cp = node.getParentNode(); + if (cp != null && cp.getNodeType() == Node.DOCUMENT_NODE) + { + out.write(encodeText(eol)); + } + break; + case Node.DOCUMENT_NODE: + case Node.DOCUMENT_FRAGMENT_NODE: + if (mode == Stylesheet.OUTPUT_XML) + { + if ("UTF-16".equalsIgnoreCase(encoding)) + { + out.write(0xfe); + out.write(0xff); + } + if (!"yes".equals(node.getUserData("omit-xml-declaration")) && + xmlDeclaration) + { + Document doc = (node instanceof Document) ? + (Document) node : null; + String version = (doc != null) ? doc.getXmlVersion() : null; + if (version == null) + { + version = (String) node.getUserData("version"); + } + if (version == null) + { + version = "1.0"; + } + out.write(BRA); + out.write(0x3f); + out.write("xml version='".getBytes("US-ASCII")); + out.write(version.getBytes("US-ASCII")); + out.write(APOS); + if (!("UTF-8".equalsIgnoreCase(encoding))) + { + out.write(" encoding='".getBytes("US-ASCII")); + out.write(encoding.getBytes("US-ASCII")); + out.write(APOS); + } + if ((doc != null && doc.getXmlStandalone()) || + "yes".equals(node.getUserData("standalone"))) + { + out.write(" standalone='yes'".getBytes("US-ASCII")); + } + out.write(0x3f); + out.write(KET); + out.write(encodeText(eol)); + } + // TODO warn if not outputting the declaration would be a + // problem + } + else if (mode == Stylesheet.OUTPUT_HTML) + { + // Ensure that encoding is accessible + String mediaType = (String) node.getUserData("media-type"); + if (mediaType == null) + { + mediaType = "text/html"; + } + String contentType = mediaType + "; charset=" + + ((encoding.indexOf(' ') != -1) ? + "\"" + encoding + "\"" : + encoding); + Document doc = (node instanceof Document) ? (Document) node : + node.getOwnerDocument(); + Node html = null; + for (Node ctx = node.getFirstChild(); ctx != null; + ctx = ctx.getNextSibling()) + { + if (ctx.getNodeType() == Node.ELEMENT_NODE) + { + html = ctx; + break; + } + } + if (html == null) + { + html = doc.createElement("html"); + node.appendChild(html); + } + Node head = null; + for (Node ctx = html.getFirstChild(); ctx != null; + ctx = ctx.getNextSibling()) + { + if (ctx.getNodeType() == Node.ELEMENT_NODE && + "head".equalsIgnoreCase(ctx.getLocalName())) + { + head = ctx; + break; + } + } + if (head == null) + { + head = doc.createElement("head"); + Node c1 = null; + for (Node ctx = html.getFirstChild(); ctx != null; + ctx = ctx.getNextSibling()) + { + if (ctx.getNodeType() == Node.ELEMENT_NODE) + { + c1 = ctx; + break; + } + } + if (c1 != null) + { + html.insertBefore(head, c1); + } + else + { + html.appendChild(head); + } + } + Node meta = null; + Node metaContent = null; + for (Node ctx = head.getFirstChild(); ctx != null; + ctx = ctx.getNextSibling()) + { + if (ctx.getNodeType() == Node.ELEMENT_NODE && + "meta".equalsIgnoreCase(ctx.getLocalName())) + { + NamedNodeMap metaAttrs = ctx.getAttributes(); + int len = metaAttrs.getLength(); + String httpEquiv = null; + Node content = null; + for (int i = 0; i < len; i++) + { + Node attr = metaAttrs.item(i); + String attrName = attr.getNodeName(); + if ("http-equiv".equalsIgnoreCase(attrName)) + { + httpEquiv = attr.getNodeValue(); + } + else if ("content".equalsIgnoreCase(attrName)) + { + content = attr; + } + } + if ("Content-Type".equalsIgnoreCase(httpEquiv)) + { + meta = ctx; + metaContent = content; + break; + } + } + } + if (meta == null) + { + meta = doc.createElement("meta"); + // Insert first + Node first = head.getFirstChild(); + if (first == null) + { + head.appendChild(meta); + } + else + { + head.insertBefore(meta, first); + } + Node metaHttpEquiv = doc.createAttribute("http-equiv"); + meta.getAttributes().setNamedItem(metaHttpEquiv); + metaHttpEquiv.setNodeValue("Content-Type"); + } + if (metaContent == null) + { + metaContent = doc.createAttribute("content"); + meta.getAttributes().setNamedItem(metaContent); + } + metaContent.setNodeValue(contentType); + // phew + } + children = node.getFirstChild(); + if (children != null) + { + serialize(children, out, convertToCdata); + } + break; + case Node.DOCUMENT_TYPE_NODE: + DocumentType doctype = (DocumentType) node; + out.write(BRA); + out.write(BANG); + value = doctype.getNodeName(); + out.write(encodeText(value)); + String publicId = doctype.getPublicId(); + if (publicId != null) + { + out.write(encodeText(" PUBLIC ")); + out.write(APOS); + out.write(encodeText(publicId)); + out.write(APOS); + } + String systemId = doctype.getSystemId(); + if (systemId != null) + { + out.write(encodeText(" SYSTEM ")); + out.write(APOS); + out.write(encodeText(systemId)); + out.write(APOS); + } + String internalSubset = doctype.getInternalSubset(); + if (internalSubset != null) + { + out.write(encodeText(internalSubset)); + } + out.write(KET); + out.write(eol.getBytes(encoding)); + break; + case Node.ENTITY_REFERENCE_NODE: + value = "&" + node.getNodeValue() + ";"; + out.write(encodeText(value)); + break; + case Node.PROCESSING_INSTRUCTION_NODE: + value = "<?" + node.getNodeName() + " " + node.getNodeValue() + "?>"; + out.write(encodeText(value)); + Node pp = node.getParentNode(); + if (pp != null && pp.getNodeType() == Node.DOCUMENT_NODE) + { + out.write(encodeText(eol)); + } + break; + } + if (defined) + { + undefine(uri); + } + if (next != null) + { + serialize(next, out, convertToCdata); + } + } + + boolean isDefined(String uri) + { + return XMLConstants.XML_NS_URI.equals(uri) || + XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(uri) || + namespaces.containsKey(uri); + } + + String define(String uri, String prefix) + { + while (namespaces.containsValue(prefix)) + { + // Fabricate new prefix + prefix = prefix + "_"; + } + namespaces.put(uri, prefix); + return prefix; + } + + void undefine(String uri) + { + namespaces.remove(uri); + } + + final byte[] encodeText(String text) + throws UnsupportedEncodingException + { + if (compatibilityMode) + { + int len = text.length(); + StringBuffer buf = null; + for (int i = 0; i < len; i++) + { + char c = text.charAt(i); + if (c >= 127) + { + if (buf == null) + { + buf = new StringBuffer(text.substring(0, i)); + } + buf.append('&'); + buf.append('#'); + buf.append((int) c); + buf.append(';'); + } + else if (buf != null) + { + buf.append(c); + } + } + if (buf != null) + { + text = buf.toString(); + } + } + return text.getBytes(encoding); + } + + String encode(String text, boolean encodeCtl, boolean inAttr) + { + int len = text.length(); + StringBuffer buf = null; + for (int i = 0; i < len; i++) + { + char c = text.charAt(i); + if (c == '<') + { + if (buf == null) + { + buf = new StringBuffer(text.substring(0, i)); + } + buf.append("<"); + } + else if (c == '>') + { + if (buf == null) + { + buf = new StringBuffer(text.substring(0, i)); + } + buf.append(">"); + } + else if (c == '&') + { + if (mode == Stylesheet.OUTPUT_HTML && (i + 1) < len && + text.charAt(i + 1) == '{') + { + if (buf != null) + { + buf.append(c); + } + } + else + { + if (buf == null) + { + buf = new StringBuffer(text.substring(0, i)); + } + buf.append("&"); + } + } + else if (c == '\'' && inAttr) + { + if (buf == null) + { + buf = new StringBuffer(text.substring(0, i)); + } + buf.append("'"); + } + else if (c == '"' && inAttr) + { + if (buf == null) + { + buf = new StringBuffer(text.substring(0, i)); + } + buf.append("""); + } + else if (encodeCtl) + { + if (c < 0x20) + { + if (buf == null) + { + buf = new StringBuffer(text.substring(0, i)); + } + buf.append('&'); + buf.append('#'); + buf.append((int) c); + buf.append(';'); + } + else if (buf != null) + { + buf.append(c); + } + } + else if (buf != null) + { + buf.append(c); + } + } + return (buf == null) ? text : buf.toString(); + } + + String toString(Node node) + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try + { + serialize(node, out); + return new String(out.toByteArray(), encoding); + } + catch (IOException e) + { + throw new RuntimeException(e.getMessage()); + } + } + +} diff --git a/libjava/gnu/xml/transform/Stylesheet.java b/libjava/gnu/xml/transform/Stylesheet.java new file mode 100644 index 0000000..80f5781 --- /dev/null +++ b/libjava/gnu/xml/transform/Stylesheet.java @@ -0,0 +1,1730 @@ +/* Stylesheet.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.TreeSet; +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; +import javax.xml.namespace.QName; +import javax.xml.transform.Source; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathFunction; +import javax.xml.xpath.XPathFunctionResolver; +import javax.xml.xpath.XPathExpressionException; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.DOMException; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.Text; +import org.w3c.dom.UserDataHandler; +import gnu.xml.xpath.Expr; +import gnu.xml.xpath.NameTest; +import gnu.xml.xpath.NodeTypeTest; +import gnu.xml.xpath.Pattern; +import gnu.xml.xpath.Selector; +import gnu.xml.xpath.Root; +import gnu.xml.xpath.Test; +import gnu.xml.xpath.XPathImpl; + +/** + * An XSL stylesheet. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class Stylesheet + implements NamespaceContext, XPathFunctionResolver, UserDataHandler, Cloneable +{ + + static final String XSL_NS = "http://www.w3.org/1999/XSL/Transform"; + + static final int OUTPUT_XML = 0; + static final int OUTPUT_HTML = 1; + static final int OUTPUT_TEXT = 2; + + final TransformerFactoryImpl factory; + TransformerImpl transformer; + Stylesheet parent; + final XPathImpl xpath; + final String systemId; + final int precedence; + + final boolean debug; + + /** + * Version of XSLT. + */ + String version; + + Collection extensionElementPrefixes; + Collection excludeResultPrefixes; + + /** + * Set of element names for which we should strip whitespace. + */ + Set stripSpace; + + /** + * Set of element names for which we should preserve whitespace. + */ + Set preserveSpace; + + /** + * Output options. + */ + int outputMethod; + String outputVersion; + String outputEncoding; + boolean outputOmitXmlDeclaration; + boolean outputStandalone; + String outputPublicId; + String outputSystemId; + Collection outputCdataSectionElements; + boolean outputIndent; + String outputMediaType; + + /** + * Keys. + */ + Collection keys; + + /** + * Decimal formats. + */ + Map decimalFormats; + + /** + * Namespace aliases. + */ + Map namespaceAliases; + + /** + * Attribute-sets. + */ + List attributeSets; + + /** + * Variables. + */ + List variables; + + /** + * Variable and parameter bindings. + */ + Bindings bindings; + + /** + * Templates. + */ + LinkedList templates; + + TemplateNode builtInNodeTemplate; + TemplateNode builtInTextTemplate; + + /** + * Holds the current node while parsing. + * Necessary to associate the document function with its declaring node, + * to resolve namespaces, and to maintain the current node for the + * current() function. + */ + Node current; + + /** + * Set by a terminating message. + */ + transient boolean terminated; + + /** + * Current template in force. + */ + transient Template currentTemplate; + + Stylesheet(TransformerFactoryImpl factory, + Stylesheet parent, + Document doc, + String systemId, + int precedence) + throws TransformerConfigurationException + { + this.factory = factory; + this.systemId = systemId; + this.precedence = precedence; + this.parent = parent; + extensionElementPrefixes = new HashSet(); + excludeResultPrefixes = new HashSet(); + stripSpace = new LinkedHashSet(); + preserveSpace = new LinkedHashSet(); + outputCdataSectionElements = new LinkedHashSet(); + xpath = (XPathImpl) factory.xpathFactory.newXPath(); + if (parent == null) + { + bindings = new Bindings(this); + attributeSets = new LinkedList(); + variables = new LinkedList(); + namespaceAliases = new LinkedHashMap(); + templates = new LinkedList(); + keys = new LinkedList(); + decimalFormats = new LinkedHashMap(); + initDefaultDecimalFormat(); + xpath.setNamespaceContext(this); + xpath.setXPathFunctionResolver(this); + } + else + { + /* Test for import circularity */ + for (Stylesheet ctx = this; ctx.parent != null; ctx = ctx.parent) + { + if (systemId != null && systemId.equals(ctx.parent.systemId)) + { + String msg = "circularity importing " + systemId; + throw new TransformerConfigurationException(msg); + } + } + /* OK */ + Stylesheet root = getRootStylesheet(); + bindings = root.bindings; + attributeSets = root.attributeSets; + variables = root.variables; + namespaceAliases = root.namespaceAliases; + templates = root.templates; + keys = root.keys; + decimalFormats = root.decimalFormats; + xpath.setNamespaceContext(root); + xpath.setXPathFunctionResolver(root); + } + xpath.setXPathVariableResolver(bindings); + + Test anyNode = new NodeTypeTest((short) 0); + List tests = Collections.singletonList(anyNode); + builtInNodeTemplate = + new ApplyTemplatesNode(null, null, + new Selector(Selector.CHILD, tests), + null, null, null, true); + builtInTextTemplate = + new ValueOfNode(null, null, + new Selector(Selector.SELF, tests), + false); + + parse(doc.getDocumentElement(), true); + current = doc; // Alow namespace resolution during processing + + debug = ("yes".equals(System.getProperty("xsl.debug"))); + + if (debug) + { + System.err.println("Stylesheet: " + doc.getDocumentURI()); + for (Iterator i = templates.iterator(); i.hasNext(); ) + { + Template t = (Template) i.next(); + t.list(System.err); + System.err.println("--------------------"); + } + } + } + + Stylesheet getRootStylesheet() + { + Stylesheet stylesheet = this; + while (stylesheet.parent != null) + { + stylesheet = stylesheet.parent; + } + return stylesheet; + } + + void initDefaultDecimalFormat() + { + DecimalFormat defaultDecimalFormat = new DecimalFormat(); + DecimalFormatSymbols symbols = new DecimalFormatSymbols(); + symbols.setDecimalSeparator('.'); + symbols.setGroupingSeparator(','); + symbols.setPercent('%'); + symbols.setPerMill('\u2030'); + symbols.setZeroDigit('0'); + symbols.setDigit('#'); + symbols.setPatternSeparator(';'); + symbols.setInfinity("Infinity"); + symbols.setNaN("NaN"); + symbols.setMinusSign('-'); + defaultDecimalFormat.setDecimalFormatSymbols(symbols); + decimalFormats.put(null, defaultDecimalFormat); + } + + // -- Cloneable -- + + public Object clone() + { + try + { + Stylesheet clone = (Stylesheet) super.clone(); + clone.bindings = (Bindings) bindings.clone(); + + LinkedList templates2 = new LinkedList(); + for (Iterator i = templates.iterator(); i.hasNext(); ) + { + Template t = (Template) i.next(); + templates2.add(t.clone(clone)); + } + clone.templates = templates2; + + LinkedList attributeSets2 = new LinkedList(); + for (Iterator i = attributeSets.iterator(); i.hasNext(); ) + { + AttributeSet as = (AttributeSet) i.next(); + attributeSets2.add(as.clone(clone)); + } + clone.attributeSets = attributeSets2; + + LinkedList variables2 = new LinkedList(); + for (Iterator i = variables.iterator(); i.hasNext(); ) + { + ParameterNode var = (ParameterNode) i.next(); + variables2.add(var.clone(clone)); + } + clone.variables = variables2; + + LinkedList keys2 = new LinkedList(); + for (Iterator i = keys.iterator(); i.hasNext(); ) + { + Key k = (Key) i.next(); + keys2.add(k.clone(clone)); + } + clone.keys = keys2; + + return clone; + } + catch (CloneNotSupportedException e) + { + throw new Error(e.getMessage()); + } + } + + // -- Variable evaluation -- + + void initTopLevelVariables(Node context) + throws TransformerException + { + for (Iterator i = variables.iterator(); i.hasNext(); ) + { + ParameterNode var = (ParameterNode) i.next(); + bindings.set(var.name, + var.getValue(this, null, context, 1, 1), + var.global); + } + } + + // -- NamespaceContext -- + + public String getNamespaceURI(String prefix) + { + return (current == null) ? null : current.lookupNamespaceURI(prefix); + } + + public String getPrefix(String namespaceURI) + { + return (current == null) ? null : current.lookupPrefix(namespaceURI); + } + + public Iterator getPrefixes(String namespaceURI) + { + // TODO + return Collections.singleton(getPrefix(namespaceURI)).iterator(); + } + + // -- Template selection -- + + TemplateNode getTemplate(QName mode, Node context, boolean applyImports) + throws TransformerException + { + //System.err.println("getTemplate: mode="+mode+" context="+context); + Set candidates = new TreeSet(); + for (Iterator j = templates.iterator(); j.hasNext(); ) + { + Template t = (Template) j.next(); + boolean isMatch = t.matches(mode, context); + if (applyImports) + { + if (currentTemplate == null) + { + String msg = "current template may not be null " + + "during apply-imports"; + throw new TransformerException(msg); + } + if (!currentTemplate.imports(t)) + { + isMatch = false; + } + } + //System.err.println("\t"+context+" "+t+"="+isMatch); + if (isMatch) + { + candidates.add(t); + } + } + //System.err.println("\tcandidates="+candidates); + if (candidates.isEmpty()) + { + // Apply built-in template + // Current template is unchanged + //System.err.println("\tbuiltInTemplate context="+context); + switch (context.getNodeType()) + { + case Node.ELEMENT_NODE: + case Node.DOCUMENT_NODE: + case Node.DOCUMENT_FRAGMENT_NODE: + case Node.PROCESSING_INSTRUCTION_NODE: + case Node.COMMENT_NODE: + return builtInNodeTemplate; + case Node.TEXT_NODE: + case Node.ATTRIBUTE_NODE: + return builtInTextTemplate; + default: + return null; + } + } + else + { + Template t = (Template) candidates.iterator().next(); + // Set current template + currentTemplate = t; + //System.err.println("\ttemplate="+t+" context="+context); + return t.node; + } + } + + TemplateNode getTemplate(QName mode, QName name) + throws TransformerException + { + //System.err.println("getTemplate: mode="+mode+" name="+name); + Set candidates = new TreeSet(); + for (Iterator j = templates.iterator(); j.hasNext(); ) + { + Template t = (Template) j.next(); + boolean isMatch = t.matches(name); + //System.err.println("\t"+name+" "+t+"="+isMatch); + if (isMatch) + { + candidates.add(t); + } + } + if (candidates.isEmpty()) + { + return null; + //throw new TransformerException("template '" + name + "' not found"); + } + Template t = (Template) candidates.iterator().next(); + //System.err.println("\ttemplate="+t+" context="+context); + return t.node; + } + + /** + * template + */ + final Template parseTemplate(Node node, NamedNodeMap attrs) + throws TransformerConfigurationException, XPathExpressionException + { + String n = getAttribute(attrs, "name"); + QName name = (n == null) ? null : getQName(n); + String m = getAttribute(attrs, "match"); + Pattern match = null; + if (m != null) + { + try + { + match = (Pattern) xpath.compile(m); + } + catch (ClassCastException e) + { + String msg = "illegal pattern: " + m; + throw new TransformerConfigurationException(msg); + } + } + String p = getAttribute(attrs, "priority"); + String mm = getAttribute(attrs, "mode"); + QName mode = (mm == null) ? null : getQName(mm); + double priority = (p == null) ? Template.DEFAULT_PRIORITY : + Double.parseDouble(p); + return new Template(this, name, match, parse(node.getFirstChild()), + precedence, priority, mode); + } + + /** + * output + */ + final void parseOutput(Node node, NamedNodeMap attrs) + throws TransformerConfigurationException + { + String method = getAttribute(attrs, "method"); + if ("xml".equals(method) || method == null) + { + outputMethod = OUTPUT_XML; + } + else if ("html".equals(method)) + { + outputMethod = OUTPUT_HTML; + } + else if ("text".equals(method)) + { + outputMethod = OUTPUT_TEXT; + } + else + { + String msg = "unsupported output method: " + method; + DOMSourceLocator l = new DOMSourceLocator(node); + throw new TransformerConfigurationException(msg, l); + } + outputPublicId = getAttribute(attrs, "public-id"); + outputSystemId = getAttribute(attrs, "system-id"); + outputEncoding = getAttribute(attrs, "encoding"); + String indent = getAttribute(attrs, "indent"); + if (indent != null) + { + outputIndent = "yes".equals(indent); + } + outputVersion = getAttribute(attrs, "version"); + String omitXmlDecl = getAttribute(attrs, "omit-xml-declaration"); + if (omitXmlDecl != null) + { + outputOmitXmlDeclaration = "yes".equals(omitXmlDecl); + } + String standalone = getAttribute(attrs, "standalone"); + if (standalone != null) + { + outputStandalone = "yes".equals(standalone); + } + outputMediaType = getAttribute(attrs, "media-type"); + String cdataSectionElements = + getAttribute(attrs, "cdata-section-elements"); + if (cdataSectionElements != null) + { + StringTokenizer st = new StringTokenizer(cdataSectionElements, " "); + while (st.hasMoreTokens()) + { + outputCdataSectionElements.add(st.nextToken()); + } + } + } + + /** + * key + */ + final void parseKey(Node node, NamedNodeMap attrs) + throws TransformerConfigurationException, XPathExpressionException + { + String n = getRequiredAttribute(attrs, "name", node); + String m = getRequiredAttribute(attrs, "match", node); + String u = getRequiredAttribute(attrs, "use", node); + QName name = getQName(n); + Expr use = (Expr) xpath.compile(u); + try + { + Pattern match = (Pattern) xpath.compile(m); + Key key = new Key(name, match, use); + keys.add(key); + } + catch (ClassCastException e) + { + throw new TransformerConfigurationException("invalid pattern: " + m); + } + } + + /** + * decimal-format + */ + final void parseDecimalFormat(Node node, NamedNodeMap attrs) + throws TransformerConfigurationException + { + String dfName = getAttribute(attrs, "name"); + DecimalFormat df = new DecimalFormat(); + DecimalFormatSymbols symbols = new DecimalFormatSymbols(); + symbols.setDecimalSeparator(parseDFChar(attrs, "decimal-separator", '.')); + symbols.setGroupingSeparator(parseDFChar(attrs, "grouping-separator", ',')); + symbols.setInfinity(parseDFString(attrs, "infinity", "Infinity")); + symbols.setMinusSign(parseDFChar(attrs, "minus-sign", '-')); + symbols.setNaN(parseDFString(attrs, "NaN", "NaN")); + symbols.setPercent(parseDFChar(attrs, "percent", '%')); + symbols.setPerMill(parseDFChar(attrs, "per-mille", '\u2030')); + symbols.setZeroDigit(parseDFChar(attrs, "zero-digit", '0')); + symbols.setDigit(parseDFChar(attrs, "digit", '#')); + symbols.setPatternSeparator(parseDFChar(attrs, "pattern-separator", ';')); + df.setDecimalFormatSymbols(symbols); + decimalFormats.put(dfName, df); + } + + private final char parseDFChar(NamedNodeMap attrs, String name, char def) + throws TransformerConfigurationException + { + Node attr = attrs.getNamedItem(name); + try + { + return (attr == null) ? def : attr.getNodeValue().charAt(0); + } + catch (StringIndexOutOfBoundsException e) + { + throw new TransformerConfigurationException("empty attribute '" + + name + + "' in decimal-format", e); + } + } + + private final String parseDFString(NamedNodeMap attrs, String name, + String def) + { + Node attr = attrs.getNamedItem(name); + return (attr == null) ? def : attr.getNodeValue(); + } + + /** + * namespace-alias + */ + final void parseNamespaceAlias(Node node, NamedNodeMap attrs) + throws TransformerConfigurationException + { + String sp = getRequiredAttribute(attrs, "stylesheet-prefix", node); + String rp = getRequiredAttribute(attrs, "result-prefix", node); + namespaceAliases.put(sp, rp); + } + + /** + * attribute-set + */ + final void parseAttributeSet(Node node, NamedNodeMap attrs) + throws TransformerConfigurationException, XPathExpressionException + { + TemplateNode children = parse(node.getFirstChild()); + String name = getRequiredAttribute(attrs, "name", node); + String uas = getAttribute(attrs, "use-attribute-sets"); + attributeSets.add(new AttributeSet(children, name, uas)); + } + + /** + * Parse top-level elements. + */ + void parse(Node node, boolean root) + throws TransformerConfigurationException + { + if (node == null) + { + return; + } + current = node; + try + { + String namespaceUri = node.getNamespaceURI(); + if (XSL_NS.equals(namespaceUri) && + node.getNodeType() == Node.ELEMENT_NODE) + { + String name = node.getLocalName(); + NamedNodeMap attrs = node.getAttributes(); + if ("stylesheet".equals(name)) + { + version = getAttribute(attrs, "version"); + String eep = getAttribute(attrs, "extension-element-prefixes"); + if (eep != null) + { + StringTokenizer st = new StringTokenizer(eep); + while (st.hasMoreTokens()) + { + extensionElementPrefixes.add(st.nextToken()); + } + } + String erp = getAttribute(attrs, "exclude-result-prefixes"); + if (erp != null) + { + StringTokenizer st = new StringTokenizer(erp); + while (st.hasMoreTokens()) + { + excludeResultPrefixes.add(st.nextToken()); + } + } + parse(node.getFirstChild(), false); + return; + } + else if ("template".equals(name)) + { + templates.addFirst(parseTemplate(node, attrs)); + } + else if ("param".equals(name) || + "variable".equals(name)) + { + boolean global = "variable".equals(name); + TemplateNode content = parse(node.getFirstChild()); + String paramName = getRequiredAttribute(attrs, "name", node); + String select = getAttribute(attrs, "select"); + if (select != null && select.length() > 0) + { + if (content != null) + { + String msg = "parameter '" + paramName + + "' has both select and content"; + DOMSourceLocator l = new DOMSourceLocator(node); + throw new TransformerConfigurationException(msg, l); + } + Expr expr = (Expr) xpath.compile(select); + variables.add(new ParameterNode(null, null, + paramName, + expr, global)); + } + else + { + variables.add(new ParameterNode(content, null, + paramName, + null, global)); + } + bindings.set(paramName, content, global); + } + else if ("include".equals(name) || "import".equals(name)) + { + int delta = "import".equals(name) ? -1 : 0; + String href = getRequiredAttribute(attrs, "href", node); + Source source; + synchronized (factory.resolver) + { + if (transformer != null) + { + factory.resolver + .setUserResolver(transformer.getURIResolver()); + factory.resolver + .setUserListener(transformer.getErrorListener()); + } + source = factory.resolver.resolve(systemId, href); + } + factory.newStylesheet(source, precedence + delta, this); + } + else if ("output".equals(name)) + { + parseOutput(node, attrs); + } + else if ("preserve-space".equals(name)) + { + String elements = + getRequiredAttribute(attrs, "elements", node); + StringTokenizer st = new StringTokenizer(elements, + " \t\n\r"); + while (st.hasMoreTokens()) + { + preserveSpace.add(parseNameTest(st.nextToken())); + } + } + else if ("strip-space".equals(name)) + { + String elements = + getRequiredAttribute(attrs, "elements", node); + StringTokenizer st = new StringTokenizer(elements, + " \t\n\r"); + while (st.hasMoreTokens()) + { + stripSpace.add(parseNameTest(st.nextToken())); + } + } + else if ("key".equals(name)) + { + parseKey(node, attrs); + } + else if ("decimal-format".equals(name)) + { + parseDecimalFormat(node, attrs); + } + else if ("namespace-alias".equals(name)) + { + parseNamespaceAlias(node, attrs); + } + else if ("attribute-set".equals(name)) + { + parseAttributeSet(node, attrs); + } + parse(node.getNextSibling(), false); + } + else if (root) + { + // Literal document element + Attr versionNode = + ((Element)node).getAttributeNodeNS(XSL_NS, "version"); + if (versionNode == null) + { + String msg = "no xsl:version attribute on literal result node"; + DOMSourceLocator l = new DOMSourceLocator(node); + throw new TransformerConfigurationException(msg, l); + } + version = versionNode.getValue(); + Node rootClone = node.cloneNode(true); + NamedNodeMap attrs = rootClone.getAttributes(); + attrs.removeNamedItemNS(XSL_NS, "version"); + templates.add(new Template(this, null, new Root(), + parse(rootClone), + precedence, + Template.DEFAULT_PRIORITY, + null)); + } + else + { + // Skip unknown elements, text, comments, etc + parse(node.getNextSibling(), false); + } + } + catch (TransformerException e) + { + DOMSourceLocator l = new DOMSourceLocator(node); + throw new TransformerConfigurationException(e.getMessage(), l, e); + } + catch (DOMException e) + { + DOMSourceLocator l = new DOMSourceLocator(node); + throw new TransformerConfigurationException(e.getMessage(), l, e); + } + catch (XPathExpressionException e) + { + DOMSourceLocator l = new DOMSourceLocator(node); + throw new TransformerConfigurationException(e.getMessage(), l, e); + } + } + + final NameTest parseNameTest(String token) + { + if ("*".equals(token)) + { + return new NameTest(null, true, true); + } + else if (token.endsWith(":*")) + { + QName qName = getQName(token.substring(0, token.length() - 2)); + return new NameTest(qName, true, false); + } + else + { + QName qName = getQName(token); + return new NameTest(qName, false, false); + } + } + + final QName getQName(String name) + { + QName qName = QName.valueOf(name); + String prefix = qName.getPrefix(); + String uri = qName.getNamespaceURI(); + if (prefix != null && (uri == null || uri.length() == 0)) + { + uri = getNamespaceURI(prefix); + String localName = qName.getLocalPart(); + qName = new QName(uri, localName, prefix); + } + return qName; + } + + final TemplateNode parseAttributeValueTemplate(String value, Node source) + throws TransformerConfigurationException, XPathExpressionException + { + current = source; + // Tokenize + int len = value.length(); + int off = 0; + List tokens = new ArrayList(); // text tokens + List types = new ArrayList(); // literal or expression + int depth = 0; + for (int i = 0; i < len; i++) + { + char c = value.charAt(i); + if (c == '{') + { + if (i < (len - 1) && value.charAt(i + 1) == '{') + { + tokens.add(value.substring(off, i + 1)); + types.add(Boolean.FALSE); + i++; + off = i + 1; + continue; + } + if (depth == 0) + { + if (i - off > 0) + { + tokens.add(value.substring(off, i)); + types.add(Boolean.FALSE); + } + off = i + 1; + } + depth++; + } + else if (c == '}') + { + if (i < (len - 1) && value.charAt(i + 1) == '}') + { + tokens.add(value.substring(off, i + 1)); + types.add(Boolean.FALSE); + i++; + off = i + 1; + continue; + } + if (depth == 1) + { + if (i - off > 0) + { + tokens.add(value.substring(off, i)); + types.add(Boolean.TRUE); + } + else + { + String msg = "attribute value template " + + "must contain expression: " + value; + DOMSourceLocator l = new DOMSourceLocator(source); + throw new TransformerConfigurationException(msg, l); + } + off = i + 1; + } + depth--; + } + } + if (depth > 0) + { + String msg = "invalid attribute value template: " + value; + throw new TransformerConfigurationException(msg); + } + if (len - off > 0) + { + // Trailing text + tokens.add(value.substring(off)); + types.add(Boolean.FALSE); + } + + // Construct template node tree + TemplateNode ret = null; + Document doc = source.getOwnerDocument(); + len = tokens.size(); + for (int i = len - 1; i >= 0; i--) + { + String token = (String) tokens.get(i); + Boolean type = (Boolean) types.get(i); + if (type == Boolean.TRUE) + { + // Expression text + Expr select = (Expr) xpath.compile(token); + ret = new ValueOfNode(null, ret, select, false); + } + else + { + // Verbatim text + ret = new LiteralNode(null, ret, doc.createTextNode(token)); + } + } + return ret; + } + + boolean isPreserved(Text text) + throws TransformerConfigurationException + { + // Check characters in text + String value = text.getData(); + if (value != null) + { + int len = value.length(); + for (int i = 0; i < len; i++) + { + char c = value.charAt(i); + if (c != 0x20 && c != 0x09 && c != 0x0a && c != 0x0d) + { + return true; + } + } + } + // Check parent node + Node ctx = text.getParentNode(); + if (!preserveSpace.isEmpty()) + { + for (Iterator i = preserveSpace.iterator(); i.hasNext(); ) + { + NameTest preserveTest = (NameTest) i.next(); + if (preserveTest.matches(ctx, 1, 1)) + { + boolean override = false; + if (!stripSpace.isEmpty()) + { + for (Iterator j = stripSpace.iterator(); j.hasNext(); ) + { + NameTest stripTest = (NameTest) j.next(); + if (stripTest.matches(ctx, 1, 1)) + { + override = true; + break; + } + } + } + if (!override) + { + return true; + } + } + } + } + // Check whether any ancestor specified xml:space + while (ctx != null) + { + if (ctx.getNodeType() == Node.ELEMENT_NODE) + { + Element element = (Element) ctx; + String xmlSpace = element.getAttribute("xml:space"); + if ("default".equals(xmlSpace)) + { + break; + } + else if ("preserve".equals(xmlSpace)) + { + return true; + } + else if (xmlSpace.length() > 0) + { + String msg = "Illegal value for xml:space: " + xmlSpace; + throw new TransformerConfigurationException(msg); + } + else if ("text".equals(ctx.getLocalName()) && + XSL_NS.equals(ctx.getNamespaceURI())) + { + // xsl:text implies xml:space='preserve' + return true; + } + } + ctx = ctx.getParentNode(); + } + return false; + } + + public XPathFunction resolveFunction(QName name, int arity) + { + String uri = name.getNamespaceURI(); + if (XSL_NS.equals(uri) || uri == null || uri.length() == 0) + { + String localName = name.getLocalPart(); + if ("document".equals(localName) && (arity == 1 || arity == 2)) + { + if (current == null) + { + throw new RuntimeException("current is null"); + } + return new DocumentFunction(getRootStylesheet(), current); + } + else if ("key".equals(localName) && (arity == 2)) + { + return new KeyFunction(getRootStylesheet()); + } + else if ("format-number".equals(localName) && + (arity == 2 || arity == 3)) + { + return new FormatNumberFunction(getRootStylesheet()); + } + else if ("current".equals(localName) && (arity == 0)) + { + return new CurrentFunction(getRootStylesheet()); + } + else if ("unparsed-entity-uri".equals(localName) && (arity == 1)) + { + return new UnparsedEntityUriFunction(); + } + else if ("generate-id".equals(localName) && + (arity == 1 || arity == 0)) + { + return new GenerateIdFunction(); + } + else if ("system-property".equals(localName) && (arity == 1)) + { + return new SystemPropertyFunction(); + } + else if ("element-available".equals(localName) && (arity == 1)) + { + return new ElementAvailableFunction(this); + } + else if ("function-available".equals(localName) && (arity == 1)) + { + return new FunctionAvailableFunction(this); + } + } + return null; + } + + // -- Parsing -- + + /** + * apply-templates + */ + final TemplateNode parseApplyTemplates(Node node, Node children, Node next) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String m = getAttribute(attrs, "mode"); + QName mode = (m == null) ? null : getQName(m); + String s = getAttribute(attrs, "select"); + if (s == null) + { + s = "child::node()"; + } + List sortKeys = parseSortKeys(children); + List withParams = parseWithParams(children); + Expr select = (Expr) xpath.compile(s); + return new ApplyTemplatesNode(null, parse(next), + select, mode, + sortKeys, withParams, false); + } + + /** + * call-template + */ + final TemplateNode parseCallTemplate(Node node, Node children, Node next) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String n = getRequiredAttribute(attrs, "name", node); + QName name = getQName(n); + List withParams = parseWithParams(children); + return new CallTemplateNode(null, parse(next), name, + withParams); + } + + /** + * value-of + */ + final TemplateNode parseValueOf(Node node, Node children, Node next) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String s = getRequiredAttribute(attrs, "select", node); + String doe = getAttribute(attrs, "disable-output-escaping"); + boolean d = "yes".equals(doe); + Expr select = (Expr) xpath.compile(s); + return new ValueOfNode(null, parse(next), select, d); + } + + /** + * for-each + */ + final TemplateNode parseForEach(Node node, Node children, Node next) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String s = getRequiredAttribute(attrs, "select", node); + List sortKeys = parseSortKeys(children); + Expr select = (Expr) xpath.compile(s); + return new ForEachNode(parse(children), parse(next), select, sortKeys); + } + + /** + * if + */ + final TemplateNode parseIf(Node node, Node children, Node next) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String t = getRequiredAttribute(attrs, "test", node); + Expr test = (Expr) xpath.compile(t); + return new IfNode(parse(children), parse(next), test); + } + + /** + * when + */ + final TemplateNode parseWhen(Node node, Node children, Node next) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String t = getRequiredAttribute(attrs, "test", node); + Expr test = (Expr) xpath.compile(t); + return new WhenNode(parse(children), parse(next), test); + } + + /** + * element + */ + final TemplateNode parseElement(Node node, Node children, Node next) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String name = getRequiredAttribute(attrs, "name", node); + String namespace = getAttribute(attrs, "namespace"); + String uas = getAttribute(attrs, "use-attribute-sets"); + TemplateNode n = parseAttributeValueTemplate(name, node); + TemplateNode ns = (namespace == null) ? null : + parseAttributeValueTemplate(namespace, node); + return new ElementNode(parse(children), parse(next), n, ns, uas, node); + } + + /** + * attribute + */ + final TemplateNode parseAttribute(Node node, Node children, Node next) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String name = getRequiredAttribute(attrs, "name", node); + String namespace = getAttribute(attrs, "namespace"); + TemplateNode n = parseAttributeValueTemplate(name, node); + TemplateNode ns = (namespace == null) ? null : + parseAttributeValueTemplate(namespace, node); + return new AttributeNode(parse(children), parse(next), n, ns, node); + } + + /** + * text + */ + final TemplateNode parseText(Node node, Node children, Node next) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String doe = getAttribute(attrs, "disable-output-escaping"); + boolean d = "yes".equals(doe); + return new TextNode(parse(children), parse(next), d); + } + + /** + * copy + */ + final TemplateNode parseCopy(Node node, Node children, Node next) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String uas = getAttribute(attrs, "use-attribute-sets"); + return new CopyNode(parse(children), parse(next), uas); + } + + /** + * processing-instruction + */ + final TemplateNode parseProcessingInstruction(Node node, Node children, + Node next) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String name = getRequiredAttribute(attrs, "name", node); + return new ProcessingInstructionNode(parse(children), + parse(next), name); + } + + /** + * number + */ + final TemplateNode parseNumber(Node node, Node children, Node next) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String v = getAttribute(attrs, "value"); + String ff = getAttribute(attrs, "format"); + if (ff == null) + { + ff = "1"; + } + TemplateNode format = parseAttributeValueTemplate(ff, node); + String lang = getAttribute(attrs, "lang"); + String lv = getAttribute(attrs, "letter-value"); + int letterValue = "traditional".equals(lv) ? + AbstractNumberNode.TRADITIONAL : + AbstractNumberNode.ALPHABETIC; + String gs = getAttribute(attrs, "grouping-separator"); + String gz = getAttribute(attrs, "grouping-size"); + int gz2 = (gz != null && gz.length() > 0) ? + Integer.parseInt(gz) : 1; + if (v != null && v.length() > 0) + { + Expr value = (Expr) xpath.compile(v); + return new NumberNode(parse(children), parse(next), + value, format, lang, + letterValue, gs, gz2); + } + else + { + String l = getAttribute(attrs, "level"); + int level = + "multiple".equals(l) ? NodeNumberNode.MULTIPLE : + "any".equals(l) ? NodeNumberNode.ANY : + NodeNumberNode.SINGLE; + String c = getAttribute(attrs, "count"); + String f = getAttribute(attrs, "from"); + Pattern count = null; + Pattern from = null; + if (c != null) + { + try + { + count = (Pattern) xpath.compile(c); + } + catch (ClassCastException e) + { + String msg = "invalid pattern: " + c; + throw new TransformerConfigurationException(msg); + } + } + if (f != null) + { + try + { + from = (Pattern) xpath.compile(f); + } + catch (ClassCastException e) + { + String msg = "invalid pattern: " + f; + throw new TransformerConfigurationException(msg); + } + } + return new NodeNumberNode(parse(children), parse(next), + level, count, from, + format, lang, + letterValue, gs, gz2); + } + } + + /** + * copy-of + */ + final TemplateNode parseCopyOf(Node node, Node children, Node next) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String s = getRequiredAttribute(attrs, "select", node); + Expr select = (Expr) xpath.compile(s); + return new CopyOfNode(parse(children), parse(next), select); + } + + /** + * message + */ + final TemplateNode parseMessage(Node node, Node children, Node next) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String t = getAttribute(attrs, "terminate"); + boolean terminate = "yes".equals(t); + return new MessageNode(parse(children), parse(next), terminate); + } + + /** + * Parse template-level elements. + */ + final TemplateNode parse(Node node) + throws TransformerConfigurationException + { + if (node == null) + { + return null; + } + // Hack to associate the document function with its declaring node + current = node; + Node children = node.getFirstChild(); + Node next = node.getNextSibling(); + try + { + String namespaceUri = node.getNamespaceURI(); + if (Stylesheet.XSL_NS.equals(namespaceUri) && + Node.ELEMENT_NODE == node.getNodeType()) + { + String name = node.getLocalName(); + if ("apply-templates".equals(name)) + { + return parseApplyTemplates(node, children, next); + } + else if ("call-template".equals(name)) + { + return parseCallTemplate(node, children, next); + } + else if ("value-of".equals(name)) + { + return parseValueOf(node, children, next); + } + else if ("for-each".equals(name)) + { + return parseForEach(node, children, next); + } + else if ("if".equals(name)) + { + return parseIf(node, children, next); + } + else if ("choose".equals(name)) + { + return new ChooseNode(parse(children), parse(next)); + } + else if ("when".equals(name)) + { + return parseWhen(node, children, next); + } + else if ("otherwise".equals(name)) + { + return new OtherwiseNode(parse(children), parse(next)); + } + else if ("element".equals(name)) + { + return parseElement(node, children, next); + } + else if ("attribute".equals(name)) + { + return parseAttribute(node, children, next); + } + else if ("text".equals(name)) + { + return parseText(node, children, next); + } + else if ("copy".equals(name)) + { + return parseCopy(node, children, next); + } + else if ("processing-instruction".equals(name)) + { + return parseProcessingInstruction(node, children, next); + } + else if ("comment".equals(name)) + { + return new CommentNode(parse(children), parse(next)); + } + else if ("number".equals(name)) + { + return parseNumber(node, children, next); + } + else if ("param".equals(name) || + "variable".equals(name)) + { + boolean global = "variable".equals(name); + NamedNodeMap attrs = node.getAttributes(); + TemplateNode content = parse(children); + String paramName = getRequiredAttribute(attrs, "name", node); + String select = getAttribute(attrs, "select"); + if (select != null) + { + if (content != null) + { + String msg = "parameter '" + paramName + + "' has both select and content"; + DOMSourceLocator l = new DOMSourceLocator(node); + throw new TransformerConfigurationException(msg, l); + } + Expr expr = (Expr) xpath.compile(select); + return new ParameterNode(null, parse(next), + paramName, expr, global); + } + else + { + return new ParameterNode(content, parse(next), + paramName, null, global); + } + } + else if ("copy-of".equals(name)) + { + return parseCopyOf(node, children, next); + } + else if ("message".equals(name)) + { + return parseMessage(node, children, next); + } + else if ("apply-imports".equals(name)) + { + return new ApplyImportsNode(parse(children), parse(next)); + } + else + { + // xsl:fallback + // Pass over any other XSLT nodes + return parse(next); + } + } + String prefix = node.getPrefix(); + if (extensionElementPrefixes.contains(prefix)) + { + // Pass over extension elements + return parse(next); + } + switch (node.getNodeType()) + { + case Node.TEXT_NODE: + // Determine whether to strip whitespace + Text text = (Text) node; + if (!isPreserved(text)) + { + // Strip + text.getParentNode().removeChild(text); + return parse(next); + } + break; + case Node.COMMENT_NODE: + // Ignore comments + return parse(next); + case Node.ELEMENT_NODE: + // Check for attribute value templates and use-attribute-sets + NamedNodeMap attrs = node.getAttributes(); + boolean convert = false; + String useAttributeSets = null; + int len = attrs.getLength(); + for (int i = 0; i < len; i++) + { + Node attr = attrs.item(i); + String value = attr.getNodeValue(); + if (Stylesheet.XSL_NS.equals(attr.getNamespaceURI()) && + "use-attribute-sets".equals(attr.getLocalName())) + { + useAttributeSets = value; + convert = true; + break; + } + int start = value.indexOf('{'); + int end = value.indexOf('}'); + if (start != -1 || end != -1) + { + convert = true; + break; + } + } + if (convert) + { + // Create an element-producing template node instead + // with appropriate attribute-producing child template nodes + TemplateNode child = parse(children); + for (int i = 0; i < len; i++) + { + Node attr = attrs.item(i); + String ans = attr.getNamespaceURI(); + String aname = attr.getNodeName(); + if (Stylesheet.XSL_NS.equals(ans) && + "use-attribute-sets".equals(attr.getLocalName())) + { + continue; + } + String value = attr.getNodeValue(); + TemplateNode grandchild = + parseAttributeValueTemplate(value, node); + TemplateNode n = + parseAttributeValueTemplate(aname, node); + TemplateNode ns = (ans == null) ? null : + parseAttributeValueTemplate(ans, node); + child = new AttributeNode(grandchild, child, n, ns, attr); + } + String ename = node.getNodeName(); + TemplateNode n = parseAttributeValueTemplate(ename, node); + TemplateNode ns = (namespaceUri == null) ? null : + parseAttributeValueTemplate(namespaceUri, node); + return new ElementNode(child, parse(next), + n, ns, useAttributeSets, + node); + } + // Otherwise fall through + break; + } + } + catch (XPathExpressionException e) + { + DOMSourceLocator l = new DOMSourceLocator(node); + throw new TransformerConfigurationException(e.getMessage(), l, e); + } + return new LiteralNode(parse(children), parse(next), node); + } + + final List parseSortKeys(Node node) + throws TransformerConfigurationException, XPathExpressionException + { + List ret = new LinkedList(); + while (node != null) + { + String namespaceUri = node.getNamespaceURI(); + if (Stylesheet.XSL_NS.equals(namespaceUri) && + Node.ELEMENT_NODE == node.getNodeType() && + "sort".equals(node.getLocalName())) + { + NamedNodeMap attrs = node.getAttributes(); + String s = getAttribute(attrs, "select"); + if (s == null) + { + s = "."; + } + Expr select = (Expr) xpath.compile(s); + String l = getAttribute(attrs, "lang"); + TemplateNode lang = (l == null) ? null : + parseAttributeValueTemplate(l, node); + String dt = getAttribute(attrs, "data-type"); + TemplateNode dataType = (dt == null) ? null : + parseAttributeValueTemplate(dt, node); + String o = getAttribute(attrs, "order"); + TemplateNode order = (o == null) ? null : + parseAttributeValueTemplate(o, node); + String co = getAttribute(attrs, "case-order"); + TemplateNode caseOrder = (co == null) ? null : + parseAttributeValueTemplate(co, node); + ret.add(new SortKey(select, lang, dataType, order, caseOrder)); + } + node = node.getNextSibling(); + } + return ret.isEmpty() ? null : ret; + } + + final List parseWithParams(Node node) + throws TransformerConfigurationException, XPathExpressionException + { + List ret = new LinkedList(); + while (node != null) + { + String namespaceUri = node.getNamespaceURI(); + if (Stylesheet.XSL_NS.equals(namespaceUri) && + Node.ELEMENT_NODE == node.getNodeType() && + "with-param".equals(node.getLocalName())) + { + NamedNodeMap attrs = node.getAttributes(); + TemplateNode content = parse(node.getFirstChild()); + String name = getRequiredAttribute(attrs, "name", node); + String select = getAttribute(attrs, "select"); + if (select != null) + { + if (content != null) + { + String msg = "parameter '" + name + + "' has both select and content"; + DOMSourceLocator l = new DOMSourceLocator(node); + throw new TransformerConfigurationException(msg, l); + } + Expr expr = (Expr) xpath.compile(select); + ret.add(new WithParam(name, expr)); + } + else + { + ret.add(new WithParam(name, content)); + } + } + node = node.getNextSibling(); + } + return ret.isEmpty() ? null : ret; + } + + /** + * Created element nodes have a copy of the namespace nodes in the + * stylesheet, except the XSLT namespace, extension namespaces, and + * exclude-result-prefixes. + */ + final void addNamespaceNodes(Node source, Node target, Document doc, + Collection elementExcludeResultPrefixes) + { + NamedNodeMap attrs = source.getAttributes(); + if (attrs != null) + { + int len = attrs.getLength(); + for (int i = 0; i < len; i++) + { + Node attr = attrs.item(i); + String uri = attr.getNamespaceURI(); + if (uri == XMLConstants.XMLNS_ATTRIBUTE_NS_URI) + { + String prefix = attr.getLocalName(); + if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) + { + prefix = "#default"; + } + String ns = attr.getNodeValue(); + // Should the namespace be excluded? + if (XSL_NS.equals(ns) || + extensionElementPrefixes.contains(prefix) || + elementExcludeResultPrefixes.contains(prefix) || + excludeResultPrefixes.contains(prefix)) + { + continue; + } + // Is the namespace already defined on the target? + if (prefix == "#default") + { + prefix = null; + } + if (target.lookupNamespaceURI(prefix) != null) + { + continue; + } + attr = attr.cloneNode(true); + attr = doc.adoptNode(attr); + target.getAttributes().setNamedItemNS(attr); + } + } + } + Node parent = source.getParentNode(); + if (parent != null) + { + addNamespaceNodes(parent, target, doc, elementExcludeResultPrefixes); + } + } + + static final String getAttribute(NamedNodeMap attrs, String name) + { + Node attr = attrs.getNamedItem(name); + if (attr == null) + { + return null; + } + String ret = attr.getNodeValue(); + if (ret.length() == 0) + { + return null; + } + return ret; + } + + static final String getRequiredAttribute(NamedNodeMap attrs, String name, + Node source) + throws TransformerConfigurationException + { + String value = getAttribute(attrs, name); + if (value == null || value.length() == 0) + { + String msg = + name + " attribute is required on " + source.getNodeName(); + DOMSourceLocator l = new DOMSourceLocator(source); + throw new TransformerConfigurationException(msg, l); + } + return value; + } + + // Handle user data changes when nodes are cloned etc + + public void handle(short op, String key, Object data, Node src, Node dst) + { + dst.setUserData(key, data, this); + } + +} + diff --git a/libjava/gnu/xml/transform/SystemPropertyFunction.java b/libjava/gnu/xml/transform/SystemPropertyFunction.java new file mode 100644 index 0000000..3d95d21d --- /dev/null +++ b/libjava/gnu/xml/transform/SystemPropertyFunction.java @@ -0,0 +1,129 @@ +/* SystemPropertyFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.xml.namespace.QName; +import javax.xml.xpath.XPathFunction; +import javax.xml.xpath.XPathFunctionException; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; +import gnu.xml.xpath.Function; + +/** + * The XSLT <code>system-property()</code>function. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class SystemPropertyFunction + extends Expr + implements XPathFunction, Function +{ + + List args; + + public Object evaluate(List args) + throws XPathFunctionException + { + String name = (String) args.get(0); + return systemProperty(QName.valueOf(name)); + } + + public void setArguments(List args) + { + this.args = args; + } + + public Object evaluate(Node context, int pos, int len) + { + int arity = args.size(); + List values = new ArrayList(arity); + for (int i = 0; i < arity; i++) + { + Expr arg = (Expr) args.get(i); + values.add(arg.evaluate(context, pos, len)); + } + String name = _string(context, values.get(0)); + return systemProperty(QName.valueOf(name)); + } + + Object systemProperty(QName name) + { + String localName = name.getLocalPart(); + String prefix = name.getPrefix(); + String uri = name.getNamespaceURI(); + if (Stylesheet.XSL_NS.equals(uri) || + "xsl".equals(prefix)) + { + if ("version".equals(localName)) + { + return new Double(1.0d); + } + else if ("vendor".equals(localName)) + { + return "The Free Software Foundation"; + } + else if ("vendor-url".equals(localName)) + { + return "http://www.gnu.org/"; + } + else + { + return ""; + } + } + return System.getProperty(localName); + } + + public Expr clone(Object context) + { + SystemPropertyFunction f = new SystemPropertyFunction(); + int len = args.size(); + List args2 = new ArrayList(len); + for (int i = 0; i < len; i++) + { + args2.add(((Expr) args.get(i)).clone(context)); + } + f.setArguments(args2); + return f; + } + +} + diff --git a/libjava/gnu/xml/transform/Template.java b/libjava/gnu/xml/transform/Template.java new file mode 100644 index 0000000..6aaa4c1 --- /dev/null +++ b/libjava/gnu/xml/transform/Template.java @@ -0,0 +1,254 @@ +/* Template.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.io.PrintStream; +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.xpath.XPathExpressionException; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; +import gnu.xml.xpath.NameTest; +import gnu.xml.xpath.NodeTypeTest; +import gnu.xml.xpath.Pattern; +import gnu.xml.xpath.Selector; +import gnu.xml.xpath.Test; + +/** + * A template in an XSL stylesheet. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class Template + implements Comparable +{ + + static final double DEFAULT_PRIORITY = 0.5d; + + final Stylesheet stylesheet; + final QName name; + final Pattern match; + final TemplateNode node; + final double priority; + final int precedence; + final QName mode; + + Template(Stylesheet stylesheet, + QName name, Pattern match, TemplateNode node, + int precedence, double priority, QName mode) + { + this.stylesheet = stylesheet; + this.name = name; + this.match = match; + this.node = node; + // adjust priority if necessary + // see XSLT section 5.5 + Test test = getNodeTest(match); + if (test != null) + { + if (test instanceof NameTest) + { + NameTest nameTest = (NameTest) test; + if (nameTest.matchesAny() || + nameTest.matchesAnyLocalName()) + { + priority = -0.25d; + } + else + { + priority = 0.0d; + } + } + else + { + NodeTypeTest nodeTypeTest = (NodeTypeTest) test; + if (nodeTypeTest.getNodeType() == + Node.PROCESSING_INSTRUCTION_NODE && + nodeTypeTest.getData() != null) + { + priority = 0.0d; + } + else + { + priority = -0.5d; + } + } + } + this.precedence = precedence; + this.priority = priority; + this.mode = mode; + } + + Template clone(Stylesheet stylesheet) + { + // FIXME by cloning we lose the imports() functionality, so + // apply-imports will be broken. + return new Template(stylesheet, + name, + (match == null) ? null : + (Pattern) match.clone(stylesheet), + (node == null) ? null : node.clone(stylesheet), + precedence, + priority, + mode); + } + + public int compareTo(Object other) + { + if (other instanceof Template) + { + Template t = (Template) other; + int d = t.precedence - precedence; + if (d != 0) + { + return d; + } + double d2 = t.priority - priority; + if (d2 != 0.0d) + { + return (int) Math.round(d2 * 1000.0d); + } + } + return 0; + } + + Test getNodeTest(Expr expr) + { + if (expr instanceof Selector) + { + Selector selector = (Selector) expr; + Test[] tests = selector.getTests(); + if (tests.length > 0) + { + return tests[0]; + } + } + return null; + } + + boolean matches(QName mode, Node node) + { + if ((mode == null && this.mode != null) || + (mode != null && !mode.equals(this.mode))) + { + return false; + } + if (match == null) + { + return false; + } + return match.matches(node); + } + + boolean matches(QName name) + { + return name.equals(this.name); + } + + boolean imports(Template other) + { + for (Stylesheet ctx = other.stylesheet.parent; + ctx != null; + ctx = ctx.parent) + { + if (ctx == stylesheet) + { + return true; + } + } + return false; + } + + /** + * @param stylesheet the stylesheet + * @param parent the parent of result nodes + * @param context the context node in the source document + * @param pos the context position + * @param len the context size + * @param nextSibling if non-null, add result nodes before this node + */ + void apply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + //System.err.println("...applying " + toString() + " to " + context); + if (node != null) + { + node.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + } + + public String toString() + { + StringBuffer buf = new StringBuffer(getClass().getName()); + buf.append('['); + if (name != null) + { + buf.append("name="); + buf.append(name); + } + else if (match != null) + { + buf.append("match="); + buf.append(match); + } + if (mode != null) + { + buf.append(",mode="); + buf.append(mode); + } + buf.append(']'); + return buf.toString(); + + //return (name != null) ? name.toString() : match.toString(); + } + + void list(PrintStream out) + { + out.println(toString()); + if (node != null) + { + node.list(1, out, true); + } + } + +} diff --git a/libjava/gnu/xml/transform/TemplateNode.java b/libjava/gnu/xml/transform/TemplateNode.java new file mode 100644 index 0000000..1fe37c2 --- /dev/null +++ b/libjava/gnu/xml/transform/TemplateNode.java @@ -0,0 +1,115 @@ +/* TemplateNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.io.PrintStream; +import java.util.Comparator; +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Node; +import gnu.xml.xpath.DocumentOrderComparator; + +/** + * Wrapper for a source node in a template. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +abstract class TemplateNode +{ + + static final Comparator documentOrderComparator = + new DocumentOrderComparator(); + + final TemplateNode children; + final TemplateNode next; + + TemplateNode(TemplateNode children, TemplateNode next) + { + this.children = children; + this.next = next; + } + + final void apply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + if (stylesheet.terminated) + { + return; + } + if (Thread.currentThread().isInterrupted()) + { + // Try to head off any infinite loops at the pass + return; + } + if (stylesheet.debug) + { + System.err.println("Applying " + toString()); + } + doApply(stylesheet, mode, context, pos, len, parent, nextSibling); + } + + abstract void doApply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException; + + abstract TemplateNode clone(Stylesheet stylesheet); + + /** + * Debugging + */ + void list(int depth, PrintStream out, boolean listNext) + { + for (int i = 0; i < depth; i++) + { + out.print(" "); + } + out.println(toString()); + if (children != null) + { + children.list(depth + 1, out, true); + } + if (listNext && next != null) + { + next.list(depth, out, listNext); + } + } + +} diff --git a/libjava/gnu/xml/transform/TemplatesImpl.java b/libjava/gnu/xml/transform/TemplatesImpl.java new file mode 100644 index 0000000..a7403e9 --- /dev/null +++ b/libjava/gnu/xml/transform/TemplatesImpl.java @@ -0,0 +1,83 @@ +/* TemplatesImpl.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.Properties; +import javax.xml.transform.ErrorListener; +import javax.xml.transform.Source; +import javax.xml.transform.Templates; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.URIResolver; + +/** + * GNU precompiled stylesheet implementation. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class TemplatesImpl + implements Templates +{ + + final TransformerFactoryImpl factory; + final Stylesheet stylesheet; + final Properties outputProperties; + + TemplatesImpl(TransformerFactoryImpl factory, Stylesheet stylesheet) + { + this.factory = factory; + this.stylesheet = stylesheet; + outputProperties = new TransformerOutputProperties(stylesheet); + } + + public Transformer newTransformer() + throws TransformerConfigurationException + { + Stylesheet stylesheet = (Stylesheet) this.stylesheet.clone(); + TransformerImpl transformer = + new TransformerImpl(factory, stylesheet, outputProperties); + stylesheet.transformer = transformer; + return transformer; + } + + public Properties getOutputProperties() + { + return (Properties) outputProperties.clone(); + } + +} diff --git a/libjava/gnu/xml/transform/TextNode.java b/libjava/gnu/xml/transform/TextNode.java new file mode 100644 index 0000000..70a8ce4 --- /dev/null +++ b/libjava/gnu/xml/transform/TextNode.java @@ -0,0 +1,116 @@ +/* TextNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; +import org.w3c.dom.Node; +import org.w3c.dom.Text; +import gnu.xml.xpath.Expr; + +/** + * A template node representing the XSL <code>text</code> instruction. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class TextNode + extends TemplateNode +{ + + final boolean disableOutputEscaping; + + TextNode(TemplateNode children, TemplateNode next, + boolean disableOutputEscaping) + { + super(children, next); + this.disableOutputEscaping = disableOutputEscaping; + } + + TemplateNode clone(Stylesheet stylesheet) + { + return new TextNode((children == null) ? null : + children.clone(stylesheet), + (next == null) ? null : + next.clone(stylesheet), + disableOutputEscaping); + } + + void doApply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + String value = ""; + Document doc = (parent instanceof Document) ? (Document) parent : + parent.getOwnerDocument(); + if (children != null) + { + // Create a document fragment to hold the text + DocumentFragment fragment = doc.createDocumentFragment(); + // Apply children to the fragment + children.apply(stylesheet, mode, + context, pos, len, + fragment, null); + // Use XPath string-value of fragment + value = Expr.stringValue(fragment); + } + Text text = doc.createTextNode(value); + if (disableOutputEscaping) + { + text.setUserData("disable-output-escaping", "yes", stylesheet); + } + // Insert into result tree + if (nextSibling != null) + { + parent.insertBefore(text, nextSibling); + } + else + { + parent.appendChild(text); + } + if (next != null) + { + next.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + } + +} diff --git a/libjava/gnu/xml/transform/TransformerFactoryImpl.java b/libjava/gnu/xml/transform/TransformerFactoryImpl.java new file mode 100644 index 0000000..9c4d9da --- /dev/null +++ b/libjava/gnu/xml/transform/TransformerFactoryImpl.java @@ -0,0 +1,345 @@ +/* TransformerFactoryImpl.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.io.IOException; +import java.net.URL; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Properties; +import javax.xml.transform.ErrorListener; +import javax.xml.transform.Source; +import javax.xml.transform.Templates; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.URIResolver; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import javax.xml.xpath.XPathFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import gnu.xml.dom.DomDocument; + +/** + * GNU transformer factory implementation. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class TransformerFactoryImpl + extends TransformerFactory +{ + + final XPathFactory xpathFactory; + final XSLURIResolver resolver; + ErrorListener userListener; + URIResolver userResolver; + + public TransformerFactoryImpl() + { + xpathFactory = new gnu.xml.xpath.XPathFactoryImpl(); + resolver = new XSLURIResolver(); + } + + public Transformer newTransformer(Source source) + throws TransformerConfigurationException + { + Stylesheet stylesheet = newStylesheet(source, 0, null); + Properties outputProperties = + new TransformerOutputProperties(stylesheet); + TransformerImpl transformer = + new TransformerImpl(this, stylesheet, outputProperties); + stylesheet.transformer = transformer; + return transformer; + } + + public Transformer newTransformer() + throws TransformerConfigurationException + { + return new TransformerImpl(this, null, new Properties()); + } + + public Templates newTemplates(Source source) + throws TransformerConfigurationException + { + Stylesheet stylesheet = newStylesheet(source, 0, null); + return new TemplatesImpl(this, stylesheet); + } + + Stylesheet newStylesheet(Source source, int precedence, Stylesheet parent) + throws TransformerConfigurationException + { + Document doc = null; + String systemId = null; + if (source != null) + { + try + { + DOMSource ds; + synchronized (resolver) + { + resolver.setUserResolver(userResolver); + resolver.setUserListener(userListener); + ds = resolver.resolveDOM(source, null, null); + } + Node node = ds.getNode(); + if (node == null) + { + throw new TransformerConfigurationException("no source document"); + } + doc = (node instanceof Document) ? (Document) node : + node.getOwnerDocument(); + systemId = ds.getSystemId(); + } + catch (TransformerException e) + { + throw new TransformerConfigurationException(e); + } + } + return new Stylesheet(this, parent, doc, systemId, precedence); + } + + public Source getAssociatedStylesheet(Source source, + String media, + String title, + String charset) + throws TransformerConfigurationException + { + try + { + DOMSource ds; + synchronized (resolver) + { + resolver.setUserResolver(userResolver); + resolver.setUserListener(userListener); + ds = resolver.resolveDOM(source, null, null); + } + Node node = ds.getNode(); + if (node == null) + { + throw new TransformerConfigurationException("no source document"); + } + Document doc = (node instanceof Document) ? (Document) node : + node.getOwnerDocument(); + LinkedList matches = new LinkedList(); + for (node = doc.getFirstChild(); + node != null; + node = node.getNextSibling()) + { + if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE && + "xml-stylesheet".equals(node.getNodeName())) + { + Map params = parseParameters(node.getNodeValue()); + if (media != null && !media.equals(params.get("media"))) + { + continue; + } + if (title != null && !title.equals(params.get("title"))) + { + continue; + } + if (charset != null && !charset.equals(params.get("charset"))) + { + continue; + } + String href = (String) params.get("href"); + URL url = resolver.resolveURL(null, node.getBaseURI(), href); + matches.add(url); + } + } + switch (matches.size()) + { + case 0: + return null; + case 1: + return new StreamSource(((URL) matches.getFirst()).toString()); + default: + // Create a source representing a stylesheet with a list of + // imports + DomDocument ssDoc = new DomDocument(); + ssDoc.setBuilding(true); + // Create document element + Node root = + ssDoc.createElementNS(Stylesheet.XSL_NS, "stylesheet"); + Node version = + ssDoc.createAttributeNS(null, "version"); + version.setNodeValue("1.0"); + root.getAttributes().setNamedItemNS(version); + ssDoc.appendChild(root); + // Create xsl:import for each URL + for (Iterator i = matches.iterator(); i.hasNext(); ) + { + URL url = (URL) i.next(); + Node imp = + ssDoc.createElementNS(Stylesheet.XSL_NS, "import"); + Node href = + ssDoc.createAttributeNS(null, "href"); + href.setNodeValue(url.toString()); + imp.getAttributes().setNamedItemNS(href); + root.appendChild(imp); + } + ssDoc.setBuilding(false); + return new DOMSource(ssDoc); + } + } + catch (IOException e) + { + throw new TransformerConfigurationException(e); + } + catch (TransformerException e) + { + throw new TransformerConfigurationException(e); + } + } + + Map parseParameters(String data) + { + Map ret = new LinkedHashMap(); + int len = data.length(); + String key = null; + int start = 0; + char quoteChar = '\u0000'; + for (int i = 0; i < len; i++) + { + char c = data.charAt(i); + if (quoteChar == '\u0000' && c == ' ') + { + if (key == null && start < i) + { + key = data.substring(start, i); + } + else + { + String val = unquote(data.substring(start, i).trim()); + ret.put(key, val); + key = null; + } + start = i + 1; + } + else if (c == '"') + { + quoteChar = (quoteChar == c) ? '\u0000' : c; + } + else if (c == '\'') + { + quoteChar = (quoteChar == c) ? '\u0000' : c; + } + } + if (start < len && key != null) + { + String val = unquote(data.substring(start, len).trim()); + ret.put(key, val); + } + return ret; + } + + String unquote(String text) + { + int end = text.length() - 1; + if (text.charAt(0) == '\'' && text.charAt(end) == '\'') + { + return text.substring(1, end); + } + if (text.charAt(0) == '"' && text.charAt(end) == '"') + { + return text.substring(1, end); + } + return text; + } + + public void setURIResolver(URIResolver resolver) + { + userResolver = resolver; + } + + public URIResolver getURIResolver() + { + return userResolver; + } + + public void setFeature(String name, boolean value) + throws TransformerConfigurationException + { + throw new TransformerConfigurationException("not supported"); + } + + public boolean getFeature(String name) + { + if (SAXSource.FEATURE.equals(name) || + SAXResult.FEATURE.equals(name) || + StreamSource.FEATURE.equals(name) || + StreamResult.FEATURE.equals(name) || + DOMSource.FEATURE.equals(name) || + DOMResult.FEATURE.equals(name)) + { + return true; + } + return false; + } + + public void setAttribute(String name, Object value) + throws IllegalArgumentException + { + throw new IllegalArgumentException("not supported"); + } + + public Object getAttribute(String name) + throws IllegalArgumentException + { + throw new IllegalArgumentException("not supported"); + } + + public void setErrorListener(ErrorListener listener) + throws IllegalArgumentException + { + userListener = listener; + } + + public ErrorListener getErrorListener() + { + return userListener; + } + +} diff --git a/libjava/gnu/xml/transform/TransformerImpl.java b/libjava/gnu/xml/transform/TransformerImpl.java new file mode 100644 index 0000000..fb4632e --- /dev/null +++ b/libjava/gnu/xml/transform/TransformerImpl.java @@ -0,0 +1,586 @@ +/* TransformerImpl.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.net.MalformedURLException; +import java.net.UnknownServiceException; +import java.net.URL; +import java.net.URLConnection; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Properties; +import javax.xml.transform.ErrorListener; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.URIResolver; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.stream.StreamResult; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentType; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.Text; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.ext.LexicalHandler; +import gnu.xml.dom.DomDoctype; +import gnu.xml.dom.DomDocument; +import gnu.xml.dom.ls.WriterOutputStream; +import gnu.xml.xpath.Expr; +import gnu.xml.xpath.Root; + +/** + * The transformation process for a given stylesheet. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class TransformerImpl + extends Transformer +{ + + final TransformerFactoryImpl factory; + final Stylesheet stylesheet; + URIResolver uriResolver; + ErrorListener errorListener; + Properties outputProperties; + + TransformerImpl(TransformerFactoryImpl factory, + Stylesheet stylesheet, + Properties outputProperties) + throws TransformerConfigurationException + { + this.factory = factory; + uriResolver = factory.userResolver; + errorListener = factory.userListener; + this.stylesheet = stylesheet; + this.outputProperties = outputProperties; + if (stylesheet != null) + { + // Set up parameter context for this transformer + stylesheet.bindings.push(false); + } + } + + public void transform(Source xmlSource, Result outputTarget) + throws TransformerException + { + // Get the source tree + DOMSource source; + synchronized (factory.resolver) + { + factory.resolver.setUserResolver(uriResolver); + factory.resolver.setUserListener(errorListener); + source = factory.resolver.resolveDOM(xmlSource, null, null); + } + Node context = source.getNode(); + Document doc = (context instanceof Document) ? (Document) context : + context.getOwnerDocument(); + if (doc instanceof DomDocument) + { + // Suppress mutation events + ((DomDocument) doc).setBuilding(true); + // TODO find a better/more generic way of doing this than + // casting + } + // Get the result tree + Node parent = null, nextSibling = null; + if (outputTarget instanceof DOMResult) + { + DOMResult dr = (DOMResult) outputTarget; + parent = dr.getNode(); + nextSibling = dr.getNextSibling(); + + Document rdoc = (parent instanceof Document) ? (Document) parent : + parent.getOwnerDocument(); + if (rdoc instanceof DomDocument) + { + // Suppress mutation events and allow multiple root elements + DomDocument drdoc = (DomDocument) rdoc; + drdoc.setBuilding(true); + drdoc.setCheckWellformedness(false); + // TODO find a better/more generic way of doing this than + // casting + } + } + boolean created = false; + // Transformation + if (stylesheet != null) + { + if (parent == null) + { + // Create a new document to hold the result + DomDocument resultDoc = new DomDocument(); + resultDoc.setBuilding(true); + resultDoc.setCheckWellformedness(false); + parent = resultDoc; + created = true; + } + // Make a copy of the source node, and strip it + context = context.cloneNode(true); + strip(context); + // XSLT transformation + try + { + // Set output properties in the underlying stylesheet + ((TransformerOutputProperties) outputProperties).apply(); + stylesheet.initTopLevelVariables(context); + TemplateNode t = stylesheet.getTemplate(null, context, false); + if (t != null) + { + stylesheet.current = context; + t.apply(stylesheet, null, context, 1, 1, parent, nextSibling); + } + } + catch (TransformerException e) + { + // Done transforming, reset document + if (doc instanceof DomDocument) + { + ((DomDocument) doc).setBuilding(false); + } + throw e; + } + } + else + { + // Identity transform + Node clone = context.cloneNode(true); + if (context.getNodeType() != Node.DOCUMENT_NODE) + { + Document resultDoc; + if (parent == null) + { + // Create a new document to hold the result + DomDocument rd = new DomDocument(); + rd.setBuilding(true); + rd.setCheckWellformedness(false); + parent = resultDoc = rd; + created = true; + } + else + { + resultDoc = (parent instanceof Document) ? + (Document) parent : + parent.getOwnerDocument(); + } + Document sourceDoc = context.getOwnerDocument(); + if (sourceDoc != resultDoc) + { + clone = resultDoc.adoptNode(clone); + } + if (nextSibling != null) + { + parent.insertBefore(clone, nextSibling); + } + else + { + parent.appendChild(clone); + } + } + else + { + // Cannot append document to another tree + parent = clone; + created = true; + } + } + String method = outputProperties.getProperty(OutputKeys.METHOD); + int outputMethod = "html".equals(method) ? Stylesheet.OUTPUT_HTML : + "text".equals(method) ? Stylesheet.OUTPUT_TEXT : + Stylesheet.OUTPUT_XML; + String encoding = outputProperties.getProperty(OutputKeys.ENCODING); + String publicId = outputProperties.getProperty(OutputKeys.DOCTYPE_PUBLIC); + String systemId = outputProperties.getProperty(OutputKeys.DOCTYPE_SYSTEM); + String version = outputProperties.getProperty(OutputKeys.VERSION); + boolean omitXmlDeclaration = + "yes".equals(outputProperties.getProperty(OutputKeys.OMIT_XML_DECLARATION)); + boolean standalone = + "yes".equals(outputProperties.getProperty(OutputKeys.STANDALONE)); + String mediaType = outputProperties.getProperty(OutputKeys.MEDIA_TYPE); + // TODO cdata-section-elements + // TODO indent + if (created) + { + // Discover document element + DomDocument resultDoc = (DomDocument) parent; + Node root = resultDoc.getDocumentElement(); + // Add doctype if specified + if ((publicId != null || systemId != null) && + root != null) + { + // We must know the name of the root element to + // create the document type + resultDoc.appendChild(new DomDoctype(resultDoc, + root.getNodeName(), + publicId, + systemId)); + } + resultDoc.setBuilding(false); + resultDoc.setCheckWellformedness(true); + } + else if (publicId != null || systemId != null) + { + switch (parent.getNodeType()) + { + case Node.DOCUMENT_NODE: + case Node.DOCUMENT_FRAGMENT_NODE: + Document resultDoc = (parent instanceof Document) ? + (Document) parent : + parent.getOwnerDocument(); + DOMImplementation impl = resultDoc.getImplementation(); + DocumentType doctype = + impl.createDocumentType(resultDoc.getNodeName(), + publicId, + systemId); + // Try to insert doctype before first element + Node ctx = parent.getFirstChild(); + for (; ctx != null && + ctx.getNodeType() != Node.ELEMENT_NODE; + ctx = ctx.getNextSibling()) + { + } + if (ctx != null) + { + parent.insertBefore(doctype, ctx); + } + else + { + parent.appendChild(doctype); + } + } + } + if (version != null) + { + parent.setUserData("version", version, stylesheet); + } + if (omitXmlDeclaration) + { + parent.setUserData("omit-xml-declaration", "yes", stylesheet); + } + if (standalone) + { + parent.setUserData("standalone", "yes", stylesheet); + } + if (mediaType != null) + { + parent.setUserData("media-type", mediaType, stylesheet); + } + // Render result to the target device + if (outputTarget instanceof DOMResult) + { + if (created) + { + DOMResult dr = (DOMResult) outputTarget; + dr.setNode(parent); + dr.setNextSibling(null); + } + } + else if (outputTarget instanceof StreamResult) + { + StreamResult sr = (StreamResult) outputTarget; + IOException ex = null; + try + { + writeStreamResult(parent, sr, outputMethod, encoding); + } + catch (UnsupportedEncodingException e) + { + try + { + writeStreamResult(parent, sr, outputMethod, "UTF-8"); + } + catch (IOException e2) + { + ex = e2; + } + } + catch (IOException e) + { + ex = e; + } + if (ex != null) + { + if (errorListener != null) + { + errorListener.error(new TransformerException(ex)); + } + else + { + ex.printStackTrace(System.err); + } + } + } + else if (outputTarget instanceof SAXResult) + { + SAXResult sr = (SAXResult) outputTarget; + try + { + ContentHandler ch = sr.getHandler(); + LexicalHandler lh = sr.getLexicalHandler(); + if (lh == null && ch instanceof LexicalHandler) + { + lh = (LexicalHandler) ch; + } + SAXSerializer serializer = new SAXSerializer(); + serializer.serialize(parent, ch, lh); + } + catch (SAXException e) + { + if (errorListener != null) + { + errorListener.error(new TransformerException(e)); + } + else + { + e.printStackTrace(System.err); + } + } + } + } + + /** + * Strip whitespace from the source tree. + */ + void strip(Node node) + throws TransformerConfigurationException + { + short nt = node.getNodeType(); + if (nt == Node.ENTITY_REFERENCE_NODE) + { + // Replace entity reference with its content + Node parent = node.getParentNode(); + Node child = node.getFirstChild(); + if (child != null) + { + strip(child); + } + while (child != null) + { + Node next = child.getNextSibling(); + node.removeChild(child); + parent.insertBefore(child, node); + child = next; + } + parent.removeChild(node); + } + if (nt == Node.TEXT_NODE) // CDATA sections ? + { + if (!stylesheet.isPreserved((Text) node)) + { + node.getParentNode().removeChild(node); + } + } + else + { + for (Node child = node.getFirstChild(); child != null; + child = child.getNextSibling()) + { + strip(child); + } + } + } + + /** + * Obtain a suitable output stream for writing the result to, + * and use the StreamSerializer to write the result tree to the stream. + */ + void writeStreamResult(Node node, StreamResult sr, int outputMethod, + String encoding) + throws IOException + { + OutputStream out = null; + try + { + out = sr.getOutputStream(); + if (out == null) + { + Writer writer = sr.getWriter(); + if (writer != null) + { + out = new WriterOutputStream(writer); + } + } + if (out == null) + { + String systemId = sr.getSystemId(); + try + { + URL url = new URL(systemId); + URLConnection connection = url.openConnection(); + connection.setDoOutput(true); + out = connection.getOutputStream(); + } + catch (MalformedURLException e) + { + out = new FileOutputStream(systemId); + } + catch (UnknownServiceException e) + { + URL url = new URL(systemId); + out = new FileOutputStream(url.getPath()); + } + } + out = new BufferedOutputStream(out); + StreamSerializer serializer = + new StreamSerializer(outputMethod, encoding, null); + if (stylesheet != null) + { + Collection celem = stylesheet.outputCdataSectionElements; + serializer.setCdataSectionElements(celem); + } + serializer.serialize(node, out); + out.flush(); + } + finally + { + try + { + if (out != null) + { + out.close(); + } + } + catch (IOException e) + { + } + } + } + + void copyChildren(Document dstDoc, Node src, Node dst) + { + Node srcChild = src.getFirstChild(); + while (srcChild != null) + { + Node dstChild = dstDoc.adoptNode(srcChild); + dst.appendChild(dstChild); + srcChild = srcChild.getNextSibling(); + } + } + + public void setParameter(String name, Object value) + { + if (stylesheet != null) + { + stylesheet.bindings.set(name, value, false); + } + } + + public Object getParameter(String name) + { + if (stylesheet != null) + { + return stylesheet.bindings.get(name, null, 1, 1); + } + return null; + } + + public void clearParameters() + { + if (stylesheet != null) + { + stylesheet.bindings.pop(false); + stylesheet.bindings.push(false); + } + } + + public void setURIResolver(URIResolver resolver) + { + uriResolver = resolver; + } + + public URIResolver getURIResolver() + { + return uriResolver; + } + + public void setOutputProperties(Properties oformat) + throws IllegalArgumentException + { + if (oformat == null) + { + outputProperties.clear(); + } + else + { + outputProperties.putAll(oformat); + } + } + + public Properties getOutputProperties() + { + return (Properties) outputProperties.clone(); + } + + public void setOutputProperty(String name, String value) + throws IllegalArgumentException + { + outputProperties.put(name, value); + } + + public String getOutputProperty(String name) + throws IllegalArgumentException + { + return outputProperties.getProperty(name); + } + + public void setErrorListener(ErrorListener listener) + { + errorListener = listener; + } + + public ErrorListener getErrorListener() + { + return errorListener; + } + +} diff --git a/libjava/gnu/xml/transform/TransformerOutputProperties.java b/libjava/gnu/xml/transform/TransformerOutputProperties.java new file mode 100644 index 0000000..d56e795 --- /dev/null +++ b/libjava/gnu/xml/transform/TransformerOutputProperties.java @@ -0,0 +1,185 @@ +/* TransformerOutputProperties.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Properties; +import java.util.StringTokenizer; +import javax.xml.transform.OutputKeys; + +/** + * Helper class to manage JAXP user setting of output properties. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class TransformerOutputProperties + extends Properties +{ + + final Properties defaultProperties; + final Stylesheet stylesheet; + boolean dirty; + + TransformerOutputProperties(Stylesheet stylesheet) + { + this.stylesheet = stylesheet; + defaultProperties = new Properties(); + switch (stylesheet.outputMethod) + { + case Stylesheet.OUTPUT_XML: + defaultProperties.put(OutputKeys.METHOD, "xml"); + break; + case Stylesheet.OUTPUT_HTML: + defaultProperties.put(OutputKeys.METHOD, "html"); + break; + case Stylesheet.OUTPUT_TEXT: + defaultProperties.put(OutputKeys.METHOD, "text"); + break; + } + if (stylesheet.outputVersion != null) + { + defaultProperties.put(OutputKeys.VERSION, stylesheet.outputVersion); + } + if (stylesheet.outputEncoding != null) + { + defaultProperties.put(OutputKeys.ENCODING, stylesheet.outputEncoding); + } + defaultProperties.put(OutputKeys.OMIT_XML_DECLARATION, + stylesheet.outputOmitXmlDeclaration ? "yes" : "no"); + defaultProperties.put(OutputKeys.STANDALONE, + stylesheet.outputStandalone ? "yes" : "no"); + if (stylesheet.outputPublicId != null) + { + defaultProperties.put(OutputKeys.DOCTYPE_PUBLIC, + stylesheet.outputPublicId); + } + if (stylesheet.outputSystemId != null) + { + defaultProperties.put(OutputKeys.DOCTYPE_SYSTEM, + stylesheet.outputSystemId); + } + StringBuffer buf = new StringBuffer(); + for (Iterator i = stylesheet.outputCdataSectionElements.iterator(); + i.hasNext(); ) + { + if (buf.length() > 0) + { + buf.append(' '); + } + buf.append((String) i.next()); + } + defaultProperties.put(OutputKeys.CDATA_SECTION_ELEMENTS, buf.toString()); + defaultProperties.put(OutputKeys.INDENT, + stylesheet.outputIndent ? "yes" : "no"); + if (stylesheet.outputMediaType != null) + { + defaultProperties.put(OutputKeys.MEDIA_TYPE, + stylesheet.outputMediaType); + } + } + + public String getProperty(String key) + { + String val = super.getProperty(key); + if (val == null) + { + val = defaultProperties.getProperty(key); + } + return val; + } + + public Object put(Object key, Object value) + { + Object ret = super.put(key, value); + dirty = true; + return ret; + } + + public void clear() + { + super.clear(); + dirty = true; + } + + /** + * Applies the current set of properties to the underlying stylesheet. + */ + void apply() + { + if (!dirty) + { + return; + } + String method = getProperty(OutputKeys.METHOD); + if ("xml".equals(method)) + { + stylesheet.outputMethod = Stylesheet.OUTPUT_XML; + } + else if ("html".equals(method)) + { + stylesheet.outputMethod = Stylesheet.OUTPUT_HTML; + } + else if ("text".equals(method)) + { + stylesheet.outputMethod = Stylesheet.OUTPUT_TEXT; + } + stylesheet.outputVersion = getProperty(OutputKeys.VERSION); + stylesheet.outputEncoding = getProperty(OutputKeys.ENCODING); + stylesheet.outputOmitXmlDeclaration = + "yes".equals(getProperty(OutputKeys.OMIT_XML_DECLARATION)); + stylesheet.outputStandalone = + "yes".equals(getProperty(OutputKeys.STANDALONE)); + stylesheet.outputPublicId = getProperty(OutputKeys.DOCTYPE_PUBLIC); + stylesheet.outputSystemId = getProperty(OutputKeys.DOCTYPE_SYSTEM); + StringTokenizer st = + new StringTokenizer(getProperty(OutputKeys.CDATA_SECTION_ELEMENTS)); + Collection acc = new LinkedHashSet(); + while (st.hasMoreTokens()) + { + acc.add(st.nextToken()); + } + stylesheet.outputCdataSectionElements = acc; + stylesheet.outputIndent = "yes".equals(getProperty(OutputKeys.INDENT)); + stylesheet.outputMediaType = getProperty(OutputKeys.MEDIA_TYPE); + dirty = false; + } + +} + diff --git a/libjava/gnu/xml/transform/URIResolverEntityResolver.java b/libjava/gnu/xml/transform/URIResolverEntityResolver.java new file mode 100644 index 0000000..ef29015 --- /dev/null +++ b/libjava/gnu/xml/transform/URIResolverEntityResolver.java @@ -0,0 +1,83 @@ +/* URIResolverEntityResolver.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.io.IOException; +import javax.xml.transform.Source; +import javax.xml.transform.TransformerException; +import javax.xml.transform.URIResolver; +import javax.xml.transform.sax.SAXSource; +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * EntityResolver that wraps a URIResolver. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class URIResolverEntityResolver + implements EntityResolver +{ + + final URIResolver resolver; + + URIResolverEntityResolver(URIResolver resolver) + { + this.resolver = resolver; + } + + public InputSource resolveEntity(String publicId, String systemId) + throws SAXException, IOException + { + try + { + Source source = resolver.resolve(null, systemId); + if (source == null) + { + return null; + } + return SAXSource.sourceToInputSource(source); + } + catch (TransformerException e) + { + throw new SAXException(e); + } + } + +} diff --git a/libjava/gnu/xml/transform/UnparsedEntityUriFunction.java b/libjava/gnu/xml/transform/UnparsedEntityUriFunction.java new file mode 100644 index 0000000..41a606e --- /dev/null +++ b/libjava/gnu/xml/transform/UnparsedEntityUriFunction.java @@ -0,0 +1,118 @@ +/* UnparsedEntityUriFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.xml.xpath.XPathFunction; +import javax.xml.xpath.XPathFunctionException; +import org.w3c.dom.DocumentType; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.Notation; +import gnu.xml.xpath.Expr; +import gnu.xml.xpath.Function; + +/** + * The XSLT <code>unparsed-entity-uri()</code>function. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class UnparsedEntityUriFunction + extends Expr + implements XPathFunction, Function +{ + + List args; + + public Object evaluate(List args) + throws XPathFunctionException + { + // Useless... + return Collections.EMPTY_SET; + } + + public void setArguments(List args) + { + this.args = args; + } + + public Object evaluate(Node context, int pos, int len) + { + int arity = args.size(); + List values = new ArrayList(arity); + for (int i = 0; i < arity; i++) + { + Expr arg = (Expr) args.get(i); + values.add(arg.evaluate(context, pos, len)); + } + String name = _string(context, values.get(0)); + DocumentType doctype = context.getOwnerDocument().getDoctype(); + if (doctype != null) + { + NamedNodeMap notations = doctype.getNotations(); + Notation notation = (Notation) notations.getNamedItem(name); + if (notation != null) + { + String systemId = notation.getSystemId(); + // XXX absolutize? + if (systemId != null) + { + return systemId; + } + } + } + return ""; + } + + public Expr clone(Object context) + { + UnparsedEntityUriFunction f = new UnparsedEntityUriFunction(); + int len = args.size(); + List args2 = new ArrayList(len); + for (int i = 0; i < len; i++) + { + args2.add(((Expr) args.get(i)).clone(context)); + } + f.setArguments(args2); + return f; + } + +} + diff --git a/libjava/gnu/xml/transform/ValueOfNode.java b/libjava/gnu/xml/transform/ValueOfNode.java new file mode 100644 index 0000000..6027052 --- /dev/null +++ b/libjava/gnu/xml/transform/ValueOfNode.java @@ -0,0 +1,129 @@ +/* ValueOfNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.Collection; +import java.util.Iterator; +import java.text.DecimalFormat; +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.Text; +import gnu.xml.xpath.Expr; + +/** + * A template node representing an XSLT <code>value-of</code> instruction. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class ValueOfNode + extends TemplateNode +{ + + final Expr select; + final boolean disableOutputEscaping; + + ValueOfNode(TemplateNode children, TemplateNode next, Expr select, + boolean disableOutputEscaping) + { + super(children, next); + this.select = select; + this.disableOutputEscaping = disableOutputEscaping; + } + + TemplateNode clone(Stylesheet stylesheet) + { + return new ValueOfNode((children == null) ? null : + children.clone(stylesheet), + (next == null) ? null : + next.clone(stylesheet), + select.clone(stylesheet), + disableOutputEscaping); + } + + void doApply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + Object ret = select.evaluate(context, pos, len); + String value = Expr._string(context, ret); + //System.err.println("value-of: "+context+" "+ select + " -> "+ value); + if (value != null && value.length() > 0) + { + Document doc = (parent instanceof Document) ? + (Document) parent : parent.getOwnerDocument(); + Text textNode = doc.createTextNode(value); + if (disableOutputEscaping) + { + textNode.setUserData("disable-output-escaping", "yes", stylesheet); + } + if (nextSibling != null) + { + parent.insertBefore(textNode, nextSibling); + } + else + { + parent.appendChild(textNode); + } + } + // value-of doesn't process children + if (next != null) + { + next.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + } + + public String toString() + { + StringBuffer buf = new StringBuffer(getClass().getName()); + buf.append('['); + buf.append("select="); + buf.append(select); + if (disableOutputEscaping) + { + buf.append(",disableOutputEscaping"); + } + buf.append(']'); + return buf.toString(); + } + +} diff --git a/libjava/gnu/xml/transform/WhenNode.java b/libjava/gnu/xml/transform/WhenNode.java new file mode 100644 index 0000000..f220466 --- /dev/null +++ b/libjava/gnu/xml/transform/WhenNode.java @@ -0,0 +1,110 @@ +/* WhenNode.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; + +/** + * A template node representing an XSL <code>when</code> instruction. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class WhenNode + extends TemplateNode +{ + + final Expr test; + + WhenNode(TemplateNode children, TemplateNode next, Expr test) + { + super(children, next); + this.test = test; + } + + TemplateNode clone(Stylesheet stylesheet) + { + return new WhenNode((children == null) ? null : + children.clone(stylesheet), + (next == null) ? null : + next.clone(stylesheet), + test.clone(stylesheet)); + } + + void doApply(Stylesheet stylesheet, QName mode, + Node context, int pos, int len, + Node parent, Node nextSibling) + throws TransformerException + { + Object ret = test.evaluate(context, pos, len); + boolean success = (ret instanceof Boolean) ? + ((Boolean) ret).booleanValue() : + Expr._boolean(context, ret); + if (success) + { + if (children != null) + { + children.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + } + else + { + if (next != null) + { + next.apply(stylesheet, mode, + context, pos, len, + parent, nextSibling); + } + } + } + + public String toString() + { + StringBuffer buf = new StringBuffer(getClass().getName()); + buf.append('['); + buf.append("test="); + buf.append(test); + buf.append(']'); + return buf.toString(); + } + +} diff --git a/libjava/gnu/xml/transform/WithParam.java b/libjava/gnu/xml/transform/WithParam.java new file mode 100644 index 0000000..c8804f8 --- /dev/null +++ b/libjava/gnu/xml/transform/WithParam.java @@ -0,0 +1,110 @@ +/* WithParam.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.Collections; +import javax.xml.namespace.QName; +import javax.xml.transform.TransformerException; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; + +/** + * A specification for setting a variable or parameter during template + * processing with <code>apply-templates</code> or + * <code>call-template</code>. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class WithParam +{ + + final String name; + final Expr select; + final TemplateNode content; + + WithParam(String name, Expr select) + { + this.name = name; + this.select = select; + content = null; + } + + WithParam(String name, TemplateNode content) + { + this.name = name; + this.content = content; + select = null; + } + + Object getValue(Stylesheet stylesheet, QName mode, + Node context, int pos, int len) + throws TransformerException + { + if (select != null) + { + return select.evaluate(context, pos, len); + } + else + { + Document doc = (context instanceof Document) ? (Document) context : + context.getOwnerDocument(); + DocumentFragment fragment = doc.createDocumentFragment(); + content.apply(stylesheet, mode, + context, pos, len, + fragment, null); + return Collections.singleton(fragment); + } + } + + WithParam clone(Stylesheet stylesheet) + { + if (content == null) + { + return new WithParam(name, + select.clone(stylesheet)); + } + else + { + return new WithParam(name, + content.clone(stylesheet)); + } + } + +} diff --git a/libjava/gnu/xml/transform/XSLComparator.java b/libjava/gnu/xml/transform/XSLComparator.java new file mode 100644 index 0000000..1b76ed6 --- /dev/null +++ b/libjava/gnu/xml/transform/XSLComparator.java @@ -0,0 +1,124 @@ +/* XSLComparator.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.text.Collator; +import org.w3c.dom.Node; +import gnu.xml.xpath.Expr; + +/** + * Comparator for sorting lists of nodes according to a list of sort keys. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class XSLComparator + implements Comparator +{ + + final List sortKeys; + + XSLComparator(List sortKeys) + { + this.sortKeys = sortKeys; + } + + public int compare(Object o1, Object o2) + { + if (o1 instanceof Node && o2 instanceof Node) + { + Node n1 = (Node) o1; + Node n2 = (Node) o2; + for (Iterator i = sortKeys.iterator(); i.hasNext(); ) + { + SortKey sortKey = (SortKey) i.next(); + String k1 = sortKey.key(n1); + String k2 = sortKey.key(n2); + if ("text".equals(sortKey.dataType)) + { + Locale locale = (sortKey.lang == null) ? Locale.getDefault() : + new Locale(sortKey.lang); + Collator collator = Collator.getInstance(locale); + int d = collator.compare(k1, k2); + if (d != 0) + { + switch (sortKey.caseOrder) + { + case SortKey.UPPER_FIRST: + // TODO + break; + case SortKey.LOWER_FIRST: + // TODO + break; + } + if (sortKey.descending) + { + d = -d; + } + return d; + } + } + else if ("number".equals(sortKey.dataType)) + { + double kn1 = Expr._number(n1, k1); + double kn2 = Expr._number(n2, k2); + int d; + if (Double.isNaN(kn1) || Double.isInfinite(kn2)) + { + d = -1; + } + else if (Double.isNaN(kn2) || Double.isInfinite(kn1)) + { + d = 1; + } + else + { + // conversion to int may give 0 for small numbers + d = (kn1 > kn2) ? 1 : (kn1 < kn2) ? -1 : 0; + } + return (sortKey.descending) ? -d : d; + } + } + } + return 0; + } + +} diff --git a/libjava/gnu/xml/transform/XSLURIResolver.java b/libjava/gnu/xml/transform/XSLURIResolver.java new file mode 100644 index 0000000..2603cdd --- /dev/null +++ b/libjava/gnu/xml/transform/XSLURIResolver.java @@ -0,0 +1,270 @@ +/* XSLURIResolver.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import java.io.File; +import java.io.InputStream; +import java.io.IOException; +import java.io.Reader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.HashMap; +import java.util.Map; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.ErrorListener; +import javax.xml.transform.Source; +import javax.xml.transform.TransformerException; +import javax.xml.transform.URIResolver; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamSource; +import org.w3c.dom.Node; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import gnu.xml.dom.ls.ReaderInputStream; + +/** + * URI resolver for XSLT. + * This resolver parses external entities into DOMSources. It + * maintains a cache of URIs to DOMSources to avoid expensive re-parsing. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class XSLURIResolver + implements URIResolver +{ + + Map lastModifiedCache = new HashMap(); + Map nodeCache = new HashMap(); + DocumentBuilder builder; + URIResolver userResolver; + ErrorListener userListener; + + void setUserResolver(URIResolver userResolver) + { + this.userResolver = userResolver; + } + + void setUserListener(ErrorListener userListener) + { + this.userListener = userListener; + } + + /** + * Clear the cache. + */ + void flush() + { + lastModifiedCache.clear(); + nodeCache.clear(); + } + + public Source resolve(String href, String base) + throws TransformerException + { + Source source = null; + if (userResolver != null) + { + source = userResolver.resolve(base, href); + } + return resolveDOM(source, href, base); + } + + DOMSource resolveDOM(Source source, String base, String href) + throws TransformerException + { + if (source != null && source instanceof DOMSource) + { + return (DOMSource) source; + } + String systemId = (source == null) ? null : source.getSystemId(); + long lastModified = 0L, lastLastModified = 0L; + + try + { + URL url = resolveURL(systemId, base, href); + Node node = null; + InputStream in = null; + if (source instanceof StreamSource) + { + StreamSource ss = (StreamSource) source; + in = ss.getInputStream(); + if (in == null) + { + Reader reader = ss.getReader(); + if (reader != null) + { + in = new ReaderInputStream(reader); + } + } + } + if (in == null && url != null) + { + systemId = url.toString(); + node = (Node) nodeCache.get(systemId); + // Is the resource up to date? + URLConnection conn = url.openConnection(); + Long llm = (Long) lastModifiedCache.get(systemId); + if (llm != null) + { + lastLastModified = llm.longValue(); + conn.setIfModifiedSince(lastLastModified); + } + conn.connect(); + lastModified = conn.getLastModified(); + if (node != null && + lastModified > 0L && + lastModified <= lastLastModified) + { + // Resource unchanged + return new DOMSource(node, systemId); + } + else + { + // Resource new or modified + in = conn.getInputStream(); + nodeCache.put(systemId, node); + lastModifiedCache.put(systemId, new Long(lastModified)); + } + } + InputSource input = new InputSource(in); + input.setSystemId(systemId); + DocumentBuilder builder = getDocumentBuilder(); + node = builder.parse(input); + return new DOMSource(node, systemId); + } + catch (IOException e) + { + throw new TransformerException(e); + } + catch (SAXException e) + { + throw new TransformerException(e); + } + } + + URL resolveURL(String systemId, String base, String href) + throws IOException + { + URL url = null; + try + { + if (systemId != null) + { + try + { + url = new URL(systemId); + } + catch (MalformedURLException e) + { + // Try building from base + href + } + } + if (url == null) + { + if (base != null) + { + URL baseURL = new URL(base); + url = new URL(baseURL, href); + } + else if (href != null) + { + url = new URL(href); + } + } + return url; + } + catch (MalformedURLException e) + { + // Fall back to local filesystem + File file = null; + if (href == null) + { + href = systemId; + } + if (base != null) + { + int lsi = base.lastIndexOf(File.separatorChar); + if (lsi != -1 && lsi < base.length() - 1) + { + base = base.substring(0, lsi); + } + File baseFile = new File(base); + file = new File(baseFile, href); + } + else if (href != null) + { + file = new File(href); + } + return (file == null) ? null : file.toURL(); + } + } + + DocumentBuilder getDocumentBuilder() + throws TransformerException + { + try + { + if (builder == null) + { + DocumentBuilderFactory factory = + DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + factory.setExpandEntityReferences(true); + builder = factory.newDocumentBuilder(); + } + if (userResolver != null) + { + builder.setEntityResolver(new URIResolverEntityResolver(userResolver)); + } + if (userListener != null) + { + builder.setErrorHandler(new ErrorListenerErrorHandler(userListener)); + } + return builder; + } + catch (Exception e) + { + throw new TransformerException(e); + } + } + +} + diff --git a/libjava/gnu/xml/transform/package.html b/libjava/gnu/xml/transform/package.html new file mode 100644 index 0000000..d435596 --- /dev/null +++ b/libjava/gnu/xml/transform/package.html @@ -0,0 +1,77 @@ +<html> +<body> + +<h1>GNU JAXP XSL transformer</h1> + +<div> +This package contains a Java XSL transformer compliant with the JAXP +specification. It depends on the GNU DOM and XPath implementations, and +will generate GNU DOM nodes unless a specific target from another +implementation was given. It understands DOM, SAX, and stream sources +and result sinks and supports these JAXP features. +</div> + +<div> +To use this transformer, set the system property +<code>javax.xml.transform.TransformerFactory</code> to the value +<code>gnu.xml.transform.TransformerFactoryImpl</code>. You can then +instantiate <a href='TransformerFactory.html'>TransformerFactory</a> +and transformers in the ordinary manner. Reuse of stylesheets is +supported using the JAXP <a href='Templates.html'>Templates</a> +mechanism. +</div> + +<h3>Architecture</h3> + +<div> +When given a stylesheet source, this implementation compiles it internally +into a Stylesheet object, which is a container for templates and state. +Each stylesheet instruction is represented by a subclass of TemplateNode, +which is arranged in a directed graph: each TemplateNode has a reference +to its first child and the next node. +</div> + +<div> +The transformation process consists of identifying the Template that matches +the root of the source context, and calling <code>apply</code> on its +corresponding TemplateNode. This in turn processes its children and next +TemplateNode, depending on the semantics of each node type. +</div> + +<div> +Template nodes may reference XPath expressions or patterns. These are fully +compiled to objects of type <a href='../xpath/Expr.html'>Expr</a> at the +time the stylesheet is compiled. +</div> + +<h3>Conformance</h3> + +<div> +This implementation is feature complete, but the XSLT specification is +large and there are still many bugs that need to be ironed out. It has +been tested against the OASIS XSLT TC test suite, comprising unit tests +from the Xalan project and Microsoft. Conformance to these unit tests +is approximately 70% at the current time, although normal usage of the +transformer should involve relatively few surprises (the test suite is +designed to test very complex and obscure functionality). +</div> + +<h3>Known bugs</h3> + +<ul> +<li>When reusing stylesheets using the JAXP Templates mechanism, XSL +<code>apply-imports</code> instructions will not work.</li> +<li>XPath filter expressions do not always work as expected (this is a +problem with the GNU XPath implementation rather than the transformer). +This can result in problems with the <code>position()</code> function, +as well as <code>select</code> expressions and numbering.</li> +</ul> + +<div> +Obviously we'd like to improve conformance and fix these bugs. If you're +interested in working on any of these issues please +<a href='mailto:classpathx-xml@gnu.org'>contact us</a>. +</div> + +</body> +</html> diff --git a/libjava/gnu/xml/util/DoParse.java b/libjava/gnu/xml/util/DoParse.java new file mode 100644 index 0000000..23721de --- /dev/null +++ b/libjava/gnu/xml/util/DoParse.java @@ -0,0 +1,306 @@ +/* DoParse.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.util; + +import java.io.*; + +import org.xml.sax.*; +import org.xml.sax.helpers.XMLReaderFactory; +import org.xml.sax.ext.*; + +// import gnu.xml.pipeline.*; +// Added imports directly to fix a GCJ problem. +import gnu.xml.pipeline.CallFilter; +import gnu.xml.pipeline.DomConsumer; +import gnu.xml.pipeline.EventConsumer; +import gnu.xml.pipeline.EventFilter; +import gnu.xml.pipeline.LinkFilter; +import gnu.xml.pipeline.NSFilter; +import gnu.xml.pipeline.PipelineFactory; +import gnu.xml.pipeline.TeeConsumer; +import gnu.xml.pipeline.TextConsumer; +import gnu.xml.pipeline.ValidationConsumer; +import gnu.xml.pipeline.WellFormednessFilter; +import gnu.xml.pipeline.XIncludeFilter; +import gnu.xml.pipeline.XsltFilter; + + +/** + * This class provides a driver which may be invoked from the command line + * to process a document using a SAX2 parser and a specified XML processing + * pipeline. + * This facilitates some common types of command line tools, such as parsing an + * XML document in order test it for well formedness or validity. + * + * <p>The SAX2 XMLReaderFactory should return a SAX2 XML parser which + * supports both of the standardized extension handlers (for declaration + * and lexical events). That parser will be used to produce events. + * + * <p>The first parameter to the command gives the name of the document that + * will be given to that processor. If it is a file name, it is converted + * to a URL first. + * + * <p>The second parameter describes a simple processing pipeline, and will + * be used as input to {@link gnu.xml.pipeline.PipelineFactory} + * methods which identify the processing to be done. Examples of such a + * pipeline include <pre> + * + * nsfix | validate <em>to validate the input document </em> + * nsfix | write ( stdout ) <em>to echo the file as XML text</em> + * dom | nsfix | write ( stdout ) <em>parse into DOM, print the result</em> + * </pre> + * + * <p> Relatively complex pipelines can be described on the command line, but + * not all interesting ones will require as little configuration as can be done + * in that way. Put filters like "nsfix", perhaps followed by "validate", + * at the front of the pipeline so they can be optimized out if a parser + * supports those modes natively. + * + * <p> If the parsing is aborted for any reason, the JVM will exit with a + * failure code. If a validating parse was done then both validation and + * well formedness errors will cause a failure. A non-validating parse + * will report failure on well formedness errors. + * + * @see gnu.xml.pipeline.PipelineFactory + * + * @author David Brownell + */ +final public class DoParse +{ + private DoParse () { /* no instances allowed */ } + + // first reported nonrecoverable error + private static SAXParseException fatal; + + // error categories + private static int errorCount; + private static int fatalCount; + + /** + * Command line invoker for this class; pass a filename or URL + * as the first argument, and a pipeline description as the second. + * Make sure to use filters to condition the input to stages that + * require it; an <em>nsfix</em> filter will be a common requirement, + * to restore syntax that SAX2 parsers delete by default. Some + * conditioning filters may be eliminated by setting parser options. + * (For example, "nsfix" can set the "namespace-prefixes" feature to + * a non-default value of "true". In the same way, "validate" can set + * the "validation" feature to "true".) + */ + public static void main (String argv []) + throws IOException + { + int exitStatus = 1; + + if (argv.length != 2) { + System.err.println ("Usage: DoParse [filename|URL] pipeline-spec"); + System.err.println ("Example pipeline specs:"); + System.err.println (" 'nsfix | validate'"); + System.err.println ( + " ... restore namespace syntax, validate"); + System.err.println (" 'nsfix | write ( stdout )'"); + System.err.println ( + " ... restore namespace syntax, write to stdout as XML" + ); + System.exit (1); + } + + try { + // + // Get input source for specified document (or try ;-) + // + argv [0] = Resolver.getURL (argv [0]); + InputSource input = new InputSource (argv [0]); + + // + // Get the producer, using the system default parser (which + // can be overridden for this particular invocation). + // + // And the pipeline, using commandline options. + // + XMLReader producer; + EventConsumer consumer; + + producer = XMLReaderFactory.createXMLReader (); + + // + // XXX pipeline factory now has a pre-tokenized input + // method, use it ... that way at least some params + // can be written using quotes (have spaces, ...) + // + consumer = PipelineFactory.createPipeline (argv [1]); + + // + // XXX want commandline option for tweaking error handler. + // Want to be able to present warnings. + // + producer.setErrorHandler (new MyErrorHandler ()); + + // XXX need facility enabling resolving to local DTDs + + // + // Parse. The pipeline may get optimized a bit, so we + // can't always fail cleanly for validation without taking + // a look at the filter stages. + // + EventFilter.bind (producer, consumer); + producer.parse (input); + + try { + if (producer.getFeature ( + "http://org.xml/sax/features/validation")) + exitStatus = ((errorCount + fatalCount) > 0) ? 1 : 0; + else if (fatalCount == 0) + exitStatus = 0; + } catch (SAXException e) { + if (hasValidator (consumer)) + exitStatus = ((errorCount + fatalCount) > 0) ? 1 : 0; + else if (fatalCount == 0) + exitStatus = 0; + } + + } catch (java.net.MalformedURLException e) { + System.err.println ("** Malformed URL: " + e.getMessage ()); + System.err.println ("Is '" + argv [0] + "' a non-existent file?"); + e.printStackTrace (); + // e.g. FNF + + } catch (SAXParseException e) { + if (e != fatal) { + System.err.print (printParseException ("Parsing Aborted", e)); + e.printStackTrace (); + if (e.getException () != null) { + System.err.println ("++ Wrapped exception:"); + e.getException ().printStackTrace (); + } + } + + } catch (SAXException e) { + Exception x = e; + if (e.getException () != null) + x = e.getException (); + x.printStackTrace (); + + } catch (Throwable t) { + t.printStackTrace (); + } + + System.exit (exitStatus); + } + + // returns true if saw a validator (before end or unrecognized node) + // false otherwise + private static boolean hasValidator (EventConsumer e) + { + if (e == null) + return false; + if (e instanceof ValidationConsumer) + return true; + if (e instanceof TeeConsumer) { + TeeConsumer t = (TeeConsumer) e; + return hasValidator (t.getFirst ()) + || hasValidator (t.getRest ()); + } + if (e instanceof WellFormednessFilter + || e instanceof NSFilter + ) + return hasValidator (((EventFilter)e).getNext ()); + + // else ... gee, we can't know. Assume not. + + return false; + } + + static class MyErrorHandler implements ErrorHandler + { + // dump validation errors, but continue + public void error (SAXParseException e) + throws SAXParseException + { + errorCount++; + System.err.print (printParseException ("Error", e)); + } + + public void warning (SAXParseException e) + throws SAXParseException + { + // System.err.print (printParseException ("Warning", e)); + } + + // try to continue fatal errors, in case a parser reports more + public void fatalError (SAXParseException e) + throws SAXParseException + { + fatalCount++; + if (fatal == null) + fatal = e; + System.err.print (printParseException ("Nonrecoverable Error", e)); + } + } + + static private String printParseException ( + String label, + SAXParseException e + ) { + StringBuffer buf = new StringBuffer (); + int temp; + + buf.append ("** "); + buf.append (label); + buf.append (": "); + buf.append (e.getMessage ()); + buf.append ('\n'); + if (e.getSystemId () != null) { + buf.append (" URI: "); + buf.append (e.getSystemId ()); + buf.append ('\n'); + } + if ((temp = e.getLineNumber ()) != -1) { + buf.append (" line: "); + buf.append (temp); + buf.append ('\n'); + } + if ((temp = e.getColumnNumber ()) != -1) { + buf.append (" char: "); + buf.append (temp); + buf.append ('\n'); + } + + return buf.toString (); + } +} diff --git a/libjava/gnu/xml/util/DomParser.java b/libjava/gnu/xml/util/DomParser.java new file mode 100644 index 0000000..0a75d54 --- /dev/null +++ b/libjava/gnu/xml/util/DomParser.java @@ -0,0 +1,804 @@ +/* DomParser.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.util; + +import java.util.Enumeration; +import java.util.Locale; + +import org.xml.sax.*; +import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.helpers.NamespaceSupport; +import org.xml.sax.ext.DeclHandler; +import org.xml.sax.ext.DefaultHandler2; +import org.xml.sax.ext.LexicalHandler; + +import org.w3c.dom.*; + + +/** + * This parser emits SAX2 parsing events as it traverses a DOM tree, using + * any conformant implementation of DOM. It exposes all SAX1 features, + * and the following SAX2 features and properties (as + * identified by standard URIs which are not fully provided here). Note + * that if a Level 1 DOM implementation is given, then this behaves as if + * namespaces were disabled, and namespace prefixes were enabled. </p> + * + * <table border="1" width='100%' cellpadding='3' cellspacing='0'> + * <tr bgcolor='#ccccff'> + * <th><font size='+1'>Name</font></th> + * <th><font size='+1'>Notes</font></th></tr> + * + * <tr><td colspan=2><center><em>Features ... URL prefix is + * <b>http://xml.org/sax/features/</b></em></center></td></tr> + * + * <tr><td>(URL)/external-general-entities</td> + * <td>false (does no parsing)</td></tr> + * <tr><td>(URL)/external-parameter-entities</td> + * <td>false (does no parsing)</td></tr> + * <tr><td>(URL)/namespaces</td> + * <td>Value is fixed at <em>true</em></td></tr> + * <tr><td>(URL)/namespace-prefixes</td> + * <td>Value is settable, defaulting to <em>false</em> + * (<code>xmlns</code> attributes hidden, and names aren't prefixed) + * </td></tr> + * <tr><td>(URL)/string-interning</td> + * <td>Value is fixed at <em>false</em> (DOM provides no + * guarantees as to interning)</td></tr> + * <tr><td>(URL)/validation</td> + * <td>false (does no parsing)</td></tr> + * <tr><td>(URL)/lexical-handler/parameter-entities</td> + * <td>false (DOM doesn't do parameter entities)</td></tr> + * + * <tr><td colspan=2><center><em>Properties ... URL prefix is + * <b>http://xml.org/sax/properties/</b></em></center></td></tr> + * + * + * <tr><td>(URL)/dom-node</td> + * <td>This property may be set before parsing to hold a DOM + * <em>Document</em> node; any arguments given to <em>parse</em> + * methods are ignored. When retrieved + * during a parse, this value contains the "current" DOM node. + * </td></tr> + * <tr><td>(URL)/declaration-handler</td> + * <td>A declaration handler may be provided. Declaration of external + * general entities is exposed, but not parameter entities; none of the + * entity names reported here will begin with "%". </td></tr> + * <tr><td>(URL)/lexical-handler</td> + * <td>A lexical handler may be provided. While the start and end of + * any external subset are reported, expansion of other parameter + * entities (e.g. inside attribute list declarations) is not exposed. + * Expansion of general entities within attributes is also not exposed + * (see below).</td></tr> + * </table> + * + * <P> The consequences of modifying a DOM document tree as it is being walked + * by this "parser" are unspecified; don't do it! </P> + * + * @author David Brownell + */ +final public class DomParser implements XMLReader +{ + // Stuff used internally to route events correctly + private DefaultHandler2 defaultHandler = new DefaultHandler2 (); + + // per-parse SAX stuff + private ContentHandler contentHandler = defaultHandler; + private DTDHandler dtdHandler = defaultHandler; + private DeclHandler declHandler = defaultHandler; + private LexicalHandler lexicalHandler = defaultHandler; + + // shared context + private ErrorHandler errHandler = defaultHandler; + private EntityResolver resolver = defaultHandler; + private Locale locale = Locale.getDefault (); + + // parser state + private Node start; + private Node current; + private boolean isL2; + private boolean showNamespaces = true; + private boolean showXML1_0 = false; + private NamespaceSupport prefixStack = new NamespaceSupport (); + private boolean isDocument; + + + /** + * Constructs an unitialized <b>SAX2</b> parser. + */ + public DomParser () { + } + + /** + * Constructs an <b>SAX2</b> parser initialized to traverse the specified + * DOM tree. If the node is a document, the startDocument() and + * endDocument() calls bracket the calls exposing children. + */ + public DomParser (Node node) { + setStart (node); + } + + + // stuff that most components in an application should be sharing: + // resolver and error locale. + + /** + * <b>SAX2</b>: Returns the object used when resolving external + * entities during parsing (both general and parameter entities). + */ + public EntityResolver getEntityResolver () + { + return resolver; + } + + /** + * <b>SAX1</b>: Provides an object which may be used when resolving external + * entities during parsing (both general and parameter entities). + */ + public void setEntityResolver (EntityResolver resolver) + { + if (resolver == null) + resolver = defaultHandler; + this.resolver = resolver; + } + + /** + * <b>SAX1</b>: Identifies the locale which the parser should use for the + * diagnostics it provides. + * + * @exception SAXException as defined in the specification for + * <em>org.xml.sax.Parser.setLocale()</em> + */ + public void setLocale (Locale locale) + throws SAXException + { + if (locale == null) + locale = Locale.getDefault (); + this.locale = locale; + } + + + // different modules will tend to handle error handling the same, + // but it may not be the same through the whole app + + /** + * <b>SAX2</b>: Returns the object used to receive callbacks for XML + * errors of all levels (fatal, nonfatal, warning). + */ + public ErrorHandler getErrorHandler () + { + return errHandler; + } + + /** + * <b>SAX1</b>: Provides an object which receives callbacks for XML errors + * of all levels (fatal, nonfatal, warning). + */ + public void setErrorHandler (ErrorHandler handler) + { + if (handler == null) + handler = defaultHandler; + errHandler = handler; + } + + + // stuff different parts of a module will handle differently + + /** + * <b>SAX2</b>: Returns the object used to report the logical + * content of an XML document. + */ + public ContentHandler getContentHandler () + { + return contentHandler; + } + + /** + * <b>SAX2</b>: Assigns the object used to report the logical + * content of an XML document. + */ + public void setContentHandler (ContentHandler handler) + { + if (handler == null) + handler = defaultHandler; + contentHandler = handler; + } + + /** + * <b>SAX2</b>: Returns the object used to process declarations related + * to notations and unparsed entities. + */ + public DTDHandler getDTDHandler () + { + return dtdHandler; + } + + /** + * <b>SAX1</b>: Provides an object which may be used to intercept + * declarations related to notations and unparsed entities. + */ + public void setDTDHandler (DTDHandler handler) + { + if (handler == null) + handler = defaultHandler; + dtdHandler = handler; + } + + + /** + * <b>SAX1</b>: Parses the previously provided DOM document (the + * input parameter is ignored). When this returns, that same + * document may be parsed again without needing a "reset". + * + * @param uri ignored (pass an empty string) + * @exception SAXException as defined in the specification for + * <em>org.xml.sax.Parser.parse()</em> + */ + public void parse (String uri) throws SAXException + { + parse (); + } + + /** + * <b>SAX1</b>: Parses the previously provided DOM document (the + * input parameter is ignored). When this returns, that same + * document may be parsed again without needing a "reset". + * + * @param input ignored + * @exception SAXException as defined in the specification for + * <em>org.xml.sax.Parser.parse()</em> + */ + public void parse (InputSource input) throws SAXException + { + parse (); + } + + private void parse () throws SAXException + { + try { + walk (); + } finally { + if (isDocument) + contentHandler.endDocument (); + current = null; + prefixStack.reset (); + } + } + + private boolean getIsL2 (Node node) + { + DOMImplementation impl; + Document doc; + + if (node instanceof Document) + doc = (Document) node; + else + doc = node.getOwnerDocument (); + if (doc == null) + throw new RuntimeException ("? unowned node - L2 DTD ?"); + impl = doc.getImplementation (); + return impl.hasFeature ("XML", "2.0"); + } + + + private static final String FEATURES = "http://xml.org/sax/features/"; + private static final String HANDLERS = "http://xml.org/sax/properties/"; + + /** + * <b>SAX2</b>: Tells whether this parser supports the specified feature. + */ + public boolean getFeature (String name) + throws SAXNotRecognizedException, SAXNotSupportedException + { + // basically, none are relevant -- they relate more to + // parsing than to walking a "parse tree". + + // FIXME: DOM feature to expose interning? + + if ((FEATURES + "validation").equals (name) + || (FEATURES + "external-general-entities") + .equals (name) + || (FEATURES + "external-parameter-entities") + .equals (name) + || (FEATURES + "string-interning").equals (name) + ) + return false; + + if ((FEATURES + "namespaces").equals (name)) + return showNamespaces; + if ((FEATURES + "namespace-prefixes").equals (name)) + return showXML1_0; + + throw new SAXNotRecognizedException (name); + } + + /** + * <b>SAX2</b>: Returns the specified property. At this time only + * the declaration and lexical handlers, and current the "DOM" node, + * are supported. + */ + public Object getProperty (String name) + throws SAXNotRecognizedException, SAXNotSupportedException + { + if ((HANDLERS + "declaration-handler").equals (name)) + return declHandler == defaultHandler ? null : declHandler; + if ((HANDLERS + "lexical-handler").equals (name)) + return lexicalHandler == defaultHandler ? null : lexicalHandler; + + if ((HANDLERS + "dom-node").equals (name)) + return current; + + // unknown properties + throw new SAXNotRecognizedException (name); + } + + /** + * <b>SAX2</b>: Sets the state of features supported in this parser. + * Only the namespace support features are mutable. + */ + public void setFeature (String name, boolean state) + throws SAXNotRecognizedException, SAXNotSupportedException + { + if (current != null) + throw new IllegalStateException ("feature change midparse"); + + boolean value = getFeature (name); + + if (value == state) + return; + + if ((FEATURES + "namespaces").equals (name)) { + if (!showXML1_0 && state == false) + throw new SAXNotSupportedException ("Illegal namespace " + + "processing configuration"); + showNamespaces = state; + return; + } + if ((FEATURES + "namespace-prefixes").equals (name)) { + if (!showNamespaces && state == false) + throw new SAXNotSupportedException ("Illegal namespace " + + "processing configuration"); + showXML1_0 = state; + return; + } + + throw new SAXNotSupportedException (name); + } + + /** + * <b>SAX2</b>: Assigns the specified property. At this time only + * declaration and lexical handlers, and the initial DOM document, are + * supported. These must not be changed to values of the wrong type. + * Like SAX1 handlers, these handlers may be changed at any time. + * Like SAX1 input source or document URI, the initial DOM document + * may not be changed during a parse. + */ + public void setProperty (String name, Object state) + throws SAXNotRecognizedException, SAXNotSupportedException + { + if ((HANDLERS + "declaration-handler").equals (name)) { + if (!(state instanceof DeclHandler || state == null)) + throw new SAXNotSupportedException (name); + declHandler = (DeclHandler) state; + return; + } + + if ((HANDLERS + "lexical-handler").equals (name)) { + if (!(state instanceof LexicalHandler || state == null)) + throw new SAXNotSupportedException (name); + lexicalHandler = (LexicalHandler) state; + return; + } + + if ((HANDLERS + "dom-node").equals (name)) { + if (state == null || state instanceof Node) { + if (current != null) + throw new SAXNotSupportedException ( + "property is readonly during parse: " + name); + setStart ((Node) state); + return; + } + throw new SAXNotSupportedException ("not a DOM Node"); + } + + // unknown properties + throw new SAXNotRecognizedException (name); + } + + private void setStart (Node property) + { + start = property; + if (start != null) { + isL2 = getIsL2 (start); + isDocument = (start instanceof Document); + } + } + + // + // Non-recursive walk, using DOM state when backtracking is needed + // + private void walk () + throws SAXException + { + int type; + NamedNodeMap nodes; + int length; + AttributesImpl attrs = new AttributesImpl (); + char chars []; + String ns, local; + + synchronized (this) { + if (current != null) + throw new IllegalStateException ("already walking tree"); + + // JVM guarantees assignments are atomic; so no other + // thread could get this far till this walk's done. + current = start; + } + + for (;;) { + type = current.getNodeType (); + + // + // First, visit the current node, including any "start" calls + // + switch (type) { + + case Node.DOCUMENT_NODE: + contentHandler.startDocument (); + break; + + case Node.ELEMENT_NODE: + nodes = current.getAttributes (); + length = nodes.getLength (); + prefixStack.pushContext (); + for (int i = 0; i < length; i++) { + Attr attr = (Attr) nodes.item (i); + String name = attr.getNodeName (); + + if (showNamespaces && name.startsWith ("xmlns")) { + String prefix; + String uri; + + // NOTE: DOM L2 (CR2+ and REC) violate the + // Namespaces REC, treat "xmlns" like a strange + // attribute instead of a magic token + if ("xmlns".equals (name)) + prefix = ""; + else + prefix = name.substring (6); + uri = attr.getNodeValue (); + + prefixStack.declarePrefix (prefix, uri); + contentHandler.startPrefixMapping (prefix, uri); + + if (!showXML1_0) + continue; + } + + // + // NOTE: DOM doesn't record the attribute type info + // which SAX exposes; so this always reports CDATA. + // + // NOTE: SAX doesn't expose the isSpecified info which + // DOM exposes; that's discarded here. Similarly with + // the information DOM hides inside itself about what + // the default values for an attribute are. + // + if (showNamespaces) { + if (isL2) { + if ((ns = attr.getNamespaceURI ()) == null) + ns = ""; + // Note: SAX2 and DOM handle "local" names + // differently + if ((local = attr.getLocalName ()) == null) + local = name; + } else { +// XXX + throw new RuntimeException ( + "NYI, ns lookup when parsing L1 DOM"); + } + } else + ns = local = ""; + attrs.addAttribute (ns, local, name, + "CDATA", attr.getNodeValue ()); + } + if (showNamespaces) { + if (isL2) { + if ((ns = current.getNamespaceURI ()) == null) + ns = ""; + // Note: SAX2 and DOM handle "local" names differently + if ((local = current.getLocalName ()) == null) + local = current.getNodeName (); + } else { +// XXX + throw new RuntimeException ( + "NYI, ns lookup when parsing L1 DOM"); + } + } else + ns = local = ""; + contentHandler.startElement (ns, local, + current.getNodeName (), attrs); + if (length != 0) + attrs.clear (); + break; + + case Node.CDATA_SECTION_NODE: + lexicalHandler.startCDATA (); + chars = current.getNodeValue ().toCharArray (); + contentHandler.characters (chars, 0, chars.length); + lexicalHandler.endCDATA (); + break; + + case Node.COMMENT_NODE: + chars = current.getNodeValue ().toCharArray (); + lexicalHandler.comment (chars, 0, chars.length); + break; + + case Node.DOCUMENT_TYPE_NODE: + { + DocumentType doctype = (DocumentType) current; + + // + // Only DOM L2 supports recreating even some DTDs in full. + // + if (isL2) { + lexicalHandler.startDTD (doctype.getName (), + doctype.getPublicId (), + doctype.getSystemId ()); + } else + lexicalHandler.startDTD (doctype.getName (), + null, null); + + // + // The only sure way to recreate is to provide both the + // internal and external subsets. Otherwise, only part + // of the job can be done ... because from the DTD, DOM + // discards both the critical data, like the attribute and + // element declarations, as well as the PIs and comments + // that are used to hold their documentation. + // + // Even the entity and notation declarations that it can + // expose can't be recorded without proprietary extensions. + // + // We construct a comment to tell what we know about how + // (in)complete this particular really DTD is. + // + { + String message; + char buf []; + + // + // Though DOM L2 lets the whole doctype be recreated, + // SAX2 can't represent it (input or output). + // So this will be the typical case. + // + if (isL2 && doctype.getInternalSubset () != null) + message = + " Full DTD known; can't be shown using SAX2. "; + + // + // Otherwise, we'll concoct a partial DTD. If there's + // any more data here at all, it was provided using a + // (proprietary) extension to DOM. + // + else + message = + " This DTD was was recreated using incomplete DOM L2 records. "; + + buf = message.toCharArray (); + lexicalHandler.comment (buf, 0, buf.length); + } + + // report notations first + nodes = doctype.getNotations (); + length = nodes.getLength (); + for (int i = 0; i < length; i++) { + Notation notation = (Notation) nodes.item (i); + dtdHandler.notationDecl ( + notation.getNodeName (), + notation.getPublicId (), + notation.getSystemId ()); + } + + // then parsed and unparsed external general entities + nodes = doctype.getEntities (); + length = nodes.getLength (); + for (int i = 0; i < length; i++) { + Entity entity = (Entity) nodes.item (i); + String notation = entity.getNotationName (); + + if (notation != null) + dtdHandler.unparsedEntityDecl ( + entity.getNodeName (), + entity.getPublicId (), + entity.getSystemId (), + notation); + else if (entity.getSystemId () != null) + declHandler.externalEntityDecl ( + entity.getNodeName (), + entity.getPublicId (), + entity.getSystemId ()); + + // + // NOTE: DOM doesn't clearly provide internal + // entity support; but in case someone tries to + // fudge such support, we defend ourselves above. + // + // NOTE: DOM doesn't expose parameter entities + // (thank you thank you thank you thank you) + // + } + + // + // NOTE: DOM (levels 1 and 2) doesn't expose real + // typing information (element or attribute decls), + // as exposed by SAX2 declaration handlers. + // + lexicalHandler.endDTD (); + } + break; + + case Node.ENTITY_REFERENCE_NODE: + // this isn't done except (a) in content, and + // (b) not within a start tag (att value) + lexicalHandler.startEntity (current.getNodeName ()); + break; + + case Node.PROCESSING_INSTRUCTION_NODE: + contentHandler.processingInstruction ( + current.getNodeName (), current.getNodeValue ()); + break; + + case Node.TEXT_NODE: + chars = current.getNodeValue ().toCharArray (); + contentHandler.characters (chars, 0, chars.length); + break; + + default: + // e.g. fragments, entities, notations, attributes + throw new SAXException ("Illegal DOM Node type in Document: " + + current.getNodeType ()); + } + + // + // Then, pick the next node to visit. If the next node isn't + // a child, an "end" call may be needed before moving on. + // If there's no next node, we're done. + // + Node next; + + switch (type) { + case Node.DOCUMENT_NODE: + case Node.ELEMENT_NODE: + case Node.ENTITY_REFERENCE_NODE: + // + // For elements that can have children, visit those + // children before any siblings (i.e. depth first) + // and after visiting this node (i.e. preorder) + // + next = current.getFirstChild (); + if (next != null) { + current = next; + break; + } + // + // Else treat this like other childless nodes, but + // handle this node's "end" immediately. + // + callEnd (current); + + // FALLTHROUGH + + case Node.CDATA_SECTION_NODE: + case Node.COMMENT_NODE: + case Node.DOCUMENT_TYPE_NODE: + case Node.ENTITY_NODE: + case Node.PROCESSING_INSTRUCTION_NODE: + case Node.TEXT_NODE: + // + // Use next sibling, if there is one. + // Else, climb up a level (calling "end") + // until we find an ancestral sibling + // or until we we climb off the top (FINISH) + // + for (;;) { + if ((next = current.getNextSibling ()) != null) + break; + current = current.getParentNode (); + if (current == null || current == start) + return; + callEnd (current); + } + current = next; + break; + + default: + throw new SAXException ( + "Illegal DOM Node type found: " + current.getNodeType ()); + } + } + } + + private void callEnd (Node node) throws SAXException + { + switch (node.getNodeType ()) { + // only these three container types may ever be found + // directly inside a Document. + case Node.DOCUMENT_NODE: + // for SAX conformance, endDocument must always + // be called ... it's done in a "finally" clause) + return; + + case Node.ELEMENT_NODE: + if (showNamespaces) { + if (isL2) + contentHandler.endElement ( + node.getNamespaceURI (), + node.getLocalName (), + node.getNodeName ()); + else +// XXX + throw new RuntimeException ( + "NYI, ns lookup when parsing L1 DOM"); + for (Enumeration e = prefixStack.getDeclaredPrefixes (); + e.hasMoreElements (); + ) { + contentHandler.endPrefixMapping ((String) e.nextElement ()); + } + } else + contentHandler.endElement ("", "", node.getNodeName ()); + prefixStack.popContext (); + return; + + case Node.ENTITY_REFERENCE_NODE: + // see above -- in content, outside start tags. + lexicalHandler.endEntity (node.getNodeName ()); + return; + + // these can be given at the top level + case Node.DOCUMENT_FRAGMENT_NODE: + case Node.ATTRIBUTE_NODE: + return; + + default: + throw new SAXException ( + "Illegal DOM container type found: " + + current.getNodeType ()); + } + } +} diff --git a/libjava/gnu/xml/util/Resolver.java b/libjava/gnu/xml/util/Resolver.java new file mode 100644 index 0000000..4ca0787 --- /dev/null +++ b/libjava/gnu/xml/util/Resolver.java @@ -0,0 +1,263 @@ +/* Resolver.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.util; + +import java.io.*; + +import java.util.Dictionary; +import java.util.Hashtable; + +import org.xml.sax.*; + + +/** + * Utility implementation of a SAX resolver, which can be used to improve + * network utilization of SAX based XML components. It does this by + * supporting local caches of external entities. + * SAX parsers <em>should</em> use such local caches when possible. + * + * @see XCat + */ +public class Resolver implements EntityResolver, Cloneable +{ + /** + * Updates a dictionary used to map PUBLIC identifiers to file names, + * so that it uses the mappings in a specified directory. + * + * @param mappings Array of string pairs, where the first member + * of each pair is a PUBLIC identifier and the second is the + * name of a file, relative to the specified directory. + * @param directory File holding the specified files. + */ + public static void addDirectoryMapping ( + Dictionary table, + String mappings [][], + File directory + ) throws IOException + { + for (int i = 0; i < mappings.length; i++) { + File file = new File (directory, mappings [i][1]); + String temp; + + if (!file.exists ()) // ?? log a warning ?? + continue; + + temp = fileToURL (file); + table.put (mappings [i][0], temp); + } + } + + // FIXME: these *URL routines don't quite belong here, except + // that they're all in the same spirit of making it easy to + // use local filesystem URIs with XML parsers. + + /** + * Provides the URL for a named file, without relying on the JDK 1.2 + * {@link java.io.File#toURL File.toURL}() utility method. + * + * @param filename the file name to convert. Relative file names + * are resolved the way the JVM resolves them (current to the + * process-global current working directory). + * + * @exception IOException if the file does not exist + */ + public static String fileNameToURL (String filename) + throws IOException + { + return fileToURL (new File (filename)); + } + + /** + * Provides the URL for a file, without relying on the JDK 1.2 + * {@link java.io.File#toURL File.toURL}() utility method. + * + * @param f the file to convert. Relative file names + * are resolved the way the JVM resolves them (current to the + * process-global current working directory). + * + * @exception IOException if the file does not exist + */ + public static String fileToURL (File f) + throws IOException + { + String temp; + + // NOTE: the javax.xml.parsers.DocumentBuilder and + // javax.xml.transform.stream.StreamSource versions + // of this don't have this test. Some JVM versions + // don't report this error sanely through URL code. + if (!f.exists ()) + throw new IOException ("no such file: " + f.getName ()); + + // FIXME: getAbsolutePath() seems buggy; I'm seeing components + // like "/foo/../" which are clearly not "absolute" + // and should have been resolved with the filesystem. + + // Substituting "/" would be wrong, "foo" may have been + // symlinked ... the URL code will make that change + // later, so that things can get _really_ broken! + + temp = f.getAbsolutePath (); + + if (File.separatorChar != '/') + temp = temp.replace (File.separatorChar, '/'); + if (!temp.startsWith ("/")) + temp = "/" + temp; + if (!temp.endsWith ("/") && f.isDirectory ()) + temp = temp + "/"; + return "file:" + temp; + } + + + /** + * Returns a URL string. Note that if a malformed URL is provided, or + * the parameter names a nonexistent file, the resulting URL may be + * malformed. + * + * @param fileOrURL If this is the name of a file which exists, + * then its URL is returned. Otherwise the argument is returned. + */ + public static String getURL (String fileOrURL) + { + try { + return fileNameToURL (fileOrURL); + } catch (Exception e) { + return fileOrURL; + } + } + + + + // note: cloneable, this is just copied; unguarded against mods + private Dictionary pubidMapping; + + /** + * Constructs a resolver which understands how to map PUBLIC identifiers + * to other URIs, typically for local copies of standard DTD components. + * + * @param dictionary maps PUBLIC identifiers to URIs. This is not + * copied; subsequent modifications will be reported through the + * resolution operations. + */ + public Resolver (Dictionary dict) + { pubidMapping = dict; } + + + // FIXME: want notion of a "system default" resolver, presumably + // loaded with all sorts of useful stuff. At the same time need + // a notion of resolver chaining (failure --> next) so that subsystems + // can set up things that won't interfere with other ones. + + /** + * This parses most MIME content type strings that have <em>charset=...</em> + * encoding declarations to and returns the specified encoding. This + * conforms to RFC 3023, and is useful when constructing InputSource + * objects from URLConnection objects or other objects using MIME + * content typing. + * + * @param contentType the MIME content type that will be parsed; must + * not be null. + * @return the appropriate encoding, or null if the content type is + * not text and there's no <code>charset=...</code> attribute + */ + static public String getEncoding (String contentType) + { + // currently a dumb parsing algorithm that works "mostly" and handles + // ..anything...charset=ABC + // ..anything...charset=ABC;otherAttr=DEF + // ..anything...charset=ABC (comment);otherAttr=DEF + // ..anything...charset= "ABC" (comment);otherAttr=DEF + + int temp; + String encoding; + String defValue = null; + + if (contentType.startsWith ("text/")) + defValue = contentType.startsWith ("text/html") + ? "ISO-8859-1" : "US-ASCII"; + + // Assumes 'charset' is only an attribute name, not part + // of a value, comment, or other attribute name + // ALSO assumes no escaped values like "\;" or "\)" + if ((temp = contentType.indexOf ("charset")) != -1) { + // strip out everything up to '=' ... + temp = contentType.indexOf ('=', temp); + if (temp == -1) + return defValue; + encoding = contentType.substring (temp + 1); + // ... and any subsequent attributes + if ((temp = encoding.indexOf (';')) != -1) + encoding = encoding.substring (0, temp); + // ... and any comments after value + if ((temp = encoding.indexOf ('(')) != -1) + encoding = encoding.substring (0, temp); + // ... then whitespace, and any (double) quotes + encoding = encoding.trim (); + if (encoding.charAt (0) == '"') + encoding = encoding.substring (1, encoding.length () - 1); + } else + encoding = defValue; + return encoding; + } + + + /** + * Uses a local dictionary of public identifiers to resolve URIs, + * normally with the goal of minimizing network traffic or latencies. + */ + public InputSource resolveEntity (String publicId, String systemId) + throws IOException, SAXException + { + InputSource retval = null; + String uri; + + if (publicId != null + && ((uri = (String) pubidMapping.get (publicId)) != null)) { + retval = new InputSource (uri); + retval.setPublicId (publicId); + } + + // Could do URN resolution here + + // URL resolution always done by parser + + // FIXME: chain to "next" resolver + + return retval; + } +} diff --git a/libjava/gnu/xml/util/SAXNullTransformerFactory.java b/libjava/gnu/xml/util/SAXNullTransformerFactory.java new file mode 100644 index 0000000..3b199f6 --- /dev/null +++ b/libjava/gnu/xml/util/SAXNullTransformerFactory.java @@ -0,0 +1,675 @@ +/* SAXNullTransformerFactory.java -- + Copyright (C) 2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.util; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.Hashtable; +import java.util.Properties; + +import gnu.xml.dom.Consumer; +import gnu.xml.dom.DomDocument; +import gnu.xml.pipeline.DomConsumer; +import gnu.xml.pipeline.EventFilter; + +import javax.xml.transform.*; +import javax.xml.transform.dom.*; +import javax.xml.transform.sax.*; +import javax.xml.transform.stream.*; + +import org.xml.sax.*; +import org.xml.sax.helpers.XMLReaderFactory; +import org.xml.sax.helpers.LocatorImpl; + + +/** + * Implements null transforms. XSLT stylesheets are not supported. + * This class provides a way to translate three representations of + * XML data (SAX event stream, DOM tree, and XML text) into each other. + * In essence it's a thinnish wrapper around basic SAX event + * <a href="../pipeline/package-summary.html">pipeline</a> facilities, which + * exposes only limited functionality. The <em>javax.xml.transform</em> + * functionality is implemented as follows: <ul> + * + * <li>The {@link javax.xml.transform.sax.SAXSource SAXSource} class + * just wraps an {@link XMLReader} and {@link InputSource}, while the + * {@link javax.xml.transform.sax.SAXResult SAXResult} class is less + * functional than a {@link gnu.xml.pipeline.EventConsumer EventConsumer}. + * (Notably, it drops all but one declaration from any DTD.)</li> + * + * <li>The {@link javax.xml.transform.dom.DOMSource DOMSource} class + * corresponds to special SAX parsers like {@link DomParser}, and the + * {@link javax.xml.transform.dom.DOMResult DOMResult} class corresponds + * to a {@link gnu.xml.pipeline.DomConsumer DomConsumer}.</li> + * + * <li>The {@link javax.xml.transform.stream.StreamSource StreamSource} + * class corresponds to a SAX {@link InputSource}, and the + * {@link javax.xml.transform.stream.StreamResult StreamResult} class + * corresponds to a {@link gnu.xml.pipeline.TextConsumer TextConsumer}.</li> + * + * </ul> + * + * <p><em>This implementation is preliminary.</em> + * + * @see gnu.xml.pipeline.XsltFilter + * + * @author David Brownell + */ +public class SAXNullTransformerFactory extends SAXTransformerFactory +{ + + private ErrorListener errListener; + private URIResolver uriResolver; + + /** Default constructor */ + public SAXNullTransformerFactory () { } + + // + // only has stuff that makes sense with null transforms + // + + /** + * Returns true if the requested feature is supported. + * All three kinds of input and output are accepted: + * XML text, SAX events, and DOM nodes. + */ + public boolean getFeature (String feature) + { + return SAXTransformerFactory.FEATURE.equals (feature) + || SAXResult.FEATURE.equals (feature) + || SAXSource.FEATURE.equals (feature) + || DOMResult.FEATURE.equals (feature) + || DOMSource.FEATURE.equals (feature) + || StreamResult.FEATURE.equals (feature) + || StreamSource.FEATURE.equals (feature) + ; + } + + public void setFeature(String name, boolean value) + throws TransformerConfigurationException + { + throw new TransformerConfigurationException(name); + } + + + /** Throws an exception (no implementation attributes are supported) */ + public void setAttribute (String key, Object value) + { + throw new IllegalArgumentException (); + } + + /** Throws an exception (no implementation attributes are supported) */ + public Object getAttribute (String key) + { + throw new IllegalArgumentException (); + } + + /** (not yet implemented) */ + public Source getAssociatedStylesheet (Source source, + String media, + String title, + String charset) + throws TransformerConfigurationException + { + // parse, and find the appropriate xsl-stylesheet PI contents + throw new IllegalArgumentException (); + } + + public Transformer newTransformer () + throws TransformerConfigurationException + { + return new NullTransformer (); + } + + /** + * Returns a TransformerHandler that knows how to generate output + * in all three standard formats. Output text is generated using + * {@link XMLWriter}, and the GNU implementation of + * {@link DomDocument DOM} is used. + * + * @see SAXResult + * @see StreamResult + * @see DOMResult + */ + public TransformerHandler newTransformerHandler () + throws TransformerConfigurationException + { + NullTransformer transformer = new NullTransformer (); + return transformer.handler; + } + + // + // Stuff that depends on XSLT support, which we don't provide + // + private static final String noXSLT = "No XSLT support"; + + /** Throws an exception (XSLT is not supported). */ + public Transformer newTransformer (Source stylesheet) + throws TransformerConfigurationException + { + throw new TransformerConfigurationException (noXSLT); + } + + /** Throws an exception (XSLT is not supported). */ + public Templates newTemplates (Source stylesheet) + throws TransformerConfigurationException + { + throw new TransformerConfigurationException (noXSLT); + } + + /** Throws an exception (XSLT is not supported). */ + public TemplatesHandler newTemplatesHandler () + throws TransformerConfigurationException + { + throw new TransformerConfigurationException (noXSLT); + } + + /** Throws an exception (XSLT is not supported). */ + public TransformerHandler newTransformerHandler (Source stylesheet) + throws TransformerConfigurationException + { + throw new TransformerConfigurationException (noXSLT); + } + + /** Throws an exception (XSLT is not supported). */ + public TransformerHandler newTransformerHandler (Templates stylesheet) + throws TransformerConfigurationException + { + throw new TransformerConfigurationException (noXSLT); + } + + /** Throws an exception (XSLT is not supported). */ + public XMLFilter newXMLFilter (Source stylesheet) + throws TransformerConfigurationException + { + throw new TransformerConfigurationException (noXSLT); + } + + /** Throws an exception (XSLT is not supported). */ + public XMLFilter newXMLFilter (Templates stylesheet) + throws TransformerConfigurationException + { + throw new TransformerConfigurationException (noXSLT); + } + + /** Returns the value assigned by {@link #setErrorListener}. */ + public ErrorListener getErrorListener () + { + return errListener; + } + + /** Assigns a value that would be used when parsing stylesheets */ + public void setErrorListener (ErrorListener e) + { + errListener = e; + } + + /** Returns the value assigned by {@link #setURIResolver}. */ + public URIResolver getURIResolver () + { + return uriResolver; + } + + /** Assigns a value that would be used when parsing stylesheets */ + public void setURIResolver (URIResolver u) + { + uriResolver = u; + } + + + // + // Helper classes. These might in theory be subclassed + // by an XSLT implementation, if they were exported. + // + + static class DomTerminus + extends DomConsumer + { + + DomTerminus (DOMResult result) + throws SAXException + { + // won't really throw SAXException + super (DomDocument.class); + setHandler (new DomHandler (this, result)); + } + + } + + static class DomHandler + extends Consumer.Backdoor + { + + private DOMResult result; + + DomHandler (DomConsumer c, DOMResult r) + throws SAXException + { + // won't really throw SAXException + super (c); + result = r; + } + + public void endDocument () + throws SAXException + { + super.endDocument (); + result.setNode (getDocument ()); + } + + } + + private static OutputStream getOutputStream (String uri) + throws IOException + { + // JDK stupidity: file "protocol does not support output" ... + if (uri.startsWith ("file:")) + return new FileOutputStream (uri.substring (5)); + + // Otherwise ... + URL url = new URL (uri); + URLConnection conn = url.openConnection (); + + conn.setDoOutput (true); + return conn.getOutputStream (); + } + + + static class NullHandler + extends EventFilter + implements TransformerHandler + { + + private String systemId; + private Transformer transformer; + + NullHandler (Transformer t) + { + transformer = t; + } + + public Transformer getTransformer () + { + return transformer; + } + + public String getSystemId () + { + return systemId; + } + + public void setSystemId (String id) + { + systemId = id; + } + + public void setResult (Result result) + { + if (result.getSystemId () != null) + systemId = result.getSystemId (); + + try + { + + // output to partial SAX event stream? + if (result instanceof SAXResult) + { + SAXResult r = (SAXResult) result; + + setContentHandler (r.getHandler ()); + setProperty (LEXICAL_HANDLER, r.getLexicalHandler ()); + // DTD info is filtered out by javax.transform + + // output to DOM tree? + } + else if (result instanceof DOMResult) + { + DomTerminus out = new DomTerminus ((DOMResult) result); + + setContentHandler (out.getContentHandler ()); + setProperty (LEXICAL_HANDLER, + out.getProperty (LEXICAL_HANDLER)); + // save DTD-derived info, if any. + setDTDHandler (out.getDTDHandler ()); + setProperty (DECL_HANDLER, + out.getProperty (DECL_HANDLER)); + + // node is saved into result on endDocument() + + // output to (XML) text? + } + else if (result instanceof StreamResult) + { + StreamResult r = (StreamResult) result; + XMLWriter out; + + // FIXME: when do output properties take effect? + // encoding, standalone decl, xml/xhtml/... ... + + // FIXME: maybe put nsfix filter up front + + try + { + if (r.getWriter () != null) + out = new XMLWriter (r.getWriter ()); + else if (r.getOutputStream () != null) + out = new XMLWriter (r.getOutputStream ()); + else if (r.getSystemId () != null) + out = new XMLWriter ( + getOutputStream (r.getSystemId ())); + else + throw new IllegalArgumentException ( + "bad StreamResult"); + } + catch (IOException e) + { + e.printStackTrace (); + // on jdk 1.4, pass the root cause ... + throw new IllegalArgumentException (e.getMessage ()); + } + + // out.setExpandingEntities (true); + // out.setPrettyPrinting (true); + // out.setXhtml (true); + + setContentHandler (out); + setProperty (LEXICAL_HANDLER, out); + // save DTD info, if any; why not? + setDTDHandler (out); + setProperty (DECL_HANDLER, out); + } + + } + catch (SAXException e) + { + // SAXNotSupportedException or SAXNotRecognizedException: + // "can't happen" ... but SAXException for DOM build probs + // could happen, so ... + // on jdk 1.4, pass the root cause ... + throw new IllegalArgumentException (e.getMessage ()); + } + } + } + + // an interface that adds no value + static class LocatorAdapter + extends LocatorImpl + implements SourceLocator + { + + LocatorAdapter (SAXParseException e) + { + setSystemId (e.getSystemId ()); + setPublicId (e.getPublicId ()); + setLineNumber (e.getLineNumber ()); + setColumnNumber (e.getColumnNumber ()); + } + + } + + // another interface that adds no value + static class ListenerAdapter + implements ErrorHandler + { + + NullTransformer transformer; + + ListenerAdapter (NullTransformer t) + { + transformer = t; + } + + private TransformerException map (SAXParseException e) + { + return new TransformerException ( + e.getMessage (), + new LocatorAdapter (e), + e); + } + + public void error (SAXParseException e) + throws SAXParseException + { + try + { + if (transformer.errListener != null) + transformer.errListener.error (map (e)); + } + catch (TransformerException ex) + { + transformer.ex = ex; + throw e; + } + } + + public void fatalError (SAXParseException e) + throws SAXParseException + { + try + { + if (transformer.errListener != null) + transformer.errListener.fatalError (map (e)); + else + throw map (e); + } catch (TransformerException ex) { + transformer.ex = ex; + throw e; + } + } + + public void warning (SAXParseException e) + throws SAXParseException + { + try + { + if (transformer.errListener != null) + transformer.errListener.warning (map (e)); + } + catch (TransformerException ex) + { + transformer.ex = ex; + throw e; + } + } + } + + static class NullTransformer + extends Transformer + { + + private URIResolver uriResolver; + private Properties props = new Properties (); + private Hashtable params = new Hashtable (7); + + ErrorListener errListener = null; + TransformerException ex = null; + NullHandler handler; + + NullTransformer () + { + super (); + handler = new NullHandler (this); + } + + public ErrorListener getErrorListener () + { + return errListener; + } + + public void setErrorListener (ErrorListener e) + { + errListener = e; + } + + public URIResolver getURIResolver () + { + return uriResolver; + } + + public void setURIResolver (URIResolver u) + { + uriResolver = u; + } + + public void setOutputProperties (Properties p) + { + props = (Properties) p.clone (); + } + + public Properties getOutputProperties () + { + return (Properties) props.clone (); + } + + public void setOutputProperty (String name, String value) + { + props.setProperty (name, value); + } + + public String getOutputProperty (String name) + { + return props.getProperty (name); + } + + public void clearParameters () + { + params.clear (); + } + + public void setParameter (String name, Object value) + { + props.put (name, value); + } + + public Object getParameter (String name) + { + return props.get (name); + } + + public void transform (Source in, Result out) + throws TransformerException + { + try + { + XMLReader producer; + InputSource input; + + // Input from DOM? + if (in instanceof DOMSource) + { + DOMSource source = (DOMSource) in; + + if (source.getNode () == null) + throw new IllegalArgumentException ("no DOM node"); + producer = new DomParser (source.getNode ()); + input = null; + + // Input from SAX? + } + else if (in instanceof SAXSource) + { + SAXSource source = (SAXSource) in; + + producer = source.getXMLReader (); + if (producer == null) + producer = XMLReaderFactory.createXMLReader (); + + input = source.getInputSource (); + if (input == null) + { + if (source.getSystemId () != null) + input = new InputSource (source.getSystemId ()); + else + throw new IllegalArgumentException ( + "missing SAX input"); + } + + // Input from a stream or something? + } + else + { + producer = XMLReaderFactory.createXMLReader (); + input = SAXSource.sourceToInputSource (in); + if (input == null) + throw new IllegalArgumentException ("missing input"); + } + + // preserve original namespace prefixes + try + { + producer.setFeature(handler.FEATURE_URI + "namespace-prefixes", + true); + } + catch (Exception e) + { + /* ignore */ + // FIXME if we couldn't, "NsFix" stage before the output .. + } + + // arrange the output + handler.setResult (out); + handler.bind (producer, handler); + + // then parse ... single element pipeline + producer.parse (input); + + } + catch (IOException e) + { + throw new TransformerException ("transform failed", e); + + } + catch (SAXException e) + { + if (ex == null && ex.getCause () == e) + throw ex; + else + throw new TransformerException ("transform failed", e); + + } + finally + { + ex = null; + } + } + } + +} diff --git a/libjava/gnu/xml/util/XCat.java b/libjava/gnu/xml/util/XCat.java new file mode 100644 index 0000000..269c97b --- /dev/null +++ b/libjava/gnu/xml/util/XCat.java @@ -0,0 +1,1609 @@ +/* XCat.java -- + Copyright (C) 2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.xml.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URL; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.StringTokenizer; +import java.util.Stack; +import java.util.Vector; + +import org.xml.sax.Attributes; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; + +import org.xml.sax.ext.DefaultHandler2; +import org.xml.sax.ext.EntityResolver2; + +import org.xml.sax.helpers.XMLReaderFactory; + +/** + * Packages <a href= + "http://www.oasis-open.org/committees/entity/spec-2001-08-06.html" + >OASIS XML Catalogs</a>, + * primarily for entity resolution by parsers. + * That specification defines an XML syntax for mappings between + * identifiers declared in DTDs (particularly PUBLIC identifiers) and + * locations. SAX has always supported such mappings, but conventions for + * an XML file syntax to maintain them have previously been lacking. + * + * <p> This has three main operational modes. The primary intended mode is + * to create a resolver, then preloading it with one or more site-standard + * catalogs before using it with one or more SAX parsers: <pre> + * XCat catalog = new XCat (); + * catalog.setErrorHandler (diagnosticErrorHandler); + * catalog.loadCatalog ("file:/local/catalogs/catalog.cat"); + * catalog.loadCatalog ("http://shared/catalog.cat"); + * ... + * catalog.disableLoading (); + * parser1.setEntityResolver (catalog); + * parser2.setEntityResolver (catalog); + * ...</pre> + * + * <p>A second mode is to arrange that your application uses instances of + * this class as its entity resolver, and automatically loads catalogs + * referenced by <em><?oasis-xml-catalog...?></em> processing + * instructions found before the DTD in documents it parses. + * It would then discard the resolver after each parse. + * + * <p> A third mode applies catalogs in contexts other than entity + * resolution for parsers. + * The {@link #resolveURI resolveURI()} method supports resolving URIs + * stored in XML application data, rather than inside DTDs. + * Catalogs would be loaded as shown above, and the catalog could + * be used concurrently for parser entity resolution and for + * application URI resolution. + * </p> + * + * <center><hr width='70%'></center> + * + * <p>Errors in catalogs implicitly loaded (during resolution) are ignored + * beyond being reported through any <em>ErrorHandler</em> assigned using + * {@link #setErrorHandler setErrorHandler()}. SAX exceptions + * thrown from such a handler won't abort resolution, although throwing a + * <em>RuntimeException</em> or <em>Error</em> will normally abort both + * resolution and parsing. Useful diagnostic information is available to + * any <em>ErrorHandler</em> used to report problems, or from any exception + * thrown from an explicit {@link #loadCatalog loadCatalog()} invocation. + * Applications can use that information as troubleshooting aids. + * + * <p>While this class requires <em>SAX2 Extensions 1.1</em> classes in + * its class path, basic functionality does not require using a SAX2 + * parser that supports the extended entity resolution functionality. + * See the original SAX1 + * {@link #resolveEntity(java.lang.String,java.lang.String) resolveEntity()} + * method for a list of restrictions which apply when it is used with + * older SAX parsers. + * + * @see EntityResolver2 + * + * @author David Brownell + */ +public class XCat implements EntityResolver2 +{ + private Catalog catalogs []; + private boolean usingPublic = true; + private boolean loadingPermitted = true; + private boolean unified = true; + private String parserClass; + private ErrorHandler errorHandler; + + // private EntityResolver next; // chain to next if we fail... + + // + // NOTE: This is a straightforward implementation, and if + // there are lots of "nextCatalog" or "delegate*" entries + // in use, two tweaks would be worth considering: + // + // - Centralize some sort of cache (key by URI) for individual + // resolvers. That'd avoid multiple copies of a given catalog. + // + // - Have resolution track what catalogs (+modes) have been + // searched. This would support loop detection. + // + + + /** + * Initializes without preloading a catalog. + * This API is convenient when you may want to arrange that catalogs + * are automatically loaded when explicitly referenced in documents, + * using the <em>oasis-xml-catalog</em> processing instruction. + * In such cases you won't usually be able to preload catalogs. + */ + public XCat () { } + + /** + * Initializes, and preloads a catalog using the default SAX parser. + * This API is convenient when you operate with one or more standard + * catalogs. + * + * <p> This just delegates to {@link #loadCatalog loadCatalog()}; + * see it for exception information. + * + * @param uri absolute URI for the catalog file. + */ + public XCat (String uri) + throws SAXException, IOException + { loadCatalog (uri); } + + + /** + * Loads an OASIS XML Catalog. + * It is appended to the list of currently active catalogs, or + * reloaded if a catalog with the same URI was already loaded. + * Callers have control over what parser is used, how catalog parsing + * errors are reported, and whether URIs will be resolved consistently. + * + * <p> The OASIS specification says that errors detected when loading + * catalogs "must recover by ignoring the catalog entry file that + * failed, and proceeding." In this API, that action can be the + * responsibility of applications, when they explicitly load any + * catalog using this method. + * + * <p>Note that catalogs referenced by this one will not be loaded + * at this time. Catalogs referenced through <em>nextCatalog</em> + * or <em>delegate*</em> elements are normally loaded only if needed. + * + * @see #setErrorHandler + * @see #setParserClass + * @see #setUnified + * + * @param uri absolute URI for the catalog file. + * + * @exception IOException As thrown by the parser, typically to + * indicate problems reading data from that URI. + * @exception SAXException As thrown by the parser, typically to + * indicate problems parsing data from that URI. It may also + * be thrown if the parser doesn't support necessary handlers. + * @exception IllegalStateException When attempting to load a + * catalog after loading has been {@link #disableLoading disabled}, + * such as after any entity or URI lookup has been performed. + */ + public synchronized void loadCatalog (String uri) + throws SAXException, IOException + { + Catalog catalog; + int index = -1; + + if (!loadingPermitted) + throw new IllegalStateException (); + + uri = normalizeURI (uri); + if (catalogs != null) { + // maybe just reload + for (index = 0; index < catalogs.length; index++) + if (uri.equals (catalogs [index].catalogURI)) + break; + } + catalog = loadCatalog (parserClass, errorHandler, uri, unified); + + // add to list of catalogs + if (catalogs == null) { + index = 0; + catalogs = new Catalog [1]; + } else if (index == catalogs.length) { + Catalog tmp []; + + tmp = new Catalog [index + 1]; + System.arraycopy (catalogs, 0, tmp, 0, index); + catalogs = tmp; + } + catalogs [index] = catalog; + } + + + /** + * "New Style" external entity resolution for parsers. + * Calls to this method prevent explicit loading of additional catalogs + * using {@link #loadCatalog loadCatalog()}. + * + * <p>This supports the full core catalog functionality for locating + * (and relocating) parsed entities that have been declared in a + * document's DTD. + * + * @param name Entity name, such as "dudley", "%nell", or "[dtd]". + * @param publicId Either a normalized public ID, or null. + * @param baseURI Absolute base URI associated with systemId. + * @param systemId URI found in entity declaration (may be + * relative to baseURI). + * + * @return Input source for accessing the external entity, or null + * if no mapping was found. The input source may have opened + * the stream, and will have a fully resolved URI. + * + * @see #getExternalSubset + */ + public InputSource resolveEntity ( + String name, // UNUSED ... systemId is always non-null + String publicId, + String baseURI, // UNUSED ... it just lets sysId be relative + String systemId + ) throws SAXException, IOException + { + if (loadingPermitted) + disableLoading (); + + try { + // steps as found in OASIS XML catalog spec 7.1.2 + // steps 1, 8 involve looping over the list of catalogs + for (int i = 0; i < catalogs.length; i++) { + InputSource retval; + retval = catalogs [i].resolve (usingPublic, publicId, systemId); + if (retval != null) + return retval;; + } + } catch (DoneDelegation x) { + // done! + } + // step 9 involves returning "no match" + return null; + } + + + /** + * "New Style" parser callback to add an external subset. + * For documents that don't include an external subset, this may + * return one according to <em>doctype</em> catalog entries. + * (This functionality is not a core part of the OASIS XML Catalog + * specification, though it's presented in an appendix.) + * If no such entry is defined, this returns null to indicate that + * this document will not be modified to include such a subset. + * Calls to this method prevent explicit loading of additional catalogs + * using {@link #loadCatalog loadCatalog()}. + * + * <p><em>Warning:</em> That catalog functionality can be dangerous. + * It can provide definitions of general entities, and thereby mask + * certain well formedess errors. + * + * @param name Name of the document element, either as declared in + * a DOCTYPE declaration or as observed in the text. + * @param baseURI Document's base URI (absolute). + * + * @return Input source for accessing the external subset, or null + * if no mapping was found. The input source may have opened + * the stream, and will have a fully resolved URI. + */ + public InputSource getExternalSubset (String name, String baseURI) + throws SAXException, IOException + { + if (loadingPermitted) + disableLoading (); + try { + for (int i = 0; i < catalogs.length; i++) { + InputSource retval = catalogs [i].getExternalSubset (name); + if (retval != null) + return retval; + } + } catch (DoneDelegation x) { + // done! + } + return null; + } + + + /** + * "Old Style" external entity resolution for parsers. + * This API provides only core functionality. + * Calls to this method prevent explicit loading of additional catalogs + * using {@link #loadCatalog loadCatalog()}. + * + * <p>The functional limitations of this interface include:</p><ul> + * + * <li>Since system IDs will be absolutized before the resolver + * sees them, matching against relative URIs won't work. + * This may affect <em>system</em>, <em>rewriteSystem</em>, + * and <em>delegateSystem</em> catalog entries. + * + * <li>Because of that absolutization, documents declaring entities + * with system IDs using URI schemes that the JVM does not recognize + * may be unparsable. URI schemes such as <em>file:/</em>, + * <em>http://</em>, <em>https://</em>, and <em>ftp://</em> + * will usually work reliably. + * + * <li>Because missing external subsets can't be provided, the + * <em>doctype</em> catalog entries will be ignored. + * (The {@link #getExternalSubset getExternalSubset()} method is + * a "New Style" resolution option.) + * + * </ul> + * + * <p>Applications can tell whether this limited functionality will be + * used: if the feature flag associated with the {@link EntityResolver2} + * interface is not <em>true</em>, the limitations apply. Applications + * can't usually know whether a given document and catalog will trigger + * those limitations. The issue can only be bypassed by operational + * procedures such as not using catalogs or documents which involve + * those features. + * + * @param publicId Either a normalized public ID, or null + * @param systemId Always an absolute URI. + * + * @return Input source for accessing the external entity, or null + * if no mapping was found. The input source may have opened + * the stream, and will have a fully resolved URI. + */ + final public InputSource resolveEntity (String publicId, String systemId) + throws SAXException, IOException + { + return resolveEntity (null, publicId, null, systemId); + } + + + /** + * Resolves a URI reference that's not defined to the DTD. + * This is intended for use with URIs found in document text, such as + * <em>xml-stylesheet</em> processing instructions and in attribute + * values, where they are not recognized as URIs by XML parsers. + * Calls to this method prevent explicit loading of additional catalogs + * using {@link #loadCatalog loadCatalog()}. + * + * <p>This functionality is supported by the OASIS XML Catalog + * specification, but will never be invoked by an XML parser. + * It corresponds closely to functionality for mapping system + * identifiers for entities declared in DTDs; closely enough that + * this implementation's default behavior is that they be + * identical, to minimize potential confusion. + * + * <p>This method could be useful when implementing the + * {@link javax.xml.transform.URIResolver} interface, wrapping the + * input source in a {@link javax.xml.transform.sax.SAXSource}. + * + * @see #isUnified + * @see #setUnified + * + * @param baseURI The relevant base URI as specified by the XML Base + * specification. This recognizes <em>xml:base</em> attributes + * as overriding the actual (physical) base URI. + * @param uri Either an absolute URI, or one relative to baseURI + * + * @return Input source for accessing the mapped URI, or null + * if no mapping was found. The input source may have opened + * the stream, and will have a fully resolved URI. + */ + public InputSource resolveURI (String baseURI, String uri) + throws SAXException, IOException + { + if (loadingPermitted) + disableLoading (); + + // NOTE: baseURI isn't used here, but caller MUST have it, + // and heuristics _might_ use it in the future ... plus, + // it's symmetric with resolveEntity (). + + // steps 1, 6 involve looping + try { + for (int i = 0; i < catalogs.length; i++) { + InputSource tmp = catalogs [i].resolveURI (uri); + if (tmp != null) + return tmp; + } + } catch (DoneDelegation x) { + // done + } + // step 7 reports no match + return null; + } + + + /** + * Records that catalog loading is no longer permitted. + * Loading is automatically disabled when lookups are performed, + * and should be manually disabled when <em>startDTD()</em> (or + * any other DTD declaration callback) is invoked, or at the latest + * when the document root element is seen. + */ + public synchronized void disableLoading () + { + // NOTE: this method and loadCatalog() are synchronized + // so that it's impossible to load (top level) catalogs + // after lookups start. Likewise, deferred loading is also + // synchronized (for "next" and delegated catalogs) to + // ensure that parsers can share resolvers. + loadingPermitted = false; + } + + + /** + * Returns the error handler used to report catalog errors. + * Null is returned if the parser's default error handling + * will be used. + * + * @see #setErrorHandler + */ + public ErrorHandler getErrorHandler () + { return errorHandler; } + + /** + * Assigns the error handler used to report catalog errors. + * These errors may come either from the SAX2 parser or + * from the catalog parsing code driven by the parser. + * + * <p> If you're sharing the resolver between parsers, don't + * change this once lookups have begun. + * + * @see #getErrorHandler + * + * @param parser The error handler, or null saying to use the default + * (no diagnostics, and only fatal errors terminate loading). + */ + public void setErrorHandler (ErrorHandler handler) + { errorHandler = handler; } + + + /** + * Returns the name of the SAX2 parser class used to parse catalogs. + * Null is returned if the system default is used. + * @see #setParserClass + */ + public String getParserClass () + { return parserClass; } + + /** + * Names the SAX2 parser class used to parse catalogs. + * + * <p> If you're sharing the resolver between parsers, don't change + * this once lookups have begun. + * + * <p> Note that in order to properly support the <em>xml:base</em> + * attribute and relative URI resolution, the SAX parser used to parse + * the catalog must provide a {@link Locator} and support the optional + * declaration and lexical handlers. + * + * @see #getParserClass + * + * @param parser The parser class name, or null saying to use the + * system default SAX2 parser. + */ + public void setParserClass (String parser) + { parserClass = parser; } + + + /** + * Returns true (the default) if all methods resolve + * a given URI in the same way. + * Returns false if calls resolving URIs as entities (such as + * {@link #resolveEntity resolveEntity()}) use different catalog entries + * than those resolving them as URIs ({@link #resolveURI resolveURI()}), + * which will generally produce different results. + * + * <p>The OASIS XML Catalog specification defines two related schemes + * to map URIs "as URIs" or "as system IDs". + * URIs use <em>uri</em>, <em>rewriteURI</em>, and <em>delegateURI</em> + * elements. System IDs do the same things with <em>systemId</em>, + * <em>rewriteSystemId</em>, and <em>delegateSystemId</em>. + * It's confusing and error prone to maintain two parallel copies of + * such data. Accordingly, this class makes that behavior optional. + * The <em>unified</em> interpretation of URI mappings is preferred, + * since it prevents surprises where one URI gets mapped to different + * contents depending on whether the reference happens to have come + * from a DTD (or not). + * + * @see #setUnified + */ + public boolean isUnified () + { return unified; } + + /** + * Assigns the value of the flag returned by {@link #isUnified}. + * Set it to false to be strictly conformant with the OASIS XML Catalog + * specification. Set it to true to make all mappings for a given URI + * give the same result, regardless of the reason for the mapping. + * + * <p>Don't change this once you've loaded the first catalog. + * + * @param value new flag setting + */ + public void setUnified (boolean value) + { unified = value; } + + + /** + * Returns true (the default) if a catalog's public identifier + * mappings will be used. + * When false is returned, such mappings are ignored except when + * system IDs are discarded, such as for + * entities using the <em>urn:publicid:</em> URI scheme in their + * system identifiers. (See RFC 3151 for information about that + * URI scheme. Using it in system identifiers may not work well + * with many SAX parsers unless the <em>resolve-dtd-uris</em> + * feature flag is set to false.) + * @see #setUsingPublic + */ + public boolean isUsingPublic () + { return usingPublic; } + + /** + * Specifies which catalog search mode is used. + * By default, public identifier mappings are able to override system + * identifiers when both are available. + * Applications may choose to ignore public + * identifier mappings in such cases, so that system identifiers + * declared in DTDs will only be overridden by an explicit catalog + * match for that system ID. + * + * <p> If you're sharing the resolver between parsers, don't + * change this once lookups have begun. + * @see #isUsingPublic + * + * @param value true to always use public identifier mappings, + * false to only use them for system ids using the <em>urn:publicid:</em> + * URI scheme. + */ + public void setUsingPublic (boolean value) + { usingPublic = value; } + + + + // hmm, what's this do? :) + private static Catalog loadCatalog ( + String parserClass, + ErrorHandler eh, + String uri, + boolean unified + ) throws SAXException, IOException + { + XMLReader parser; + Loader loader; + boolean doesIntern = false; + + if (parserClass == null) + parser = XMLReaderFactory.createXMLReader (); + else + parser = XMLReaderFactory.createXMLReader (parserClass); + if (eh != null) + parser.setErrorHandler (eh); + // resolve-dtd-entities is at default value (unrecognized == true) + + try { + doesIntern = parser.getFeature ( + "http://xml.org/sax/features/string-interning"); + } catch (SAXNotRecognizedException e) { } + + loader = new Loader (doesIntern, eh, unified); + loader.cat.parserClass = parserClass; + loader.cat.catalogURI = uri; + + parser.setContentHandler (loader); + parser.setProperty ( + "http://xml.org/sax/properties/declaration-handler", + loader); + parser.setProperty ( + "http://xml.org/sax/properties/lexical-handler", + loader); + parser.parse (uri); + + return loader.cat; + } + + // perform one or both the normalizations for public ids + private static String normalizePublicId (boolean full, String publicId) + { + if (publicId.startsWith ("urn:publicid:")) { + StringBuffer buf = new StringBuffer (); + char chars [] = publicId.toCharArray (); +boolean hasbug = false; + + for (int i = 13; i < chars.length; i++) { + switch (chars [i]) { + case '+': buf.append (' '); continue; + case ':': buf.append ("//"); continue; + case ';': buf.append ("::"); continue; + case '%': +// FIXME unhex that char! meanwhile, warn and fallthrough ... + hasbug = true; + default: buf.append (chars [i]); continue; + } + } + publicId = buf.toString (); +if (hasbug) +System.err.println ("nyet unhexing public id: " + publicId); + full = true; + } + + // SAX parsers do everything except that URN mapping, but + // we can't trust other sources to normalize correctly + if (full) { + StringTokenizer tokens; + String token; + + tokens = new StringTokenizer (publicId, " \r\n"); + publicId = null; + while (tokens.hasMoreTokens ()) { + if (publicId == null) + publicId = tokens.nextToken (); + else + publicId += " " + tokens.nextToken (); + } + } + return publicId; + } + + private static boolean isUriExcluded (int c) + { return c <= 0x20 || c >= 0x7f || "\"<>^`{|}".indexOf (c) != -1; } + + private static int hexNibble (int c) + { + if (c < 10) + return c + '0'; + return ('a' - 10) + c; + } + + // handles URIs with "excluded" characters + private static String normalizeURI (String systemId) + { + int length = systemId.length (); + + for (int i = 0; i < length; i++) { + char c = systemId.charAt (i); + + // escape non-ASCII plus "excluded" characters + if (isUriExcluded (c)) { + byte buf []; + ByteArrayOutputStream out; + int b; + + // a JVM that doesn't know UTF8 and 8859_1 is unusable! + try { + buf = systemId.getBytes ("UTF8"); + out = new ByteArrayOutputStream (buf.length + 10); + + for (i = 0; i < buf.length; i++) { + b = buf [i] & 0x0ff; + if (isUriExcluded (b)) { + out.write ((int) '%'); + out.write (hexNibble (b >> 4)); + out.write (hexNibble (b & 0x0f)); + } else + out.write (b); + } + return out.toString ("8859_1"); + } catch (IOException e) { + throw new RuntimeException ( + "can't normalize URI: " + e.getMessage ()); + } + } + } + return systemId; + } + + // thrown to mark authoritative end of a search + private static class DoneDelegation extends SAXException + { + DoneDelegation () { } + } + + + /** + * Represents a OASIS XML Catalog, and encapsulates much of + * the catalog functionality. + */ + private static class Catalog + { + // loading infrastructure + String catalogURI; + ErrorHandler eh; + boolean unified; + String parserClass; + + // catalog data + boolean hasPreference; + boolean usingPublic; + + Hashtable publicIds; + Hashtable publicDelegations; + + Hashtable systemIds; + Hashtable systemRewrites; + Hashtable systemDelegations; + + Hashtable uris; + Hashtable uriRewrites; + Hashtable uriDelegations; + + Hashtable doctypes; + + Vector next; + + // nonpublic! + Catalog () { } + + + // steps as found in OASIS XML catalog spec 7.1.2 + private InputSource locatePublicId (String publicId) + throws SAXException, IOException + { + // 5. return (first) 'public' entry + if (publicIds != null) { + String retval = (String) publicIds.get (publicId); + if (retval != null) { + // IF the URI is accessible ... + return new InputSource (retval); + } + } + + // 6. return delegatePublic catalog match [complex] + if (publicDelegations != null) + return checkDelegations (publicDelegations, publicId, + publicId, null); + + return null; + } + + // steps as found in OASIS XML catalog spec 7.1.2 or 7.2.2 + private InputSource mapURI ( + String uri, + Hashtable ids, + Hashtable rewrites, + Hashtable delegations + ) throws SAXException, IOException + { + // 7.1.2: 2. return (first) 'system' entry + // 7.2.2: 2. return (first) 'uri' entry + if (ids != null) { + String retval = (String) ids.get (uri); + if (retval != null) { + // IF the URI is accessible ... + return new InputSource (retval); + } + } + + // 7.1.2: 3. return 'rewriteSystem' entries + // 7.2.2: 3. return 'rewriteURI' entries + if (rewrites != null) { + String prefix = null; + String replace = null; + int prefixLen = -1; + + for (Enumeration e = rewrites.keys (); + e.hasMoreElements (); + /* NOP */) { + String temp = (String) e.nextElement (); + int len = -1; + + if (!uri.startsWith (temp)) + continue; + if (prefix != null + && (len = temp.length ()) < prefixLen) + continue; + prefix = temp; + prefixLen = len; + replace = (String) rewrites.get (temp); + } + if (prefix != null) { + StringBuffer buf = new StringBuffer (replace); + buf.append (uri.substring (prefixLen)); + // IF the URI is accessible ... + return new InputSource (buf.toString ()); + } + } + + // 7.1.2: 4. return 'delegateSystem' catalog match [complex] + // 7.2.2: 4. return 'delegateURI' catalog match [complex] + if (delegations != null) + return checkDelegations (delegations, uri, null, uri); + + return null; + } + + + /** + * Returns a URI for an external entity. + */ + public InputSource resolve ( + boolean usingPublic, + String publicId, + String systemId + ) throws SAXException, IOException + { + boolean preferSystem; + InputSource retval; + + if (hasPreference) + preferSystem = !this.usingPublic; + else + preferSystem = !usingPublic; + + if (publicId != null) + publicId = normalizePublicId (false, publicId); + + // behavior here matches section 7.1.1 of the oasis spec + if (systemId != null) { + if (systemId.startsWith ("urn:publicid:")) { + String temp = normalizePublicId (true, systemId); + if (publicId == null) { + publicId = temp; + systemId = null; + } else if (!publicId.equals (temp)) { + // error; ok to recover by: + systemId = null; + } + } else + systemId = normalizeURI (systemId); + } + + if (systemId == null && publicId == null) + return null; + + if (systemId != null) { + retval = mapURI (systemId, systemIds, systemRewrites, + systemDelegations); + if (retval != null) { + retval.setPublicId (publicId); + return retval; + } + } + + if (publicId != null + && !(systemId != null && preferSystem)) { + retval = locatePublicId (publicId); + if (retval != null) { + retval.setPublicId (publicId); + return retval; + } + } + + // 7. apply nextCatalog entries + if (next != null) { + int length = next.size (); + for (int i = 0; i < length; i++) { + Catalog n = getNext (i); + retval = n.resolve (usingPublic, publicId, systemId); + if (retval != null) + return retval; + } + } + + return null; + } + + /** + * Maps one URI into another, for resources that are not defined + * using XML external entity or notation syntax. + */ + public InputSource resolveURI (String uri) + throws SAXException, IOException + { + if (uri.startsWith ("urn:publicid:")) + return resolve (true, normalizePublicId (true, uri), null); + + InputSource retval; + + uri = normalizeURI (uri); + + // 7.2.2 steps 2-4 + retval = mapURI (uri, uris, uriRewrites, uriDelegations); + if (retval != null) + return retval; + + // 7.2.2 step 5. apply nextCatalog entries + if (next != null) { + int length = next.size (); + for (int i = 0; i < length; i++) { + Catalog n = getNext (i); + retval = n.resolveURI (uri); + if (retval != null) + return retval; + } + } + + return null; + } + + + /** + * Finds the external subset associated with a given root element. + */ + public InputSource getExternalSubset (String name) + throws SAXException, IOException + { + if (doctypes != null) { + String value = (String) doctypes.get (name); + if (value != null) { + // IF the URI is accessible ... + return new InputSource (value); + } + } + if (next != null) { + int length = next.size (); + for (int i = 0; i < length; i++) { + Catalog n = getNext (i); + if (n == null) + continue; + InputSource retval = n.getExternalSubset (name); + if (retval != null) + return retval; + } + } + return null; + } + + private synchronized Catalog getNext (int i) + throws SAXException, IOException + { + Object obj; + + if (next == null || i < 0 || i >= next.size ()) + return null; + obj = next.elementAt (i); + if (obj instanceof Catalog) + return (Catalog) obj; + + // ok, we deferred reading that catalog till now. + // load and cache it. + Catalog cat = null; + + try { + cat = loadCatalog (parserClass, eh, (String) obj, unified); + next.setElementAt (cat, i); + } catch (SAXException e) { + // must fail quietly, says the OASIS spec + } catch (IOException e) { + // same applies here + } + return cat; + } + + private InputSource checkDelegations ( + Hashtable delegations, + String id, + String publicId, // only one of public/system + String systemId // will be non-null... + ) throws SAXException, IOException + { + Vector matches = null; + int length = 0; + + // first, see if any prefixes match. + for (Enumeration e = delegations.keys (); + e.hasMoreElements (); + /* NOP */) { + String prefix = (String) e.nextElement (); + + if (!id.startsWith (prefix)) + continue; + if (matches == null) + matches = new Vector (); + + // maintain in longer->shorter sorted order + // NOTE: assumes not many matches will fire! + int index; + + for (index = 0; index < length; index++) { + String temp = (String) matches.elementAt (index); + if (prefix.length () > temp.length ()) { + matches.insertElementAt (prefix, index); + break; + } + } + if (index == length) + matches.addElement (prefix); + length++; + } + if (matches == null) + return null; + + // now we know the list of catalogs to replace our "top level" + // list ... we use it here, rather than somehow going back and + // restarting, since this helps avoid reading most catalogs. + // this assumes stackspace won't be a problem. + for (int i = 0; i < length; i++) { + Catalog catalog = null; + InputSource result; + + // get this catalog. we may not have read it yet. + synchronized (delegations) { + Object prefix = matches.elementAt (i); + Object cat = delegations.get (prefix); + + if (cat instanceof Catalog) + catalog = (Catalog) cat; + else { + try { + // load and cache that catalog + catalog = loadCatalog (parserClass, eh, + (String) cat, unified); + delegations.put (prefix, catalog); + } catch (SAXException e) { + // must ignore, says the OASIS spec + } catch (IOException e) { + // same applies here + } + } + } + + // ignore failed loads, and proceed + if (catalog == null) + continue; + + // we have a catalog ... resolve! + // usingPublic value can't matter, there's no choice + result = catalog.resolve (true, publicId, systemId); + if (result != null) + return result; + } + + // if there were no successes, the entire + // lookup failed (all the way to top level) + throw new DoneDelegation (); + } + } + + + /** This is the namespace URI used for OASIS XML Catalogs. */ + private static final String catalogNamespace = + "urn:oasis:names:tc:entity:xmlns:xml:catalog"; + + + /** + * Loads/unmarshals one catalog. + */ + private static class Loader extends DefaultHandler2 + { + private boolean preInterned; + private ErrorHandler handler; + private boolean unified; + private int ignoreDepth; + private Locator locator; + private boolean started; + private Hashtable externals; + private Stack bases; + + Catalog cat = new Catalog (); + + + /** + * Constructor. + * @param flag true iff the parser already interns strings. + * @param eh Errors and warnings are delegated to this. + * @param unified true keeps one table for URI mappings; + * false matches OASIS spec, storing mappings + * for URIs and SYSTEM ids in parallel tables. + */ + Loader (boolean flag, ErrorHandler eh, boolean unified) + { + preInterned = flag; + handler = eh; + this.unified = unified; + cat.unified = unified; + cat.eh = eh; + } + + + // strips out fragments + private String nofrag (String uri) + throws SAXException + { + if (uri.indexOf ('#') != -1) { + warn ("URI with fragment: " + uri); + uri = uri.substring (0, uri.indexOf ('#')); + } + return uri; + } + + // absolutizes relative URIs + private String absolutize (String uri) + throws SAXException + { + // avoid creating URLs if they're already absolutized, + // or if the URI is already using a known scheme + if (uri.startsWith ("file:/") + || uri.startsWith ("http:/") + || uri.startsWith ("https:/") + || uri.startsWith ("ftp:/") + || uri.startsWith ("urn:") + ) + return uri; + + // otherwise, let's hope the JDK handles this URI scheme. + try { + URL base = (URL) bases.peek (); + return new URL (base, uri).toString (); + } catch (Exception e) { + fatal ("can't absolutize URI: " + uri); + return null; + } + } + + // recoverable error + private void error (String message) + throws SAXException + { + if (handler == null) + return; + handler.error (new SAXParseException (message, locator)); + } + + // nonrecoverable error + private void fatal (String message) + throws SAXException + { + SAXParseException spe; + + spe = new SAXParseException (message, locator); + if (handler != null) + handler.fatalError (spe); + throw spe; + } + + // low severity problem + private void warn (String message) + throws SAXException + { + if (handler == null) + return; + handler.warning (new SAXParseException (message, locator)); + } + + // callbacks: + + public void setDocumentLocator (Locator l) + { locator = l; } + + public void startDocument () + throws SAXException + { + if (locator == null) + error ("no locator!"); + bases = new Stack (); + String uri = locator.getSystemId (); + try { + bases.push (new URL (uri)); + } catch (IOException e) { + fatal ("bad document base URI: " + uri); + } + } + + public void endDocument () + throws SAXException + { + try { + if (!started) + error ("not a catalog!"); + } finally { + locator = null; + handler = null; + externals = null; + bases = null; + } + } + + // XML Base support for external entities. + + // NOTE: expects parser is in default "resolve-dtd-uris" mode. + public void externalEntityDecl (String name, String pub, String sys) + throws SAXException + { + if (externals == null) + externals = new Hashtable (); + if (externals.get (name) == null) + externals.put (name, pub); + } + + public void startEntity (String name) + throws SAXException + { + if (externals == null) + return; + String uri = (String) externals.get (name); + + // NOTE: breaks if an EntityResolver substitutes these URIs. + // If toplevel loader supports one, must intercept calls... + if (uri != null) { + try { + bases.push (new URL (uri)); + } catch (IOException e) { + fatal ("entity '" + name + "', bad URI: " + uri); + } + } + } + + public void endEntity (String name) + { + if (externals == null) + return; + String value = (String) externals.get (name); + + if (value != null) + bases.pop (); + } + + /** + * Processes catalog elements, saving their data. + */ + public void startElement (String namespace, String local, + String qName, Attributes atts) + throws SAXException + { + // must ignore non-catalog elements, and their contents + if (ignoreDepth != 0 || !catalogNamespace.equals (namespace)) { + ignoreDepth++; + return; + } + + // basic sanity checks + if (!preInterned) + local = local.intern (); + if (!started) { + started = true; + if ("catalog" != local) + fatal ("root element not 'catalog': " + local); + } + + // Handle any xml:base attribute + String xmlbase = atts.getValue ("xml:base"); + + if (xmlbase != null) { + URL base = (URL) bases.peek (); + try { + base = new URL (base, xmlbase); + } catch (IOException e) { + fatal ("can't resolve xml:base attribute: " + xmlbase); + } + bases.push (base); + } else + bases.push (bases.peek ()); + + // fetch multi-element attributes, apply standard tweaks + // values (uri, catalog, rewritePrefix) get normalized too, + // as a precaution and since we may compare the values + String catalog = atts.getValue ("catalog"); + if (catalog != null) + catalog = normalizeURI (absolutize (catalog)); + + String rewritePrefix = atts.getValue ("rewritePrefix"); + if (rewritePrefix != null) + rewritePrefix = normalizeURI (absolutize (rewritePrefix)); + + String systemIdStartString; + systemIdStartString = atts.getValue ("systemIdStartString"); + if (systemIdStartString != null) { + systemIdStartString = normalizeURI (systemIdStartString); + // unmatchable <rewriteSystemId>, <delegateSystemId> elements + if (systemIdStartString.startsWith ("urn:publicid:")) { + error ("systemIdStartString is really a publicId!!"); + return; + } + } + + String uri = atts.getValue ("uri"); + if (uri != null) + uri = normalizeURI (absolutize (uri)); + + String uriStartString; + uriStartString = atts.getValue ("uriStartString"); + if (uriStartString != null) { + uriStartString = normalizeURI (uriStartString); + // unmatchable <rewriteURI>, <delegateURI> elements + if (uriStartString.startsWith ("urn:publicid:")) { + error ("uriStartString is really a publicId!!"); + return; + } + } + + // strictly speaking "group" and "catalog" shouldn't nest + // ... arbitrary restriction, no evident motivation + +// FIXME stack "prefer" settings (two elements only!) and use +// them to populate different public mapping/delegation tables + + if ("catalog" == local || "group" == local) { + String prefer = atts.getValue ("prefer"); + + if (prefer != null && !"public".equals (prefer)) { + if (!"system".equals (prefer)) { + error ("in <" + local + " ... prefer='...'>, " + + "assuming 'public'"); + prefer = "public"; + } + } + if (prefer != null) { + if ("catalog" == local) { + cat.hasPreference = true; + cat.usingPublic = "public".equals (prefer); + } else { + if (!cat.hasPreference || cat.usingPublic + != "public".equals (prefer)) { +fatal ("<group prefer=...> case not handled"); + } + } + } else if ("group" == local && cat.hasPreference) { +fatal ("<group prefer=...> case not handled"); + } + + // + // PUBLIC ids: cleanly set up for id substitution + // + } else if ("public" == local) { + String publicId = atts.getValue ("publicId"); + String value = null; + + if (publicId == null || uri == null) { + error ("expecting <public publicId=... uri=.../>"); + return; + } + publicId = normalizePublicId (true, publicId); + uri = nofrag (uri); + if (cat.publicIds == null) + cat.publicIds = new Hashtable (); + else + value = (String) cat.publicIds.get (publicId); + if (value != null) { + if (!value.equals (uri)) + warn ("ignoring <public...> entry for " + publicId); + } else + cat.publicIds.put (publicId, uri); + + } else if ("delegatePublic" == local) { + String publicIdStartString; + Object value = null; + + publicIdStartString = atts.getValue ("publicIdStartString"); + if (publicIdStartString == null || catalog == null) { + error ("expecting <delegatePublic " + + "publicIdStartString=... catalog=.../>"); + return; + } + publicIdStartString = normalizePublicId (true, + publicIdStartString); + if (cat.publicDelegations == null) + cat.publicDelegations = new Hashtable (); + else + value = cat.publicDelegations.get (publicIdStartString); + if (value != null) { + if (!value.equals (catalog)) + warn ("ignoring <delegatePublic...> entry for " + + uriStartString); + } else + cat.publicDelegations.put (publicIdStartString, catalog); + + + // + // SYSTEM ids: need substitution due to operational issues + // + } else if ("system" == local) { + String systemId = atts.getValue ("systemId"); + String value = null; + + if (systemId == null || uri == null) { + error ("expecting <system systemId=... uri=.../>"); + return; + } + systemId = normalizeURI (systemId); + uri = nofrag (uri); + if (systemId.startsWith ("urn:publicid:")) { + error ("systemId is really a publicId!!"); + return; + } + if (cat.systemIds == null) { + cat.systemIds = new Hashtable (); + if (unified) + cat.uris = cat.systemIds; + } else + value = (String) cat.systemIds.get (systemId); + if (value != null) { + if (!value.equals (uri)) + warn ("ignoring <system...> entry for " + systemId); + } else + cat.systemIds.put (systemId, uri); + + } else if ("rewriteSystem" == local) { + String value = null; + + if (systemIdStartString == null || rewritePrefix == null + || systemIdStartString.length () == 0 + || rewritePrefix.length () == 0 + ) { + error ("expecting <rewriteSystem " + + "systemIdStartString=... rewritePrefix=.../>"); + return; + } + if (cat.systemRewrites == null) { + cat.systemRewrites = new Hashtable (); + if (unified) + cat.uriRewrites = cat.systemRewrites; + } else + value = (String) cat.systemRewrites.get ( + systemIdStartString); + if (value != null) { + if (!value.equals (rewritePrefix)) + warn ("ignoring <rewriteSystem...> entry for " + + systemIdStartString); + } else + cat.systemRewrites.put (systemIdStartString, + rewritePrefix); + + } else if ("delegateSystem" == local) { + Object value = null; + + if (systemIdStartString == null || catalog == null) { + error ("expecting <delegateSystem " + + "systemIdStartString=... catalog=.../>"); + return; + } + if (cat.systemDelegations == null) { + cat.systemDelegations = new Hashtable (); + if (unified) + cat.uriDelegations = cat.systemDelegations; + } else + value = cat.systemDelegations.get (systemIdStartString); + if (value != null) { + if (!value.equals (catalog)) + warn ("ignoring <delegateSystem...> entry for " + + uriStartString); + } else + cat.systemDelegations.put (systemIdStartString, catalog); + + + // + // URI: just like "system" ID support, except that + // fragment IDs are disallowed in "system" elements. + // + } else if ("uri" == local) { + String name = atts.getValue ("name"); + String value = null; + + if (name == null || uri == null) { + error ("expecting <uri name=... uri=.../>"); + return; + } + if (name.startsWith ("urn:publicid:")) { + error ("name is really a publicId!!"); + return; + } + name = normalizeURI (name); + if (cat.uris == null) { + cat.uris = new Hashtable (); + if (unified) + cat.systemIds = cat.uris; + } else + value = (String) cat.uris.get (name); + if (value != null) { + if (!value.equals (uri)) + warn ("ignoring <uri...> entry for " + name); + } else + cat.uris.put (name, uri); + + } else if ("rewriteURI" == local) { + String value = null; + + if (uriStartString == null || rewritePrefix == null + || uriStartString.length () == 0 + || rewritePrefix.length () == 0 + ) { + error ("expecting <rewriteURI " + + "uriStartString=... rewritePrefix=.../>"); + return; + } + if (cat.uriRewrites == null) { + cat.uriRewrites = new Hashtable (); + if (unified) + cat.systemRewrites = cat.uriRewrites; + } else + value = (String) cat.uriRewrites.get (uriStartString); + if (value != null) { + if (!value.equals (rewritePrefix)) + warn ("ignoring <rewriteURI...> entry for " + + uriStartString); + } else + cat.uriRewrites.put (uriStartString, rewritePrefix); + + } else if ("delegateURI" == local) { + Object value = null; + + if (uriStartString == null || catalog == null) { + error ("expecting <delegateURI " + + "uriStartString=... catalog=.../>"); + return; + } + if (cat.uriDelegations == null) { + cat.uriDelegations = new Hashtable (); + if (unified) + cat.systemDelegations = cat.uriDelegations; + } else + value = cat.uriDelegations.get (uriStartString); + if (value != null) { + if (!value.equals (catalog)) + warn ("ignoring <delegateURI...> entry for " + + uriStartString); + } else + cat.uriDelegations.put (uriStartString, catalog); + + // + // NON-DELEGATING approach to modularity + // + } else if ("nextCatalog" == local) { + if (catalog == null) { + error ("expecting <nextCatalog catalog=.../>"); + return; + } + if (cat.next == null) + cat.next = new Vector (); + cat.next.addElement (catalog); + + // + // EXTENSIONS from appendix E + // + } else if ("doctype" == local) { + String name = atts.getValue ("name"); + String value = null; + + if (name == null || uri == null) { + error ("expecting <doctype name=... uri=.../>"); + return; + } + name = normalizeURI (name); + if (cat.doctypes == null) + cat.doctypes = new Hashtable (); + else + value = (String) cat.doctypes.get (name); + if (value != null) { + if (!value.equals (uri)) + warn ("ignoring <doctype...> entry for " + + uriStartString); + } else + cat.doctypes.put (name, uri); + + + // + // RESERVED ... ignore (like reserved attributes) but warn + // + } else { + warn ("ignoring unknown catalog element: " + local); + ignoreDepth++; + } + } + + public void endElement (String uri, String local, String qName) + throws SAXException + { + if (ignoreDepth != 0) + ignoreDepth--; + else + bases.pop (); + } + } +} diff --git a/libjava/gnu/xml/util/XHTMLWriter.java b/libjava/gnu/xml/util/XHTMLWriter.java new file mode 100644 index 0000000..c1502b7 --- /dev/null +++ b/libjava/gnu/xml/util/XHTMLWriter.java @@ -0,0 +1,112 @@ +/* XHTMLWriter.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.util; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; + + +/** + * This extends XMLWriter to create a class which defaults to writing + * XHTML text, preferring the US-ASCII encoding. It adds no unique + * functionality, only changing the defaults slightly to simplify writing + * XHTML processing components by providing a bean class whose properties + * have more convenient defaults. An artifact of using the US-ASCII + * encoding is that no XML declaration is written, so that HTML tools + * that can't accept them will not become confused. Components can treat + * the output as UTF-8, ISO-8859-1, or US-ASCII without incurring any + * data loss. + * + * @author David Brownell + */ +public class XHTMLWriter extends XMLWriter +{ + /** + * Constructs this handler with System.out used to write + * SAX events using the US-ASCII encoding, as XHTML. + */ + public XHTMLWriter () + throws IOException + { + this (System.out); + } + + /** + * Constructs this handler such that the specified output stream + * is used to write SAX events in the US-ASCII encoding, as XHTML. + * + * @param out Where US-ASCII encoding of the stream of SAX + * events will be sent. + */ + public XHTMLWriter (OutputStream out) + throws IOException + { + // not all JVMs understand "ASCII" as an encoding name, so + // we use 8859_1 (they all seem to handle that one) and + // make the echo handler filter out non-ASCII characters + this (new OutputStreamWriter (out, "8859_1"), "US-ASCII"); + } + + /** + * Constructs this handler such that the specified output stream + * is used to write SAX events as XHTML. + * + * @param out Where the stream of SAX events will be written. + */ + public XHTMLWriter (Writer out) + { + this (out, null); + } + + /** + * Constructs this handler such that the specified output stream + * is used to write SAX events as XHTML, labeled with the specified + * encoding. + * + * @param out Where the stream of SAX events will be written. + * @param encoding If non-null, this names the encoding to be + * placed in the encoding declaration. + */ + public XHTMLWriter (Writer out, String encoding) + { + super (out, encoding); + setXhtml (true); + } +} diff --git a/libjava/gnu/xml/util/XMLWriter.java b/libjava/gnu/xml/util/XMLWriter.java new file mode 100644 index 0000000..feb9e4f --- /dev/null +++ b/libjava/gnu/xml/util/XMLWriter.java @@ -0,0 +1,1927 @@ +/* XMLWriter.java -- + Copyright (C) 1999,2000,2001 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.util; + +import java.io.BufferedWriter; +import java.io.CharConversionException; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.Stack; + +import org.xml.sax.*; +import org.xml.sax.ext.*; +import org.xml.sax.helpers.*; + + +/** + * This class is a SAX handler which writes all its input as a well formed + * XML or XHTML document. If driven using SAX2 events, this output may + * include a recreated document type declaration, subject to limitations + * of SAX (no internal subset exposed) or DOM (the important declarations, + * with their documentation, are discarded). + * + * <p> By default, text is generated "as-is", but some optional modes + * are supported. Pretty-printing is supported, to make life easier + * for people reading the output. XHTML (1.0) output has can be made + * particularly pretty; all the built-in character entities are known. + * Canonical XML can also be generated, assuming the input is properly + * formed. + * + * <hr> + * + * <p> Some of the methods on this class are intended for applications to + * use directly, rather than as pure SAX2 event callbacks. Some of those + * methods access the JavaBeans properties (used to tweak output formats, + * for example canonicalization and pretty printing). Subclasses + * are expected to add new behaviors, not to modify current behavior, so + * many such methods are final.</p> + * + * <p> The <em>write*()</em> methods may be slightly simpler for some + * applications to use than direct callbacks. For example, they support + * a simple policy for encoding data items as the content of a single element. + * + * <p> To reuse an XMLWriter you must provide it with a new Writer, since + * this handler closes the writer it was given as part of its endDocument() + * handling. (XML documents have an end of input, and the way to encode + * that on a stream is to close it.) </p> + * + * <hr> + * + * <p> Note that any relative URIs in the source document, as found in + * entity and notation declarations, ought to have been fully resolved by + * the parser providing events to this handler. This means that the + * output text should only have fully resolved URIs, which may not be + * the desired behavior in cases where later binding is desired. </p> + * + * <p> <em>Note that due to SAX2 defaults, you may need to manually + * ensure that the input events are XML-conformant with respect to namespace + * prefixes and declarations. {@link gnu.xml.pipeline.NSFilter} is + * one solution to this problem, in the context of processing pipelines.</em> + * Something as simple as connecting this handler to a parser might not + * generate the correct output. Another workaround is to ensure that the + * <em>namespace-prefixes</em> feature is always set to true, if you're + * hooking this directly up to some XMLReader implementation. + * + * @see gnu.xml.pipeline.TextConsumer + * + * @author David Brownell + */ +public class XMLWriter + implements ContentHandler, LexicalHandler, DTDHandler, DeclHandler +{ + // text prints/escapes differently depending on context + // CTX_ENTITY ... entity literal value + // CTX_ATTRIBUTE ... attribute literal value + // CTX_CONTENT ... content of an element + // CTX_UNPARSED ... CDATA, comment, PI, names, etc + // CTX_NAME ... name or nmtoken, no escapes possible + private static final int CTX_ENTITY = 1; + private static final int CTX_ATTRIBUTE = 2; + private static final int CTX_CONTENT = 3; + private static final int CTX_UNPARSED = 4; + private static final int CTX_NAME = 5; + +// FIXME: names (element, attribute, PI, notation, etc) are not +// currently written out with range checks (escapeChars). +// In non-XHTML, some names can't be directly written; panic! + + private static String sysEOL; + + static { + try { + sysEOL = System.getProperty ("line.separator", "\n"); + + // don't use the system's EOL if it's illegal XML. + if (!isLineEnd (sysEOL)) + sysEOL = "\n"; + + } catch (SecurityException e) { + sysEOL = "\n"; + } + } + + private static boolean isLineEnd (String eol) + { + return "\n".equals (eol) + || "\r".equals (eol) + || "\r\n".equals (eol); + } + + private Writer out; + private boolean inCDATA; + private int elementNestLevel; + private String eol = sysEOL; + + private short dangerMask; + private StringBuffer stringBuf; + private Locator locator; + private ErrorHandler errHandler; + + private boolean expandingEntities = false; + private int entityNestLevel; + private boolean xhtml; + private boolean startedDoctype; + private String encoding; + + private boolean canonical; + private boolean inDoctype; + private boolean inEpilogue; + + // pretty printing controls + private boolean prettyPrinting; + private int column; + private boolean noWrap; + private Stack space = new Stack (); + + // this is not a hard'n'fast rule -- longer lines are OK, + // but are to be avoided. Here, prettyprinting is more to + // show structure "cleanly" than to be precise about it. + // better to have ragged layout than one line 24Kb long. + private static final int lineLength = 75; + + + /** + * Constructs this handler with System.out used to write SAX events + * using the UTF-8 encoding. Avoid using this except when you know + * it's safe to close System.out at the end of the document. + */ + public XMLWriter () throws IOException + { this (System.out); } + + /** + * Constructs a handler which writes all input to the output stream + * in the UTF-8 encoding, and closes it when endDocument is called. + * (Yes it's annoying that this throws an exception -- but there's + * really no way around it, since it's barely possible a JDK may + * exist somewhere that doesn't know how to emit UTF-8.) + */ + public XMLWriter (OutputStream out) throws IOException + { + this (new OutputStreamWriter (out, "UTF8")); + } + + /** + * Constructs a handler which writes all input to the writer, and then + * closes the writer when the document ends. If an XML declaration is + * written onto the output, and this class can determine the name of + * the character encoding for this writer, that encoding name will be + * included in the XML declaration. + * + * <P> See the description of the constructor which takes an encoding + * name for imporant information about selection of encodings. + * + * @param writer XML text is written to this writer. + */ + public XMLWriter (Writer writer) + { + this (writer, null); + } + + /** + * Constructs a handler which writes all input to the writer, and then + * closes the writer when the document ends. If an XML declaration is + * written onto the output, this class will use the specified encoding + * name in that declaration. If no encoding name is specified, no + * encoding name will be declared unless this class can otherwise + * determine the name of the character encoding for this writer. + * + * <P> At this time, only the UTF-8 ("UTF8") and UTF-16 ("Unicode") + * output encodings are fully lossless with respect to XML data. If you + * use any other encoding you risk having your data be silently mangled + * on output, as the standard Java character encoding subsystem silently + * maps non-encodable characters to a question mark ("?") and will not + * report such errors to applications. + * + * <p> For a few other encodings the risk can be reduced. If the writer is + * a java.io.OutputStreamWriter, and uses either the ISO-8859-1 ("8859_1", + * "ISO8859_1", etc) or US-ASCII ("ASCII") encodings, content which + * can't be encoded in those encodings will be written safely. Where + * relevant, the XHTML entity names will be used; otherwise, numeric + * character references will be emitted. + * + * <P> However, there remain a number of cases where substituting such + * entity or character references is not an option. Such references are + * not usable within a DTD, comment, PI, or CDATA section. Neither may + * they be used when element, attribute, entity, or notation names have + * the problematic characters. + * + * @param writer XML text is written to this writer. + * @param encoding if non-null, and an XML declaration is written, + * this is the name that will be used for the character encoding. + */ + public XMLWriter (Writer writer, String encoding) + { + setWriter (writer, encoding); + } + + private void setEncoding (String encoding) + { + if (encoding == null && out instanceof OutputStreamWriter) + encoding = ((OutputStreamWriter)out).getEncoding (); + + if (encoding != null) { + encoding = encoding.toUpperCase (); + + // Use official encoding names where we know them, + // avoiding the Java-only names. When using common + // encodings where we can easily tell if characters + // are out of range, we'll escape out-of-range + // characters using character refs for safety. + + // I _think_ these are all the main synonyms for these! + if ("UTF8".equals (encoding)) { + encoding = "UTF-8"; + } else if ("US-ASCII".equals (encoding) + || "ASCII".equals (encoding)) { + dangerMask = (short) 0xff80; + encoding = "US-ASCII"; + } else if ("ISO-8859-1".equals (encoding) + || "8859_1".equals (encoding) + || "ISO8859_1".equals (encoding)) { + dangerMask = (short) 0xff00; + encoding = "ISO-8859-1"; + } else if ("UNICODE".equals (encoding) + || "UNICODE-BIG".equals (encoding) + || "UNICODE-LITTLE".equals (encoding)) { + encoding = "UTF-16"; + + // TODO: UTF-16BE, UTF-16LE ... no BOM; what + // release of JDK supports those Unicode names? + } + + if (dangerMask != 0) + stringBuf = new StringBuffer (); + } + + this.encoding = encoding; + } + + + /** + * Resets the handler to write a new text document. + * + * @param writer XML text is written to this writer. + * @param encoding if non-null, and an XML declaration is written, + * this is the name that will be used for the character encoding. + * + * @exception IllegalStateException if the current + * document hasn't yet ended (with {@link #endDocument}) + */ + final public void setWriter (Writer writer, String encoding) + { + if (out != null) + throw new IllegalStateException ( + "can't change stream in mid course"); + out = writer; + if (out != null) + setEncoding (encoding); + if (!(out instanceof BufferedWriter)) + out = new BufferedWriter (out); + space.push ("default"); + } + + /** + * Assigns the line ending style to be used on output. + * @param eolString null to use the system default; else + * "\n", "\r", or "\r\n". + */ + final public void setEOL (String eolString) + { + if (eolString == null) + eol = sysEOL; + else if (!isLineEnd (eolString)) + eol = eolString; + else + throw new IllegalArgumentException (eolString); + } + + /** + * Assigns the error handler to be used to present most fatal + * errors. + */ + public void setErrorHandler (ErrorHandler handler) + { + errHandler = handler; + } + + /** + * Used internally and by subclasses, this encapsulates the logic + * involved in reporting fatal errors. It uses locator information + * for good diagnostics, if available, and gives the application's + * ErrorHandler the opportunity to handle the error before throwing + * an exception. + */ + protected void fatal (String message, Exception e) + throws SAXException + { + SAXParseException x; + + if (locator == null) + x = new SAXParseException (message, null, null, -1, -1, e); + else + x = new SAXParseException (message, locator, e); + if (errHandler != null) + errHandler.fatalError (x); + throw x; + } + + + // JavaBeans properties + + /** + * Controls whether the output should attempt to follow the "transitional" + * XHTML rules so that it meets the "HTML Compatibility Guidelines" + * appendix in the XHTML specification. A "transitional" Document Type + * Declaration (DTD) is placed near the beginning of the output document, + * instead of whatever DTD would otherwise have been placed there, and + * XHTML empty elements are printed specially. When writing text in + * US-ASCII or ISO-8859-1 encodings, the predefined XHTML internal + * entity names are used (in preference to character references) when + * writing content characters which can't be expressed in those encodings. + * + * <p> When this option is enabled, it is the caller's responsibility + * to ensure that the input is otherwise valid as XHTML. Things to + * be careful of in all cases, as described in the appendix referenced + * above, include: <ul> + * + * <li> Element and attribute names must be in lower case, both + * in the document and in any CSS style sheet. + * <li> All XML constructs must be valid as defined by the XHTML + * "transitional" DTD (including all familiar constructs, + * even deprecated ones). + * <li> The root element must be "html". + * <li> Elements that must be empty (such as <em><br></em> + * must have no content. + * <li> Use both <em>lang</em> and <em>xml:lang</em> attributes + * when specifying language. + * <li> Similarly, use both <em>id</em> and <em>name</em> attributes + * when defining elements that may be referred to through + * URI fragment identifiers ... and make sure that the + * value is a legal NMTOKEN, since not all such HTML 4.0 + * identifiers are valid in XML. + * <li> Be careful with character encodings; make sure you provide + * a <em><meta http-equiv="Content-type" + * content="text/xml;charset=..." /></em> element in + * the HTML "head" element, naming the same encoding + * used to create this handler. Also, if that encoding + * is anything other than US-ASCII, make sure that if + * the document is given a MIME content type, it has + * a <em>charset=...</em> attribute with that encoding. + * </ul> + * + * <p> Additionally, some of the oldest browsers have additional + * quirks, to address with guidelines such as: <ul> + * + * <li> Processing instructions may be rendered, so avoid them. + * (Similarly for an XML declaration.) + * <li> Embedded style sheets and scripts should not contain XML + * markup delimiters: &, <, and ]]> are trouble. + * <li> Attribute values should not have line breaks or multiple + * consecutive white space characters. + * <li> Use no more than one of the deprecated (transitional) + * <em><isindex></em> elements. + * <li> Some boolean attributes (such as <em>compact, checked, + * disabled, readonly, selected,</em> and more) confuse + * some browsers, since they only understand minimized + * versions which are illegal in XML. + * </ul> + * + * <p> Also, some characteristics of the resulting output may be + * a function of whether the document is later given a MIME + * content type of <em>text/html</em> rather than one indicating + * XML (<em>application/xml</em> or <em>text/xml</em>). Worse, + * some browsers ignore MIME content types and prefer to rely URI + * name suffixes -- so an "index.xml" could always be XML, never + * XHTML, no matter its MIME type. + */ + final public void setXhtml (boolean value) + { + if (locator != null) + throw new IllegalStateException ("started parsing"); + xhtml = value; + if (xhtml) + canonical = false; + } + + /** + * Returns true if the output attempts to echo the input following + * "transitional" XHTML rules and matching the "HTML Compatibility + * Guidelines" so that an HTML version 3 browser can read the output + * as HTML; returns false (the default) othewise. + */ + final public boolean isXhtml () + { + return xhtml; + } + + /** + * Controls whether the output text contains references to + * entities (the default), or instead contains the expanded + * values of those entities. + */ + final public void setExpandingEntities (boolean value) + { + if (locator != null) + throw new IllegalStateException ("started parsing"); + expandingEntities = value; + if (!expandingEntities) + canonical = false; + } + + /** + * Returns true if the output will have no entity references; + * returns false (the default) otherwise. + */ + final public boolean isExpandingEntities () + { + return expandingEntities; + } + + /** + * Controls pretty-printing, which by default is not enabled + * (and currently is most useful for XHTML output). + * Pretty printing enables structural indentation, sorting of attributes + * by name, line wrapping, and potentially other mechanisms for making + * output more or less readable. + * + * <p> At this writing, structural indentation and line wrapping are + * enabled when pretty printing is enabled and the <em>xml:space</em> + * attribute has the value <em>default</em> (its other legal value is + * <em>preserve</em>, as defined in the XML specification). The three + * XHTML element types which use another value are recognized by their + * names (namespaces are ignored). + * + * <p> Also, for the record, the "pretty" aspect of printing here + * is more to provide basic structure on outputs that would otherwise + * risk being a single long line of text. For now, expect the + * structure to be ragged ... unless you'd like to submit a patch + * to make this be more strictly formatted! + * + * @exception IllegalStateException thrown if this method is invoked + * after output has begun. + */ + final public void setPrettyPrinting (boolean value) + { + if (locator != null) + throw new IllegalStateException ("started parsing"); + prettyPrinting = value; + if (prettyPrinting) + canonical = false; + } + + /** + * Returns value of flag controlling pretty printing. + */ + final public boolean isPrettyPrinting () + { + return prettyPrinting; + } + + + /** + * Sets the output style to be canonicalized. Input events must + * meet requirements that are slightly more stringent than the + * basic well-formedness ones, and include: <ul> + * + * <li> Namespace prefixes must not have been changed from those + * in the original document. (This may only be ensured by setting + * the SAX2 XMLReader <em>namespace-prefixes</em> feature flag; + * by default, it is cleared.) + * + * <li> Redundant namespace declaration attributes have been + * removed. (If an ancestor element defines a namespace prefix + * and that declaration hasn't been overriden, an element must + * not redeclare it.) + * + * <li> If comments are not to be included in the canonical output, + * they must first be removed from the input event stream; this + * <em>Canonical XML with comments</em> by default. + * + * <li> If the input character encoding was not UCS-based, the + * character data must have been normalized using Unicode + * Normalization Form C. (UTF-8 and UTF-16 are UCS-based.) + * + * <li> Attribute values must have been normalized, as is done + * by any conformant XML processor which processes all external + * parameter entities. + * + * <li> Similarly, attribute value defaulting has been performed. + * + * </ul> + * + * <p> Note that fragments of XML documents, as specified by an XPath + * node set, may be canonicalized. In such cases, elements may need + * some fixup (for <em>xml:*</em> attributes and application-specific + * context). + * + * @exception IllegalArgumentException if the output encoding + * is anything other than UTF-8. + */ + final public void setCanonical (boolean value) + { + if (value && !"UTF-8".equals (encoding)) + throw new IllegalArgumentException ("encoding != UTF-8"); + canonical = value; + if (canonical) { + prettyPrinting = xhtml = false; + expandingEntities = true; + eol = "\n"; + } + } + + + /** + * Returns value of flag controlling canonical output. + */ + final public boolean isCanonical () + { + return canonical; + } + + + /** + * Flushes the output stream. When this handler is used in long lived + * pipelines, it can be important to flush buffered state, for example + * so that it can reach the disk as part of a state checkpoint. + */ + final public void flush () + throws IOException + { + if (out != null) + out.flush (); + } + + + // convenience routines + +// FIXME: probably want a subclass that holds a lot of these... +// and maybe more! + + /** + * Writes the string as if characters() had been called on the contents + * of the string. This is particularly useful when applications act as + * producers and write data directly to event consumers. + */ + final public void write (String data) + throws SAXException + { + char buf [] = data.toCharArray (); + characters (buf, 0, buf.length); + } + + + /** + * Writes an element that has content consisting of a single string. + * @see #writeEmptyElement + * @see #startElement + */ + public void writeElement ( + String uri, + String localName, + String qName, + Attributes atts, + String content + ) throws SAXException + { + if (content == null || content.length () == 0) { + writeEmptyElement (uri, localName, qName, atts); + return; + } + startElement (uri, localName, qName, atts); + char chars [] = content.toCharArray (); + characters (chars, 0, chars.length); + endElement (uri, localName, qName); + } + + + /** + * Writes an element that has content consisting of a single integer, + * encoded as a decimal string. + * @see #writeEmptyElement + * @see #startElement + */ + public void writeElement ( + String uri, + String localName, + String qName, + Attributes atts, + int content + ) throws SAXException + { + writeElement (uri, localName, qName, atts, Integer.toString (content)); + } + + + // SAX1 ContentHandler + /** <b>SAX1</b>: provides parser status information */ + final public void setDocumentLocator (Locator l) + { + locator = l; + } + + + // URL for dtd that validates against all normal HTML constructs + private static final String xhtmlFullDTD = + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"; + + + /** + * <b>SAX1</b>: indicates the beginning of a document parse. + * If you're writing (well formed) fragments of XML, neither + * this nor endDocument should be called. + */ + // NOT final + public void startDocument () + throws SAXException + { + try { + if (out == null) + throw new IllegalStateException ( + "null Writer given to XMLWriter"); + + // Not all parsers provide the locator we want; this also + // flags whether events are being sent to this object yet. + // We could only have this one call if we only printed whole + // documents ... but we also print fragments, so most of the + // callbacks here replicate this test. + + if (locator == null) + locator = new LocatorImpl (); + + // Unless the data is in US-ASCII or we're canonicalizing, write + // the XML declaration if we know the encoding. US-ASCII won't + // normally get mangled by web server confusion about the + // character encodings used. Plus, it's an easy way to + // ensure we can write ASCII that's unlikely to confuse + // elderly HTML parsers. + + if (!canonical + && dangerMask != (short) 0xff80 + && encoding != null) { + rawWrite ("<?xml version='1.0'"); + rawWrite (" encoding='" + encoding + "'"); + rawWrite ("?>"); + newline (); + } + + if (xhtml) { + + rawWrite ("<!DOCTYPE html PUBLIC"); + newline (); + rawWrite (" '-//W3C//DTD XHTML 1.0 Transitional//EN'"); + newline (); + rawWrite (" '"); + // NOTE: URL (above) matches the REC + rawWrite (xhtmlFullDTD); + rawWrite ("'>"); + newline (); + newline (); + + // fake the rest of the handler into ignoring + // everything until the root element, so any + // XHTML DTD comments, PIs, etc are ignored + startedDoctype = true; + } + + entityNestLevel = 0; + + } catch (IOException e) { + fatal ("can't write", e); + } + } + + /** + * <b>SAX1</b>: indicates the completion of a parse. + * Note that all complete SAX event streams make this call, even + * if an error is reported during a parse. + */ + // NOT final + public void endDocument () + throws SAXException + { + try { + if (!canonical) { + newline (); + newline (); + } + out.close (); + out = null; + locator = null; + } catch (IOException e) { + fatal ("can't write", e); + } + } + + // XHTML elements declared as EMPTY print differently + final private static boolean isEmptyElementTag (String tag) + { + switch (tag.charAt (0)) { + case 'a': return "area".equals (tag); + case 'b': return "base".equals (tag) + || "basefont".equals (tag) + || "br".equals (tag); + case 'c': return "col".equals (tag); + case 'f': return "frame".equals (tag); + case 'h': return "hr".equals (tag); + case 'i': return "img".equals (tag) + || "input".equals (tag) + || "isindex".equals (tag); + case 'l': return "link".equals (tag); + case 'm': return "meta".equals (tag); + case 'p': return "param".equals (tag); + } + return false; + } + + private static boolean indentBefore (String tag) + { + // basically indent before block content + // and within structure like tables, lists + switch (tag.charAt (0)) { + case 'a': return "applet".equals (tag); + case 'b': return "body".equals (tag) + || "blockquote".equals (tag); + case 'c': return "center".equals (tag); + case 'f': return "frame".equals (tag) + || "frameset".equals (tag); + case 'h': return "head".equals (tag); + case 'm': return "meta".equals (tag); + case 'o': return "object".equals (tag); + case 'p': return "param".equals (tag) + || "pre".equals (tag); + case 's': return "style".equals (tag); + case 't': return "title".equals (tag) + || "td".equals (tag) + || "th".equals (tag); + } + // ... but not inline elements like "em", "b", "font" + return false; + } + + private static boolean spaceBefore (String tag) + { + // blank line AND INDENT before certain structural content + switch (tag.charAt (0)) { + case 'h': return "h1".equals (tag) + || "h2".equals (tag) + || "h3".equals (tag) + || "h4".equals (tag) + || "h5".equals (tag) + || "h6".equals (tag) + || "hr".equals (tag); + case 'l': return "li".equals (tag); + case 'o': return "ol".equals (tag); + case 'p': return "p".equals (tag); + case 't': return "table".equals (tag) + || "tr".equals (tag); + case 'u': return "ul".equals (tag); + } + return false; + } + + // XHTML DTDs say these three have xml:space="preserve" + private static boolean spacePreserve (String tag) + { + return "pre".equals (tag) + || "style".equals (tag) + || "script".equals (tag); + } + + /** + * <b>SAX2</b>: ignored. + */ + final public void startPrefixMapping (String prefix, String uri) + {} + + /** + * <b>SAX2</b>: ignored. + */ + final public void endPrefixMapping (String prefix) + {} + + private void writeStartTag ( + String name, + Attributes atts, + boolean isEmpty + ) throws SAXException, IOException + { + rawWrite ('<'); + rawWrite (name); + + // write out attributes ... sorting is particularly useful + // with output that's been heavily defaulted. + if (atts != null && atts.getLength () != 0) { + + // Set up to write, with optional sorting + int indices [] = new int [atts.getLength ()]; + + for (int i= 0; i < indices.length; i++) + indices [i] = i; + + // optionally sort + +// FIXME: canon xml demands xmlns nodes go first, +// and sorting by URI first (empty first) then localname +// it should maybe use a different sort + + if (canonical || prettyPrinting) { + + // insertion sort by attribute name + for (int i = 1; i < indices.length; i++) { + int n = indices [i], j; + String s = atts.getQName (n); + + for (j = i - 1; j >= 0; j--) { + if (s.compareTo (atts.getQName (indices [j])) + >= 0) + break; + indices [j + 1] = indices [j]; + } + indices [j + 1] = n; + } + } + + // write, sorted or no + for (int i= 0; i < indices.length; i++) { + String s = atts.getQName (indices [i]); + + if (s == null || "".equals (s)) + throw new IllegalArgumentException ("no XML name"); + rawWrite (" "); + rawWrite (s); + rawWrite ("="); + writeQuotedValue (atts.getValue (indices [i]), + CTX_ATTRIBUTE); + } + } + if (isEmpty) + rawWrite (" /"); + rawWrite ('>'); + } + + /** + * <b>SAX2</b>: indicates the start of an element. + * When XHTML is in use, avoid attribute values with + * line breaks or multiple whitespace characters, since + * not all user agents handle them correctly. + */ + final public void startElement ( + String uri, + String localName, + String qName, + Attributes atts + ) throws SAXException + { + startedDoctype = false; + + if (locator == null) + locator = new LocatorImpl (); + + if (qName == null || "".equals (qName)) + throw new IllegalArgumentException ("no XML name"); + + try { + if (entityNestLevel != 0) + return; + if (prettyPrinting) { + String whitespace = null; + + if (xhtml && spacePreserve (qName)) + whitespace = "preserve"; + else if (atts != null) + whitespace = atts.getValue ("xml:space"); + if (whitespace == null) + whitespace = (String) space.peek (); + space.push (whitespace); + + if ("default".equals (whitespace)) { + if (xhtml) { + if (spaceBefore (qName)) { + newline (); + doIndent (); + } else if (indentBefore (qName)) + doIndent (); + // else it's inlined, modulo line length + // FIXME: incrementing element nest level + // for inlined elements causes ugliness + } else + doIndent (); + } + } + elementNestLevel++; + writeStartTag (qName, atts, xhtml && isEmptyElementTag (qName)); + + if (xhtml) { +// FIXME: if this is an XHTML "pre" element, turn +// off automatic wrapping. + } + + } catch (IOException e) { + fatal ("can't write", e); + } + } + + /** + * Writes an empty element. + * @see #startElement + */ + public void writeEmptyElement ( + String uri, + String localName, + String qName, + Attributes atts + ) throws SAXException + { + if (canonical) { + startElement (uri, localName, qName, atts); + endElement (uri, localName, qName); + } else { + try { + writeStartTag (qName, atts, true); + } catch (IOException e) { + fatal ("can't write", e); + } + } + } + + + /** <b>SAX2</b>: indicates the end of an element */ + final public void endElement (String uri, String localName, String qName) + throws SAXException + { + if (qName == null || "".equals (qName)) + throw new IllegalArgumentException ("no XML name"); + + try { + elementNestLevel--; + if (entityNestLevel != 0) + return; + if (xhtml && isEmptyElementTag (qName)) + return; + rawWrite ("</"); + rawWrite (qName); + rawWrite ('>'); + + if (prettyPrinting) { + if (!space.empty ()) + space.pop (); + else + fatal ("stack discipline", null); + } + if (elementNestLevel == 0) + inEpilogue = true; + + } catch (IOException e) { + fatal ("can't write", e); + } + } + + /** <b>SAX1</b>: reports content characters */ + final public void characters (char ch [], int start, int length) + throws SAXException + { + if (locator == null) + locator = new LocatorImpl (); + + try { + if (entityNestLevel != 0) + return; + if (inCDATA) { + escapeChars (ch, start, length, CTX_UNPARSED); + } else { + escapeChars (ch, start, length, CTX_CONTENT); + } + } catch (IOException e) { + fatal ("can't write", e); + } + } + + /** <b>SAX1</b>: reports ignorable whitespace */ + final public void ignorableWhitespace (char ch [], int start, int length) + throws SAXException + { + if (locator == null) + locator = new LocatorImpl (); + + try { + if (entityNestLevel != 0) + return; + // don't forget to map NL to CRLF, CR, etc + escapeChars (ch, start, length, CTX_CONTENT); + } catch (IOException e) { + fatal ("can't write", e); + } + } + + /** + * <b>SAX1</b>: reports a PI. + * This doesn't check for illegal target names, such as "xml" or "XML", + * or namespace-incompatible ones like "big:dog"; the caller is + * responsible for ensuring those names are legal. + */ + final public void processingInstruction (String target, String data) + throws SAXException + { + if (locator == null) + locator = new LocatorImpl (); + + // don't print internal subset for XHTML + if (xhtml && startedDoctype) + return; + + // ancient HTML browsers might render these ... their loss. + // to prevent: "if (xhtml) return;". + + try { + if (entityNestLevel != 0) + return; + if (canonical && inEpilogue) + newline (); + rawWrite ("<?"); + rawWrite (target); + rawWrite (' '); + escapeChars (data.toCharArray (), -1, -1, CTX_UNPARSED); + rawWrite ("?>"); + if (elementNestLevel == 0 && !(canonical && inEpilogue)) + newline (); + } catch (IOException e) { + fatal ("can't write", e); + } + } + + /** <b>SAX1</b>: indicates a non-expanded entity reference */ + public void skippedEntity (String name) + throws SAXException + { + try { + rawWrite ("&"); + rawWrite (name); + rawWrite (";"); + } catch (IOException e) { + fatal ("can't write", e); + } + } + + // SAX2 LexicalHandler + + /** <b>SAX2</b>: called before parsing CDATA characters */ + final public void startCDATA () + throws SAXException + { + if (locator == null) + locator = new LocatorImpl (); + + if (canonical) + return; + + try { + inCDATA = true; + if (entityNestLevel == 0) + rawWrite ("<![CDATA["); + } catch (IOException e) { + fatal ("can't write", e); + } + } + + /** <b>SAX2</b>: called after parsing CDATA characters */ + final public void endCDATA () + throws SAXException + { + if (canonical) + return; + + try { + inCDATA = false; + if (entityNestLevel == 0) + rawWrite ("]]>"); + } catch (IOException e) { + fatal ("can't write", e); + } + } + + /** + * <b>SAX2</b>: called when the doctype is partially parsed + * Note that this, like other doctype related calls, is ignored + * when XHTML is in use. + */ + final public void startDTD (String name, String publicId, String systemId) + throws SAXException + { + if (locator == null) + locator = new LocatorImpl (); + if (xhtml) + return; + try { + inDoctype = startedDoctype = true; + if (canonical) + return; + rawWrite ("<!DOCTYPE "); + rawWrite (name); + rawWrite (' '); + + if (!expandingEntities) { + if (publicId != null) + rawWrite ("PUBLIC '" + publicId + "' '" + systemId + "' "); + else if (systemId != null) + rawWrite ("SYSTEM '" + systemId + "' "); + } + + rawWrite ('['); + newline (); + } catch (IOException e) { + fatal ("can't write", e); + } + } + + /** <b>SAX2</b>: called after the doctype is parsed */ + final public void endDTD () + throws SAXException + { + inDoctype = false; + if (canonical || xhtml) + return; + try { + rawWrite ("]>"); + newline (); + } catch (IOException e) { + fatal ("can't write", e); + } + } + + /** + * <b>SAX2</b>: called before parsing a general entity in content + */ + final public void startEntity (String name) + throws SAXException + { + try { + boolean writeEOL = true; + + // Predefined XHTML entities (for characters) will get + // mapped back later. + if (xhtml || expandingEntities) + return; + + entityNestLevel++; + if (name.equals ("[dtd]")) + return; + if (entityNestLevel != 1) + return; + if (!name.startsWith ("%")) { + writeEOL = false; + rawWrite ('&'); + } + rawWrite (name); + rawWrite (';'); + if (writeEOL) + newline (); + } catch (IOException e) { + fatal ("can't write", e); + } + } + + /** + * <b>SAX2</b>: called after parsing a general entity in content + */ + final public void endEntity (String name) + throws SAXException + { + if (xhtml || expandingEntities) + return; + entityNestLevel--; + } + + /** + * <b>SAX2</b>: called when comments are parsed. + * When XHTML is used, the old HTML tradition of using comments + * to for inline CSS, or for JavaScript code is discouraged. + * This is because XML processors are encouraged to discard, on + * the grounds that comments are for users (and perhaps text + * editors) not programs. Instead, use external scripts + */ + final public void comment (char ch [], int start, int length) + throws SAXException + { + if (locator == null) + locator = new LocatorImpl (); + + // don't print internal subset for XHTML + if (xhtml && startedDoctype) + return; + // don't print comment in doctype for canon xml + if (canonical && inDoctype) + return; + + try { + boolean indent; + + if (prettyPrinting && space.empty ()) + fatal ("stack discipline", null); + indent = prettyPrinting && "default".equals (space.peek ()); + if (entityNestLevel != 0) + return; + if (indent) + doIndent (); + if (canonical && inEpilogue) + newline (); + rawWrite ("<!--"); + escapeChars (ch, start, length, CTX_UNPARSED); + rawWrite ("-->"); + if (indent) + doIndent (); + if (elementNestLevel == 0 && !(canonical && inEpilogue)) + newline (); + } catch (IOException e) { + fatal ("can't write", e); + } + } + + // SAX1 DTDHandler + + /** <b>SAX1</b>: called on notation declarations */ + final public void notationDecl (String name, + String publicId, String systemId) + throws SAXException + { + if (xhtml) + return; + try { + // At this time, only SAX2 callbacks start these. + if (!startedDoctype) + return; + + if (entityNestLevel != 0) + return; + rawWrite ("<!NOTATION " + name + " "); + if (publicId != null) + rawWrite ("PUBLIC \"" + publicId + '"'); + else + rawWrite ("SYSTEM "); + if (systemId != null) + rawWrite ('"' + systemId + '"'); + rawWrite (">"); + newline (); + } catch (IOException e) { + fatal ("can't write", e); + } + } + + /** <b>SAX1</b>: called on unparsed entity declarations */ + final public void unparsedEntityDecl (String name, + String publicId, String systemId, + String notationName) + throws SAXException + { + if (xhtml) + return; + try { + // At this time, only SAX2 callbacks start these. + if (!startedDoctype) { + // FIXME: write to temporary buffer, and make the start + // of the root element write these declarations. + return; + } + + if (entityNestLevel != 0) + return; + rawWrite ("<!ENTITY " + name + " "); + if (publicId != null) + rawWrite ("PUBLIC \"" + publicId + '"'); + else + rawWrite ("SYSTEM "); + rawWrite ('"' + systemId + '"'); + rawWrite (" NDATA " + notationName + ">"); + newline (); + } catch (IOException e) { + fatal ("can't write", e); + } + } + + // SAX2 DeclHandler + + /** <b>SAX2</b>: called on attribute declarations */ + final public void attributeDecl (String eName, String aName, + String type, String mode, String value) + throws SAXException + { + if (xhtml) + return; + try { + // At this time, only SAX2 callbacks start these. + if (!startedDoctype) + return; + if (entityNestLevel != 0) + return; + rawWrite ("<!ATTLIST " + eName + ' ' + aName + ' '); + rawWrite (type); + rawWrite (' '); + if (mode != null) + rawWrite (mode + ' '); + if (value != null) + writeQuotedValue (value, CTX_ATTRIBUTE); + rawWrite ('>'); + newline (); + } catch (IOException e) { + fatal ("can't write", e); + } + } + + /** <b>SAX2</b>: called on element declarations */ + final public void elementDecl (String name, String model) + throws SAXException + { + if (xhtml) + return; + try { + // At this time, only SAX2 callbacks start these. + if (!startedDoctype) + return; + if (entityNestLevel != 0) + return; + rawWrite ("<!ELEMENT " + name + ' ' + model + '>'); + newline (); + } catch (IOException e) { + fatal ("can't write", e); + } + } + + /** <b>SAX2</b>: called on external entity declarations */ + final public void externalEntityDecl ( + String name, + String publicId, + String systemId) + throws SAXException + { + if (xhtml) + return; + try { + // At this time, only SAX2 callbacks start these. + if (!startedDoctype) + return; + if (entityNestLevel != 0) + return; + rawWrite ("<!ENTITY "); + if (name.startsWith ("%")) { + rawWrite ("% "); + rawWrite (name.substring (1)); + } else + rawWrite (name); + if (publicId != null) + rawWrite (" PUBLIC \"" + publicId + '"'); + else + rawWrite (" SYSTEM "); + rawWrite ('"' + systemId + "\">"); + newline (); + } catch (IOException e) { + fatal ("can't write", e); + } + } + + /** <b>SAX2</b>: called on internal entity declarations */ + final public void internalEntityDecl (String name, String value) + throws SAXException + { + if (xhtml) + return; + try { + // At this time, only SAX2 callbacks start these. + if (!startedDoctype) + return; + if (entityNestLevel != 0) + return; + rawWrite ("<!ENTITY "); + if (name.startsWith ("%")) { + rawWrite ("% "); + rawWrite (name.substring (1)); + } else + rawWrite (name); + rawWrite (' '); + writeQuotedValue (value, CTX_ENTITY); + rawWrite ('>'); + newline (); + } catch (IOException e) { + fatal ("can't write", e); + } + } + + private void writeQuotedValue (String value, int code) + throws SAXException, IOException + { + char buf [] = value.toCharArray (); + int off = 0, len = buf.length; + + // we can't add line breaks to attribute/entity/... values + noWrap = true; + rawWrite ('"'); + escapeChars (buf, off, len, code); + rawWrite ('"'); + noWrap = false; + } + + // From "HTMLlat1x.ent" ... names of entities for ISO-8859-1 + // (Latin/1) characters, all codes: 160-255 (0xA0-0xFF). + // Codes 128-159 have no assigned values. + private static final String HTMLlat1x [] = { + // 160 + "nbsp", "iexcl", "cent", "pound", "curren", + "yen", "brvbar", "sect", "uml", "copy", + + // 170 + "ordf", "laquo", "not", "shy", "reg", + "macr", "deg", "plusmn", "sup2", "sup3", + + // 180 + "acute", "micro", "para", "middot", "cedil", + "sup1", "ordm", "raquo", "frac14", "frac12", + + // 190 + "frac34", "iquest", "Agrave", "Aacute", "Acirc", + "Atilde", "Auml", "Aring", "AElig", "Ccedil", + + // 200 + "Egrave", "Eacute", "Ecirc", "Euml", "Igrave", + "Iacute", "Icirc", "Iuml", "ETH", "Ntilde", + + // 210 + "Ograve", "Oacute", "Ocirc", "Otilde", "Ouml", + "times", "Oslash", "Ugrave", "Uacute", "Ucirc", + + // 220 + "Uuml", "Yacute", "THORN", "szlig", "agrave", + "aacute", "acirc", "atilde", "auml", "aring", + + // 230 + "aelig", "ccedil", "egrave", "eacute", "ecirc", + "euml", "igrave", "iacute", "icirc", "iuml", + + // 240 + "eth", "ntilde", "ograve", "oacute", "ocirc", + "otilde", "ouml", "divide", "oslash", "ugrave", + + // 250 + "uacute", "ucirc", "uuml", "yacute", "thorn", + "yuml" + }; + + // From "HTMLsymbolx.ent" ... some of the symbols that + // we can conveniently handle. Entities for the Greek. + // alphabet (upper and lower cases) are compact. + private static final String HTMLsymbolx_GR [] = { + // 913 + "Alpha", "Beta", "Gamma", "Delta", "Epsilon", + "Zeta", "Eta", "Theta", "Iota", "Kappa", + + // 923 + "Lambda", "Mu", "Nu", "Xi", "Omicron", + "Pi", "Rho", null, "Sigma", "Tau", + + // 933 + "Upsilon", "Phi", "Chi", "Psi", "Omega" + }; + + private static final String HTMLsymbolx_gr [] = { + // 945 + "alpha", "beta", "gamma", "delta", "epsilon", + "zeta", "eta", "theta", "iota", "kappa", + + // 955 + "lambda", "mu", "nu", "xi", "omicron", + "pi", "rho", "sigmaf", "sigma", "tau", + + // 965 + "upsilon", "phi", "chi", "psi", "omega" + }; + + + // General routine to write text and substitute predefined + // entities (XML, and a special case for XHTML) as needed. + private void escapeChars (char buf [], int off, int len, int code) + throws SAXException, IOException + { + int first = 0; + + if (off < 0) { + off = 0; + len = buf.length; + } + for (int i = 0; i < len; i++) { + String esc; + char c = buf [off + i]; + + switch (c) { + // Note that CTX_ATTRIBUTE isn't explicitly tested here; + // all syntax delimiters are escaped in CTX_ATTRIBUTE, + // otherwise it's similar to CTX_CONTENT + + // ampersand flags entity references; entity replacement + // text has unexpanded references, other text doesn't. + case '&': + if (code == CTX_ENTITY || code == CTX_UNPARSED) + continue; + esc = "amp"; + break; + + // attributes and text may NOT have literal '<', but + // entities may have markup constructs + case '<': + if (code == CTX_ENTITY || code == CTX_UNPARSED) + continue; + esc = "lt"; + break; + + // as above re markup constructs; but otherwise + // except when canonicalizing, this is for consistency + case '>': + if (code == CTX_ENTITY || code == CTX_UNPARSED) + continue; + esc = "gt"; + break; + case '\'': + if (code == CTX_CONTENT || code == CTX_UNPARSED) + continue; + if (canonical) + continue; + esc = "apos"; + break; + + // needed when printing quoted attribute/entity values + case '"': + if (code == CTX_CONTENT || code == CTX_UNPARSED) + continue; + esc = "quot"; + break; + + // make line ends work per host OS convention + case '\n': + esc = eol; + break; + + // + // No other characters NEED special treatment ... except + // for encoding-specific issues, like whether the character + // can really be represented in that encoding. + // + default: + // + // There are characters we can never write safely; getting + // them is an error. + // + // (a) They're never legal in XML ... detected by range + // checks, and (eventually) by remerging surrogate + // pairs on output. (Easy error for apps to prevent.) + // + // (b) This encoding can't represent them, and we + // can't make reference substitution (e.g. inside + // CDATA sections, names, PI data, etc). (Hard for + // apps to prevent, except by using UTF-8 or UTF-16 + // as their output encoding.) + // + // We know a very little bit about what characters + // the US-ASCII and ISO-8859-1 encodings support. For + // other encodings we can't detect the second type of + // error at all. (Never an issue for UTF-8 or UTF-16.) + // + +// FIXME: CR in CDATA is an error; in text, turn to a char ref + +// FIXME: CR/LF/TAB in attributes should become char refs + + if ((c > 0xfffd) + || ((c < 0x0020) && !((c == 0x0009) + || (c == 0x000A) || (c == 0x000D))) + || (((c & dangerMask) != 0) + && (code == CTX_UNPARSED))) { + + // if case (b) in CDATA, we might end the section, + // write a reference, then restart ... possible + // in one DOM L3 draft. + + throw new CharConversionException ( + "Illegal or non-writable character: U+" + + Integer.toHexString (c)); + } + + // + // If the output encoding represents the character + // directly, let it do so! Else we'll escape it. + // + if ((c & dangerMask) == 0) + continue; + esc = null; + + // Avoid numeric refs where symbolic ones exist, as + // symbolic ones make more sense to humans reading! + if (xhtml) { + // all the HTMLlat1x.ent entities + // (all the "ISO-8859-1" characters) + if (c >= 160 && c <= 255) + esc = HTMLlat1x [c - 160]; + + // not quite half the HTMLsymbolx.ent entities + else if (c >= 913 && c <= 937) + esc = HTMLsymbolx_GR [c - 913]; + else if (c >= 945 && c <= 969) + esc = HTMLsymbolx_gr [c - 945]; + + else switch (c) { + // all of the HTMLspecialx.ent entities + case 338: esc = "OElig"; break; + case 339: esc = "oelig"; break; + case 352: esc = "Scaron"; break; + case 353: esc = "scaron"; break; + case 376: esc = "Yuml"; break; + case 710: esc = "circ"; break; + case 732: esc = "tilde"; break; + case 8194: esc = "ensp"; break; + case 8195: esc = "emsp"; break; + case 8201: esc = "thinsp"; break; + case 8204: esc = "zwnj"; break; + case 8205: esc = "zwj"; break; + case 8206: esc = "lrm"; break; + case 8207: esc = "rlm"; break; + case 8211: esc = "ndash"; break; + case 8212: esc = "mdash"; break; + case 8216: esc = "lsquo"; break; + case 8217: esc = "rsquo"; break; + case 8218: esc = "sbquo"; break; + case 8220: esc = "ldquo"; break; + case 8221: esc = "rdquo"; break; + case 8222: esc = "bdquo"; break; + case 8224: esc = "dagger"; break; + case 8225: esc = "Dagger"; break; + case 8240: esc = "permil"; break; + case 8249: esc = "lsaquo"; break; + case 8250: esc = "rsaquo"; break; + case 8364: esc = "euro"; break; + + // the other HTMLsymbox.ent entities + case 402: esc = "fnof"; break; + case 977: esc = "thetasym"; break; + case 978: esc = "upsih"; break; + case 982: esc = "piv"; break; + case 8226: esc = "bull"; break; + case 8230: esc = "hellip"; break; + case 8242: esc = "prime"; break; + case 8243: esc = "Prime"; break; + case 8254: esc = "oline"; break; + case 8260: esc = "frasl"; break; + case 8472: esc = "weierp"; break; + case 8465: esc = "image"; break; + case 8476: esc = "real"; break; + case 8482: esc = "trade"; break; + case 8501: esc = "alefsym"; break; + case 8592: esc = "larr"; break; + case 8593: esc = "uarr"; break; + case 8594: esc = "rarr"; break; + case 8595: esc = "darr"; break; + case 8596: esc = "harr"; break; + case 8629: esc = "crarr"; break; + case 8656: esc = "lArr"; break; + case 8657: esc = "uArr"; break; + case 8658: esc = "rArr"; break; + case 8659: esc = "dArr"; break; + case 8660: esc = "hArr"; break; + case 8704: esc = "forall"; break; + case 8706: esc = "part"; break; + case 8707: esc = "exist"; break; + case 8709: esc = "empty"; break; + case 8711: esc = "nabla"; break; + case 8712: esc = "isin"; break; + case 8713: esc = "notin"; break; + case 8715: esc = "ni"; break; + case 8719: esc = "prod"; break; + case 8721: esc = "sum"; break; + case 8722: esc = "minus"; break; + case 8727: esc = "lowast"; break; + case 8730: esc = "radic"; break; + case 8733: esc = "prop"; break; + case 8734: esc = "infin"; break; + case 8736: esc = "ang"; break; + case 8743: esc = "and"; break; + case 8744: esc = "or"; break; + case 8745: esc = "cap"; break; + case 8746: esc = "cup"; break; + case 8747: esc = "int"; break; + case 8756: esc = "there4"; break; + case 8764: esc = "sim"; break; + case 8773: esc = "cong"; break; + case 8776: esc = "asymp"; break; + case 8800: esc = "ne"; break; + case 8801: esc = "equiv"; break; + case 8804: esc = "le"; break; + case 8805: esc = "ge"; break; + case 8834: esc = "sub"; break; + case 8835: esc = "sup"; break; + case 8836: esc = "nsub"; break; + case 8838: esc = "sube"; break; + case 8839: esc = "supe"; break; + case 8853: esc = "oplus"; break; + case 8855: esc = "otimes"; break; + case 8869: esc = "perp"; break; + case 8901: esc = "sdot"; break; + case 8968: esc = "lceil"; break; + case 8969: esc = "rceil"; break; + case 8970: esc = "lfloor"; break; + case 8971: esc = "rfloor"; break; + case 9001: esc = "lang"; break; + case 9002: esc = "rang"; break; + case 9674: esc = "loz"; break; + case 9824: esc = "spades"; break; + case 9827: esc = "clubs"; break; + case 9829: esc = "hearts"; break; + case 9830: esc = "diams"; break; + } + } + + // else escape with numeric char refs + if (esc == null) { + stringBuf.setLength (0); + stringBuf.append ("#x"); + stringBuf.append (Integer.toHexString (c).toUpperCase ()); + esc = stringBuf.toString (); + + // FIXME: We don't write surrogate pairs correctly. + // They should work as one ref per character, since + // each pair is one character. For reading back into + // Unicode, it matters beginning in Unicode 3.1 ... + } + break; + } + if (i != first) + rawWrite (buf, off + first, i - first); + first = i + 1; + if (esc == eol) + newline (); + else { + rawWrite ('&'); + rawWrite (esc); + rawWrite (';'); + } + } + if (first < len) + rawWrite (buf, off + first, len - first); + } + + + + private void newline () + throws SAXException, IOException + { + out.write (eol); + column = 0; + } + + private void doIndent () + throws SAXException, IOException + { + int space = elementNestLevel * 2; + + newline (); + column = space; + // track tabs only at line starts + while (space > 8) { + out.write ("\t"); + space -= 8; + } + while (space > 0) { + out.write (" "); + space -= 2; + } + } + + private void rawWrite (char c) + throws IOException + { + out.write (c); + column++; + } + + private void rawWrite (String s) + throws SAXException, IOException + { + if (prettyPrinting && "default".equals (space.peek ())) { + char data [] = s.toCharArray (); + rawWrite (data, 0, data.length); + } else { + out.write (s); + column += s.length (); + } + } + + // NOTE: if xhtml, the REC gives some rules about whitespace + // which we could follow ... notably, many places where conformant + // agents "must" consolidate/normalize whitespace. Line ends can + // be removed there, etc. This may not be the right place to do + // such mappings though. + + // Line buffering may help clarify algorithms and improve results. + + // It's likely xml:space needs more attention. + + private void rawWrite (char buf [], int offset, int length) + throws SAXException, IOException + { + boolean wrap; + + if (prettyPrinting && space.empty ()) + fatal ("stack discipline", null); + + wrap = prettyPrinting && "default".equals (space.peek ()); + if (!wrap) { + out.write (buf, offset, length); + column += length; + return; + } + + // we're pretty printing and want to fill lines out only + // to the desired line length. + while (length > 0) { + int target = lineLength - column; + boolean wrote = false; + + // Do we even have a problem? + if (target > length || noWrap) { + out.write (buf, offset, length); + column += length; + return; + } + + // break the line at a space character, trying to fill + // as much of the line as possible. + char c; + + for (int i = target - 1; i >= 0; i--) { + if ((c = buf [offset + i]) == ' ' || c == '\t') { + i++; + out.write (buf, offset, i); + doIndent (); + offset += i; + length -= i; + wrote = true; + break; + } + } + if (wrote) + continue; + + // no space character permitting break before target + // line length is filled. So, take the next one. + if (target < 0) + target = 0; + for (int i = target; i < length; i++) + if ((c = buf [offset + i]) == ' ' || c == '\t') { + i++; + out.write (buf, offset, i); + doIndent (); + offset += i; + length -= i; + wrote = true; + break; + } + if (wrote) + continue; + + // no such luck. + out.write (buf, offset, length); + column += length; + break; + } + } +} diff --git a/libjava/gnu/xml/util/package.html b/libjava/gnu/xml/util/package.html new file mode 100644 index 0000000..6e6c0d7 --- /dev/null +++ b/libjava/gnu/xml/util/package.html @@ -0,0 +1,20 @@ +<!DOCTYPE html PUBLIC + "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/1999/PR-xhtml1-19991210/DTD/xhtml1-transitional.dtd"> + +<html><head><title> org.brownell.xml package </title> </head> +<!-- +/* + * Copyright (C) 1999,2000 The Free Software Foundation, Inc. + */ +--> +<body> + <p> This package contains XML utilities, including SAX2 XML writers + and a parser of DOM trees, plus a command line driver. + That <a href="DoParse.html">driver</a> + connects parsers simple processing pipelines. + It can be handy for command line validation or + transformation tasks, possibly in batch mode, + or within Makefiles. </p> + +</body></html> diff --git a/libjava/gnu/xml/xpath/AndExpr.java b/libjava/gnu/xml/xpath/AndExpr.java new file mode 100644 index 0000000..91fb3bc --- /dev/null +++ b/libjava/gnu/xml/xpath/AndExpr.java @@ -0,0 +1,81 @@ +/* AndExpr.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import org.w3c.dom.Node; + +/** + * Logical and. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public final class AndExpr + extends Expr +{ + + final Expr lhs; + final Expr rhs; + + public AndExpr(Expr lhs, Expr rhs) + { + this.lhs = lhs; + this.rhs = rhs; + } + + public Object evaluate(Node context, int pos, int len) + { + Object left = lhs.evaluate(context, pos, len); + if (!_boolean(context, left)) + { + return Boolean.FALSE; + } + Object right = rhs.evaluate(context, pos, len); + return _boolean(context, right) ? Boolean.TRUE : Boolean.FALSE; + } + + public Expr clone(Object context) + { + return new AndExpr(lhs.clone(context), rhs.clone(context)); + } + + public String toString() + { + return lhs + " and " + rhs; + } + +} diff --git a/libjava/gnu/xml/xpath/ArithmeticExpr.java b/libjava/gnu/xml/xpath/ArithmeticExpr.java new file mode 100644 index 0000000..ad9da1e --- /dev/null +++ b/libjava/gnu/xml/xpath/ArithmeticExpr.java @@ -0,0 +1,148 @@ +/* ArithmeticExpr.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import org.w3c.dom.Node; + +/** + * Binary arithmetic expression. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class ArithmeticExpr + extends Expr +{ + + static final int ADD = 0; + static final int SUBTRACT = 1; + static final int MULTIPLY = 2; + static final int DIVIDE = 3; + static final int MODULO = 4; + + final Expr lhs; + final Expr rhs; + final int op; + + ArithmeticExpr(Expr lhs, Expr rhs, int op) + { + this.lhs = lhs; + this.rhs = rhs; + switch (op) + { + case ADD: + case SUBTRACT: + case MULTIPLY: + case DIVIDE: + case MODULO: + this.op = op; + break; + default: + throw new IllegalArgumentException(); + } + } + + public Object evaluate(Node context, int pos, int len) + { + Object left = lhs.evaluate(context, pos, len); + Object right = rhs.evaluate(context, pos, len); + + double ln = _number(context, left); + double rn = _number(context, right); + switch (op) + { + case ADD: + return new Double(ln + rn); + case SUBTRACT: + return new Double(ln - rn); + case MULTIPLY: + return new Double(ln * rn); + case DIVIDE: + if (rn == 0.0d || rn == -0.0d) + { + return new Double(ln < 0.0d ? + Double.NEGATIVE_INFINITY : + Double.POSITIVE_INFINITY); + } + return new Double(ln / rn); + case MODULO: + if (rn == 0.0d || rn == -0.0d) + { + return new Double(ln < 0.0d ? + Double.NEGATIVE_INFINITY : + Double.POSITIVE_INFINITY); + } + return new Double(ln % rn); + default: + throw new IllegalStateException(); + } + } + + public Expr clone(Object context) + { + return new ArithmeticExpr(lhs.clone(context), rhs.clone(context), op); + } + + public String toString() + { + StringBuffer buf = new StringBuffer(); + buf.append(lhs); + buf.append(' '); + switch (op) + { + case ADD: + buf.append('+'); + break; + case SUBTRACT: + buf.append('-'); + break; + case MULTIPLY: + buf.append('*'); + break; + case DIVIDE: + buf.append("div"); + break; + case MODULO: + buf.append("mod"); + break; + } + buf.append(' '); + buf.append(rhs); + return buf.toString(); + } + +} diff --git a/libjava/gnu/xml/xpath/BooleanFunction.java b/libjava/gnu/xml/xpath/BooleanFunction.java new file mode 100644 index 0000000..df54ff5 --- /dev/null +++ b/libjava/gnu/xml/xpath/BooleanFunction.java @@ -0,0 +1,89 @@ +/* BooleanFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.List; +import org.w3c.dom.Node; + +/** + * The <code>boolean</code> function converts its argument to a boolean as + * follows: + * <ul> + * <li>a number is true if and only if it is neither positive or negative + * zero nor NaN</li> + * <li>a node-set is true if and only if it is non-empty</li> + * <li>a string is true if and only if its length is non-zero</li> + * <li>an object of a type other than the four basic types is converted to a + * boolean in a way that is dependent on that type</li> + * </ul> + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class BooleanFunction + extends Expr +{ + + final Expr arg; + + BooleanFunction(List args) + { + this((Expr) args.get(0)); + } + + BooleanFunction(Expr arg) + { + this.arg = arg; + } + + public Object evaluate(Node context, int pos, int len) + { + Object val = arg.evaluate(context, pos, len); + return _boolean(context, val) ? Boolean.TRUE : Boolean.FALSE; + } + + public Expr clone(Object context) + { + return new BooleanFunction(arg.clone(context)); + } + + public String toString() + { + return "boolean(" + arg + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/CeilingFunction.java b/libjava/gnu/xml/xpath/CeilingFunction.java new file mode 100644 index 0000000..8c0b8b1 --- /dev/null +++ b/libjava/gnu/xml/xpath/CeilingFunction.java @@ -0,0 +1,83 @@ +/* CeilingFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.List; +import org.w3c.dom.Node; + +/** + * The <code>ceiling</code> function returns the smallest (closest to + * negative infinity) number that is not less than the argument and that + * is an integer. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class CeilingFunction + extends Expr +{ + + final Expr arg; + + CeilingFunction(List args) + { + this((Expr) args.get(0)); + } + + CeilingFunction(Expr arg) + { + this.arg = arg; + } + + public Object evaluate(Node context, int pos, int len) + { + Object val = arg.evaluate(context, pos, len); + double n = _number(context, val); + return new Double(Math.ceil(n)); + } + + public Expr clone(Object context) + { + return new CeilingFunction(arg.clone(context)); + } + + public String toString() + { + return "ceiling(" + arg + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/ConcatFunction.java b/libjava/gnu/xml/xpath/ConcatFunction.java new file mode 100644 index 0000000..7c51f9d --- /dev/null +++ b/libjava/gnu/xml/xpath/ConcatFunction.java @@ -0,0 +1,100 @@ +/* ConcatFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import org.w3c.dom.Node; + +/** + * The <code>concat</code> function returns the concatenation of its arguments. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class ConcatFunction + extends Expr +{ + + final List args; + + ConcatFunction(List args) + { + this.args = args; + } + + public Object evaluate(Node context, int pos, int len) + { + StringBuffer buf = new StringBuffer(); + for (Iterator i = args.iterator(); i.hasNext(); ) + { + Expr arg = (Expr) i.next(); + Object val = arg.evaluate(context, pos, len); + buf.append(_string(context, val)); + } + return buf.toString(); + } + + public Expr clone(Object context) + { + int len = args.size(); + List args2 = new ArrayList(len); + for (int i = 0; i < len; i++) + { + args2.add(((Expr) args.get(i)).clone(context)); + } + return new ConcatFunction(args2); + } + + public String toString() + { + StringBuffer buf = new StringBuffer("concat("); + int len = args.size(); + for (int i = 0; i < len; i++) + { + if (i > 0) + { + buf.append(','); + } + buf.append(args.get(i)); + } + buf.append(')'); + return buf.toString(); + } + +} diff --git a/libjava/gnu/xml/xpath/Constant.java b/libjava/gnu/xml/xpath/Constant.java new file mode 100644 index 0000000..d38f7ea --- /dev/null +++ b/libjava/gnu/xml/xpath/Constant.java @@ -0,0 +1,92 @@ +/* Constant.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import org.w3c.dom.Node; + +/** + * Constant value (string literal or number). + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public final class Constant + extends Expr +{ + + final Object value; + + public Constant(Object value) + { + this.value = value; + } + + public Object evaluate(Node context, int pos, int len) + { + return value; + } + + public Expr clone(Object context) + { + return new Constant(value); + } + + public String toString() + { + String ret = value.toString(); + if (value instanceof String) + { + if (ret.indexOf('\'') == -1) + { + return '\'' + ret + '\''; + } + else + { + return '"' + ret + '"'; + } + } + if (value instanceof Double) + { + if (ret.endsWith(".0")) + { + ret = ret.substring(0, ret.length() - 2); + } + } + return ret; + } + +} diff --git a/libjava/gnu/xml/xpath/ContainsFunction.java b/libjava/gnu/xml/xpath/ContainsFunction.java new file mode 100644 index 0000000..2a0d76d --- /dev/null +++ b/libjava/gnu/xml/xpath/ContainsFunction.java @@ -0,0 +1,86 @@ +/* ContainsFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.List; +import org.w3c.dom.Node; + +/** + * The <code>contains</code> function returns true if the first argument + * string contains the second argument string, and otherwise returns false. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class ContainsFunction + extends Expr +{ + + final Expr arg1; + final Expr arg2; + + ContainsFunction(List args) + { + this((Expr) args.get(0), (Expr) args.get(1)); + } + + ContainsFunction(Expr arg1, Expr arg2) + { + this.arg1 = arg1; + this.arg2 = arg2; + } + + public Object evaluate(Node context, int pos, int len) + { + Object val1 = arg1.evaluate(context, pos, len); + Object val2 = arg2.evaluate(context, pos, len); + String s1 = _string(context, val1); + String s2 = _string(context, val2); + return (s1.indexOf(s2) != -1) ? Boolean.TRUE : Boolean.FALSE; + } + + public Expr clone(Object context) + { + return new ContainsFunction(arg1.clone(context), arg2.clone(context)); + } + + public String toString() + { + return "contains(" + arg1 + "," + arg2 + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/CountFunction.java b/libjava/gnu/xml/xpath/CountFunction.java new file mode 100644 index 0000000..33663c9 --- /dev/null +++ b/libjava/gnu/xml/xpath/CountFunction.java @@ -0,0 +1,82 @@ +/* CountFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.Collection; +import java.util.List; +import org.w3c.dom.Node; + +/** + * The <code>count</code> function returns the number of nodes in the + * argument node-set. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class CountFunction + extends Expr +{ + + final Expr arg; + + CountFunction(List args) + { + this((Expr) args.get(0)); + } + + CountFunction(Expr arg) + { + this.arg = arg; + } + + public Object evaluate(Node context, int pos, int len) + { + Object val = arg.evaluate(context, pos, len); + return new Double((double) ((Collection) val).size()); + } + + public Expr clone(Object context) + { + return new CountFunction(arg.clone(context)); + } + + public String toString() + { + return "count(" + arg + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/DocumentOrderComparator.java b/libjava/gnu/xml/xpath/DocumentOrderComparator.java new file mode 100644 index 0000000..28d97eb --- /dev/null +++ b/libjava/gnu/xml/xpath/DocumentOrderComparator.java @@ -0,0 +1,63 @@ +/* DocumentOrderComparator.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.Comparator; +import org.w3c.dom.Node; + +/** + * Sorts nodes into document order. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class DocumentOrderComparator + implements Comparator +{ + + public int compare(Object o1, Object o2) + { + if (o1 instanceof Node && o2 instanceof Node) + { + Node n1 = (Node)o1; + Node n2 = (Node)o2; + return (int) n1.compareDocumentPosition(n2); + } + return 0; + } + +} diff --git a/libjava/gnu/xml/xpath/EqualityExpr.java b/libjava/gnu/xml/xpath/EqualityExpr.java new file mode 100644 index 0000000..bedf1e9 --- /dev/null +++ b/libjava/gnu/xml/xpath/EqualityExpr.java @@ -0,0 +1,258 @@ +/* EqualityExpr.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.Collection; +import java.util.Iterator; +import org.w3c.dom.Node; + +/** + * Boolean equality expression. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class EqualityExpr + extends Expr +{ + + final Expr lhs; + final Expr rhs; + final boolean invert; + + EqualityExpr(Expr lhs, Expr rhs, boolean invert) + { + this.lhs = lhs; + this.rhs = rhs; + this.invert = invert; + } + + public Object evaluate(Node context, int pos, int len) + { + boolean val = evaluateImpl(context, pos, len); + if (invert) + { + return val ? Boolean.FALSE : Boolean.TRUE; + } + else + { + return val ? Boolean.TRUE : Boolean.FALSE; + } + } + + private boolean evaluateImpl(Node context, int pos, int len) + { + Object left = lhs.evaluate(context, pos, len); + Object right = rhs.evaluate(context, pos, len); + + /* + * If both objects to be compared are node-sets, then the comparison + * will be true if and only if there is a node in the first node-set and + * a node in the second node-set such that the result of performing the + * comparison on the string-values of the two nodes is true. + */ + boolean flns = left instanceof Collection; + boolean frns = right instanceof Collection; + if (flns && frns) + { + Collection lns = (Collection) left; + Collection rns = (Collection) right; + boolean all = true; + for (Iterator i = lns.iterator(); i.hasNext(); ) + { + Node ltest = (Node) i.next(); + for (Iterator j = rns.iterator(); j.hasNext(); ) + { + Node rtest = (Node) j.next(); + if (ltest == rtest || ltest.equals(rtest)) + { + // much shorter + if (!invert) + { + return true; + } + } + else if (stringValue(ltest).equals(stringValue(rtest))) + { + if (!invert) + { + return true; + } + } + else + { + all = false; + } + } + } + return false; + } + /* + * If one object to be compared is a node-set and the other is a number, + * then the comparison will be true if and only if there is a node in + * the node-set such that the result of performing the comparison on the + * number to be compared and on the result of converting the + * string-value of that node to a number using the number function is + * true. + */ + boolean fln = left instanceof Double; + boolean frn = right instanceof Double; + if ((flns && frn) || (frns && fln)) + { + Collection ns = flns ? (Collection) left : (Collection) right; + double n = fln ? ((Double) left).doubleValue() : + ((Double) right).doubleValue(); + boolean all = true; + for (Iterator i = ns.iterator(); i.hasNext(); ) + { + Node test = (Node) i.next(); + double nn = _number(context, stringValue(test)); + if (nn == n) + { + if (!invert) + { + return true; + } + } + else + { + all = false; + } + } + return invert ? all : false; + } + /* + * If one object to be compared is a node-set and the other is a + * string, then the comparison will be true if and only if there is a + * node in the node-set such that the result of performing the + * comparison on the string-value of the node and the other string is + * true. + */ + boolean fls = left instanceof String; + boolean frs = right instanceof String; + if ((flns && frs) || (frns && fls)) + { + Collection ns = flns ? (Collection) left : (Collection) right; + String s = fls ? (String) left : (String) right; + boolean all = true; + for (Iterator i = ns.iterator(); i.hasNext(); ) + { + Node test = (Node) i.next(); + if (stringValue(test).equals(s)) + { + if (!invert) + { + return true; + } + } + else + { + all = false; + } + } + return invert ? all : false; + } + /* + * If one object to be compared is a node-set and the other is a + * boolean, then the comparison will be true if and only if the result + * of performing the comparison on the boolean and on the result of + * converting the node-set to a boolean using the boolean function is + * true. + */ + boolean flb = left instanceof Boolean; + boolean frb = right instanceof Boolean; + if ((flns && frb) || (frns && flb)) + { + Collection ns = flns ? (Collection) left : (Collection) right; + boolean b = flb ? ((Boolean) left).booleanValue() : + ((Boolean) right).booleanValue(); + return _boolean(context, ns) == b; + } + /* + * If at least one object to be compared is a boolean, then each object + * to be compared is converted to a boolean as if by applying the + * boolean function. + */ + if (flb || frb) + { + boolean lb = flb ? ((Boolean) left).booleanValue() : + _boolean(context, left); + boolean rb = frb ? ((Boolean) right).booleanValue() : + _boolean(context, right); + return lb == rb; + } + /* + * Otherwise, if at least one object to be compared is + * a number, then each object to be compared is converted to a number as + * if by applying the number function. + */ + if (fln || frn) + { + double ln = fln ? ((Double) left).doubleValue() : + _number(context, left); + double rn = frn ? ((Double) right).doubleValue() : + _number(context, right); + return ln == rn; + } + /* + * Otherwise, both objects to be + * compared are converted to strings as if by applying the string + * function. + */ + String ls = fls ? (String) left : _string(context, left); + String rs = frs ? (String) right : _string(context, right); + return ls.equals(rs); + } + + public Expr clone(Object context) + { + return new EqualityExpr(lhs.clone(context), rhs.clone(context), invert); + } + + public String toString() + { + if (invert) + { + return lhs + " != " + rhs; + } + else + { + return lhs + " = " + rhs; + } + } + +} diff --git a/libjava/gnu/xml/xpath/Expr.java b/libjava/gnu/xml/xpath/Expr.java new file mode 100644 index 0000000..6a933c8 --- /dev/null +++ b/libjava/gnu/xml/xpath/Expr.java @@ -0,0 +1,476 @@ +/* Expr.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.io.IOException; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.StringTokenizer; +import javax.xml.namespace.QName; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * An XPath expression. + * This can be evaluated in the context of a node to produce a result. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public abstract class Expr + implements XPathExpression +{ + + protected static final Comparator documentOrderComparator = + new DocumentOrderComparator(); + + protected static final DecimalFormat decimalFormat = + new DecimalFormat("####################################################" + + ".####################################################", + new DecimalFormatSymbols(Locale.US)); + + public Object evaluate(Object item, QName returnType) + throws XPathExpressionException + { + Object ret = null; + Node context = null; + if (item instanceof Node) + { + context = (Node) item; + ret = evaluate(context, 1, 1); + if (XPathConstants.STRING == returnType && + !(ret instanceof String)) + { + ret = _string(context, ret); + } + else if (XPathConstants.NUMBER == returnType && + !(ret instanceof Double)) + { + ret = new Double(_number(context, ret)); + } + else if (XPathConstants.BOOLEAN == returnType && + !(ret instanceof Boolean)) + { + ret = _boolean(context, ret) ? Boolean.TRUE : Boolean.FALSE; + } + else if (XPathConstants.NODE == returnType) + { + if (ret instanceof Collection) + { + Collection ns = (Collection) ret; + switch (ns.size()) + { + case 0: + ret = null; + break; + case 1: + ret = (Node) ns.iterator().next(); + break; + default: + throw new XPathExpressionException("multiple nodes in node-set"); + } + } + else if (ret != null) + { + throw new XPathExpressionException("return value is not a node-set"); + } + } + else if (XPathConstants.NODESET == returnType) + { + if (ret != null && !(ret instanceof Collection)) + { + throw new XPathExpressionException("return value is not a node-set"); + } + } + } + return ret; + } + + public String evaluate(Object item) + throws XPathExpressionException + { + return (String) evaluate(item, XPathConstants.STRING); + } + + public Object evaluate(InputSource source, QName returnType) + throws XPathExpressionException + { + try + { + DocumentBuilderFactory factory = + new gnu.xml.dom.JAXPFactory(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document doc = builder.parse(source); + return evaluate(doc, returnType); + } + catch (ParserConfigurationException e) + { + throw new XPathExpressionException(e); + } + catch (SAXException e) + { + throw new XPathExpressionException(e); + } + catch (IOException e) + { + throw new XPathExpressionException(e); + } + } + + public String evaluate(InputSource source) + throws XPathExpressionException + { + return (String) evaluate(source, XPathConstants.STRING); + } + + public abstract Object evaluate(Node context, int pos, int len); + + public abstract Expr clone(Object context); + + /* -- 4.1 Node Set Functions -- */ + + /** + * The id function selects elements by their unique ID. + * When the argument to id is of type node-set, then the result is + * the union of the result of applying id to the string-value of each of + * the nodes in the argument node-set. When the argument to id is of any + * other type, the argument is converted to a string as if by a call to + * the string function; the string is split into a whitespace-separated + * list of tokens (whitespace is any sequence of characters matching the + * production S); the result is a node-set containing the elements in the + * same document as the context node that have a unique ID equal to any of + * the tokens in the list. + */ + public static Collection _id(Node context, Object object) + { + Set ret = new HashSet(); + if (object instanceof Collection) + { + Collection nodeSet = (Collection) object; + for (Iterator i = nodeSet.iterator(); i.hasNext(); ) + { + String string = stringValue((Node) i.next()); + ret.addAll(_id (context, string)); + } + } + else + { + Document doc = (context instanceof Document) ? (Document) context : + context.getOwnerDocument(); + String string = _string(context, object); + StringTokenizer st = new StringTokenizer(string, " \t\r\n"); + while (st.hasMoreTokens()) + { + Node element = doc.getElementById(st.nextToken()); + if (element != null) + { + ret.add(element); + } + } + } + return ret; + } + + /** + * The local-name function returns the local part of the expanded-name of + * the node in the argument node-set that is first in document order. If + * the argument node-set is empty or the first node has no expanded-name, + * an empty string is returned. If the argument is omitted, it defaults to + * a node-set with the context node as its only member. + */ + public static String _local_name(Node context, Collection nodeSet) + { + Node node = (nodeSet == null || nodeSet.size() == 0) ? context : + firstNode(nodeSet); + return node.getLocalName(); + } + + /** + * The namespace-uri function returns the namespace URI of the + * expanded-name of the node in the argument node-set that is first in + * document order. If the argument node-set is empty, the first node has + * no expanded-name, or the namespace URI of the expanded-name is null, an + * empty string is returned. If the argument is omitted, it defaults to a + * node-set with the context node as its only member. + */ + public static String _namespace_uri(Node context, Collection nodeSet) + { + Node node = (nodeSet == null || nodeSet.size() == 0) ? context : + firstNode(nodeSet); + return node.getNamespaceURI(); + } + + /** + * The name function returns a string containing a QName representing the + * expanded-name of the node in the argument node-set that is first in + * document order. The QName must represent the expanded-name with respect + * to the namespace declarations in effect on the node whose expanded-name + * is being represented. Typically, this will be the QName that occurred + * in the XML source. This need not be the case if there are namespace + * declarations in effect on the node that associate multiple prefixes + * with the same namespace. However, an implementation may include + * information about the original prefix in its representation of nodes; + * in this case, an implementation can ensure that the returned string is + * always the same as the QName used in the XML source. If the argument + * node-set is empty or the first node has no expanded-name, an empty + * string is returned. If the argument it omitted, it defaults to a + * node-set with the context node as its only member. + */ + public static String _name(Node context, Collection nodeSet) + { + Node node = (nodeSet == null || nodeSet.size() == 0) ? context : + firstNode(nodeSet); + switch (node.getNodeType()) + { + case Node.ATTRIBUTE_NODE: + case Node.ELEMENT_NODE: + case Node.PROCESSING_INSTRUCTION_NODE: + return node.getNodeName(); + default: + return ""; + } + } + + /** + * Returns the first node in the set in document order. + */ + static Node firstNode(Collection nodeSet) + { + List list = new ArrayList(nodeSet); + Collections.sort(list, documentOrderComparator); + return (Node) list.get(0); + } + + /* -- 4.2 String Functions -- */ + + /** + * Implementation of the XPath <code>string</code> function. + */ + public static String _string(Node context, Object object) + { + if (object == null) + { + return stringValue(context); + } + if (object instanceof String) + { + return (String) object; + } + if (object instanceof Boolean) + { + return object.toString(); + } + if (object instanceof Double) + { + double d = ((Double) object).doubleValue(); + if (Double.isNaN(d)) + { + return "NaN"; + } + else if (d == 0.0d) + { + return "0"; + } + else if (Double.isInfinite(d)) + { + if (d < 0) + { + return "-Infinity"; + } + else + { + return "Infinity"; + } + } + else + { + String ret = decimalFormat.format(d); + if (ret.endsWith (".0")) + { + ret = ret.substring(0, ret.length() - 2); + } + return ret; + } + } + if (object instanceof Collection) + { + Collection nodeSet = (Collection) object; + if (nodeSet.isEmpty()) + { + return ""; + } + Node node = firstNode(nodeSet); + return stringValue(node); + } + throw new IllegalArgumentException(object.toString()); + } + + /* -- 4.3 Boolean Functions -- */ + + /** + * Implementation of the XPath <code>boolean</code> function. + */ + public static boolean _boolean(Node context, Object object) + { + if (object instanceof Boolean) + { + return ((Boolean) object).booleanValue(); + } + if (object instanceof Double) + { + return ((Double) object).doubleValue() != 0.0; + } + if (object instanceof String) + { + return ((String) object).length() != 0; + } + if (object instanceof Collection) + { + return ((Collection) object).size() != 0; + } + return false; // TODO user defined types + } + + /* -- 4.4 Number Functions -- */ + + /** + * Implementation of the XPath <code>number</code> function. + */ + public static double _number(Node context, Object object) + { + if (object == null) + { + object = Collections.singleton(context); + } + if (object instanceof Double) + { + return ((Double) object).doubleValue(); + } + if (object instanceof Boolean) + { + return ((Boolean) object).booleanValue() ? 1.0 : 0.0; + } + if (object instanceof Collection) + { + // Convert node-set to string + object = stringValue((Collection) object); + } + if (object instanceof String) + { + String string = ((String) object).trim(); + try + { + return Double.parseDouble(string); + } + catch (NumberFormatException e) + { + return Double.NaN; + } + } + return Double.NaN; // TODO user-defined types + } + + /** + * Computes the XPath string-value of the specified node-set. + */ + public static String stringValue(Collection nodeSet) + { + StringBuffer buf = new StringBuffer(); + for (Iterator i = nodeSet.iterator(); i.hasNext(); ) + { + buf.append(stringValue((Node) i.next())); + } + return buf.toString(); + } + + /** + * Computes the XPath string-value of the specified node. + */ + public static String stringValue(Node node) + { + return stringValue(node, false); + } + + static String stringValue(Node node, boolean elementMode) + { + switch (node.getNodeType()) + { + case Node.DOCUMENT_NODE: // 5.1 Root Node + case Node.DOCUMENT_FRAGMENT_NODE: + case Node.ELEMENT_NODE: // 5.2 Element Nodes + StringBuffer buf = new StringBuffer(); + for (Node ctx = node.getFirstChild(); ctx != null; + ctx = ctx.getNextSibling()) + { + buf.append(stringValue(ctx, true)); + } + return buf.toString(); + case Node.TEXT_NODE: // 5.7 Text Nodes + case Node.CDATA_SECTION_NODE: + return node.getNodeValue(); + case Node.ATTRIBUTE_NODE: // 5.3 Attribute Nodes + case Node.PROCESSING_INSTRUCTION_NODE: // 5.5 Processing Instruction + case Node.COMMENT_NODE: // 5.6 Comment Nodes + if (!elementMode) + { + return node.getNodeValue(); + } + default: + return ""; + } + } + +} diff --git a/libjava/gnu/xml/xpath/FalseFunction.java b/libjava/gnu/xml/xpath/FalseFunction.java new file mode 100644 index 0000000..85417a1 --- /dev/null +++ b/libjava/gnu/xml/xpath/FalseFunction.java @@ -0,0 +1,66 @@ +/* FalseFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import org.w3c.dom.Node; + +/** + * The <code>false</code> function returns false. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class FalseFunction + extends Expr +{ + + public Object evaluate(Node context, int pos, int len) + { + return Boolean.FALSE; + } + + public Expr clone(Object context) + { + return new FalseFunction(); + } + + public String toString() + { + return "false()"; + } + +} diff --git a/libjava/gnu/xml/xpath/FloorFunction.java b/libjava/gnu/xml/xpath/FloorFunction.java new file mode 100644 index 0000000..dd9cd84 --- /dev/null +++ b/libjava/gnu/xml/xpath/FloorFunction.java @@ -0,0 +1,83 @@ +/* FloorFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.List; +import org.w3c.dom.Node; + +/** + * The <code>floor</code> function returns the largest (closest to positive + * infinity) number that is not greater than the argument and that is an + * integer. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class FloorFunction + extends Expr +{ + + final Expr arg; + + FloorFunction(List args) + { + this((Expr) args.get(0)); + } + + FloorFunction(Expr arg) + { + this.arg = arg; + } + + public Object evaluate(Node context, int pos, int len) + { + Object val = arg.evaluate(context, pos, len); + double n = _number(context, val); + return new Double(Math.floor(n)); + } + + public Expr clone(Object context) + { + return new FloorFunction(arg.clone(context)); + } + + public String toString() + { + return "floor(" + arg + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/Function.java b/libjava/gnu/xml/xpath/Function.java new file mode 100644 index 0000000..d40dff6 --- /dev/null +++ b/libjava/gnu/xml/xpath/Function.java @@ -0,0 +1,57 @@ +/* Function.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.List; + +/** + * Interface to be implemented by external functions that need to receive + * parameter values. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public interface Function +{ + + /** + * Sets the list of expressions to evaluate as parameter values. + */ + void setArguments(List args); + +} + diff --git a/libjava/gnu/xml/xpath/FunctionCall.java b/libjava/gnu/xml/xpath/FunctionCall.java new file mode 100644 index 0000000..2c77b58 --- /dev/null +++ b/libjava/gnu/xml/xpath/FunctionCall.java @@ -0,0 +1,151 @@ +/* FunctionCall.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import javax.xml.namespace.QName; +import javax.xml.xpath.XPathFunction; +import javax.xml.xpath.XPathFunctionException; +import javax.xml.xpath.XPathFunctionResolver; +import org.w3c.dom.Node; + +/** + * Executes an XPath core or extension function. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public final class FunctionCall + extends Expr +{ + + final XPathFunctionResolver resolver; + final String name; + final List args; + + public FunctionCall(XPathFunctionResolver resolver, String name) + { + this(resolver, name, Collections.EMPTY_LIST); + } + + public FunctionCall(XPathFunctionResolver resolver, String name, List args) + { + this.resolver = resolver; + this.name = name; + this.args = args; + } + + public Object evaluate(Node context, int pos, int len) + { + if (resolver != null) + { + QName qname = QName.valueOf(name); + int arity = args.size(); + XPathFunction function = resolver.resolveFunction(qname, arity); + if (function != null) + { + //System.err.println("Calling "+toString()+" with "+values); + if (function instanceof Expr) + { + if (function instanceof Function) + { + ((Function) function).setArguments(args); + } + return ((Expr) function).evaluate(context, pos, len); + } + else + { + List values = new ArrayList(arity); + for (int i = 0; i < arity; i++) + { + Expr arg = (Expr) args.get(i); + values.add(arg.evaluate(context, pos, len)); + } + try + { + return function.evaluate(values); + } + catch (XPathFunctionException e) + { + e.printStackTrace(System.err); // FIXME + throw new RuntimeException(e.getMessage(), e); + } + } + } + } + throw new IllegalArgumentException("Invalid function call: " + + toString()); + } + + public Expr clone(Object context) + { + int len = args.size(); + List args2 = new ArrayList(len); + for (int i = 0; i < len; i++) + { + args2.add(((Expr) args.get(i)).clone(context)); + } + XPathFunctionResolver r = resolver; + if (context instanceof XPathFunctionResolver) + { + r = (XPathFunctionResolver) context; + } + return new FunctionCall(r, name, args2); + } + + public String toString() + { + StringBuffer buf = new StringBuffer(); + buf.append(name); + buf.append('('); + int len = args.size(); + for (int i = 0; i < len; i++) + { + if (i > 0) + { + buf.append(','); + } + buf.append(args.get(i)); + } + buf.append(')'); + return buf.toString(); + } + +} diff --git a/libjava/gnu/xml/xpath/IdFunction.java b/libjava/gnu/xml/xpath/IdFunction.java new file mode 100644 index 0000000..a32cb89 --- /dev/null +++ b/libjava/gnu/xml/xpath/IdFunction.java @@ -0,0 +1,96 @@ +/* IdFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.Collection; +import java.util.List; +import org.w3c.dom.Node; + +/** + * The <code>id</code> function selects elements by their unique ID. + * When the argument to id is of type node-set, then the result is + * the union of the result of applying id to the string-value of each of the + * nodes in the argument node-set. When the argument to id is of any other + * type, the argument is converted to a string as if by a call to the string + * function; the string is split into a whitespace-separated list of tokens + * (whitespace is any sequence of characters matching the production S); the + * result is a node-set containing the elements in the same document as the + * context node that have a unique ID equal to any of the tokens in the + * list. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public final class IdFunction + extends Pattern +{ + + final Expr arg; + + IdFunction(List args) + { + this((Expr) args.get(0)); + } + + public IdFunction(Expr arg) + { + this.arg = arg; + } + + public boolean matches(Node context) + { + Object ret = evaluate(context, 1, 1); + return !((Collection) ret).isEmpty(); + } + + public Object evaluate(Node context, int pos, int len) + { + Object val = arg.evaluate(context, pos, len); + return _id(context, val); + } + + public Expr clone(Object context) + { + return new IdFunction(arg.clone(context)); + } + + public String toString() + { + return "id(" + arg + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/LangFunction.java b/libjava/gnu/xml/xpath/LangFunction.java new file mode 100644 index 0000000..d44c72c --- /dev/null +++ b/libjava/gnu/xml/xpath/LangFunction.java @@ -0,0 +1,110 @@ +/* LangFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.List; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +/** + * The <code>lang</code> function returns true or false depending on whether + * the language of the context node as specified by xml:lang attributes is + * the same as or is a sublanguage of the language specified by the argument + * string. The language of the context node is determined by the value of + * the xml:lang attribute on the context node, or, if the context node has + * no xml:lang attribute, by the value of the xml:lang attribute on the + * nearest ancestor of the context node that has an xml:lang attribute. If + * there is no such attribute, then lang returns false. If there is such an + * attribute, then lang returns true if the attribute value is equal to the + * argument ignoring case, or if there is some suffix starting with - such + * that the attribute value is equal to the argument ignoring that suffix of + * the attribute value and ignoring case. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class LangFunction + extends Expr +{ + + final Expr arg; + + LangFunction(List args) + { + this((Expr) args.get(0)); + } + + LangFunction(Expr arg) + { + this.arg = arg; + } + + public Object evaluate(Node context, int pos, int len) + { + Object val = arg.evaluate(context, pos, len); + String lang = _string(context, val); + String clang = getLang(context); + while (clang == null && context != null) + { + context = context.getParentNode(); + clang = getLang(context); + } + boolean ret = (clang == null) ? false : + clang.toLowerCase().startsWith(lang.toLowerCase()); + return ret ? Boolean.TRUE : Boolean.FALSE; + } + + String getLang(Node node) + { + if (node.getNodeType() == Node.ELEMENT_NODE) + { + return ((Element) node).getAttribute("xml:lang"); + } + return null; + } + + public Expr clone(Object context) + { + return new IdFunction(arg.clone(context)); + } + + public String toString() + { + return "lang(" + arg + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/LastFunction.java b/libjava/gnu/xml/xpath/LastFunction.java new file mode 100644 index 0000000..a7458a3 --- /dev/null +++ b/libjava/gnu/xml/xpath/LastFunction.java @@ -0,0 +1,67 @@ +/* LastFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import org.w3c.dom.Node; + +/** + * The <code>last</code> function returns a number equal to the context + * size from the expression evaluation context. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class LastFunction + extends Expr +{ + + public Object evaluate(Node context, int pos, int len) + { + return new Double((double) len); + } + + public Expr clone(Object context) + { + return new LastFunction(); + } + + public String toString() + { + return "last()"; + } + +} diff --git a/libjava/gnu/xml/xpath/LocalNameFunction.java b/libjava/gnu/xml/xpath/LocalNameFunction.java new file mode 100644 index 0000000..d91f973 --- /dev/null +++ b/libjava/gnu/xml/xpath/LocalNameFunction.java @@ -0,0 +1,87 @@ +/* LocalNameFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.Collection; +import java.util.List; +import org.w3c.dom.Node; + +/** + * The <code>local-name</code> function returns the local part of the + * expanded-name of the node in the argument node-set that is first in + * document order. + * If the argument node-set is empty or the first node has no expanded-name, + * an empty string is returned. If the argument is omitted, it defaults to a + * node-set with the context node as its only member. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class LocalNameFunction + extends Expr +{ + + final Expr arg; + + LocalNameFunction(List args) + { + this(args.size() > 0 ? (Expr) args.get(0) : null); + } + + LocalNameFunction(Expr arg) + { + this.arg = arg; + } + + public Object evaluate(Node context, int pos, int len) + { + Object val = (arg == null) ? null : arg.evaluate(context, pos, len); + return _local_name(context, (Collection) val); + } + + public Expr clone(Object context) + { + return new LocalNameFunction((arg == null) ? null : + arg.clone(context)); + } + + public String toString() + { + return (arg == null) ? "local-name()" : "local-name(" + arg + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/NameFunction.java b/libjava/gnu/xml/xpath/NameFunction.java new file mode 100644 index 0000000..a42681d --- /dev/null +++ b/libjava/gnu/xml/xpath/NameFunction.java @@ -0,0 +1,95 @@ +/* NameFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.Collection; +import java.util.List; +import org.w3c.dom.Node; + +/** + * The <code>name</code> function returns a string containing a QName + * representing the expanded-name of the node in the argument node-set that + * is first in document order. The QName must represent the expanded-name + * with respect to the namespace declarations in effect on the node whose + * expanded-name is being represented. Typically, this will be the QName + * that occurred in the XML source. This need not be the case if there are + * namespace declarations in effect on the node that associate multiple + * prefixes with the same namespace. However, an implementation may include + * information about the original prefix in its representation of nodes; in + * this case, an implementation can ensure that the returned string is + * always the same as the QName used in the XML source. If the argument + * node-set is empty or the first node has no expanded-name, an empty string + * is returned. If the argument it omitted, it defaults to a node-set with + * the context node as its only member. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class NameFunction + extends Expr +{ + + final Expr arg; + + NameFunction(List args) + { + this(args.size() > 0 ? (Expr) args.get(0) : null); + } + + NameFunction(Expr arg) + { + this.arg = arg; + } + + public Object evaluate(Node context, int pos, int len) + { + Object val = (arg == null) ? null : arg.evaluate(context, pos, len); + return _name(context, (Collection) val); + } + + public Expr clone(Object context) + { + return new NameFunction((arg == null) ? null : + arg.clone(context)); + } + + public String toString() + { + return (arg == null) ? "name()" : "name(" + arg + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/NameTest.java b/libjava/gnu/xml/xpath/NameTest.java new file mode 100644 index 0000000..ececc92 --- /dev/null +++ b/libjava/gnu/xml/xpath/NameTest.java @@ -0,0 +1,131 @@ +/* NameTest.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import javax.xml.XMLConstants; +import javax.xml.namespace.QName; +import org.w3c.dom.Node; + +/** + * Tests whether a node has the specified name. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public final class NameTest + extends Test +{ + + final QName qName; + final boolean anyLocalName; + final boolean any; + + public NameTest(QName qName, boolean anyLocalName, boolean any) + { + this.anyLocalName = anyLocalName; + this.any = any; + this.qName = qName; + } + + public boolean matchesAny() + { + return any; + } + + public boolean matchesAnyLocalName() + { + return anyLocalName; + } + + public boolean matches(Node node, int pos, int len) + { + switch (node.getNodeType()) + { + case Node.ATTRIBUTE_NODE: + // Do not match namespace attributes + String uri = node.getNamespaceURI(); + if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(uri) || + XMLConstants.XMLNS_ATTRIBUTE.equals(node.getPrefix()) || + XMLConstants.XMLNS_ATTRIBUTE.equals(node.getNodeName())) + { + return false; + } + // Fall through + case Node.ELEMENT_NODE: + break; + default: + return false; + } + if (any) + { + return true; + } + String uri = qName.getNamespaceURI(); + if (!equal(uri, node.getNamespaceURI())) + { + return false; + } + if (anyLocalName) + { + return true; + } + String localName = qName.getLocalPart(); + return (localName.equals(node.getLocalName())); + } + + final boolean equal(String s1, String s2) + { + return (((s1 == null || s1.length() == 0) && + (s2 == null || s2.length() == 0)) || + s1 != null && s1.equals(s2)); + } + + public Test clone(Object context) + { + return new NameTest(qName, anyLocalName, any); + } + + public String toString () + { + if (any) + { + return "*"; + } + return qName.toString(); + } + +} diff --git a/libjava/gnu/xml/xpath/NamespaceTest.java b/libjava/gnu/xml/xpath/NamespaceTest.java new file mode 100644 index 0000000..bdfe961 --- /dev/null +++ b/libjava/gnu/xml/xpath/NamespaceTest.java @@ -0,0 +1,118 @@ +/* NamespaceTest.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import javax.xml.XMLConstants; +import javax.xml.namespace.QName; +import org.w3c.dom.Node; + +/** + * Tests whether a namespace attribute has the specified name. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public final class NamespaceTest + extends Test +{ + + final QName qName; + final boolean anyLocalName; + final boolean any; + + public NamespaceTest(QName qName, boolean anyLocalName, boolean any) + { + this.anyLocalName = anyLocalName; + this.any = any; + this.qName = qName; + } + + public boolean matchesAny() + { + return any; + } + + public boolean matchesAnyLocalName() + { + return anyLocalName; + } + + public boolean matches(Node node, int pos, int len) + { + switch (node.getNodeType()) + { + case Node.ATTRIBUTE_NODE: + // Only match namespace attributes + String uri = node.getNamespaceURI(); + if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(uri) || + XMLConstants.XMLNS_ATTRIBUTE.equals(node.getPrefix()) || + XMLConstants.XMLNS_ATTRIBUTE.equals(node.getNodeName())) + { + break; + } + // Fall through + default: + // Only process namespace attributes + return false; + } + if (any) + { + return true; + } + if (anyLocalName) + { + return true; + } + String localName = qName.getLocalPart(); + return (localName.equals(node.getLocalName())); + } + + public Test clone(Object context) + { + return new NamespaceTest(qName, anyLocalName, any); + } + + public String toString () + { + if (any) + { + return "*"; + } + return qName.toString(); + } + +} diff --git a/libjava/gnu/xml/xpath/NamespaceUriFunction.java b/libjava/gnu/xml/xpath/NamespaceUriFunction.java new file mode 100644 index 0000000..ee8978e --- /dev/null +++ b/libjava/gnu/xml/xpath/NamespaceUriFunction.java @@ -0,0 +1,87 @@ +/* NamespaceUriFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.Collection; +import java.util.List; +import org.w3c.dom.Node; + +/** + * The <code>namespace-uri</code> function returns the namespace URI of the + * expanded-name of the node in the argument node-set that is first in + * document order. If the argument node-set is empty, the first node has no + * expanded-name, or the namespace URI of the expanded-name is null, an + * empty string is returned. If the argument is omitted, it defaults to a + * node-set with the context node as its only member. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class NamespaceUriFunction + extends Expr +{ + + final Expr arg; + + NamespaceUriFunction(List args) + { + this(args.size() > 0 ? (Expr) args.get(0) : null); + } + + NamespaceUriFunction(Expr arg) + { + this.arg = arg; + } + + public Object evaluate(Node context, int pos, int len) + { + Object val = (arg == null) ? null : arg.evaluate(context, pos, len); + return _namespace_uri(context, (Collection) val); + } + + public Expr clone(Object context) + { + return new NamespaceUriFunction((arg == null) ? null : + arg.clone(context)); + } + + public String toString() + { + return (arg == null) ? "namespace-uri()" : "namespace-uri(" + arg + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/NegativeExpr.java b/libjava/gnu/xml/xpath/NegativeExpr.java new file mode 100644 index 0000000..5d7c00d --- /dev/null +++ b/libjava/gnu/xml/xpath/NegativeExpr.java @@ -0,0 +1,75 @@ +/* NegativeExpr.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import org.w3c.dom.Node; + +/** + * Unary negative. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class NegativeExpr + extends Expr +{ + + final Expr expr; + + NegativeExpr(Expr expr) + { + this.expr = expr; + } + + public Object evaluate(Node context, int pos, int len) + { + Object val = expr.evaluate(context, pos, len); + double n = _number(context, val); + return new Double(-n); + } + + public Expr clone(Object context) + { + return new NegativeExpr(expr.clone(context)); + } + + public String toString() + { + return "-" + expr; + } + +} diff --git a/libjava/gnu/xml/xpath/NodeTypeTest.java b/libjava/gnu/xml/xpath/NodeTypeTest.java new file mode 100644 index 0000000..d7798a2 --- /dev/null +++ b/libjava/gnu/xml/xpath/NodeTypeTest.java @@ -0,0 +1,130 @@ +/* NodeTypeTest.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import org.w3c.dom.Node; + +/** + * Tests whether a node is of a given type. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public final class NodeTypeTest + extends Test +{ + + final short type; + final String data; + + public NodeTypeTest(short type) + { + this(type, null); + } + + public NodeTypeTest(short type, String data) + { + this.type = type; + this.data = data; + } + + public short getNodeType() + { + return type; + } + + public String getData() + { + return data; + } + + public boolean matches(Node node, int pos, int len) + { + short nodeType = node.getNodeType(); + switch (nodeType) + { + case Node.ELEMENT_NODE: + case Node.ATTRIBUTE_NODE: + case Node.TEXT_NODE: + case Node.CDATA_SECTION_NODE: + case Node.COMMENT_NODE: + case Node.PROCESSING_INSTRUCTION_NODE: + if (type > 0) + { + if (nodeType != type) + { + return false; + } + if (data != null && !data.equals(node.getNodeValue())) + { + return false; + } + } + return true; + default: + // Not part of XPath data model + return false; + } + } + + public Test clone(Object context) + { + return new NodeTypeTest(type, data); + } + + public String toString() + { + switch (type) + { + case 0: + return "node()"; + case Node.TEXT_NODE: + return "text()"; + case Node.COMMENT_NODE: + return "comment()"; + case Node.PROCESSING_INSTRUCTION_NODE: + if (data != null) + { + return "processing-instruction('" + data + "')"; + } + return "processing-instruction()"; + default: + throw new IllegalStateException(); + } + } + +} diff --git a/libjava/gnu/xml/xpath/NormalizeSpaceFunction.java b/libjava/gnu/xml/xpath/NormalizeSpaceFunction.java new file mode 100644 index 0000000..2dabe1e --- /dev/null +++ b/libjava/gnu/xml/xpath/NormalizeSpaceFunction.java @@ -0,0 +1,99 @@ +/* NormalizeSpaceFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.List; +import java.util.StringTokenizer; +import org.w3c.dom.Node; + +/** + * The <code>normalize-space</code> function returns the argument string + * with whitespace normalized by stripping leading and trailing whitespace + * and replacing sequences of whitespace characters by a single space. + * Whitespace characters are the same as those allowed by the S production + * in XML. If the argument is omitted, it defaults to the context node + * converted to a string, in other words the string-value of the context + * node. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class NormalizeSpaceFunction + extends Expr +{ + + final Expr arg; + + NormalizeSpaceFunction(List args) + { + this((Expr) args.get(0)); + } + + NormalizeSpaceFunction(Expr arg) + { + this.arg = arg; + } + + public Object evaluate(Node context, int pos, int len) + { + Object val = (arg == null) ? null : arg.evaluate(context, pos, len); + String s = _string(context, val); + StringTokenizer st = new StringTokenizer(s, " \t\r\n"); + StringBuffer buf = new StringBuffer(); + if (st.hasMoreTokens()) + { + buf.append(st.nextToken()); + while (st.hasMoreTokens()) + { + buf.append(' '); + buf.append(st.nextToken()); + } + } + return buf.toString(); + } + + public Expr clone(Object context) + { + return new NormalizeSpaceFunction(arg.clone(context)); + } + + public String toString() + { + return (arg == null) ? "normalize-space()" : "normalize-space(" + arg + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/NotFunction.java b/libjava/gnu/xml/xpath/NotFunction.java new file mode 100644 index 0000000..d7d1ad1 --- /dev/null +++ b/libjava/gnu/xml/xpath/NotFunction.java @@ -0,0 +1,81 @@ +/* NotFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.List; +import org.w3c.dom.Node; + +/** + * The <code>not</code> function returns true if its argument is false, + * and false otherwise. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class NotFunction + extends Expr +{ + + final Expr arg; + + NotFunction(List args) + { + this((Expr) args.get(0)); + } + + NotFunction(Expr arg) + { + this.arg = arg; + } + + public Object evaluate(Node context, int pos, int len) + { + Object val = arg.evaluate(context, pos, len); + return _boolean(context, val) ? Boolean.FALSE : Boolean.TRUE; + } + + public Expr clone(Object context) + { + return new NotFunction(arg.clone(context)); + } + + public String toString() + { + return "not(" + arg + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/NumberFunction.java b/libjava/gnu/xml/xpath/NumberFunction.java new file mode 100644 index 0000000..9436533 --- /dev/null +++ b/libjava/gnu/xml/xpath/NumberFunction.java @@ -0,0 +1,96 @@ +/* NumberFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.List; +import org.w3c.dom.Node; + +/** + * The <code>number</code> function converts its argument to a number as + * follows: + * <ul> + * <li>a string that consists of optional whitespace followed by an optional + * minus sign followed by a Number followed by whitespace is converted to + * the IEEE 754 number that is nearest (according to the IEEE 754 + * round-to-nearest rule) to the mathematical value represented by the + * string; any other string is converted to NaN</li> + * <li>boolean true is converted to 1; boolean false is converted to 0</li> + * <li>a node-set is first converted to a string as if by a call to the + * string function and then converted in the same way as a string + * argument</li> + * <li>an object of a type other than the four basic types is converted to a + * number in a way that is dependent on that type</li> + * </ul> + * If the argument is omitted, it defaults to a node-set with the context + * node as its only member. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class NumberFunction + extends Expr +{ + + final Expr arg; + + NumberFunction(List args) + { + this(args.size() > 0 ? (Expr) args.get(0) : null); + } + + NumberFunction(Expr arg) + { + this.arg = arg; + } + + public Object evaluate(Node context, int pos, int len) + { + Object val = (arg == null) ? null : arg.evaluate(context, pos, len); + return new Double(_number(context, val)); + } + + public Expr clone(Object context) + { + return new NumberFunction(arg.clone(context)); + } + + public String toString() + { + return "number(" + arg + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/OrExpr.java b/libjava/gnu/xml/xpath/OrExpr.java new file mode 100644 index 0000000..4cfda4a --- /dev/null +++ b/libjava/gnu/xml/xpath/OrExpr.java @@ -0,0 +1,81 @@ +/* OrExpr.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import org.w3c.dom.Node; + +/** + * Logical or. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public final class OrExpr + extends Expr +{ + + final Expr lhs; + final Expr rhs; + + public OrExpr(Expr lhs, Expr rhs) + { + this.lhs = lhs; + this.rhs = rhs; + } + + public Object evaluate(Node context, int pos, int len) + { + Object left = lhs.evaluate(context, pos, len); + if (_boolean(context, left)) + { + return Boolean.TRUE; + } + Object right = rhs.evaluate(context, pos, len); + return _boolean(context, right) ? Boolean.TRUE : Boolean.FALSE; + } + + public Expr clone(Object context) + { + return new OrExpr(lhs.clone(context), rhs.clone(context)); + } + + public String toString() + { + return lhs + " or " + rhs; + } + +} diff --git a/libjava/gnu/xml/xpath/ParenthesizedExpr.java b/libjava/gnu/xml/xpath/ParenthesizedExpr.java new file mode 100644 index 0000000..6c2d571 --- /dev/null +++ b/libjava/gnu/xml/xpath/ParenthesizedExpr.java @@ -0,0 +1,84 @@ +/* ParenthesizedExpr.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.w3c.dom.Node; + +/** + * Simple subexpression. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class ParenthesizedExpr + extends Expr +{ + + final Expr expr; + + ParenthesizedExpr(Expr expr) + { + this.expr = expr; + } + + public Object evaluate(Node context, int pos, int len) + { + Object ret = expr.evaluate(context, pos, len); + if (ret instanceof Collection) + { + List list = new ArrayList((Collection) ret); + Collections.sort(list, documentOrderComparator); + ret = list; + } + return ret; + } + + public Expr clone(Object context) + { + return new ParenthesizedExpr(expr.clone(context)); + } + + public String toString() + { + return "(" + expr + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/Path.java b/libjava/gnu/xml/xpath/Path.java new file mode 100644 index 0000000..488215e --- /dev/null +++ b/libjava/gnu/xml/xpath/Path.java @@ -0,0 +1,54 @@ +/* Path.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.Collection; +import org.w3c.dom.Node; + +/** + * An XPath path component expression. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +abstract class Path + extends Pattern +{ + + abstract Collection evaluate(Node context, Collection nodeSet); + +} diff --git a/libjava/gnu/xml/xpath/Pattern.java b/libjava/gnu/xml/xpath/Pattern.java new file mode 100644 index 0000000..dcce68f --- /dev/null +++ b/libjava/gnu/xml/xpath/Pattern.java @@ -0,0 +1,54 @@ +/* Pattern.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import org.w3c.dom.Node; + +/** + * Interface implemented by expressions that can for part of XSL patterns. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public abstract class Pattern + extends Expr +{ + + public abstract boolean matches(Node context); + +} + diff --git a/libjava/gnu/xml/xpath/PositionFunction.java b/libjava/gnu/xml/xpath/PositionFunction.java new file mode 100644 index 0000000..6d6f7ad --- /dev/null +++ b/libjava/gnu/xml/xpath/PositionFunction.java @@ -0,0 +1,67 @@ +/* PositionFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import org.w3c.dom.Node; + +/** + * The <code>position</code> function returns a number equal to the context + * position from the expression evaluation context. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class PositionFunction + extends Expr +{ + + public Object evaluate(Node context, int pos, int len) + { + return new Double((double) pos); + } + + public Expr clone(Object context) + { + return new PositionFunction(); + } + + public String toString() + { + return "position()"; + } + +} diff --git a/libjava/gnu/xml/xpath/Predicate.java b/libjava/gnu/xml/xpath/Predicate.java new file mode 100644 index 0000000..81e1755 --- /dev/null +++ b/libjava/gnu/xml/xpath/Predicate.java @@ -0,0 +1,79 @@ +/* Predicate.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import org.w3c.dom.Node; + +/** + * Tests whether an expression matches against a given context node. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class Predicate + extends Test +{ + + final Expr expr; + + Predicate(Expr expr) + { + this.expr = expr; + } + + public boolean matches(Node node, int pos, int len) + { + Object ret = expr.evaluate(node, pos, len); + if (ret instanceof Double) + { + // Same as [position() = x] + return ((Double) ret).intValue() == pos; + } + return expr._boolean(node, expr.evaluate(node, pos, len)); + } + + public Test clone(Object context) + { + return new Predicate(expr.clone(context)); + } + + public String toString() + { + return "[" + expr + "]"; + } + +} diff --git a/libjava/gnu/xml/xpath/RelationalExpr.java b/libjava/gnu/xml/xpath/RelationalExpr.java new file mode 100644 index 0000000..e039cc1 --- /dev/null +++ b/libjava/gnu/xml/xpath/RelationalExpr.java @@ -0,0 +1,102 @@ +/* RelationalExpr.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.Collection; +import org.w3c.dom.Node; + +/** + * Numerical comparison expression. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class RelationalExpr + extends Expr +{ + + final Expr lhs; + final Expr rhs; + final boolean lt; + final boolean eq; + + RelationalExpr(Expr lhs, Expr rhs, boolean lt, boolean eq) + { + this.lhs = lhs; + this.rhs = rhs; + this.lt = lt; + this.eq = eq; + } + + public Object evaluate(Node context, int pos, int len) + { + Object left = lhs.evaluate(context, pos, len); + Object right = rhs.evaluate(context, pos, len); + double ln = _number(context, left); + double rn = _number(context, right); + if (eq && ln == rn) + { + return Boolean.TRUE; + } + if (lt) + { + if (ln < rn || Double.isInfinite(rn)) + { + return Boolean.TRUE; + } + } + else + { + if (ln > rn || Double.isInfinite(ln)) + { + return Boolean.TRUE; + } + } + return Boolean.FALSE; + } + + public Expr clone(Object context) + { + return new RelationalExpr(lhs.clone(context), rhs.clone(context), lt, eq); + } + + public String toString() + { + return lhs + " " + (lt ? "<" : ">") + (eq ? "=" : "") + " " + rhs; + } + +} diff --git a/libjava/gnu/xml/xpath/Root.java b/libjava/gnu/xml/xpath/Root.java new file mode 100644 index 0000000..b8fe5df --- /dev/null +++ b/libjava/gnu/xml/xpath/Root.java @@ -0,0 +1,81 @@ +/* Root.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.Collection; +import java.util.Collections; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +/** + * Expression that evaluates to the document root. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public final class Root + extends Path +{ + + public boolean matches(Node node) + { + return (node.getNodeType() == Node.DOCUMENT_NODE); + } + + public Object evaluate(Node context, int pos, int len) + { + return evaluate(context, Collections.EMPTY_SET); + } + + Collection evaluate(Node context, Collection ns) + { + Document doc = (context instanceof Document) ? (Document) context : + context.getOwnerDocument(); + return Collections.singleton(doc); + } + + public Expr clone(Object context) + { + return new Root(); + } + + public String toString() + { + return "/"; + } + +} diff --git a/libjava/gnu/xml/xpath/RoundFunction.java b/libjava/gnu/xml/xpath/RoundFunction.java new file mode 100644 index 0000000..76f5d8a --- /dev/null +++ b/libjava/gnu/xml/xpath/RoundFunction.java @@ -0,0 +1,90 @@ +/* RoundFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.List; +import org.w3c.dom.Node; + +/** + * The <code>round</code> function returns the number that is closest to the + * argument and that is an integer. If there are two such numbers, then the + * one that is closest to positive infinity is returned. If the argument is + * NaN, then NaN is returned. If the argument is positive infinity, then + * positive infinity is returned. If the argument is negative infinity, then + * negative infinity is returned. If the argument is positive zero, then + * positive zero is returned. If the argument is negative zero, then + * negative zero is returned. If the argument is less than zero, but greater + * than or equal to -0.5, then negative zero is returned. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class RoundFunction + extends Expr +{ + + final Expr arg; + + RoundFunction(List args) + { + this((Expr) args.get(0)); + } + + RoundFunction(Expr arg) + { + this.arg = arg; + } + + public Object evaluate(Node context, int pos, int len) + { + Object val = arg.evaluate(context, pos, len); + double n = _number(context, val); + return (Double.isNaN(n) || Double.isInfinite(n)) ? + new Double(n) : new Double(Math.round(n)); + } + + public Expr clone(Object context) + { + return new RoundFunction(arg.clone(context)); + } + + public String toString() + { + return "round(" + arg + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/Selector.java b/libjava/gnu/xml/xpath/Selector.java new file mode 100644 index 0000000..5431cbe --- /dev/null +++ b/libjava/gnu/xml/xpath/Selector.java @@ -0,0 +1,500 @@ +/* Selector.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import javax.xml.XMLConstants; +import org.w3c.dom.Attr; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * A single component of a location path. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public final class Selector + extends Path +{ + + public static final int ANCESTOR = 0; + public static final int ANCESTOR_OR_SELF = 1; + public static final int ATTRIBUTE = 2; + public static final int CHILD = 3; + public static final int DESCENDANT = 4; + public static final int DESCENDANT_OR_SELF = 5; + public static final int FOLLOWING = 6; + public static final int FOLLOWING_SIBLING = 7; + public static final int NAMESPACE = 8; + public static final int PARENT = 9; + public static final int PRECEDING = 10; + public static final int PRECEDING_SIBLING = 11; + public static final int SELF = 12; + + /** + * Axis to select nodes in. + */ + final int axis; + + /** + * List of tests to perform on candidates. + */ + final Test[] tests; + + public Selector(int axis, List tests) + { + this.axis = axis; + this.tests = new Test[tests.size()]; + tests.toArray(this.tests); + if (axis == NAMESPACE && + this.tests.length > 0 && + this.tests[0] instanceof NameTest) + { + NameTest nt = (NameTest) this.tests[0]; + this.tests[0] = new NamespaceTest(nt.qName, nt.anyLocalName, nt.any); + } + } + + /** + * Returns the list of tests to perform on candidates. + */ + public Test[] getTests() + { + return tests; + } + + public boolean matches(Node context) + { + short nodeType = context.getNodeType(); + switch (axis) + { + case CHILD: + if (nodeType == Node.ATTRIBUTE_NODE) + { + return false; + } + break; + case ATTRIBUTE: + case NAMESPACE: + if (nodeType != Node.ATTRIBUTE_NODE) + { + return false; + } + break; + case DESCENDANT_OR_SELF: + return true; + default: + return false; + } + int tlen = tests.length; + if (tlen > 0) + { + int pos = getContextPosition(context); + int len = getContextSize(context); + for (int j = 0; j < tlen && len > 0; j++) + { + Test test = tests[j]; + if (!test.matches(context, pos, len)) + { + return false; + } + } + } + return true; + } + + private int getContextPosition(Node ctx) + { + int pos = 1; + for (ctx = ctx.getPreviousSibling(); ctx != null; + ctx = ctx.getPreviousSibling()) + { + pos++; + } + return pos; + } + + private int getContextSize(Node ctx) + { + if (ctx.getNodeType() == Node.ATTRIBUTE_NODE) + { + Node parent = ((Attr) ctx).getOwnerElement(); + return parent.getAttributes().getLength(); + } + Node parent = ctx.getParentNode(); + if (parent != null) + { + return parent.getChildNodes().getLength(); + } + return 1; + } + + public Object evaluate(Node context, int pos, int len) + { + Set acc = new LinkedHashSet(); + addCandidates(context, acc); + List candidates = new ArrayList(acc); + //Collections.sort(candidates, documentOrderComparator); + List ret = filterCandidates(candidates, false); + return ret; + } + + Collection evaluate(Node context, Collection ns) + { + Set acc = new LinkedHashSet(); + for (Iterator i = ns.iterator(); i.hasNext(); ) + { + addCandidates((Node) i.next(), acc); + } + List candidates = new ArrayList(acc); + //Collections.sort(candidates, documentOrderComparator); + List ret = filterCandidates(candidates, true); + return ret; + } + + /** + * Filter the given list of candidates according to the node tests. + */ + List filterCandidates(List candidates, boolean cascade) + { + int len = candidates.size(); + int tlen = tests.length; + if (tlen > 0 && len > 0) + { + // Present the result of each successful generation to the next test + for (int j = 0; j < tlen && len > 0; j++) + { + Test test = tests[j]; + List successful = new ArrayList(len); + for (int i = 0; i < len; i++) + { + Node node = (Node) candidates.get(i); + if (cascade) + { + // Documents and DocumentFragments should be considered + // if part of a location path where the axis involves + // the SELF concept + short nodeType = node.getNodeType(); + if ((nodeType == Node.DOCUMENT_NODE || + nodeType == Node.DOCUMENT_FRAGMENT_NODE) && + (axis == DESCENDANT_OR_SELF || + axis == ANCESTOR_OR_SELF || + axis == SELF) && + (tests.length == 1 && + tests[0] instanceof NodeTypeTest && + ((NodeTypeTest) tests[0]).type == (short) 0)) + { + successful.add(node); + continue; + } + } + if (test.matches(node, i + 1, len)) + { + successful.add(node); + } + /* + System.err.println("Testing "+node); + int p = getContextPosition(node); + int l = getContextSize(node); + if (test.matches(node, p, l)) + { + successful.add(node); + }*/ + } + candidates = successful; + len = candidates.size(); + } + } + return candidates; + } + + void addCandidates(Node context, Collection candidates) + { + // Build list of candidates + switch (axis) + { + case CHILD: + addChildNodes(context, candidates, false); + break; + case DESCENDANT: + addChildNodes(context, candidates, true); + break; + case DESCENDANT_OR_SELF: + candidates.add (context); + addChildNodes(context, candidates, true); + break; + case PARENT: + addParentNode(context, candidates, false); + break; + case ANCESTOR: + addParentNode(context, candidates, true); + break; + case ANCESTOR_OR_SELF: + candidates.add(context); + addParentNode(context, candidates, true); + break; + case FOLLOWING_SIBLING: + addFollowingNodes(context, candidates, false); + break; + case PRECEDING_SIBLING: + addPrecedingNodes(context, candidates, false); + break; + case FOLLOWING: + addFollowingNodes(context, candidates, true); + break; + case PRECEDING: + addPrecedingNodes(context, candidates, true); + break; + case ATTRIBUTE: + addAttributes(context, candidates); + break; + case NAMESPACE: + addNamespaceAttributes(context, candidates); + break; + case SELF: + candidates.add(context); + break; + } + } + + void addChildNodes(Node context, Collection acc, boolean recurse) + { + Node child = context.getFirstChild(); + while (child != null) + { + acc.add(child); + if (recurse) + { + addChildNodes(child, acc, recurse); + } + child = child.getNextSibling(); + } + } + + void addParentNode(Node context, Collection acc, boolean recurse) + { + Node parent = (context.getNodeType() == Node.ATTRIBUTE_NODE) ? + ((Attr) context).getOwnerElement() : context.getParentNode(); + if (parent != null) + { + acc.add(parent); + if (recurse) + { + addParentNode(parent, acc, recurse); + } + } + } + + void addFollowingNodes(Node context, Collection acc, boolean recurse) + { + Node cur = context.getNextSibling(); + while (cur != null) + { + acc.add(cur); + if (recurse) + { + addChildNodes(cur, acc, true); + } + cur = cur.getNextSibling(); + } + if (recurse) + { + context = (context.getNodeType() == Node.ATTRIBUTE_NODE) ? + ((Attr) context).getOwnerElement() : context.getParentNode(); + if (context != null) + { + addFollowingNodes(context, acc, recurse); + } + } + } + + void addPrecedingNodes(Node context, Collection acc, boolean recurse) + { + Node cur = context.getPreviousSibling(); + while (cur != null) + { + acc.add(cur); + if (recurse) + { + addChildNodes(cur, acc, true); + } + cur = cur.getPreviousSibling(); + } + if (recurse) + { + context = (context.getNodeType() == Node.ATTRIBUTE_NODE) ? + ((Attr) context).getOwnerElement() : context.getParentNode(); + if (context != null) + { + addPrecedingNodes(context, acc, recurse); + } + } + } + + void addAttributes(Node context, Collection acc) + { + NamedNodeMap attrs = context.getAttributes(); + if (attrs != null) + { + int attrLen = attrs.getLength(); + for (int i = 0; i < attrLen; i++) + { + Node attr = attrs.item(i); + if (!isNamespaceAttribute(attr)) + { + acc.add(attr); + } + } + } + } + + void addNamespaceAttributes(Node context, Collection acc) + { + NamedNodeMap attrs = context.getAttributes(); + if (attrs != null) + { + int attrLen = attrs.getLength(); + for (int i = 0; i < attrLen; i++) + { + Node attr = attrs.item(i); + if (isNamespaceAttribute(attr)) + { + acc.add(attr); + } + } + } + } + + final boolean isNamespaceAttribute(Node node) + { + String uri = node.getNamespaceURI(); + return (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(uri) || + XMLConstants.XMLNS_ATTRIBUTE.equals(node.getPrefix()) || + XMLConstants.XMLNS_ATTRIBUTE.equals(node.getNodeName())); + } + + public Expr clone(Object context) + { + int len = tests.length; + List tests2 = new ArrayList(len); + for (int i = 0; i < len; i++) + { + tests2.add(tests[i].clone(context)); + } + return new Selector(axis, tests2); + } + + public String toString() + { + StringBuffer buf = new StringBuffer(); + switch (axis) + { + case ANCESTOR: + buf.append("ancestor::"); + break; + case ANCESTOR_OR_SELF: + buf.append("ancestor-or-self::"); + break; + case ATTRIBUTE: + buf.append("attribute::"); + break; + case CHILD: + //buf.append("child::"); + break; + case DESCENDANT: + buf.append("descendant::"); + break; + case DESCENDANT_OR_SELF: + buf.append("descendant-or-self::"); + break; + case FOLLOWING: + buf.append("following::"); + break; + case FOLLOWING_SIBLING: + buf.append("following-sibling::"); + break; + case NAMESPACE: + buf.append("namespace::"); + break; + case PARENT: + if (tests.length == 0 || + (tests[0] instanceof NodeTypeTest && + ((NodeTypeTest) tests[0]).type == 0)) + { + return ".."; + } + buf.append("parent::"); + break; + case PRECEDING: + buf.append("preceding::"); + break; + case PRECEDING_SIBLING: + buf.append("preceding-sibling::"); + break; + case SELF: + if (tests.length == 0 || + (tests[0] instanceof NodeTypeTest && + ((NodeTypeTest) tests[0]).type == 0)) + { + return "."; + } + buf.append("self::"); + break; + } + if (tests.length == 0) + { + buf.append('*'); + } + else + { + for (int i = 0; i < tests.length; i++) + { + buf.append(tests[i]); + } + } + return buf.toString(); + } + +} diff --git a/libjava/gnu/xml/xpath/StartsWithFunction.java b/libjava/gnu/xml/xpath/StartsWithFunction.java new file mode 100644 index 0000000..c748295 --- /dev/null +++ b/libjava/gnu/xml/xpath/StartsWithFunction.java @@ -0,0 +1,86 @@ +/* StartsWithFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.List; +import org.w3c.dom.Node; + +/** + * The <code>starts-with</code> function returns true if the first argument + * string starts with the second argument string, and otherwise returns false. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class StartsWithFunction + extends Expr +{ + + final Expr arg1; + final Expr arg2; + + StartsWithFunction(List args) + { + this((Expr) args.get(0), (Expr) args.get(1)); + } + + StartsWithFunction(Expr arg1, Expr arg2) + { + this.arg1 = arg1; + this.arg2 = arg2; + } + + public Object evaluate(Node context, int pos, int len) + { + Object val1 = arg1.evaluate(context, pos, len); + Object val2 = arg2.evaluate(context, pos, len); + String s1 = _string(context, val1); + String s2 = _string(context, val2); + return s1.startsWith(s2) ? Boolean.TRUE : Boolean.FALSE; + } + + public Expr clone(Object context) + { + return new StartsWithFunction(arg1.clone(context), arg2.clone(context)); + } + + public String toString() + { + return "starts-with(" + arg1 + "," + arg2 + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/Steps.java b/libjava/gnu/xml/xpath/Steps.java new file mode 100644 index 0000000..abd30e4 --- /dev/null +++ b/libjava/gnu/xml/xpath/Steps.java @@ -0,0 +1,240 @@ +/* Steps.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.Set; +import org.w3c.dom.Attr; +import org.w3c.dom.Node; + +/** + * A list of transitions between components in a location path. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public final class Steps + extends Path +{ + + final LinkedList path; + + public Steps() + { + this(new LinkedList()); + } + + Steps(LinkedList path) + { + this.path = path; + } + + public boolean matches(Node context) + { + // Right to left + return matches(context, path.size() - 1); + } + + boolean matches(Node context, int pos) + { + Pattern right = (Pattern) path.get(pos); + if (!right.matches(context)) + { + return false; + } + if (pos > 0) + { + Pattern left = (Pattern) path.get(pos - 1); + Iterator j = possibleContexts(right, context).iterator(); + while (j.hasNext()) + { + Node candidate = (Node) j.next(); + if (left.matches(candidate) && + matches(candidate, pos - 1)) + { + return true; + } + // keep going, there may be another candidate + } + return false; + } + return true; + } + + /** + * Essentially the reverse of Selector.addCandidates. + * The idea is to determine possible context nodes for a match. + */ + Collection possibleContexts(Pattern pattern, Node context) + { + if (pattern instanceof Selector) + { + Selector s = (Selector) pattern; + Collection candidates = new LinkedHashSet(); + switch (s.axis) + { + case Selector.PARENT: + s.addChildNodes(context, candidates, false); + break; + case Selector.ANCESTOR: + s.addChildNodes(context, candidates, true); + break; + case Selector.ANCESTOR_OR_SELF: + candidates.add (context); + s.addChildNodes(context, candidates, true); + break; + case Selector.CHILD: + s.addParentNode(context, candidates, false); + break; + case Selector.DESCENDANT: + s.addParentNode(context, candidates, true); + break; + case Selector.DESCENDANT_OR_SELF: + candidates.add(context); + s.addParentNode(context, candidates, true); + break; + case Selector.PRECEDING_SIBLING: + s.addFollowingNodes(context, candidates, false); + break; + case Selector.FOLLOWING_SIBLING: + s.addPrecedingNodes(context, candidates, false); + break; + case Selector.PRECEDING: + s.addFollowingNodes(context, candidates, true); + break; + case Selector.FOLLOWING: + s.addPrecedingNodes(context, candidates, true); + break; + case Selector.ATTRIBUTE: + case Selector.NAMESPACE: + if (context.getNodeType() == Node.ATTRIBUTE_NODE) + { + candidates.add(((Attr) context).getOwnerElement()); + } + break; + case Selector.SELF: + candidates.add(context); + break; + } + return candidates; + } + return Collections.EMPTY_SET; + } + + public Object evaluate(Node context, int pos, int len) + { + //System.err.println(toString()+" evaluate"); + // Left to right + Iterator i = path.iterator(); + Expr lhs = (Expr) i.next(); + Object val = lhs.evaluate(context, pos, len); + //System.err.println("\tevaluate "+lhs+" = "+val); + while (val instanceof Collection && i.hasNext()) + { + Path rhs = (Path) i.next(); + val = rhs.evaluate(context, (Collection) val); + //System.err.println("\tevaluate "+rhs+" = "+val); + } + return val; + } + + Collection evaluate(Node context, Collection ns) + { + // Left to right + Iterator i = path.iterator(); + Expr lhs = (Expr) i.next(); + if (lhs instanceof Path) + { + ns = ((Path) lhs).evaluate(context, ns); + } + else + { + Set acc = new LinkedHashSet(); + int pos = 1, len = ns.size(); + for (Iterator j = ns.iterator(); j.hasNext(); ) + { + Node node = (Node) j.next(); + Object ret = lhs.evaluate(node, pos++, len); + if (ret instanceof Collection) + { + acc.addAll((Collection) ret); + } + } + ns = acc; + } + while (i.hasNext()) + { + Path rhs = (Path) i.next(); + ns = rhs.evaluate(context, ns); + } + return ns; + } + + public Expr clone(Object context) + { + int len = path.size(); + LinkedList path2 = new LinkedList(); + for (int i = 0; i < len; i++) + { + path2.add(((Expr) path.get(i)).clone(context)); + } + return new Steps(path2); + } + + public String toString() + { + StringBuffer buf = new StringBuffer(); + Iterator i = path.iterator(); + Expr expr = (Expr) i.next(); + if (!(expr instanceof Root)) + { + buf.append(expr); + } + while (i.hasNext()) + { + expr = (Expr) i.next(); + buf.append('/'); + buf.append(expr); + } + return buf.toString(); + } + +} diff --git a/libjava/gnu/xml/xpath/StringFunction.java b/libjava/gnu/xml/xpath/StringFunction.java new file mode 100644 index 0000000..658995c --- /dev/null +++ b/libjava/gnu/xml/xpath/StringFunction.java @@ -0,0 +1,113 @@ +/* StringFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.List; +import org.w3c.dom.Node; + +/** + * The <code>string function converts an object to a string as follows: + * <ul> + * <li>A node-set is converted to a string by returning the string-value of + * the node in the node-set that is first in document order. If the node-set + * is empty, an empty string is returned.</li> + * <li>A number is converted to a string as follows + * <ul> + * <li>NaN is converted to the string NaN</li> + * <li>positive zero is converted to the string 0</li> + * <li>negative zero is converted to the string 0</li> + * <li>positive infinity is converted to the string Infinity</li> + * <li>negative infinity is converted to the string -Infinity</li> + * <li>if the number is an integer, the number is represented in decimal + * form as a Number with no decimal point and no leading zeros, preceded by + * a minus sign (-) if the number is negative</li> + * <li>otherwise, the number is represented in decimal form as a Number + * including a decimal point with at least one digit before the decimal + * point and at least one digit after the decimal point, preceded by a minus + * sign (-) if the number is negative; there must be no leading zeros before + * the decimal point apart possibly from the one required digit immediately + * before the decimal point; beyond the one required digit after the decimal + * point there must be as many, but only as many, more digits as are needed + * to uniquely distinguish the number from all other IEEE 754 numeric + * values.</li> + * </ul> + * </li> + * <li>The boolean false value is converted to the string false. The boolean + * true value is converted to the string true.</li> + * <li>An object of a type other than the four basic types is converted to a + * string in a way that is dependent on that type.</li> + * </ul> + * If the argument is omitted, it defaults to a node-set with the context + * node as its only member. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class StringFunction + extends Expr +{ + + final Expr arg; + + StringFunction(List args) + { + this(args.size() > 0 ? (Expr) args.get(0) : null); + } + + StringFunction(Expr arg) + { + this.arg = arg; + } + + public Object evaluate(Node context, int pos, int len) + { + Object val = (arg == null) ? null : arg.evaluate(context, pos, len); + return _string(context, val); + } + + public Expr clone(Object context) + { + return new StringFunction((arg == null) ? null : + arg.clone(context)); + } + + public String toString() + { + return (arg == null) ? "string()" : "string(" + arg + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/StringLengthFunction.java b/libjava/gnu/xml/xpath/StringLengthFunction.java new file mode 100644 index 0000000..7ed0463 --- /dev/null +++ b/libjava/gnu/xml/xpath/StringLengthFunction.java @@ -0,0 +1,85 @@ +/* StringLengthFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.List; +import org.w3c.dom.Node; + +/** + * The <code>string-length</code> function returns the number of characters + * in the string. + * If the argument is omitted, it defaults to the context node converted to + * a string, in other words the string-value of the context node. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class StringLengthFunction + extends Expr +{ + + final Expr arg; + + StringLengthFunction(List args) + { + this(args.isEmpty() ? null : (Expr) args.get(0)); + } + + StringLengthFunction(Expr arg) + { + this.arg = arg; + } + + public Object evaluate(Node context, int pos, int len) + { + Object val = (arg == null) ? null : arg.evaluate(context, pos, len); + String s = _string(context, val); + return new Double((double) s.length()); + } + + public Expr clone(Object context) + { + return new StringLengthFunction((arg == null) ? null : + arg.clone(context)); + } + + public String toString() + { + return (arg == null) ? "string-length()" : "string-length(" + arg + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/SubstringAfterFunction.java b/libjava/gnu/xml/xpath/SubstringAfterFunction.java new file mode 100644 index 0000000..90b6060 --- /dev/null +++ b/libjava/gnu/xml/xpath/SubstringAfterFunction.java @@ -0,0 +1,92 @@ +/* SubstringAfterFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.List; +import org.w3c.dom.Node; + +/** + * The <code>substring-after</code> function returns the substring of the + * first argument string that follows the first occurrence of the second + * argument string in the first argument string, or the empty string if the + * first argument string does not contain the second argument string. For + * example, substring-after("1999/04/01","/") returns 04/01, and + * substring-after("1999/04/01","19") returns 99/04/01. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class SubstringAfterFunction + extends Expr +{ + + final Expr arg1; + final Expr arg2; + + SubstringAfterFunction(List args) + { + this((Expr) args.get(0), (Expr) args.get(1)); + } + + SubstringAfterFunction(Expr arg1, Expr arg2) + { + this.arg1 = arg1; + this.arg2 = arg2; + } + + public Object evaluate(Node context, int pos, int len) + { + Object val1 = arg1.evaluate(context, pos, len); + Object val2 = arg2.evaluate(context, pos, len); + String s1 = _string(context, val1); + String s2 = _string(context, val2); + int index = s1.indexOf(s2); + return (index == -1) ? "" : s1.substring(index + s2.length()); + } + + public Expr clone(Object context) + { + return new SubstringAfterFunction(arg1.clone(context), + arg2.clone(context)); + } + + public String toString() + { + return "substring-after(" + arg1 + "," + arg2 + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/SubstringBeforeFunction.java b/libjava/gnu/xml/xpath/SubstringBeforeFunction.java new file mode 100644 index 0000000..cadb77c --- /dev/null +++ b/libjava/gnu/xml/xpath/SubstringBeforeFunction.java @@ -0,0 +1,91 @@ +/* SubstringBeforeFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.List; +import org.w3c.dom.Node; + +/** + * The <code>substring-before</code> function returns the substring of the + * first argument string that precedes the first occurrence of the second + * argument string in the first argument string, or the empty string if the + * first argument string does not contain the second argument string. For + * example, substring-before("1999/04/01","/") returns 1999. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class SubstringBeforeFunction + extends Expr +{ + + final Expr arg1; + final Expr arg2; + + SubstringBeforeFunction(List args) + { + this((Expr) args.get(0), (Expr) args.get(1)); + } + + SubstringBeforeFunction(Expr arg1, Expr arg2) + { + this.arg1 = arg1; + this.arg2 = arg2; + } + + public Object evaluate(Node context, int pos, int len) + { + Object val1 = arg1.evaluate(context, pos, len); + Object val2 = arg2.evaluate(context, pos, len); + String s1 = _string(context, val1); + String s2 = _string(context, val2); + int index = s1.indexOf(s2); + return (index == -1) ? "" : s1.substring(0, index); + } + + public Expr clone(Object context) + { + return new SubstringBeforeFunction(arg1.clone(context), + arg2.clone(context)); + } + + public String toString() + { + return "substring-before(" + arg1 + "," + arg2 + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/SubstringFunction.java b/libjava/gnu/xml/xpath/SubstringFunction.java new file mode 100644 index 0000000..fdbbd06 --- /dev/null +++ b/libjava/gnu/xml/xpath/SubstringFunction.java @@ -0,0 +1,122 @@ +/* SubstringFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.List; +import org.w3c.dom.Node; + +/** + * The substring function returns the substring of the first argument + * starting at the position specified in the second argument with length + * specified in the third argument. For example, substring("12345",2,3) + * returns "234". If the third argument is not specified, it returns the + * substring starting at the position specified in the second argument and + * continuing to the end of the string. For example, substring("12345",2) + * returns "2345". + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class SubstringFunction + extends Expr +{ + + final Expr arg1; + final Expr arg2; + final Expr arg3; + + SubstringFunction(List args) + { + this((Expr) args.get(0), (Expr) args.get(1), + (args.size() > 2) ? (Expr) args.get(2) : null); + } + + SubstringFunction(Expr arg1, Expr arg2, Expr arg3) + { + this.arg1 = arg1; + this.arg2 = arg2; + this.arg3 = arg3; + } + + public Object evaluate(Node context, int pos, int len) + { + Object val1 = arg1.evaluate(context, pos, len); + Object val2 = arg2.evaluate(context, pos, len); + String s = _string(context, val1); + int p = (val2 instanceof Double) ? + ((Double) val2).intValue() : + (int) Math.round(_number(context, val2)); + p--; + if (p < 0) + { + p = 0; + } + + int l = s.length() - p; + if (l <= 0) + { + return ""; + } + + if (arg3 != null) + { + Object val3 = arg3.evaluate(context, pos, len); + int v3 = (val3 instanceof Double) ? + ((Double) val3).intValue() : + (int) Math.round(_number(context, val3)); + if (v3 < l) + { + l = v3; + } + } + + return s.substring(p, p + l); + } + + public Expr clone(Object context) + { + return new SubstringFunction(arg1.clone(context), arg2.clone(context), + (arg3 == null) ? null : arg3.clone(context)); + } + + public String toString() + { + return (arg3 == null) ? "substring(" + arg1 + "," + arg2 + ")" : + "substring(" + arg1 + "," + arg2 + "," + arg3 + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/SumFunction.java b/libjava/gnu/xml/xpath/SumFunction.java new file mode 100644 index 0000000..1501b46 --- /dev/null +++ b/libjava/gnu/xml/xpath/SumFunction.java @@ -0,0 +1,94 @@ +/* SumFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import org.w3c.dom.Node; + +/** + * The <code>sum</code> function returns the sum, for each node in the + * argument node-set, of the result of converting the string-values of the + * node to a number. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class SumFunction + extends Expr +{ + + final Expr arg; + + SumFunction(List args) + { + this((Expr) args.get(0)); + } + + SumFunction(Expr arg) + { + this.arg = arg; + } + + public Object evaluate(Node context, int pos, int len) + { + Object val = arg.evaluate(context, pos, len); + double sum = 0.0d; + if (val instanceof Collection) + { + for (Iterator i = ((Collection) val).iterator(); i.hasNext(); ) + { + Node node = (Node) i.next(); + String s = stringValue(node); + sum += _number(context, s); + } + } + return new Double(sum); + } + + public Expr clone(Object context) + { + return new SumFunction(arg.clone(context)); + } + + public String toString() + { + return "sum(" + arg + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/Test.java b/libjava/gnu/xml/xpath/Test.java new file mode 100644 index 0000000..865461c --- /dev/null +++ b/libjava/gnu/xml/xpath/Test.java @@ -0,0 +1,55 @@ +/* Test.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import org.w3c.dom.Node; + +/** + * A test that can be performed on a node to determine whether to include it + * in a selection. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public abstract class Test +{ + + public abstract boolean matches(Node node, int pos, int len); + + public abstract Test clone(Object context); + +} diff --git a/libjava/gnu/xml/xpath/TranslateFunction.java b/libjava/gnu/xml/xpath/TranslateFunction.java new file mode 100644 index 0000000..b92ec31 --- /dev/null +++ b/libjava/gnu/xml/xpath/TranslateFunction.java @@ -0,0 +1,126 @@ +/* TranslateFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.List; +import org.w3c.dom.Node; + +/** + * The <code>translate</code> function returns the first argument string + * with occurrences of characters in the second argument string replaced by + * the character at the corresponding position in the third argument string. + * For example, translate("bar","abc","ABC") returns the string BAr. If + * there is a character in the second argument string with no character at a + * corresponding position in the third argument string (because the second + * argument string is longer than the third argument string), then + * occurrences of that character in the first argument string are removed. + * For example, translate("--aaa--","abc-","ABC") returns "AAA". If a + * character occurs more than once in the second argument string, then the + * first occurrence determines the replacement character. If the third + * argument string is longer than the second argument string, then excess + * characters are ignored. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class TranslateFunction + extends Expr +{ + + final Expr arg1; + final Expr arg2; + final Expr arg3; + + TranslateFunction(List args) + { + this((Expr) args.get(0), (Expr) args.get(1), (Expr) args.get(2)); + } + + TranslateFunction(Expr arg1, Expr arg2, Expr arg3) + { + this.arg1 = arg1; + this.arg2 = arg2; + this.arg3 = arg3; + } + + public Object evaluate(Node context, int pos, int len) + { + Object val1 = arg1.evaluate(context, pos, len); + Object val2 = arg2.evaluate(context, pos, len); + Object val3 = arg3.evaluate(context, pos, len); + String string = _string(context, val1); + String search = _string(context, val2); + String replace = _string(context, val3); + StringBuffer buf = new StringBuffer(); + int l1 = string.length(); + int l2 = search.length(); + int l3 = replace.length(); + for (int i = 0; i < l1; i++) + { + char c = string.charAt(i); + boolean replaced = false; + for (int j = 0; j < l2; j++) + { + if (c == search.charAt(j)) + { + if (j < l3) + { + buf.append(replace.charAt(j)); + } + replaced = true; + } + } + if (!replaced) + { + buf.append(c); + } + } + return new String(buf); + } + + public Expr clone(Object context) + { + return new TranslateFunction(arg1.clone(context), arg2.clone(context), + arg3.clone(context)); + } + + public String toString() + { + return "translate(" + arg1 + "," + arg2 + "," + arg3 + ")"; + } + +} diff --git a/libjava/gnu/xml/xpath/TrueFunction.java b/libjava/gnu/xml/xpath/TrueFunction.java new file mode 100644 index 0000000..2d47ed3 --- /dev/null +++ b/libjava/gnu/xml/xpath/TrueFunction.java @@ -0,0 +1,66 @@ +/* TrueFunction.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import org.w3c.dom.Node; + +/** + * The <code>true</code> function returns true. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +final class TrueFunction + extends Expr +{ + + public Object evaluate(Node context, int pos, int len) + { + return Boolean.TRUE; + } + + public Expr clone(Object context) + { + return new TrueFunction(); + } + + public String toString() + { + return "true()"; + } + +} diff --git a/libjava/gnu/xml/xpath/UnionExpr.java b/libjava/gnu/xml/xpath/UnionExpr.java new file mode 100644 index 0000000..1b1344e --- /dev/null +++ b/libjava/gnu/xml/xpath/UnionExpr.java @@ -0,0 +1,102 @@ +/* UnionExpr.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.w3c.dom.Node; + +/** + * The union of two node-sets. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public final class UnionExpr + extends Pattern +{ + + final Expr lhs; + final Expr rhs; + + public UnionExpr(Expr lhs, Expr rhs) + { + this.lhs = lhs; + this.rhs = rhs; + } + + public boolean matches(Node context) + { + if (lhs instanceof Pattern && rhs instanceof Pattern) + { + return ((Pattern) lhs).matches(context) || + ((Pattern) rhs).matches(context); + } + return false; + } + + public Object evaluate(Node context, int pos, int len) + { + Object left = lhs.evaluate(context, pos, len); + Object right = rhs.evaluate(context, pos, len); + if (left instanceof Collection && right instanceof Collection) + { + Set set = new HashSet(); + set.addAll ((Collection) left); + set.addAll ((Collection) right); + List list = new ArrayList(set); + Collections.sort(list, documentOrderComparator); + return list; + } + return Collections.EMPTY_SET; + } + + public Expr clone(Object context) + { + return new UnionExpr(lhs.clone(context), rhs.clone(context)); + } + + public String toString() + { + return lhs + " | " + rhs; + } + +} diff --git a/libjava/gnu/xml/xpath/VariableReference.java b/libjava/gnu/xml/xpath/VariableReference.java new file mode 100644 index 0000000..faa5bfa --- /dev/null +++ b/libjava/gnu/xml/xpath/VariableReference.java @@ -0,0 +1,88 @@ +/* VariableReference.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import javax.xml.namespace.QName; +import javax.xml.xpath.XPathVariableResolver; +import org.w3c.dom.Node; +import gnu.xml.transform.Bindings; + +public class VariableReference + extends Expr +{ + + final XPathVariableResolver resolver; + final String name; + + public VariableReference(XPathVariableResolver resolver, String name) + { + this.resolver = resolver; + this.name = name; + } + + public Object evaluate(Node context, int pos, int len) + { + if (resolver != null) + { + if (resolver instanceof Bindings) + { + // Needs context to operate properly + return ((Bindings) resolver).get(name, context, pos, len); + } + QName qname = QName.valueOf(name); + return resolver.resolveVariable(qname); + } + throw new IllegalStateException("no variable resolver"); + } + + public Expr clone(Object context) + { + XPathVariableResolver r = resolver; + if (context instanceof XPathVariableResolver) + { + r = (XPathVariableResolver) context; + } + return new VariableReference(r, name); + } + + public String toString() + { + return "$" + name; + } + +} diff --git a/libjava/gnu/xml/xpath/XPathFactoryImpl.java b/libjava/gnu/xml/xpath/XPathFactoryImpl.java new file mode 100644 index 0000000..f9f70a3 --- /dev/null +++ b/libjava/gnu/xml/xpath/XPathFactoryImpl.java @@ -0,0 +1,90 @@ +/* XPathFactoryImpl.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathFactory; +import javax.xml.xpath.XPathFactoryConfigurationException; +import javax.xml.xpath.XPathFunctionResolver; +import javax.xml.xpath.XPathVariableResolver; + +/** + * GNU XPath factory implementation. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class XPathFactoryImpl + extends XPathFactory +{ + + XPathVariableResolver variableResolver; + XPathFunctionResolver functionResolver; + + public boolean isObjectModelSupported(String objectModel) + { + return XPathFactory.DEFAULT_OBJECT_MODEL_URI.equals(objectModel); + } + + public void setFeature(String name, boolean value) + throws XPathFactoryConfigurationException + { + throw new XPathFactoryConfigurationException(name); + } + + public boolean getFeature(String name) + throws XPathFactoryConfigurationException + { + throw new XPathFactoryConfigurationException(name); + } + + public void setXPathVariableResolver(XPathVariableResolver resolver) + { + variableResolver = resolver; + } + + public void setXPathFunctionResolver(XPathFunctionResolver resolver) + { + functionResolver = resolver; + } + + public XPath newXPath() + { + return new XPathImpl(null, variableResolver, functionResolver); + } + +} diff --git a/libjava/gnu/xml/xpath/XPathImpl.java b/libjava/gnu/xml/xpath/XPathImpl.java new file mode 100644 index 0000000..2189364 --- /dev/null +++ b/libjava/gnu/xml/xpath/XPathImpl.java @@ -0,0 +1,164 @@ +/* XPathImpl.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.io.IOException; +import javax.xml.namespace.NamespaceContext; +import javax.xml.namespace.QName; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFunctionResolver; +import javax.xml.xpath.XPathVariableResolver; +import org.xml.sax.InputSource; + +/** + * JAXP XPath implementation. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class XPathImpl + implements XPath +{ + + XPathParser parser; + NamespaceContext namespaceContext; + XPathVariableResolver variableResolver; + XPathFunctionResolver functionResolver; + + XPathImpl(NamespaceContext namespaceContext, + XPathVariableResolver variableResolver, + XPathFunctionResolver functionResolver) + { + parser = new XPathParser(); + this.namespaceContext = namespaceContext; + this.variableResolver = variableResolver; + this.functionResolver = functionResolver; + reset(); + } + + public void reset() + { + parser.namespaceContext = namespaceContext; + parser.variableResolver = variableResolver; + parser.functionResolver = functionResolver; + } + + public void setXPathVariableResolver(XPathVariableResolver resolver) + { + parser.variableResolver = resolver; + } + + public XPathVariableResolver getXPathVariableResolver() + { + return parser.variableResolver; + } + + public void setXPathFunctionResolver(XPathFunctionResolver resolver) + { + parser.functionResolver = resolver; + } + + public XPathFunctionResolver getXPathFunctionResolver() + { + return parser.functionResolver; + } + + public void setNamespaceContext(NamespaceContext nsContext) + { + parser.namespaceContext = nsContext; + } + + public NamespaceContext getNamespaceContext() + { + return parser.namespaceContext; + } + + public XPathExpression compile(String expression) + throws XPathExpressionException + { + XPathTokenizer tokenizer = new XPathTokenizer(expression); + try + { + return (Expr) parser.yyparse(tokenizer); + } + catch (IOException e) + { + throw new XPathExpressionException(e); + } + catch (XPathParser.yyException e) + { + throw new XPathExpressionException(expression); + } + } + + public Object evaluate(String expression, + Object item, + QName returnType) + throws XPathExpressionException + { + XPathExpression expr = compile(expression); + return expr.evaluate(item, returnType); + } + + public String evaluate(String expression, + Object item) + throws XPathExpressionException + { + XPathExpression expr = compile(expression); + return expr.evaluate(item); + } + + public Object evaluate(String expression, + InputSource source, + QName returnType) + throws XPathExpressionException + { + XPathExpression expr = compile(expression); + return expr.evaluate(source, returnType); + } + + public String evaluate(String expression, + InputSource source) + throws XPathExpressionException + { + XPathExpression expr = compile(expression); + return expr.evaluate(source); + } + +} diff --git a/libjava/gnu/xml/xpath/XPathParser.java b/libjava/gnu/xml/xpath/XPathParser.java new file mode 100644 index 0000000..973919b --- /dev/null +++ b/libjava/gnu/xml/xpath/XPathParser.java @@ -0,0 +1,1462 @@ +// created by jay 0.8 (c) 1998 Axel.Schreiner@informatik.uni-osnabrueck.de + + // line 2 "XPathParser.y" +/* XPathParser.java -- An XPath 1.0 parser. + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.xml.namespace.NamespaceContext; +import javax.xml.namespace.QName; +import javax.xml.xpath.XPathFunctionResolver; +import javax.xml.xpath.XPathVariableResolver; +import org.w3c.dom.Node; + +/** + * An XPath 1.0 parser. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class XPathParser +{ + + NamespaceContext namespaceContext; + XPathVariableResolver variableResolver; + XPathFunctionResolver functionResolver; + + QName getQName(String name) + { + QName qName = QName.valueOf(name); + if (namespaceContext != null) + { + String prefix = qName.getPrefix(); + String uri = qName.getNamespaceURI(); + if (prefix != null && (uri == null || uri.length() == 0)) + { + uri = namespaceContext.getNamespaceURI(prefix); + String localName = qName.getLocalPart(); + qName = new QName(uri, localName, prefix); + } + } + return qName; + } + + Expr lookupFunction(String name, List args) + { + int arity = args.size(); + if ("position".equals(name) && arity == 0) + { + return new PositionFunction(); + } + else if ("last".equals(name) && arity == 0) + { + return new LastFunction(); + } + else if ("string".equals(name) && (arity == 1 || arity == 0)) + { + return new StringFunction(args); + } + else if ("number".equals(name) && (arity == 1 || arity == 0)) + { + return new NumberFunction(args); + } + else if ("boolean".equals(name) && arity == 1) + { + return new BooleanFunction(args); + } + else if ("count".equals(name) && arity == 1) + { + return new CountFunction(args); + } + else if ("not".equals(name) && arity == 1) + { + return new NotFunction(args); + } + else if ("id".equals(name) && arity == 1) + { + return new IdFunction(args); + } + else if ("concat".equals(name) && arity > 1) + { + return new ConcatFunction(args); + } + else if ("true".equals(name) && arity == 0) + { + return new TrueFunction(); + } + else if ("false".equals(name) && arity == 0) + { + return new FalseFunction(); + } + else if ("name".equals(name) && (arity == 1 || arity == 0)) + { + return new NameFunction(args); + } + else if ("local-name".equals(name) && (arity == 1 || arity == 0)) + { + return new LocalNameFunction(args); + } + else if ("namespace-uri".equals(name) && (arity == 1 || arity == 0)) + { + return new NamespaceUriFunction(args); + } + else if ("starts-with".equals(name) && arity == 2) + { + return new StartsWithFunction(args); + } + else if ("contains".equals(name) && arity == 2) + { + return new ContainsFunction(args); + } + else if ("string-length".equals(name) && (arity == 1 || arity == 0)) + { + return new StringLengthFunction(args); + } + else if ("translate".equals(name) && arity == 3) + { + return new TranslateFunction(args); + } + else if ("normalize-space".equals(name) && (arity == 1 || arity == 0)) + { + return new NormalizeSpaceFunction(args); + } + else if ("substring".equals(name) && (arity == 2 || arity == 3)) + { + return new SubstringFunction(args); + } + else if ("substring-before".equals(name) && arity == 2) + { + return new SubstringBeforeFunction(args); + } + else if ("substring-after".equals(name) && arity == 2) + { + return new SubstringAfterFunction(args); + } + else if ("lang".equals(name) && arity == 1) + { + return new LangFunction(args); + } + else if ("sum".equals(name) && arity == 1) + { + return new SumFunction(args); + } + else if ("floor".equals(name) && arity == 1) + { + return new FloorFunction(args); + } + else if ("ceiling".equals(name) && arity == 1) + { + return new CeilingFunction(args); + } + else if ("round".equals(name) && arity == 1) + { + return new RoundFunction(args); + } + else if (functionResolver != null) + { + QName qName = QName.valueOf(name); + Object function = functionResolver.resolveFunction(qName, arity); + if (function != null && + function instanceof Function && + function instanceof Expr) + { + Function f = (Function) function; + f.setArguments(args); + return (Expr) function; + } + } + return new FunctionCall(functionResolver, name, args); + } + + // line 210 "-" +// %token constants + + public static final int LITERAL = 257; + public static final int DIGITS = 258; + public static final int NAME = 259; + public static final int LP = 260; + public static final int RP = 261; + public static final int LB = 262; + public static final int RB = 263; + public static final int COMMA = 264; + public static final int PIPE = 265; + public static final int SLASH = 266; + public static final int DOUBLE_SLASH = 267; + public static final int EQ = 268; + public static final int NE = 269; + public static final int GT = 270; + public static final int LT = 271; + public static final int GTE = 272; + public static final int LTE = 273; + public static final int PLUS = 274; + public static final int MINUS = 275; + public static final int AT = 276; + public static final int STAR = 277; + public static final int DOLLAR = 278; + public static final int COLON = 279; + public static final int DOUBLE_COLON = 280; + public static final int DOT = 281; + public static final int DOUBLE_DOT = 282; + public static final int ANCESTOR = 283; + public static final int ANCESTOR_OR_SELF = 284; + public static final int ATTRIBUTE = 285; + public static final int CHILD = 286; + public static final int DESCENDANT = 287; + public static final int DESCENDANT_OR_SELF = 288; + public static final int FOLLOWING = 289; + public static final int FOLLOWING_SIBLING = 290; + public static final int NAMESPACE = 291; + public static final int PARENT = 292; + public static final int PRECEDING = 293; + public static final int PRECEDING_SIBLING = 294; + public static final int SELF = 295; + public static final int DIV = 296; + public static final int MOD = 297; + public static final int OR = 298; + public static final int AND = 299; + public static final int COMMENT = 300; + public static final int PROCESSING_INSTRUCTION = 301; + public static final int TEXT = 302; + public static final int NODE = 303; + public static final int UNARY = 304; + public static final int yyErrorCode = 256; + + /** thrown for irrecoverable syntax errors and stack overflow. + */ + public static class yyException extends java.lang.Exception { + public yyException (String message) { + super(message); + } + } + + /** must be implemented by a scanner object to supply input to the parser. + */ + public interface yyInput { + /** move on to next token. + @return false if positioned beyond tokens. + @throws IOException on input error. + */ + boolean advance () throws java.io.IOException; + /** classifies current token. + Should not be called if advance() returned false. + @return current %token or single character. + */ + int token (); + /** associated with current token. + Should not be called if advance() returned false. + @return value for token(). + */ + Object value (); + } + + /** simplified error message. + @see <a href="#yyerror(java.lang.String, java.lang.String[])">yyerror</a> + */ + public void yyerror (String message) { + yyerror(message, null); + } + + /** (syntax) error message. + Can be overwritten to control message format. + @param message text to be displayed. + @param expected vector of acceptable tokens, if available. + */ + public void yyerror (String message, String[] expected) { + if (expected != null && expected.length > 0) { + System.err.print(message+", expecting"); + for (int n = 0; n < expected.length; ++ n) + System.err.print(" "+expected[n]); + System.err.println(); + } else + System.err.println(message); + } + + /** debugging support, requires the package jay.yydebug. + Set to null to suppress debugging messages. + */ +//t protected jay.yydebug.yyDebug yydebug; + + protected static final int yyFinal = 30; + + /** index-checked interface to yyName[]. + @param token single character or %token value. + @return token name or [illegal] or [unknown]. + */ +//t public static final String yyname (int token) { +//t if (token < 0 || token > YyNameClass.yyName.length) return "[illegal]"; +//t String name; +//t if ((name = YyNameClass.yyName[token]) != null) return name; +//t return "[unknown]"; +//t } + + /** computes list of expected tokens on error by tracing the tables. + @param state for which to compute the list. + @return list of token names. + */ + protected String[] yyExpecting (int state) { + int token, n, len = 0; + boolean[] ok = new boolean[YyNameClass.yyName.length]; + + if ((n = YySindexClass.yySindex[state]) != 0) + for (token = n < 0 ? -n : 0; + token < YyNameClass.yyName.length && n+token < YyTableClass.yyTable.length; ++ token) + if (YyCheckClass.yyCheck[n+token] == token && !ok[token] && YyNameClass.yyName[token] != null) { + ++ len; + ok[token] = true; + } + if ((n = YyRindexClass.yyRindex[state]) != 0) + for (token = n < 0 ? -n : 0; + token < YyNameClass.yyName.length && n+token < YyTableClass.yyTable.length; ++ token) + if (YyCheckClass.yyCheck[n+token] == token && !ok[token] && YyNameClass.yyName[token] != null) { + ++ len; + ok[token] = true; + } + + String result[] = new String[len]; + for (n = token = 0; n < len; ++ token) + if (ok[token]) result[n++] = YyNameClass.yyName[token]; + return result; + } + + /** the generated parser, with debugging messages. + Maintains a state and a value stack, currently with fixed maximum size. + @param yyLex scanner. + @param yydebug debug message writer implementing yyDebug, or null. + @return result of the last reduction, if any. + @throws yyException on irrecoverable parse error. + */ + public Object yyparse (yyInput yyLex, Object yydebug) + throws java.io.IOException, yyException { +//t this.yydebug = (jay.yydebug.yyDebug)yydebug; + return yyparse(yyLex); + } + + /** initial size and increment of the state/value stack [default 256]. + This is not final so that it can be overwritten outside of invocations + of yyparse(). + */ + protected int yyMax; + + /** executed at the beginning of a reduce action. + Used as $$ = yyDefault($1), prior to the user-specified action, if any. + Can be overwritten to provide deep copy, etc. + @param first value for $1, or null. + @return first. + */ + protected Object yyDefault (Object first) { + return first; + } + + /** the generated parser. + Maintains a state and a value stack, currently with fixed maximum size. + @param yyLex scanner. + @return result of the last reduction, if any. + @throws yyException on irrecoverable parse error. + */ + public Object yyparse (yyInput yyLex) + throws java.io.IOException, yyException { + if (yyMax <= 0) yyMax = 256; // initial size + int yyState = 0, yyStates[] = new int[yyMax]; // state stack + Object yyVal = null, yyVals[] = new Object[yyMax]; // value stack + int yyToken = -1; // current input + int yyErrorFlag = 0; // #tks to shift + + yyLoop: for (int yyTop = 0;; ++ yyTop) { + if (yyTop >= yyStates.length) { // dynamically increase + int[] i = new int[yyStates.length+yyMax]; + System.arraycopy(yyStates, 0, i, 0, yyStates.length); + yyStates = i; + Object[] o = new Object[yyVals.length+yyMax]; + System.arraycopy(yyVals, 0, o, 0, yyVals.length); + yyVals = o; + } + yyStates[yyTop] = yyState; + yyVals[yyTop] = yyVal; +//t if (yydebug != null) yydebug.push(yyState, yyVal); + + yyDiscarded: for (;;) { // discarding a token does not change stack + int yyN; + if ((yyN = YyDefRedClass.yyDefRed[yyState]) == 0) { // else [default] reduce (yyN) + if (yyToken < 0) { + yyToken = yyLex.advance() ? yyLex.token() : 0; +//t if (yydebug != null) +//t yydebug.lex(yyState, yyToken, yyname(yyToken), yyLex.value()); + } + if ((yyN = YySindexClass.yySindex[yyState]) != 0 && (yyN += yyToken) >= 0 + && yyN < YyTableClass.yyTable.length && YyCheckClass.yyCheck[yyN] == yyToken) { +//t if (yydebug != null) +//t yydebug.shift(yyState, YyTableClass.yyTable[yyN], yyErrorFlag-1); + yyState = YyTableClass.yyTable[yyN]; // shift to yyN + yyVal = yyLex.value(); + yyToken = -1; + if (yyErrorFlag > 0) -- yyErrorFlag; + continue yyLoop; + } + if ((yyN = YyRindexClass.yyRindex[yyState]) != 0 && (yyN += yyToken) >= 0 + && yyN < YyTableClass.yyTable.length && YyCheckClass.yyCheck[yyN] == yyToken) + yyN = YyTableClass.yyTable[yyN]; // reduce (yyN) + else + switch (yyErrorFlag) { + + case 0: + yyerror("syntax error", yyExpecting(yyState)); +//t if (yydebug != null) yydebug.error("syntax error"); + + case 1: case 2: + yyErrorFlag = 3; + do { + if ((yyN = YySindexClass.yySindex[yyStates[yyTop]]) != 0 + && (yyN += yyErrorCode) >= 0 && yyN < YyTableClass.yyTable.length + && YyCheckClass.yyCheck[yyN] == yyErrorCode) { +//t if (yydebug != null) +//t yydebug.shift(yyStates[yyTop], YyTableClass.yyTable[yyN], 3); + yyState = YyTableClass.yyTable[yyN]; + yyVal = yyLex.value(); + continue yyLoop; + } +//t if (yydebug != null) yydebug.pop(yyStates[yyTop]); + } while (-- yyTop >= 0); +//t if (yydebug != null) yydebug.reject(); + throw new yyException("irrecoverable syntax error"); + + case 3: + if (yyToken == 0) { +//t if (yydebug != null) yydebug.reject(); + throw new yyException("irrecoverable syntax error at end-of-file"); + } +//t if (yydebug != null) +//t yydebug.discard(yyState, yyToken, yyname(yyToken), +//t yyLex.value()); + yyToken = -1; + continue yyDiscarded; // leave stack alone + } + } + int yyV = yyTop + 1-YyLenClass.yyLen[yyN]; +//t if (yydebug != null) +//t yydebug.reduce(yyState, yyStates[yyV-1], yyN, YyRuleClass.yyRule[yyN], YyLenClass.yyLen[yyN]); + yyVal = yyDefault(yyV > yyTop ? null : yyVals[yyV]); + switch (yyN) { +case 4: + // line 276 "XPathParser.y" + { + yyVal = new Root(); + } + break; +case 5: + // line 280 "XPathParser.y" + { + Steps steps; + if (yyVals[0+yyTop] instanceof Steps) + { + steps = (Steps) yyVals[0+yyTop]; + } + else + { + steps = new Steps(); + steps.path.addFirst(yyVals[0+yyTop]); + } + steps.path.addFirst(new Root()); + yyVal = steps; + /*$$ = new Step(new Root(), (Path) $2);*/ + } + break; +case 6: + // line 296 "XPathParser.y" + { + Test nt = new NodeTypeTest((short) 0); + Selector s = new Selector(Selector.DESCENDANT_OR_SELF, + Collections.singletonList (nt)); + Steps steps; + if (yyVals[0+yyTop] instanceof Steps) + { + steps = (Steps) yyVals[0+yyTop]; + } + else + { + steps = new Steps(); + steps.path.addFirst(yyVals[0+yyTop]); + } + steps.path.addFirst(s); + steps.path.addFirst(new Root()); + yyVal = steps; + /*Step step = new Step(s, (Path) $2);*/ + /*$$ = new Step(new Root(), step);*/ + } + break; +case 8: + // line 321 "XPathParser.y" + { + Steps steps; + if (yyVals[-2+yyTop] instanceof Steps) + { + steps = (Steps) yyVals[-2+yyTop]; + } + else + { + steps = new Steps(); + steps.path.addFirst(yyVals[-2+yyTop]); + } + steps.path.addLast(yyVals[0+yyTop]); + yyVal = steps; + /*$$ = new Step((Expr) $1, (Path) $3);*/ + } + break; +case 9: + // line 337 "XPathParser.y" + { + Test nt = new NodeTypeTest((short) 0); + Selector s = new Selector(Selector.DESCENDANT_OR_SELF, + Collections.singletonList (nt)); + Steps steps; + if (yyVals[-2+yyTop] instanceof Steps) + { + steps = (Steps) yyVals[-2+yyTop]; + } + else + { + steps = new Steps(); + steps.path.addFirst(yyVals[-2+yyTop]); + } + steps.path.addLast(s); + steps.path.addLast(yyVals[0+yyTop]); + yyVal = steps; + /*Step step = new Step(s, (Path) $3);*/ + /*$$ = new Step((Expr) $1, step);*/ + } + break; +case 10: + // line 361 "XPathParser.y" + { + yyVal = new Selector (Selector.CHILD, (List) yyVals[0+yyTop]); + } + break; +case 11: + // line 365 "XPathParser.y" + { + yyVal = new Selector (Selector.ATTRIBUTE, (List) yyVals[0+yyTop]); + } + break; +case 12: + // line 369 "XPathParser.y" + { + yyVal = new Selector (((Integer) yyVals[-2+yyTop]).intValue (), (List) yyVals[0+yyTop]); + } + break; +case 13: + // line 373 "XPathParser.y" + { + yyVal = new Selector (Selector.SELF, Collections.EMPTY_LIST); + } + break; +case 14: + // line 377 "XPathParser.y" + { + yyVal = new Selector (Selector.PARENT, Collections.EMPTY_LIST); + } + break; +case 15: + // line 384 "XPathParser.y" + { + List list = new ArrayList(); + list.add(yyVals[0+yyTop]); + yyVal = list; + } + break; +case 16: + // line 390 "XPathParser.y" + { + List list = (List)yyVals[-1+yyTop]; + list.add(yyVals[0+yyTop]); + yyVal = list; + } + break; +case 17: + // line 414 "XPathParser.y" + { + yyVal = new Integer(Selector.ANCESTOR); + } + break; +case 18: + // line 418 "XPathParser.y" + { + yyVal = new Integer(Selector.ANCESTOR_OR_SELF); + } + break; +case 19: + // line 422 "XPathParser.y" + { + yyVal = new Integer(Selector.ATTRIBUTE); + } + break; +case 20: + // line 426 "XPathParser.y" + { + yyVal = new Integer(Selector.CHILD); + } + break; +case 21: + // line 430 "XPathParser.y" + { + yyVal = new Integer(Selector.DESCENDANT); + } + break; +case 22: + // line 434 "XPathParser.y" + { + yyVal = new Integer(Selector.DESCENDANT_OR_SELF); + } + break; +case 23: + // line 438 "XPathParser.y" + { + yyVal = new Integer(Selector.FOLLOWING); + } + break; +case 24: + // line 442 "XPathParser.y" + { + yyVal = new Integer(Selector.FOLLOWING_SIBLING); + } + break; +case 25: + // line 446 "XPathParser.y" + { + yyVal = new Integer(Selector.NAMESPACE); + } + break; +case 26: + // line 450 "XPathParser.y" + { + yyVal = new Integer(Selector.PARENT); + } + break; +case 27: + // line 454 "XPathParser.y" + { + yyVal = new Integer(Selector.PRECEDING); + } + break; +case 28: + // line 458 "XPathParser.y" + { + yyVal = new Integer(Selector.PRECEDING_SIBLING); + } + break; +case 29: + // line 462 "XPathParser.y" + { + yyVal = new Integer(Selector.SELF); + } + break; +case 31: + // line 471 "XPathParser.y" + { + yyVal = new NodeTypeTest(Node.PROCESSING_INSTRUCTION_NODE, (String) yyVals[-1+yyTop]); + } + break; +case 32: + // line 476 "XPathParser.y" + { + yyVal = new NodeTypeTest(((Short) yyVals[-1+yyTop]).shortValue()); + } + break; +case 33: + // line 483 "XPathParser.y" + { + yyVal = new Predicate((Expr) yyVals[-1+yyTop]); + } + break; +case 35: + // line 491 "XPathParser.y" + { + yyVal = new ParenthesizedExpr((Expr) yyVals[-1+yyTop]); + } + break; +case 36: + // line 495 "XPathParser.y" + { + yyVal = new Constant(yyVals[0+yyTop]); + } + break; +case 37: + // line 499 "XPathParser.y" + { + yyVal = new Constant(yyVals[0+yyTop]); + } + break; +case 39: + // line 507 "XPathParser.y" + { + yyVal = lookupFunction((String) yyVals[-2+yyTop], Collections.EMPTY_LIST); + } + break; +case 40: + // line 511 "XPathParser.y" + { + yyVal = lookupFunction((String) yyVals[-3+yyTop], (List) yyVals[-1+yyTop]); + } + break; +case 41: + // line 518 "XPathParser.y" + { + List list = new ArrayList(); + list.add(yyVals[0+yyTop]); + yyVal = list; + } + break; +case 42: + // line 524 "XPathParser.y" + { + List list = (List) yyVals[0+yyTop]; + list.add(0, yyVals[-2+yyTop]); + yyVal = list; + } + break; +case 44: + // line 534 "XPathParser.y" + { + yyVal = new UnionExpr((Expr) yyVals[-2+yyTop], (Expr) yyVals[0+yyTop]); + } + break; +case 47: + // line 543 "XPathParser.y" + { + Steps steps; + if (yyVals[0+yyTop] instanceof Steps) + { + steps = (Steps) yyVals[0+yyTop]; + } + else + { + steps = new Steps(); + steps.path.addFirst(yyVals[0+yyTop]); + } + steps.path.addFirst(yyVals[-2+yyTop]); + yyVal = steps; + /*$$ = new Step ((Expr) $1, (Path) $3);*/ + } + break; +case 48: + // line 559 "XPathParser.y" + { + Test nt = new NodeTypeTest((short) 0); + Selector s = new Selector(Selector.DESCENDANT_OR_SELF, + Collections.singletonList(nt)); + Steps steps; + if (yyVals[0+yyTop] instanceof Steps) + { + steps = (Steps) yyVals[0+yyTop]; + } + else + { + steps = new Steps(); + steps.path.addFirst(yyVals[0+yyTop]); + } + steps.path.addFirst(s); + steps.path.addFirst(yyVals[-2+yyTop]); + yyVal = steps; + /*Step step = new Step (s, (Path) $3);*/ + /*$$ = new Step ((Expr) $1, step);*/ + } + break; +case 50: + // line 584 "XPathParser.y" + { + Predicate filter = (Predicate) yyVals[0+yyTop]; + Selector s = new Selector(Selector.SELF, + Collections.singletonList(filter)); + Steps steps; + if (yyVals[-1+yyTop] instanceof Steps) + { + steps = (Steps) yyVals[-1+yyTop]; + } + else + { + steps = new Steps(); + steps.path.addFirst(yyVals[-1+yyTop]); + } + steps.path.addLast(s); + yyVal = steps; + /*$$ = new Step ((Expr) $1, s);*/ + } + break; +case 52: + // line 607 "XPathParser.y" + { + yyVal = new OrExpr((Expr) yyVals[-2+yyTop], (Expr) yyVals[0+yyTop]); + } + break; +case 54: + // line 615 "XPathParser.y" + { + yyVal = new AndExpr((Expr) yyVals[-2+yyTop], (Expr) yyVals[0+yyTop]); + } + break; +case 56: + // line 623 "XPathParser.y" + { + yyVal = new EqualityExpr((Expr) yyVals[-2+yyTop], (Expr) yyVals[0+yyTop], false); + } + break; +case 57: + // line 627 "XPathParser.y" + { + yyVal = new EqualityExpr((Expr) yyVals[-2+yyTop], (Expr) yyVals[0+yyTop], true); + } + break; +case 59: + // line 635 "XPathParser.y" + { + yyVal = new RelationalExpr((Expr) yyVals[-2+yyTop], (Expr) yyVals[0+yyTop], true, false); + } + break; +case 60: + // line 639 "XPathParser.y" + { + yyVal = new RelationalExpr((Expr) yyVals[-2+yyTop], (Expr) yyVals[0+yyTop], false, false); + } + break; +case 61: + // line 643 "XPathParser.y" + { + yyVal = new RelationalExpr((Expr) yyVals[-2+yyTop], (Expr) yyVals[0+yyTop], true, true); + } + break; +case 62: + // line 647 "XPathParser.y" + { + yyVal = new RelationalExpr((Expr) yyVals[-2+yyTop], (Expr) yyVals[0+yyTop], false, true); + } + break; +case 64: + // line 655 "XPathParser.y" + { + yyVal = new ArithmeticExpr((Expr) yyVals[-2+yyTop], (Expr) yyVals[0+yyTop], ArithmeticExpr.ADD); + } + break; +case 65: + // line 659 "XPathParser.y" + { + yyVal = new ArithmeticExpr((Expr) yyVals[-2+yyTop], (Expr) yyVals[0+yyTop], ArithmeticExpr.SUBTRACT); + } + break; +case 67: + // line 667 "XPathParser.y" + { + yyVal = new ArithmeticExpr((Expr) yyVals[-2+yyTop], (Expr) yyVals[0+yyTop], ArithmeticExpr.MULTIPLY); + } + break; +case 68: + // line 671 "XPathParser.y" + { + yyVal = new ArithmeticExpr((Expr) yyVals[-2+yyTop], (Expr) yyVals[0+yyTop], ArithmeticExpr.DIVIDE); + } + break; +case 69: + // line 675 "XPathParser.y" + { + yyVal = new ArithmeticExpr((Expr) yyVals[-2+yyTop], (Expr) yyVals[0+yyTop], ArithmeticExpr.MODULO); + } + break; +case 71: + // line 683 "XPathParser.y" + { + yyVal = new NegativeExpr((Expr) yyVals[0+yyTop]); + } + break; +case 72: + // line 690 "XPathParser.y" + { + yyVal = new Double((String) yyVals[0+yyTop] + ".0"); + } + break; +case 73: + // line 694 "XPathParser.y" + { + yyVal = new Double((String) yyVals[-1+yyTop] + ".0"); + } + break; +case 74: + // line 698 "XPathParser.y" + { + yyVal = new Double((String) yyVals[-2+yyTop] + "." + (String) yyVals[0+yyTop]); + } + break; +case 75: + // line 702 "XPathParser.y" + { + yyVal = new Double("0." + (String) yyVals[0+yyTop]); + } + break; +case 77: + // line 731 "XPathParser.y" + { + yyVal = new VariableReference(variableResolver, (String) yyVals[0+yyTop]); + } + break; +case 78: + // line 738 "XPathParser.y" + { + yyVal = new NameTest(null, true, true); + } + break; +case 79: + // line 742 "XPathParser.y" + { + QName qName = getQName((String) yyVals[-2+yyTop]); + yyVal = new NameTest(qName, true, false); + } + break; +case 80: + // line 747 "XPathParser.y" + { + QName qName = getQName((String) yyVals[0+yyTop]); + yyVal = new NameTest(qName, false, false); + } + break; +case 82: + // line 756 "XPathParser.y" + { + yyVal = (String) yyVals[-2+yyTop] + ':' + (String) yyVals[0+yyTop]; + } + break; +case 83: + // line 763 "XPathParser.y" + { + yyVal = new Short(Node.COMMENT_NODE); + } + break; +case 84: + // line 767 "XPathParser.y" + { + yyVal = new Short(Node.TEXT_NODE); + } + break; +case 85: + // line 771 "XPathParser.y" + { + yyVal = new Short(Node.PROCESSING_INSTRUCTION_NODE); + } + break; +case 86: + // line 775 "XPathParser.y" + { + yyVal = new Short((short) 0); + } + break; + // line 986 "-" + } + yyTop -= YyLenClass.yyLen[yyN]; + yyState = yyStates[yyTop]; + int yyM = YyLhsClass.yyLhs[yyN]; + if (yyState == 0 && yyM == 0) { +//t if (yydebug != null) yydebug.shift(0, yyFinal); + yyState = yyFinal; + if (yyToken < 0) { + yyToken = yyLex.advance() ? yyLex.token() : 0; +//t if (yydebug != null) +//t yydebug.lex(yyState, yyToken,yyname(yyToken), yyLex.value()); + } + if (yyToken == 0) { +//t if (yydebug != null) yydebug.accept(yyVal); + return yyVal; + } + continue yyLoop; + } + if ((yyN = YyGindexClass.yyGindex[yyM]) != 0 && (yyN += yyState) >= 0 + && yyN < YyTableClass.yyTable.length && YyCheckClass.yyCheck[yyN] == yyState) + yyState = YyTableClass.yyTable[yyN]; + else + yyState = YyDgotoClass.yyDgoto[yyM]; +//t if (yydebug != null) yydebug.shift(yyStates[yyTop], yyState); + continue yyLoop; + } + } + } + + protected static final class YyLhsClass { + + public static final short yyLhs [] = { -1, + 0, 2, 2, 4, 4, 4, 3, 3, 3, 5, + 5, 5, 5, 5, 6, 6, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, + 8, 8, 9, 12, 12, 12, 12, 12, 15, 15, + 17, 17, 18, 18, 19, 19, 19, 19, 20, 20, + 1, 1, 21, 21, 22, 22, 22, 23, 23, 23, + 23, 23, 24, 24, 24, 25, 25, 25, 25, 26, + 26, 14, 14, 14, 14, 16, 13, 10, 10, 10, + 27, 27, 11, 11, 11, 11, + }; + } /* End of class YyLhsClass */ + + protected static final class YyLenClass { + + public static final short yyLen [] = { 2, + 1, 1, 1, 1, 2, 2, 1, 3, 3, 1, + 2, 3, 1, 1, 1, 2, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 3, 2, 3, 1, 3, 1, 1, 1, 3, 4, + 1, 3, 1, 3, 1, 1, 3, 3, 1, 2, + 1, 3, 1, 3, 1, 3, 3, 1, 3, 3, + 3, 3, 1, 3, 3, 1, 3, 3, 3, 1, + 2, 1, 2, 3, 2, 1, 2, 1, 3, 1, + 1, 3, 1, 1, 1, 1, + }; + } /* End class YyLenClass */ + + protected static final class YyDefRedClass { + + public static final short yyDefRed [] = { 0, + 36, 0, 0, 0, 0, 0, 0, 0, 78, 0, + 0, 14, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 83, 0, 84, 86, 0, + 0, 45, 0, 3, 7, 0, 0, 15, 30, 0, + 49, 34, 37, 38, 0, 0, 43, 0, 0, 0, + 0, 0, 0, 66, 0, 0, 0, 0, 13, 0, + 80, 0, 71, 0, 0, 77, 75, 0, 0, 0, + 0, 0, 16, 0, 32, 0, 0, 0, 0, 50, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 74, 82, 79, 35, 0, 31, 0, 8, + 9, 0, 0, 39, 0, 0, 44, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 67, 68, + 69, 33, 0, 40, 42, + }; + } /* End of class YyDefRedClass */ + + protected static final class YyDgotoClass { + + public static final short yyDgoto [] = { 105, + 31, 32, 33, 34, 35, 36, 37, 38, 73, 39, + 40, 41, 42, 43, 44, 45, 106, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55, + }; + } /* End of class YyDgotoClass */ + + protected static final class YySindexClass { + + public static final short yySindex [] = { -97, + 0, -271, -267, -97, -239, -239, -97, -199, 0, -236, + -222, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, -218, 0, 0, 0, + -257, 0, -241, 0, 0, -205, -221, 0, 0, -194, + 0, 0, 0, 0, -190, -185, 0, -238, -211, -234, + -255, -209, -275, 0, 0, -169, -250, -168, 0, -241, + 0, -241, 0, -205, -187, 0, 0, -167, -97, -239, + -239, -97, 0, -199, 0, -151, -43, -239, -239, 0, + -97, -97, -97, -97, -97, -97, -97, -97, -97, -97, + -97, -97, 0, 0, 0, 0, -164, 0, -211, 0, + 0, -166, -205, 0, -165, -163, 0, -241, -241, -234, + -255, -255, -209, -209, -209, -209, -275, -275, 0, 0, + 0, 0, -97, 0, 0, + }; + } /* End of class YySindexClass */ + + protected static final class YyRindexClass { + + public static final short yyRindex [] = { 0, + 0, 58, 1, 0, 420, 0, 0, 0, 0, 0, + 129, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, -161, 0, 0, 0, + 40, 0, 237, 0, 0, 168, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 459, 0, 277, 557, 544, + 656, 561, 474, 0, 19, 75, 0, 0, 0, 295, + 0, 334, 0, 183, 114, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 686, 0, + 0, 0, 222, 0, -156, 0, 0, 351, 405, 553, + 665, 697, 577, 600, 617, 639, 513, 528, 0, 0, + 0, 0, 0, 0, 0, + }; + } /* End of class YyRindexClass */ + + protected static final class YyGindexClass { + + public static final short yyGindex [] = { 7, + 0, 0, 8, 0, 3, -3, 0, 0, 48, 0, + 0, 0, 0, 0, 0, 0, -12, 0, 35, 0, + 44, 36, -1, -54, 2, -7, -2, + }; + } /* End of class YyGindexClass */ + + protected static final class YyTableClass { + + public static final short yyTable [] = { 63, + 81, 90, 61, 61, 64, 61, 30, 66, 94, 56, + 58, 57, 60, 62, 84, 85, 86, 87, 80, 3, + 91, 92, 65, 72, 70, 71, 95, 78, 79, 113, + 114, 115, 116, 82, 83, 67, 8, 9, 68, 1, + 69, 59, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 72, 72, 74, 3, + 26, 27, 28, 29, 88, 89, 75, 61, 61, 76, + 103, 61, 100, 101, 73, 61, 61, 9, 102, 77, + 111, 112, 119, 120, 121, 108, 109, 81, 93, 117, + 118, 97, 96, 98, 94, 80, 122, 124, 123, 85, + 26, 27, 28, 29, 41, 1, 2, 3, 4, 104, + 125, 107, 99, 81, 5, 6, 110, 0, 0, 0, + 0, 0, 0, 7, 8, 9, 10, 0, 13, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 0, 0, 0, 0, 26, 27, + 28, 29, 0, 0, 0, 0, 0, 0, 0, 1, + 2, 3, 4, 0, 0, 0, 0, 10, 5, 6, + 0, 0, 0, 0, 0, 0, 0, 7, 8, 9, + 10, 0, 11, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, + 0, 0, 26, 27, 28, 29, 0, 0, 0, 0, + 0, 0, 0, 1, 2, 3, 4, 0, 0, 0, + 0, 12, 5, 6, 0, 0, 0, 0, 0, 0, + 0, 0, 8, 9, 10, 0, 2, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 0, 0, 0, 0, 26, 27, 28, 29, + 81, 81, 81, 81, 81, 81, 81, 81, 81, 81, + 81, 81, 81, 81, 81, 81, 46, 81, 76, 80, + 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, + 80, 80, 80, 80, 5, 80, 81, 81, 81, 81, + 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 80, 80, 80, 80, 72, 72, + 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, + 72, 72, 72, 6, 72, 73, 73, 73, 73, 73, + 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, + 47, 73, 0, 72, 72, 72, 72, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 73, 73, 73, 73, 81, 81, 81, 81, 81, 81, + 81, 81, 81, 81, 81, 81, 81, 81, 81, 13, + 81, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 48, 13, 0, 0, 0, 81, + 81, 81, 81, 0, 0, 0, 0, 0, 0, 4, + 0, 0, 0, 0, 13, 13, 13, 13, 10, 0, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 11, 10, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 70, 11, + 0, 0, 0, 10, 10, 10, 10, 0, 0, 0, + 0, 0, 0, 63, 0, 0, 0, 0, 11, 11, + 11, 11, 12, 0, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 2, 12, 2, + 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, + 2, 2, 64, 2, 0, 0, 0, 12, 12, 12, + 12, 0, 0, 0, 0, 0, 0, 65, 0, 0, + 0, 0, 2, 2, 2, 2, 0, 46, 0, 46, + 46, 46, 0, 53, 46, 46, 46, 46, 46, 46, + 46, 46, 54, 46, 0, 5, 51, 5, 5, 5, + 58, 0, 5, 5, 5, 5, 5, 5, 5, 5, + 0, 5, 46, 46, 46, 46, 60, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 5, 5, 5, 5, 6, 0, 6, 6, 6, 59, + 0, 6, 6, 6, 6, 6, 6, 6, 6, 0, + 6, 47, 0, 47, 47, 47, 62, 0, 47, 47, + 47, 47, 47, 47, 47, 47, 0, 47, 0, 6, + 6, 6, 6, 0, 0, 0, 0, 0, 61, 0, + 0, 0, 0, 0, 0, 0, 47, 47, 47, 47, + 0, 0, 0, 0, 0, 55, 0, 0, 0, 0, + 0, 0, 0, 0, 56, 48, 0, 48, 48, 48, + 0, 0, 48, 48, 48, 48, 48, 48, 48, 48, + 4, 48, 4, 4, 4, 52, 0, 4, 4, 4, + 4, 4, 4, 4, 4, 0, 57, 0, 0, 0, + 48, 48, 48, 48, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 4, 4, 4, 4, 70, + 0, 70, 70, 0, 0, 0, 70, 70, 70, 70, + 70, 70, 70, 70, 63, 70, 63, 63, 0, 0, + 0, 63, 63, 63, 63, 63, 63, 63, 63, 0, + 0, 0, 0, 0, 70, 70, 70, 70, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 63, 63, 64, 0, 64, 64, 0, 0, 0, + 64, 64, 64, 64, 64, 64, 64, 64, 65, 0, + 65, 65, 0, 0, 0, 65, 65, 65, 65, 65, + 65, 65, 65, 0, 53, 0, 53, 53, 0, 0, + 64, 64, 0, 54, 0, 54, 54, 51, 0, 51, + 51, 58, 0, 58, 58, 65, 65, 0, 58, 58, + 58, 58, 58, 58, 0, 0, 0, 60, 0, 60, + 60, 53, 53, 0, 60, 60, 60, 60, 60, 60, + 54, 54, 0, 0, 51, 0, 0, 0, 58, 58, + 59, 0, 59, 59, 0, 0, 0, 59, 59, 59, + 59, 59, 59, 0, 60, 60, 0, 62, 0, 62, + 62, 0, 0, 0, 62, 62, 62, 62, 62, 62, + 0, 0, 0, 0, 0, 0, 0, 59, 59, 61, + 0, 61, 61, 0, 0, 0, 61, 61, 61, 61, + 61, 61, 0, 0, 62, 62, 55, 0, 55, 55, + 0, 0, 0, 55, 55, 56, 0, 56, 56, 0, + 0, 0, 56, 56, 0, 0, 61, 61, 0, 0, + 0, 0, 0, 0, 0, 0, 52, 0, 52, 52, + 0, 0, 0, 55, 55, 0, 0, 57, 0, 57, + 57, 0, 56, 56, 57, 57, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 52, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 57, 57, + }; + } /* End of class YyTableClass */ + + protected static final class YyCheckClass { + + public static final short yyCheck [] = { 7, + 0, 277, 5, 6, 8, 8, 0, 10, 259, 281, + 4, 279, 5, 6, 270, 271, 272, 273, 0, 259, + 296, 297, 259, 262, 266, 267, 277, 266, 267, 84, + 85, 86, 87, 268, 269, 258, 276, 277, 257, 0, + 298, 281, 282, 283, 284, 285, 286, 287, 288, 289, + 290, 291, 292, 293, 294, 295, 262, 0, 280, 259, + 300, 301, 302, 303, 274, 275, 261, 70, 71, 260, + 74, 74, 70, 71, 0, 78, 79, 277, 72, 265, + 82, 83, 90, 91, 92, 78, 79, 299, 258, 88, + 89, 279, 261, 261, 259, 48, 263, 261, 264, 261, + 300, 301, 302, 303, 261, 257, 258, 259, 260, 261, + 123, 77, 69, 0, 266, 267, 81, -1, -1, -1, + -1, -1, -1, 275, 276, 277, 278, -1, 0, 281, + 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, + 292, 293, 294, 295, -1, -1, -1, -1, 300, 301, + 302, 303, -1, -1, -1, -1, -1, -1, -1, 257, + 258, 259, 260, -1, -1, -1, -1, 0, 266, 267, + -1, -1, -1, -1, -1, -1, -1, 275, 276, 277, + 278, -1, 0, 281, 282, 283, 284, 285, 286, 287, + 288, 289, 290, 291, 292, 293, 294, 295, -1, -1, + -1, -1, 300, 301, 302, 303, -1, -1, -1, -1, + -1, -1, -1, 257, 258, 259, 260, -1, -1, -1, + -1, 0, 266, 267, -1, -1, -1, -1, -1, -1, + -1, -1, 276, 277, 278, -1, 0, 281, 282, 283, + 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, + 294, 295, -1, -1, -1, -1, 300, 301, 302, 303, + 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, + 270, 271, 272, 273, 274, 275, 0, 277, 260, 261, + 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, + 272, 273, 274, 275, 0, 277, 296, 297, 298, 299, + 261, -1, 263, 264, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 296, 297, 298, 299, 261, 262, + 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, + 273, 274, 275, 0, 277, 261, 262, 263, 264, 265, + 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, + 0, 277, -1, 296, 297, 298, 299, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 296, 297, 298, 299, 261, 262, 263, 264, 265, 266, + 267, 268, 269, 270, 271, 272, 273, 274, 275, 261, + 277, 263, 264, 265, 266, 267, 268, 269, 270, 271, + 272, 273, 274, 275, 0, 277, -1, -1, -1, 296, + 297, 298, 299, -1, -1, -1, -1, -1, -1, 0, + -1, -1, -1, -1, 296, 297, 298, 299, 261, -1, + 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, + 273, 274, 275, 261, 277, 263, 264, 265, 266, 267, + 268, 269, 270, 271, 272, 273, 274, 275, 0, 277, + -1, -1, -1, 296, 297, 298, 299, -1, -1, -1, + -1, -1, -1, 0, -1, -1, -1, -1, 296, 297, + 298, 299, 261, -1, 263, 264, 265, 266, 267, 268, + 269, 270, 271, 272, 273, 274, 275, 261, 277, 263, + 264, 265, -1, -1, 268, 269, 270, 271, 272, 273, + 274, 275, 0, 277, -1, -1, -1, 296, 297, 298, + 299, -1, -1, -1, -1, -1, -1, 0, -1, -1, + -1, -1, 296, 297, 298, 299, -1, 261, -1, 263, + 264, 265, -1, 0, 268, 269, 270, 271, 272, 273, + 274, 275, 0, 277, -1, 261, 0, 263, 264, 265, + 0, -1, 268, 269, 270, 271, 272, 273, 274, 275, + -1, 277, 296, 297, 298, 299, 0, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 296, 297, 298, 299, 261, -1, 263, 264, 265, 0, + -1, 268, 269, 270, 271, 272, 273, 274, 275, -1, + 277, 261, -1, 263, 264, 265, 0, -1, 268, 269, + 270, 271, 272, 273, 274, 275, -1, 277, -1, 296, + 297, 298, 299, -1, -1, -1, -1, -1, 0, -1, + -1, -1, -1, -1, -1, -1, 296, 297, 298, 299, + -1, -1, -1, -1, -1, 0, -1, -1, -1, -1, + -1, -1, -1, -1, 0, 261, -1, 263, 264, 265, + -1, -1, 268, 269, 270, 271, 272, 273, 274, 275, + 261, 277, 263, 264, 265, 0, -1, 268, 269, 270, + 271, 272, 273, 274, 275, -1, 0, -1, -1, -1, + 296, 297, 298, 299, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 296, 297, 298, 299, 261, + -1, 263, 264, -1, -1, -1, 268, 269, 270, 271, + 272, 273, 274, 275, 261, 277, 263, 264, -1, -1, + -1, 268, 269, 270, 271, 272, 273, 274, 275, -1, + -1, -1, -1, -1, 296, 297, 298, 299, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 298, 299, 261, -1, 263, 264, -1, -1, -1, + 268, 269, 270, 271, 272, 273, 274, 275, 261, -1, + 263, 264, -1, -1, -1, 268, 269, 270, 271, 272, + 273, 274, 275, -1, 261, -1, 263, 264, -1, -1, + 298, 299, -1, 261, -1, 263, 264, 261, -1, 263, + 264, 261, -1, 263, 264, 298, 299, -1, 268, 269, + 270, 271, 272, 273, -1, -1, -1, 261, -1, 263, + 264, 298, 299, -1, 268, 269, 270, 271, 272, 273, + 298, 299, -1, -1, 298, -1, -1, -1, 298, 299, + 261, -1, 263, 264, -1, -1, -1, 268, 269, 270, + 271, 272, 273, -1, 298, 299, -1, 261, -1, 263, + 264, -1, -1, -1, 268, 269, 270, 271, 272, 273, + -1, -1, -1, -1, -1, -1, -1, 298, 299, 261, + -1, 263, 264, -1, -1, -1, 268, 269, 270, 271, + 272, 273, -1, -1, 298, 299, 261, -1, 263, 264, + -1, -1, -1, 268, 269, 261, -1, 263, 264, -1, + -1, -1, 268, 269, -1, -1, 298, 299, -1, -1, + -1, -1, -1, -1, -1, -1, 261, -1, 263, 264, + -1, -1, -1, 298, 299, -1, -1, 261, -1, 263, + 264, -1, 298, 299, 268, 269, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 298, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 298, 299, + }; + } /* End of class YyCheckClass */ + + +//t protected static final class YyRuleClass { + +//t public static final String yyRule [] = { +//t "$accept : expr", +//t "expr : or_expr", +//t "location_path : relative_location_path", +//t "location_path : absolute_location_path", +//t "absolute_location_path : SLASH", +//t "absolute_location_path : SLASH relative_location_path", +//t "absolute_location_path : DOUBLE_SLASH relative_location_path", +//t "relative_location_path : step", +//t "relative_location_path : relative_location_path SLASH step", +//t "relative_location_path : relative_location_path DOUBLE_SLASH step", +//t "step : step_node_test", +//t "step : AT step_node_test", +//t "step : axis_name DOUBLE_COLON step_node_test", +//t "step : DOT", +//t "step : DOUBLE_DOT", +//t "step_node_test : node_test", +//t "step_node_test : step_node_test predicate", +//t "axis_name : ANCESTOR", +//t "axis_name : ANCESTOR_OR_SELF", +//t "axis_name : ATTRIBUTE", +//t "axis_name : CHILD", +//t "axis_name : DESCENDANT", +//t "axis_name : DESCENDANT_OR_SELF", +//t "axis_name : FOLLOWING", +//t "axis_name : FOLLOWING_SIBLING", +//t "axis_name : NAMESPACE", +//t "axis_name : PARENT", +//t "axis_name : PRECEDING", +//t "axis_name : PRECEDING_SIBLING", +//t "axis_name : SELF", +//t "node_test : name_test", +//t "node_test : PROCESSING_INSTRUCTION LITERAL RP", +//t "node_test : node_type RP", +//t "predicate : LB expr RB", +//t "primary_expr : variable_reference", +//t "primary_expr : LP expr RP", +//t "primary_expr : LITERAL", +//t "primary_expr : number", +//t "primary_expr : function_call", +//t "function_call : function_name LP RP", +//t "function_call : function_name LP argument_list RP", +//t "argument_list : expr", +//t "argument_list : expr COMMA argument_list", +//t "union_expr : path_expr", +//t "union_expr : union_expr PIPE path_expr", +//t "path_expr : location_path", +//t "path_expr : filter_expr", +//t "path_expr : filter_expr SLASH relative_location_path", +//t "path_expr : filter_expr DOUBLE_SLASH relative_location_path", +//t "filter_expr : primary_expr", +//t "filter_expr : filter_expr predicate", +//t "or_expr : and_expr", +//t "or_expr : or_expr OR and_expr", +//t "and_expr : equality_expr", +//t "and_expr : and_expr AND equality_expr", +//t "equality_expr : relational_expr", +//t "equality_expr : equality_expr EQ relational_expr", +//t "equality_expr : equality_expr NE relational_expr", +//t "relational_expr : additive_expr", +//t "relational_expr : relational_expr LT additive_expr", +//t "relational_expr : relational_expr GT additive_expr", +//t "relational_expr : relational_expr LTE additive_expr", +//t "relational_expr : relational_expr GTE additive_expr", +//t "additive_expr : multiplicative_expr", +//t "additive_expr : additive_expr PLUS multiplicative_expr", +//t "additive_expr : additive_expr MINUS multiplicative_expr", +//t "multiplicative_expr : unary_expr", +//t "multiplicative_expr : multiplicative_expr STAR unary_expr", +//t "multiplicative_expr : multiplicative_expr DIV unary_expr", +//t "multiplicative_expr : multiplicative_expr MOD unary_expr", +//t "unary_expr : union_expr", +//t "unary_expr : MINUS unary_expr", +//t "number : DIGITS", +//t "number : DIGITS DOT", +//t "number : DIGITS DOT DIGITS", +//t "number : DOT DIGITS", +//t "function_name : qname", +//t "variable_reference : DOLLAR qname", +//t "name_test : STAR", +//t "name_test : NAME COLON STAR", +//t "name_test : qname", +//t "qname : NAME", +//t "qname : NAME COLON NAME", +//t "node_type : COMMENT", +//t "node_type : TEXT", +//t "node_type : PROCESSING_INSTRUCTION", +//t "node_type : NODE", +//t }; +//t } /* End of class YyRuleClass */ + + protected static final class YyNameClass { + + public static final String yyName [] = { + "end-of-file",null,null,null,null,null,null,null,null,null,null,null, + null,null,null,null,null,null,null,null,null,null,null,null,null,null, + null,null,null,null,null,null,null,null,null,null,null,null,null,null, + null,null,null,null,null,null,null,null,null,null,null,null,null,null, + null,null,null,null,null,null,null,null,null,null,null,null,null,null, + null,null,null,null,null,null,null,null,null,null,null,null,null,null, + null,null,null,null,null,null,null,null,null,null,null,null,null,null, + null,null,null,null,null,null,null,null,null,null,null,null,null,null, + null,null,null,null,null,null,null,null,null,null,null,null,null,null, + null,null,null,null,null,null,null,null,null,null,null,null,null,null, + null,null,null,null,null,null,null,null,null,null,null,null,null,null, + null,null,null,null,null,null,null,null,null,null,null,null,null,null, + null,null,null,null,null,null,null,null,null,null,null,null,null,null, + null,null,null,null,null,null,null,null,null,null,null,null,null,null, + null,null,null,null,null,null,null,null,null,null,null,null,null,null, + null,null,null,null,null,null,null,null,null,null,null,null,null,null, + null,null,null,null,null,null,null,null,null,null,null,null,null,null, + null,null,null,null,null,null,null,null,null,null,null,null,null,null, + null,null,null,null,null,null,null,"LITERAL","DIGITS","NAME","LP", + "RP","LB","RB","COMMA","PIPE","SLASH","DOUBLE_SLASH","EQ","NE","GT", + "LT","GTE","LTE","PLUS","MINUS","AT","STAR","DOLLAR","COLON", + "DOUBLE_COLON","DOT","DOUBLE_DOT","ANCESTOR","ANCESTOR_OR_SELF", + "ATTRIBUTE","CHILD","DESCENDANT","DESCENDANT_OR_SELF","FOLLOWING", + "FOLLOWING_SIBLING","NAMESPACE","PARENT","PRECEDING", + "PRECEDING_SIBLING","SELF","DIV","MOD","OR","AND","COMMENT", + "PROCESSING_INSTRUCTION","TEXT","NODE","UNARY", + }; + } /* End of class YyNameClass */ + + + // line 781 "XPathParser.y" + +} + // line 1461 "-" diff --git a/libjava/gnu/xml/xpath/XPathParser.y b/libjava/gnu/xml/xpath/XPathParser.y new file mode 100644 index 0000000..8af7cb8 --- /dev/null +++ b/libjava/gnu/xml/xpath/XPathParser.y @@ -0,0 +1,782 @@ +%{ +/* XPathParser.java -- An XPath 1.0 parser. + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.xml.namespace.NamespaceContext; +import javax.xml.namespace.QName; +import javax.xml.xpath.XPathFunctionResolver; +import javax.xml.xpath.XPathVariableResolver; +import org.w3c.dom.Node; + +/** + * An XPath 1.0 parser. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class XPathParser +{ + + NamespaceContext namespaceContext; + XPathVariableResolver variableResolver; + XPathFunctionResolver functionResolver; + + QName getQName(String name) + { + QName qName = QName.valueOf(name); + if (namespaceContext != null) + { + String prefix = qName.getPrefix(); + String uri = qName.getNamespaceURI(); + if (prefix != null && (uri == null || uri.length() == 0)) + { + uri = namespaceContext.getNamespaceURI(prefix); + String localName = qName.getLocalPart(); + qName = new QName(uri, localName, prefix); + } + } + return qName; + } + + Expr lookupFunction(String name, List args) + { + int arity = args.size(); + if ("position".equals(name) && arity == 0) + { + return new PositionFunction(); + } + else if ("last".equals(name) && arity == 0) + { + return new LastFunction(); + } + else if ("string".equals(name) && (arity == 1 || arity == 0)) + { + return new StringFunction(args); + } + else if ("number".equals(name) && (arity == 1 || arity == 0)) + { + return new NumberFunction(args); + } + else if ("boolean".equals(name) && arity == 1) + { + return new BooleanFunction(args); + } + else if ("count".equals(name) && arity == 1) + { + return new CountFunction(args); + } + else if ("not".equals(name) && arity == 1) + { + return new NotFunction(args); + } + else if ("id".equals(name) && arity == 1) + { + return new IdFunction(args); + } + else if ("concat".equals(name) && arity > 1) + { + return new ConcatFunction(args); + } + else if ("true".equals(name) && arity == 0) + { + return new TrueFunction(); + } + else if ("false".equals(name) && arity == 0) + { + return new FalseFunction(); + } + else if ("name".equals(name) && (arity == 1 || arity == 0)) + { + return new NameFunction(args); + } + else if ("local-name".equals(name) && (arity == 1 || arity == 0)) + { + return new LocalNameFunction(args); + } + else if ("namespace-uri".equals(name) && (arity == 1 || arity == 0)) + { + return new NamespaceUriFunction(args); + } + else if ("starts-with".equals(name) && arity == 2) + { + return new StartsWithFunction(args); + } + else if ("contains".equals(name) && arity == 2) + { + return new ContainsFunction(args); + } + else if ("string-length".equals(name) && (arity == 1 || arity == 0)) + { + return new StringLengthFunction(args); + } + else if ("translate".equals(name) && arity == 3) + { + return new TranslateFunction(args); + } + else if ("normalize-space".equals(name) && (arity == 1 || arity == 0)) + { + return new NormalizeSpaceFunction(args); + } + else if ("substring".equals(name) && (arity == 2 || arity == 3)) + { + return new SubstringFunction(args); + } + else if ("substring-before".equals(name) && arity == 2) + { + return new SubstringBeforeFunction(args); + } + else if ("substring-after".equals(name) && arity == 2) + { + return new SubstringAfterFunction(args); + } + else if ("lang".equals(name) && arity == 1) + { + return new LangFunction(args); + } + else if ("sum".equals(name) && arity == 1) + { + return new SumFunction(args); + } + else if ("floor".equals(name) && arity == 1) + { + return new FloorFunction(args); + } + else if ("ceiling".equals(name) && arity == 1) + { + return new CeilingFunction(args); + } + else if ("round".equals(name) && arity == 1) + { + return new RoundFunction(args); + } + else if (functionResolver != null) + { + QName qName = QName.valueOf(name); + Object function = functionResolver.resolveFunction(qName, arity); + if (function != null && + function instanceof Function && + function instanceof Expr) + { + Function f = (Function) function; + f.setArguments(args); + return (Expr) function; + } + } + return new FunctionCall(functionResolver, name, args); + } + +%} + +%token LITERAL +%token DIGITS +%token NAME + +%token LP // '(' +%token RP // ')' +%token LB // '[' +%token RB // ']' +%token COMMA // ',' +%token PIPE // '|' +%token SLASH // '/' +%token DOUBLE_SLASH // '//' +%token EQ // '=' +%token NE // '!=' +%token GT // '>' +%token LT // '<' +%token GTE // '>=' +%token LTE // '<=' +%token PLUS // '+' +%token MINUS // '-' +%token AT // '@' +%token STAR // '*' +%token DOLLAR // '$' +%token COLON // ':' +%token DOUBLE_COLON // '::' +%token DOT // '.' +%token DOUBLE_DOT // '..' + +%token ANCESTOR +%token ANCESTOR_OR_SELF +%token ATTRIBUTE +%token CHILD +%token DESCENDANT +%token DESCENDANT_OR_SELF +%token FOLLOWING +%token FOLLOWING_SIBLING +%token NAMESPACE +%token PARENT +%token PRECEDING +%token PRECEDING_SIBLING +%token SELF +%token DIV +%token MOD +%token OR +%token AND +%token COMMENT +%token PROCESSING_INSTRUCTION +%token TEXT +%token NODE + +%right UNARY + +%start expr + +%% + +expr: + or_expr + ; + +location_path: + relative_location_path + | absolute_location_path + ; + +absolute_location_path: + SLASH + { + $$ = new Root(); + } + | SLASH relative_location_path + { + Steps steps; + if ($2 instanceof Steps) + { + steps = (Steps) $2; + } + else + { + steps = new Steps(); + steps.path.addFirst($2); + } + steps.path.addFirst(new Root()); + $$ = steps; + //$$ = new Step(new Root(), (Path) $2); + } + | DOUBLE_SLASH relative_location_path + { + Test nt = new NodeTypeTest((short) 0); + Selector s = new Selector(Selector.DESCENDANT_OR_SELF, + Collections.singletonList (nt)); + Steps steps; + if ($2 instanceof Steps) + { + steps = (Steps) $2; + } + else + { + steps = new Steps(); + steps.path.addFirst($2); + } + steps.path.addFirst(s); + steps.path.addFirst(new Root()); + $$ = steps; + //Step step = new Step(s, (Path) $2); + //$$ = new Step(new Root(), step); + } + ; + +relative_location_path: + step + | relative_location_path SLASH step + { + Steps steps; + if ($1 instanceof Steps) + { + steps = (Steps) $1; + } + else + { + steps = new Steps(); + steps.path.addFirst($1); + } + steps.path.addLast($3); + $$ = steps; + //$$ = new Step((Expr) $1, (Path) $3); + } + | relative_location_path DOUBLE_SLASH step + { + Test nt = new NodeTypeTest((short) 0); + Selector s = new Selector(Selector.DESCENDANT_OR_SELF, + Collections.singletonList (nt)); + Steps steps; + if ($1 instanceof Steps) + { + steps = (Steps) $1; + } + else + { + steps = new Steps(); + steps.path.addFirst($1); + } + steps.path.addLast(s); + steps.path.addLast($3); + $$ = steps; + //Step step = new Step(s, (Path) $3); + //$$ = new Step((Expr) $1, step); + } + ; + +step: + step_node_test + { + $$ = new Selector (Selector.CHILD, (List) $1); + } + | AT step_node_test + { + $$ = new Selector (Selector.ATTRIBUTE, (List) $2); + } + | axis_name DOUBLE_COLON step_node_test + { + $$ = new Selector (((Integer) $1).intValue (), (List) $3); + } + | DOT + { + $$ = new Selector (Selector.SELF, Collections.EMPTY_LIST); + } + | DOUBLE_DOT + { + $$ = new Selector (Selector.PARENT, Collections.EMPTY_LIST); + } + ; + +step_node_test: + node_test + { + List list = new ArrayList(); + list.add($1); + $$ = list; + } + | step_node_test predicate + { + List list = (List)$1; + list.add($2); + $$ = list; + } + ; + +/*predicate_list: + predicate + { + List list = new ArrayList (); + list.add ($1); + $$ = list; + } + | predicate predicate_list + { + List list = (List) $3; + list.add (0, $1); + $$ = list; + } + ;*/ + +axis_name: + ANCESTOR + { + $$ = new Integer(Selector.ANCESTOR); + } + | ANCESTOR_OR_SELF + { + $$ = new Integer(Selector.ANCESTOR_OR_SELF); + } + | ATTRIBUTE + { + $$ = new Integer(Selector.ATTRIBUTE); + } + | CHILD + { + $$ = new Integer(Selector.CHILD); + } + | DESCENDANT + { + $$ = new Integer(Selector.DESCENDANT); + } + | DESCENDANT_OR_SELF + { + $$ = new Integer(Selector.DESCENDANT_OR_SELF); + } + | FOLLOWING + { + $$ = new Integer(Selector.FOLLOWING); + } + | FOLLOWING_SIBLING + { + $$ = new Integer(Selector.FOLLOWING_SIBLING); + } + | NAMESPACE + { + $$ = new Integer(Selector.NAMESPACE); + } + | PARENT + { + $$ = new Integer(Selector.PARENT); + } + | PRECEDING + { + $$ = new Integer(Selector.PRECEDING); + } + | PRECEDING_SIBLING + { + $$ = new Integer(Selector.PRECEDING_SIBLING); + } + | SELF + { + $$ = new Integer(Selector.SELF); + } + ; + +node_test: + name_test + /*| PROCESSING_INSTRUCTION LP LITERAL RP*/ + | PROCESSING_INSTRUCTION LITERAL RP + { + $$ = new NodeTypeTest(Node.PROCESSING_INSTRUCTION_NODE, (String) $2); + } + /*| node_type LP RP*/ + | node_type RP + { + $$ = new NodeTypeTest(((Short) $1).shortValue()); + } + ; + +predicate: + LB expr RB + { + $$ = new Predicate((Expr) $2); + } + ; + +primary_expr: + variable_reference + | LP expr RP + { + $$ = new ParenthesizedExpr((Expr) $2); + } + | LITERAL + { + $$ = new Constant($1); + } + | number + { + $$ = new Constant($1); + } + | function_call + ; + +function_call: + function_name LP RP + { + $$ = lookupFunction((String) $1, Collections.EMPTY_LIST); + } + | function_name LP argument_list RP + { + $$ = lookupFunction((String) $1, (List) $3); + } + ; + +argument_list: + expr + { + List list = new ArrayList(); + list.add($1); + $$ = list; + } + | expr COMMA argument_list + { + List list = (List) $3; + list.add(0, $1); + $$ = list; + } + ; + +union_expr: + path_expr + | union_expr PIPE path_expr + { + $$ = new UnionExpr((Expr) $1, (Expr) $3); + } + ; + +path_expr: + location_path + | filter_expr + | filter_expr SLASH relative_location_path + { + Steps steps; + if ($3 instanceof Steps) + { + steps = (Steps) $3; + } + else + { + steps = new Steps(); + steps.path.addFirst($3); + } + steps.path.addFirst($1); + $$ = steps; + //$$ = new Step ((Expr) $1, (Path) $3); + } + | filter_expr DOUBLE_SLASH relative_location_path + { + Test nt = new NodeTypeTest((short) 0); + Selector s = new Selector(Selector.DESCENDANT_OR_SELF, + Collections.singletonList(nt)); + Steps steps; + if ($3 instanceof Steps) + { + steps = (Steps) $3; + } + else + { + steps = new Steps(); + steps.path.addFirst($3); + } + steps.path.addFirst(s); + steps.path.addFirst($1); + $$ = steps; + //Step step = new Step (s, (Path) $3); + //$$ = new Step ((Expr) $1, step); + } + ; + +filter_expr: + primary_expr + | filter_expr predicate + { + Predicate filter = (Predicate) $2; + Selector s = new Selector(Selector.SELF, + Collections.singletonList(filter)); + Steps steps; + if ($1 instanceof Steps) + { + steps = (Steps) $1; + } + else + { + steps = new Steps(); + steps.path.addFirst($1); + } + steps.path.addLast(s); + $$ = steps; + //$$ = new Step ((Expr) $1, s); + } + ; + +or_expr: + and_expr + | or_expr OR and_expr + { + $$ = new OrExpr((Expr) $1, (Expr) $3); + } + ; + +and_expr: + equality_expr + | and_expr AND equality_expr + { + $$ = new AndExpr((Expr) $1, (Expr) $3); + } + ; + +equality_expr: + relational_expr + | equality_expr EQ relational_expr + { + $$ = new EqualityExpr((Expr) $1, (Expr) $3, false); + } + | equality_expr NE relational_expr + { + $$ = new EqualityExpr((Expr) $1, (Expr) $3, true); + } + ; + +relational_expr: + additive_expr + | relational_expr LT additive_expr + { + $$ = new RelationalExpr((Expr) $1, (Expr) $3, true, false); + } + | relational_expr GT additive_expr + { + $$ = new RelationalExpr((Expr) $1, (Expr) $3, false, false); + } + | relational_expr LTE additive_expr + { + $$ = new RelationalExpr((Expr) $1, (Expr) $3, true, true); + } + | relational_expr GTE additive_expr + { + $$ = new RelationalExpr((Expr) $1, (Expr) $3, false, true); + } + ; + +additive_expr: + multiplicative_expr + | additive_expr PLUS multiplicative_expr + { + $$ = new ArithmeticExpr((Expr) $1, (Expr) $3, ArithmeticExpr.ADD); + } + | additive_expr MINUS multiplicative_expr + { + $$ = new ArithmeticExpr((Expr) $1, (Expr) $3, ArithmeticExpr.SUBTRACT); + } + ; + +multiplicative_expr: + unary_expr + | multiplicative_expr STAR unary_expr + { + $$ = new ArithmeticExpr((Expr) $1, (Expr) $3, ArithmeticExpr.MULTIPLY); + } + | multiplicative_expr DIV unary_expr + { + $$ = new ArithmeticExpr((Expr) $1, (Expr) $3, ArithmeticExpr.DIVIDE); + } + | multiplicative_expr MOD unary_expr + { + $$ = new ArithmeticExpr((Expr) $1, (Expr) $3, ArithmeticExpr.MODULO); + } + ; + +unary_expr: + union_expr + | MINUS unary_expr %prec UNARY + { + $$ = new NegativeExpr((Expr) $2); + } + ; + +number: + DIGITS + { + $$ = new Double((String) $1 + ".0"); + } + | DIGITS DOT + { + $$ = new Double((String) $1 + ".0"); + } + | DIGITS DOT DIGITS + { + $$ = new Double((String) $1 + "." + (String) $3); + } + | DOT DIGITS + { + $$ = new Double("0." + (String) $2); + } + ; + +function_name: + qname +/* | node_type + { + switch (((Short) $1).shortValue ()) + { + case Node.COMMENT_NODE: + $$ = "comment"; + break; + case Node.TEXT_NODE: + $$ = "text"; + break; + case Node.PROCESSING_INSTRUCTION_NODE: + $$ = "processing-instruction"; + break; + default: + $$ = "node"; + break; + } + }*/ + ; + +variable_reference: + DOLLAR qname + { + $$ = new VariableReference(variableResolver, (String) $2); + } + ; + +name_test: + STAR + { + $$ = new NameTest(null, true, true); + } + | NAME COLON STAR + { + QName qName = getQName((String) $1); + $$ = new NameTest(qName, true, false); + } + | qname + { + QName qName = getQName((String) $1); + $$ = new NameTest(qName, false, false); + } + ; + +qname: + NAME + | NAME COLON NAME + { + $$ = (String) $1 + ':' + (String) $3; + } + ; + +node_type: + COMMENT + { + $$ = new Short(Node.COMMENT_NODE); + } + | TEXT + { + $$ = new Short(Node.TEXT_NODE); + } + | PROCESSING_INSTRUCTION + { + $$ = new Short(Node.PROCESSING_INSTRUCTION_NODE); + } + | NODE + { + $$ = new Short((short) 0); + } + ; + +%% + +} diff --git a/libjava/gnu/xml/xpath/XPathTokenizer.java b/libjava/gnu/xml/xpath/XPathTokenizer.java new file mode 100644 index 0000000..6bf31fd --- /dev/null +++ b/libjava/gnu/xml/xpath/XPathTokenizer.java @@ -0,0 +1,592 @@ +/* XPathTokenizer.java -- + Copyright (C) 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.xpath; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.util.Map; +import java.util.TreeMap; + +/*import antlr.Token; +import antlr.TokenStream; +import antlr.TokenStreamException; +import antlr.TokenStreamIOException;*/ + +/** + * XPath 1.0 expression tokenizer. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +public class XPathTokenizer +implements XPathParser.yyInput +//implements TokenStream +{ + + static class XPathToken + //extends Token + { + + int type; + String val; + + XPathToken (int type) + { + this (type, null); + } + + XPathToken (int type, String val) + { + //super (type); + this.type = type; + this.val = val; + } + + public String getText () + { + return val; + } + + public String toString () + { + return val; + } + + } + + static final Map keywords = new TreeMap (); + static + { + keywords.put ("ancestor", new Integer (XPathParser.ANCESTOR)); + keywords.put ("ancestor-or-self", new Integer (XPathParser.ANCESTOR_OR_SELF)); + keywords.put ("attribute", new Integer (XPathParser.ATTRIBUTE)); + keywords.put ("child", new Integer (XPathParser.CHILD)); + keywords.put ("descendant", new Integer (XPathParser.DESCENDANT)); + keywords.put ("descendant-or-self", new Integer (XPathParser.DESCENDANT_OR_SELF)); + keywords.put ("following", new Integer (XPathParser.FOLLOWING)); + keywords.put ("following-sibling", new Integer (XPathParser.FOLLOWING_SIBLING)); + keywords.put ("namespace", new Integer (XPathParser.NAMESPACE)); + keywords.put ("parent", new Integer (XPathParser.PARENT)); + keywords.put ("preceding", new Integer (XPathParser.PRECEDING)); + keywords.put ("preceding-sibling", new Integer (XPathParser.PRECEDING_SIBLING)); + keywords.put ("self", new Integer (XPathParser.SELF)); + keywords.put ("div", new Integer (XPathParser.DIV)); + keywords.put ("mod", new Integer (XPathParser.MOD)); + keywords.put ("or", new Integer (XPathParser.OR)); + keywords.put ("and", new Integer (XPathParser.AND)); + keywords.put ("comment", new Integer (XPathParser.COMMENT)); + keywords.put ("processing-instruction", new Integer (XPathParser.PROCESSING_INSTRUCTION)); + keywords.put ("text", new Integer (XPathParser.TEXT)); + keywords.put ("node", new Integer (XPathParser.NODE)); + } + + Reader in; + XPathToken token; + XPathToken lastToken; + + public XPathTokenizer (String expr) + { + this (new StringReader (expr)); + } + + XPathTokenizer (Reader in) + { + this.in = in.markSupported () ? in : new BufferedReader (in); + } + + /* Begin ANTLR specific * + + public Token nextToken () + throws TokenStreamException + { + try + { + if (!advance ()) + { + throw new TokenStreamException ("eof"); + } + token (); + return token; + } + catch (IOException e) + { + throw new TokenStreamIOException (e); + } + } + + * End ANTLR specific */ + + public boolean advance () + throws IOException + { + lastToken = token; + int c = in.read (); + switch (c) + { + case -1: // eof + return false; + case 0x20: + case 0x09: + case 0x0d: + case 0x0a: // skip whitespace + return advance (); + case 0x22: // " + case 0x27: // ' + token = consume_literal (c); + break; + case 0x28: // ( + token = new XPathToken (XPathParser.LP); + break; + case 0x29: // ) + token = new XPathToken (XPathParser.RP); + break; + case 0x5b: // [ + token = new XPathToken (XPathParser.LB); + break; + case 0x5d: // ] + token = new XPathToken (XPathParser.RB); + break; + case 0x2c: // , + token = new XPathToken (XPathParser.COMMA); + break; + case 0x7c: // | + token = new XPathToken (XPathParser.PIPE); + break; + case 0x2f: // / + in.mark (1); + int d1 = in.read (); + if (d1 == 0x2f) + { + token = new XPathToken (XPathParser.DOUBLE_SLASH); + } + else + { + in.reset (); + token = new XPathToken (XPathParser.SLASH); + } + break; + case 0x3d: // = + token = new XPathToken (XPathParser.EQ); + break; + case 0x21: // ! + in.mark (1); + int d2 = in.read (); + if (d2 == 0x3d) // = + { + token = new XPathToken (XPathParser.NE); + } + else + { + in.reset (); + token = new XPathToken (XPathParser.yyErrorCode); + } + break; + case 0x3e: // > + in.mark (1); + int d3 = in.read (); + if (d3 == 0x3d) // = + { + token = new XPathToken (XPathParser.GTE); + } + else + { + in.reset (); + token = new XPathToken (XPathParser.GT); + } + break; + case 0x3c: // < + in.mark (1); + int d4 = in.read (); + if (d4 == 0x3d) // = + { + token = new XPathToken (XPathParser.LTE); + } + else + { + in.reset (); + token = new XPathToken (XPathParser.LT); + } + break; + case 0x2b: // + + token = new XPathToken (XPathParser.PLUS); + break; + case 0x2d: // - + token = new XPathToken (XPathParser.MINUS); + break; + case 0x40: // @ + token = new XPathToken (XPathParser.AT); + break; + case 0x2a: // * + token = new XPathToken (XPathParser.STAR); + break; + case 0x24: // $ + token = new XPathToken (XPathParser.DOLLAR); + break; + case 0x3a: // : + in.mark (1); + int d5 = in.read (); + if (d5 == 0x3a) + { + token = new XPathToken (XPathParser.DOUBLE_COLON); + } + else + { + in.reset (); + token = new XPathToken (XPathParser.COLON); + } + break; + case 0x2e: // . + in.mark (1); + int d6 = in.read (); + if (d6 == 0x2e) + { + token = new XPathToken (XPathParser.DOUBLE_DOT); + } + else + { + in.reset (); + token = new XPathToken (XPathParser.DOT); + } + break; + default: + if (c >= 0x30 && c <= 0x39) + { + token = consume_digits (c); + } + else if (c == 0x5f || Character.isLetter ((char) c)) + { + token = consume_name (c); + } + else + { + token = new XPathToken (XPathParser.yyErrorCode); + } + } + return true; + } + + public int token () + { + return token.type; + } + + public Object value () + { + return token.val; + } + + XPathToken consume_literal (int delimiter) + throws IOException + { + StringBuffer buf = new StringBuffer (); + while (true) + { + int c = in.read (); + if (c == -1) + { + return new XPathToken (XPathParser.yyErrorCode); + } + else if (c == delimiter) + { + return new XPathToken (XPathParser.LITERAL, buf.toString ()); + } + else + { + buf.append ((char) c); + } + } + } + + XPathToken consume_digits (int c) + throws IOException + { + StringBuffer buf = new StringBuffer (); + buf.append ((char) c); + while (true) + { + in.mark (1); + c = in.read (); + if (c >= 0x30 && c <= 0x39) + { + buf.append ((char) c); + } + else + { + in.reset (); + return new XPathToken (XPathParser.DIGITS, buf.toString ()); + } + } + } + + XPathToken consume_name (int c) + throws IOException + { + StringBuffer buf = new StringBuffer (); + buf.append ((char) c); + while (true) + { + in.mark (1); + c = in.read (); + if (isNameChar (c)) + { + buf.append ((char) c); + } + else + { + in.reset (); + String name = buf.toString (); + Integer keyword = (Integer) keywords.get (name); + if (keyword == null) + { + return new XPathToken (XPathParser.NAME, name); + } + else + { + int val = keyword.intValue (); + switch (val) + { + case XPathParser.NODE: + case XPathParser.COMMENT: + case XPathParser.TEXT: + case XPathParser.PROCESSING_INSTRUCTION: + // Consume subsequent ( + in.mark (1); + do + { + c = in.read (); + } + while (c == 0x20 || c == 0x09); + if (c != 0x28) + { + in.reset (); + return new XPathToken (XPathParser.NAME, name); + } + break; + case XPathParser.CHILD: + case XPathParser.PARENT: + case XPathParser.SELF: + case XPathParser.DESCENDANT: + case XPathParser.ANCESTOR: + case XPathParser.DESCENDANT_OR_SELF: + case XPathParser.ANCESTOR_OR_SELF: + case XPathParser.ATTRIBUTE: + case XPathParser.NAMESPACE: + case XPathParser.FOLLOWING: + case XPathParser.FOLLOWING_SIBLING: + case XPathParser.PRECEDING: + case XPathParser.PRECEDING_SIBLING: + // Check that this is an axis specifier + in.mark(1); + do + { + c = in.read(); + } + while (c == 0x20 || c == 0x09); + if (c == 0x3a) + { + c = in.read(); + if (c == 0x3a) + { + in.reset(); + return new XPathToken(val); + } + } + in.reset(); + return new XPathToken(XPathParser.NAME, name); + case XPathParser.DIV: + case XPathParser.MOD: + // May be a name + if (lastToken == null) + { + return new XPathToken(XPathParser.NAME, name); + } + switch (lastToken.type) + { + case XPathParser.LP: + case XPathParser.LB: + case XPathParser.COMMA: + case XPathParser.PIPE: + case XPathParser.EQ: + case XPathParser.NE: + case XPathParser.GT: + case XPathParser.LT: + case XPathParser.GTE: + case XPathParser.LTE: + case XPathParser.PLUS: + case XPathParser.MINUS: + case XPathParser.STAR: + case XPathParser.AT: + case XPathParser.DOLLAR: + case XPathParser.COLON: + case XPathParser.DOUBLE_COLON: + case XPathParser.DIV: + case XPathParser.MOD: + case XPathParser.OR: + case XPathParser.AND: + case XPathParser.SLASH: + return new XPathToken(XPathParser.NAME, name); + } + break; + } + return new XPathToken (val); + } + } + } + } + + boolean isNameChar (int c) + { + /* Name */ + return (c == 0x5f + || c == 0x2d + || c == 0x2e + || (c >= 0x30 && c <= 0x39) + /* CombiningChar */ + || (c >= 0x0300 && c <= 0x0345) + || (c >= 0x0360 && c <= 0x0361) + || (c >= 0x0483 && c <= 0x0486) + || (c >= 0x0591 && c <= 0x05A1) + || (c >= 0x05A3 && c <= 0x05B9) + || (c >= 0x05BB && c <= 0x05BD) + || c == 0x05BF + || (c >= 0x05C1 && c <= 0x05C2) + || c == 0x05C4 + || (c >= 0x064B && c <= 0x0652) + || c == 0x0670 + || (c >= 0x06D6 && c <= 0x06DC) + || (c >= 0x06DD && c <= 0x06DF) + || (c >= 0x06E0 && c <= 0x06E4) + || (c >= 0x06E7 && c <= 0x06E8) + || (c >= 0x06EA && c <= 0x06ED) + || (c >= 0x0901 && c <= 0x0903) + || c == 0x093C + || (c >= 0x093E && c <= 0x094C) + || c == 0x094D + || (c >= 0x0951 && c <= 0x0954) + || (c >= 0x0962 && c <= 0x0963) + || (c >= 0x0981 && c <= 0x0983) + || c == 0x09BC + || c == 0x09BE + || c == 0x09BF + || (c >= 0x09C0 && c <= 0x09C4) + || (c >= 0x09C7 && c <= 0x09C8) + || (c >= 0x09CB && c <= 0x09CD) + || c == 0x09D7 + || (c >= 0x09E2 && c <= 0x09E3) + || c == 0x0A02 + || c == 0x0A3C + || c == 0x0A3E + || c == 0x0A3F + || (c >= 0x0A40 && c <= 0x0A42) + || (c >= 0x0A47 && c <= 0x0A48) + || (c >= 0x0A4B && c <= 0x0A4D) + || (c >= 0x0A70 && c <= 0x0A71) + || (c >= 0x0A81 && c <= 0x0A83) + || c == 0x0ABC + || (c >= 0x0ABE && c <= 0x0AC5) + || (c >= 0x0AC7 && c <= 0x0AC9) + || (c >= 0x0ACB && c <= 0x0ACD) + || (c >= 0x0B01 && c <= 0x0B03) + || c == 0x0B3C + || (c >= 0x0B3E && c <= 0x0B43) + || (c >= 0x0B47 && c <= 0x0B48) + || (c >= 0x0B4B && c <= 0x0B4D) + || (c >= 0x0B56 && c <= 0x0B57) + || (c >= 0x0B82 && c <= 0x0B83) + || (c >= 0x0BBE && c <= 0x0BC2) + || (c >= 0x0BC6 && c <= 0x0BC8) + || (c >= 0x0BCA && c <= 0x0BCD) + || c == 0x0BD7 + || (c >= 0x0C01 && c <= 0x0C03) + || (c >= 0x0C3E && c <= 0x0C44) + || (c >= 0x0C46 && c <= 0x0C48) + || (c >= 0x0C4A && c <= 0x0C4D) + || (c >= 0x0C55 && c <= 0x0C56) + || (c >= 0x0C82 && c <= 0x0C83) + || (c >= 0x0CBE && c <= 0x0CC4) + || (c >= 0x0CC6 && c <= 0x0CC8) + || (c >= 0x0CCA && c <= 0x0CCD) + || (c >= 0x0CD5 && c <= 0x0CD6) + || (c >= 0x0D02 && c <= 0x0D03) + || (c >= 0x0D3E && c <= 0x0D43) + || (c >= 0x0D46 && c <= 0x0D48) + || (c >= 0x0D4A && c <= 0x0D4D) + || c == 0x0D57 + || c == 0x0E31 + || (c >= 0x0E34 && c <= 0x0E3A) + || (c >= 0x0E47 && c <= 0x0E4E) + || c == 0x0EB1 + || (c >= 0x0EB4 && c <= 0x0EB9) + || (c >= 0x0EBB && c <= 0x0EBC) + || (c >= 0x0EC8 && c <= 0x0ECD) + || (c >= 0x0F18 && c <= 0x0F19) + || c == 0x0F35 + || c == 0x0F37 + || c == 0x0F39 + || c == 0x0F3E + || c == 0x0F3F + || (c >= 0x0F71 && c <= 0x0F84) + || (c >= 0x0F86 && c <= 0x0F8B) + || (c >= 0x0F90 && c <= 0x0F95) + || c == 0x0F97 + || (c >= 0x0F99 && c <= 0x0FAD) + || (c >= 0x0FB1 && c <= 0x0FB7) + || c == 0x0FB9 + || (c >= 0x20D0 && c <= 0x20DC) + || c == 0x20E1 + || (c >= 0x302A && c <= 0x302F) + || c == 0x3099 + || c == 0x309A + /* Extender */ + || c == 0x00B7 + || c == 0x02D0 + || c == 0x02D1 + || c == 0x0387 + || c == 0x0640 + || c == 0x0E46 + || c == 0x0EC6 + || c == 0x3005 + || (c >= 0x3031 && c <= 0x3035) + || (c >= 0x309D && c <= 0x309E) + || (c >= 0x30FC && c <= 0x30FE) + /* Name */ + || Character.isLetter ((char) c)); + } + +} |