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