diff options
Diffstat (limited to 'libjava/classpath/gnu/xml/pipeline/NSFilter.java')
-rw-r--r-- | libjava/classpath/gnu/xml/pipeline/NSFilter.java | 341 |
1 files changed, 341 insertions, 0 deletions
diff --git a/libjava/classpath/gnu/xml/pipeline/NSFilter.java b/libjava/classpath/gnu/xml/pipeline/NSFilter.java new file mode 100644 index 000000000..0fa4621d3 --- /dev/null +++ b/libjava/classpath/gnu/xml/pipeline/NSFilter.java @@ -0,0 +1,341 @@ +/* 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., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 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.Enumeration; +import java.util.Stack; + +import org.xml.sax.Attributes; +import org.xml.sax.ErrorHandler; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +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 (); + } +} |