Source for gnu.xml.pipeline.NSFilter

   1: /* NSFilter.java -- 
   2:    Copyright (C) 1999,2000,2001 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: package gnu.xml.pipeline;
  39: 
  40: import java.util.Enumeration;
  41: import java.util.Stack;
  42: 
  43: import org.xml.sax.Attributes;
  44: import org.xml.sax.ErrorHandler;
  45: import org.xml.sax.Locator;
  46: import org.xml.sax.SAXException;
  47: import org.xml.sax.SAXParseException;
  48: import org.xml.sax.helpers.AttributesImpl;
  49: import org.xml.sax.helpers.NamespaceSupport;
  50: 
  51: /**
  52:  * This filter ensures that element and attribute names are properly prefixed,
  53:  * and that such prefixes are declared.  Such data is critical for operations
  54:  * like writing XML text, and validating against DTDs:  names or their prefixes
  55:  * may have been discarded, although they are essential to the exchange of
  56:  * information using XML.  There are various common ways that such data
  57:  * gets discarded: <ul>
  58:  *
  59:  *    <li> By default, SAX2 parsers must discard the "xmlns*"
  60:  *    attributes, and may also choose not to report properly prefixed
  61:  *    names for elements or attributes.  (Some parsers may support
  62:  *    changing the <em>namespace-prefixes</em> value from the default
  63:  *    to <em>true</em>, effectively eliminating the need to use this
  64:  *    filter on their output.)
  65:  *
  66:  *    <li> When event streams are generated from a DOM tree, they may
  67:  *    have never have had prefixes or declarations for namespaces; or
  68:  *    the existing prefixes or declarations may have been invalidated
  69:  *    by structural modifications to that DOM tree.
  70:  *
  71:  *    <li> Other software writing SAX event streams won't necessarily
  72:  *    be worrying about prefix management, and so they will need to
  73:  *    have a transparent solution for managing them.
  74:  *
  75:  *    </ul>
  76:  *
  77:  * <p> This filter uses a heuristic to choose the prefix to assign to any
  78:  * particular name which wasn't already corectly prefixed.  The associated
  79:  * namespace will be correct, and the prefix will be declared.  Original
  80:  * structures facilitating text editing, such as conventions about use of
  81:  * mnemonic prefix names or the scoping of prefixes, can't always be
  82:  * reconstructed after they are discarded, as strongly encouraged by the
  83:  * current SAX2 defaults.
  84:  *
  85:  * <p> Note that this can't possibly know whether values inside attribute
  86:  * value or document content involve prefixed names.  If your application
  87:  * requires using prefixed names in such locations you'll need to add some
  88:  * appropriate logic (perhaps adding additional heuristics in a subclass).
  89:  *
  90:  * @author David Brownell
  91:  */
  92: public class NSFilter extends EventFilter
  93: {
  94:     private NamespaceSupport    nsStack = new NamespaceSupport ();
  95:     private Stack        elementStack = new Stack ();
  96: 
  97:     private boolean        pushedContext;
  98:     private String        nsTemp [] = new String [3];
  99:     private AttributesImpl    attributes = new AttributesImpl ();
 100:     private boolean        usedDefault;
 101: 
 102:     // gensymmed prefixes use this root name
 103:     private static final String    prefixRoot = "prefix-";
 104: 
 105:     
 106:     /**
 107:      * Passes events through to the specified consumer, after first
 108:      * processing them.
 109:      *
 110:      * @param next the next event consumer to receive events.
 111:      */
 112:     // constructor used by PipelineFactory
 113:     public NSFilter (EventConsumer next)
 114:     {
 115:     super (next);
 116: 
 117:     setContentHandler (this);
 118:     }
 119: 
 120:     private void fatalError (String message)
 121:     throws SAXException
 122:     {
 123:     SAXParseException    e;
 124:     ErrorHandler        handler = getErrorHandler ();
 125:     Locator            locator = getDocumentLocator ();
 126: 
 127:     if (locator == null)
 128:         e = new SAXParseException (message, null, null, -1, -1);
 129:     else
 130:         e = new SAXParseException (message, locator);
 131:     if (handler != null)
 132:         handler.fatalError (e);
 133:     throw e;
 134:     }
 135: 
 136: 
 137:     public void startDocument () throws SAXException
 138:     {
 139:     elementStack.removeAllElements ();
 140:     nsStack.reset ();
 141:     pushedContext = false;
 142:     super.startDocument ();
 143:     }
 144: 
 145:     /**
 146:      * This call is not passed to the next consumer in the chain.
 147:      * Prefix declarations and scopes are only exposed in the form
 148:      * of attributes; this callback just records a declaration that
 149:      * will be exposed as an attribute.
 150:      */
 151:     public void startPrefixMapping (String prefix, String uri)
 152:     throws SAXException
 153:     {
 154:     if (pushedContext == false) {
 155:         nsStack.pushContext ();
 156:         pushedContext = true;
 157:     }
 158: 
 159:     // this check is awkward, but the paranoia prevents big trouble
 160:     for (Enumeration e = nsStack.getDeclaredPrefixes ();
 161:         e.hasMoreElements ();
 162:         /* NOP */ ) {
 163:         String    declared = (String) e.nextElement ();
 164: 
 165:         if (!declared.equals (prefix))
 166:         continue;
 167:         if (uri.equals (nsStack.getURI (prefix)))
 168:         return;
 169:         fatalError ("inconsistent binding for prefix '" + prefix
 170:         + "' ... " + uri + " (was " + nsStack.getURI (prefix) + ")");
 171:     }
 172: 
 173:     if (!nsStack.declarePrefix (prefix, uri))
 174:         fatalError ("illegal prefix declared: " + prefix);
 175:     }
 176: 
 177:     private String fixName (String ns, String l, String name, boolean isAttr)
 178:     throws SAXException
 179:     {
 180:     if ("".equals (name) || name == null) {
 181:         name = l;
 182:         if ("".equals (name) || name == null)
 183:         fatalError ("empty/null name");
 184:     }
 185: 
 186:     // can we correctly process the name as-is?
 187:     // handles "element scope" attribute names here.
 188:     if (nsStack.processName (name, nsTemp, isAttr) != null
 189:         && nsTemp [0].equals (ns)
 190:         ) {
 191:         return nsTemp [2];
 192:     }
 193: 
 194:     // nope, gotta modify the name or declare a default mapping
 195:     int    temp;
 196: 
 197:     // get rid of any current prefix
 198:     if ((temp = name.indexOf (':')) >= 0) {
 199:         name = name.substring (temp + 1);
 200: 
 201:         // ... maybe that's enough (use/prefer default namespace) ...
 202:         if (!isAttr && nsStack.processName (name, nsTemp, false) != null
 203:             && nsTemp [0].equals (ns)
 204:             ) {
 205:         return nsTemp [2];
 206:         }
 207:     }
 208: 
 209:     // must we define and use the default/undefined prefix?
 210:     if ("".equals (ns)) {
 211:         if (isAttr)
 212:         fatalError ("processName bug");
 213:         if (attributes.getIndex ("xmlns") != -1)
 214:         fatalError ("need to undefine default NS, but it's bound: "
 215:             + attributes.getValue ("xmlns"));
 216:         
 217:         nsStack.declarePrefix ("", "");
 218:         attributes.addAttribute ("", "", "xmlns", "CDATA", "");
 219:         return name;
 220:     }
 221: 
 222:     // is there at least one non-null prefix we can use?
 223:     for (Enumeration e = nsStack.getDeclaredPrefixes ();
 224:         e.hasMoreElements ();
 225:         /* NOP */) {
 226:         String prefix = (String) e.nextElement ();
 227:         String uri = nsStack.getURI (prefix);
 228: 
 229:         if (uri == null || !uri.equals (ns))
 230:         continue;
 231:         return prefix + ":" + name;
 232:     }
 233: 
 234:     // no such luck.  create a prefix name, declare it, use it.
 235:     for (temp = 0; temp >= 0; temp++) {
 236:         String    prefix = prefixRoot + temp;
 237: 
 238:         if (nsStack.getURI (prefix) == null) {
 239:         nsStack.declarePrefix (prefix, ns);
 240:         attributes.addAttribute ("", "", "xmlns:" + prefix,
 241:             "CDATA", ns);
 242:         return prefix + ":" + name;
 243:         }
 244:     }
 245:     fatalError ("too many prefixes genned");
 246:     // NOTREACHED
 247:     return null;
 248:     }
 249: 
 250:     public void startElement (
 251:     String uri, String localName,
 252:     String qName, Attributes atts
 253:     ) throws SAXException
 254:     {
 255:     if (!pushedContext)
 256:         nsStack.pushContext ();
 257:     pushedContext = false;
 258: 
 259:     // make sure we have all NS declarations handy before we start
 260:     int    length = atts.getLength ();
 261: 
 262:     for (int i = 0; i < length; i++) {
 263:         String    aName = atts.getQName (i);
 264: 
 265:         if (!aName.startsWith ("xmlns"))
 266:         continue;
 267: 
 268:         String    prefix;
 269: 
 270:         if ("xmlns".equals (aName))
 271:         prefix = "";
 272:         else if (aName.indexOf (':') == 5)
 273:         prefix = aName.substring (6);
 274:         else    // "xmlnsfoo" etc.
 275:         continue;
 276:         startPrefixMapping (prefix, atts.getValue (i));
 277:     }
 278: 
 279:     // put namespace decls at the start of our regenned attlist
 280:     attributes.clear ();
 281:     for (Enumeration e = nsStack.getDeclaredPrefixes ();
 282:         e.hasMoreElements ();
 283:         /* NOP */) {
 284:         String prefix = (String) e.nextElement ();
 285: 
 286:         attributes.addAttribute ("", "",
 287:             ("".equals (prefix)
 288:             ? "xmlns"
 289:             : "xmlns:" + prefix),
 290:             "CDATA",
 291:             nsStack.getURI (prefix));
 292:     }
 293: 
 294:     // name fixups:  element, then attributes.
 295:     // fixName may declare a new prefix or, for the element,
 296:     // redeclare the default (if element name needs it).
 297:     qName = fixName (uri, localName, qName, false);
 298: 
 299:     for (int i = 0; i < length; i++) {
 300:         String    aName = atts.getQName (i);
 301:         String    aNS = atts.getURI (i);
 302:         String    aLocal = atts.getLocalName (i);
 303:         String    aType = atts.getType (i);
 304:         String    aValue = atts.getValue (i);
 305: 
 306:         if (aName.startsWith ("xmlns"))
 307:         continue;
 308:         aName = fixName (aNS, aLocal, aName, true);
 309:         attributes.addAttribute (aNS, aLocal, aName, aType, aValue);
 310:     }
 311: 
 312:     elementStack.push (qName);
 313: 
 314:     // pass event along, with cleaned-up names and decls.
 315:     super.startElement (uri, localName, qName, attributes);
 316:     }
 317: 
 318:     public void endElement (String uri, String localName, String qName)
 319:     throws SAXException
 320:     {
 321:     nsStack.popContext ();
 322:     qName = (String) elementStack.pop ();
 323:     super.endElement (uri, localName, qName);
 324:     }
 325: 
 326:     /**
 327:      * This call is not passed to the next consumer in the chain.
 328:      * Prefix declarations and scopes are only exposed in their
 329:      * attribute form.
 330:      */
 331:     public void endPrefixMapping (String prefix)
 332:     throws SAXException
 333:     { }
 334: 
 335:     public void endDocument () throws SAXException
 336:     {
 337:     elementStack.removeAllElements ();
 338:     nsStack.reset ();
 339:     super.endDocument ();
 340:     }
 341: }