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: }