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 NSFilter(EventConsumer next)113 public NSFilter (EventConsumer next) 114 { 115 super (next); 116 117 setContentHandler (this); 118 } 119 fatalError(String message)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 startDocument()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 */ startPrefixMapping(String prefix, String uri)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 fixName(String ns, String l, String name, boolean isAttr)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 startElement( String uri, String localName, String qName, Attributes atts )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 endElement(String uri, String localName, String qName)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 */ endPrefixMapping(String prefix)331 public void endPrefixMapping (String prefix) 332 throws SAXException 333 { } 334 endDocument()335 public void endDocument () throws SAXException 336 { 337 elementStack.removeAllElements (); 338 nsStack.reset (); 339 super.endDocument (); 340 } 341 } 342