001: /*
002: * Copyright 2004-2008 Andy Clark
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.cyberneko.html.filters;
018:
019: import org.cyberneko.html.HTMLElements;
020:
021: import java.lang.reflect.InvocationTargetException;
022: import java.lang.reflect.Method;
023: import java.util.Enumeration;
024: import java.util.Vector;
025:
026: import org.apache.xerces.xni.Augmentations;
027: import org.apache.xerces.xni.NamespaceContext;
028: import org.apache.xerces.xni.QName;
029: import org.apache.xerces.xni.XMLAttributes;
030: import org.apache.xerces.xni.XMLLocator;
031: import org.apache.xerces.xni.XNIException;
032: import org.apache.xerces.xni.parser.XMLComponentManager;
033: import org.apache.xerces.xni.parser.XMLConfigurationException;
034:
035: /**
036: * This filter binds namespaces if namespace processing is turned on
037: * by setting the feature "http://xml.org/sax/features/namespaces" is
038: * set to <code>true</code>.
039: * <p>
040: * This configuration recognizes the following features:
041: * <ul>
042: * <li>http://xml.org/sax/features/namespaces
043: * </ul>
044: *
045: * @author Andy Clark
046: *
047: * @version $Id: NamespaceBinder.java,v 1.8 2005/05/30 00:19:28 andyc Exp $
048: */
049: public class NamespaceBinder extends DefaultFilter {
050:
051: //
052: // Constants
053: //
054:
055: // namespace uris
056:
057: /** XHTML 1.0 namespace URI (http://www.w3.org/1999/xhtml). */
058: public static final String XHTML_1_0_URI = "http://www.w3.org/1999/xhtml";
059:
060: /** XML namespace URI (http://www.w3.org/XML/1998/namespace). */
061: public static final String XML_URI = "http://www.w3.org/XML/1998/namespace";
062:
063: /** XMLNS namespace URI (http://www.w3.org/2000/xmlns/). */
064: public static final String XMLNS_URI = "http://www.w3.org/2000/xmlns/";
065:
066: // features
067:
068: /** Namespaces. */
069: protected static final String NAMESPACES = "http://xml.org/sax/features/namespaces";
070:
071: /** Override namespace binding URI. */
072: protected static final String OVERRIDE_NAMESPACES = "http://cyberneko.org/html/features/override-namespaces";
073:
074: /** Insert namespace binding URIs. */
075: protected static final String INSERT_NAMESPACES = "http://cyberneko.org/html/features/insert-namespaces";
076:
077: /** Recognized features. */
078: private static final String[] RECOGNIZED_FEATURES = { NAMESPACES,
079: OVERRIDE_NAMESPACES, INSERT_NAMESPACES, };
080:
081: /** Feature defaults. */
082: private static final Boolean[] FEATURE_DEFAULTS = { null,
083: Boolean.FALSE, Boolean.FALSE, };
084:
085: // properties
086:
087: /** Modify HTML element names: { "upper", "lower", "default" }. */
088: protected static final String NAMES_ELEMS = "http://cyberneko.org/html/properties/names/elems";
089:
090: /** Modify HTML attribute names: { "upper", "lower", "default" }. */
091: protected static final String NAMES_ATTRS = "http://cyberneko.org/html/properties/names/attrs";
092:
093: /** Namespaces URI. */
094: protected static final String NAMESPACES_URI = "http://cyberneko.org/html/properties/namespaces-uri";
095:
096: /** Recognized properties. */
097: private static final String[] RECOGNIZED_PROPERTIES = new String[] {
098: NAMES_ELEMS, NAMES_ATTRS, NAMESPACES_URI, };
099:
100: /** Property defaults. */
101: private static final Object[] PROPERTY_DEFAULTS = { null, null,
102: XHTML_1_0_URI, };
103:
104: // modify HTML names
105:
106: /** Don't modify HTML names. */
107: protected static final short NAMES_NO_CHANGE = 0;
108:
109: /** Uppercase HTML names. */
110: protected static final short NAMES_UPPERCASE = 1;
111:
112: /** Lowercase HTML names. */
113: protected static final short NAMES_LOWERCASE = 2;
114:
115: //
116: // Data
117: //
118:
119: // features
120:
121: /** Namespaces. */
122: protected boolean fNamespaces;
123:
124: /** Namespace prefixes. */
125: protected boolean fNamespacePrefixes;
126:
127: /** Override namespaces. */
128: protected boolean fOverrideNamespaces;
129:
130: /** Insert namespaces. */
131: protected boolean fInsertNamespaces;
132:
133: // properties
134:
135: /** Modify HTML element names. */
136: protected short fNamesElems;
137:
138: /** Modify HTML attribute names. */
139: protected short fNamesAttrs;
140:
141: /** Namespaces URI. */
142: protected String fNamespacesURI;
143:
144: // state
145:
146: /** Namespace context. */
147: protected final NamespaceSupport fNamespaceContext = new NamespaceSupport();
148:
149: // temp vars
150:
151: /** QName. */
152: private final QName fQName = new QName();
153:
154: //
155: // HTMLComponent methods
156: //
157:
158: /**
159: * Returns a list of feature identifiers that are recognized by
160: * this component. This method may return null if no features
161: * are recognized by this component.
162: */
163: public String[] getRecognizedFeatures() {
164: return merge(super .getRecognizedFeatures(), RECOGNIZED_FEATURES);
165: } // getRecognizedFeatures():String[]
166:
167: /**
168: * Returns the default state for a feature, or null if this
169: * component does not want to report a default value for this
170: * feature.
171: */
172: public Boolean getFeatureDefault(String featureId) {
173: for (int i = 0; i < RECOGNIZED_FEATURES.length; i++) {
174: if (RECOGNIZED_FEATURES[i].equals(featureId)) {
175: return FEATURE_DEFAULTS[i];
176: }
177: }
178: return super .getFeatureDefault(featureId);
179: } // getFeatureDefault(String):Boolean
180:
181: /**
182: * Returns a list of property identifiers that are recognized by
183: * this component. This method may return null if no properties
184: * are recognized by this component.
185: */
186: public String[] getRecognizedProperties() {
187: return merge(super .getRecognizedProperties(),
188: RECOGNIZED_PROPERTIES);
189: } // getRecognizedProperties():String[]
190:
191: /**
192: * Returns the default value for a property, or null if this
193: * component does not want to report a default value for this
194: * property.
195: */
196: public Object getPropertyDefault(String propertyId) {
197: for (int i = 0; i < RECOGNIZED_PROPERTIES.length; i++) {
198: if (RECOGNIZED_PROPERTIES[i].equals(propertyId)) {
199: return PROPERTY_DEFAULTS[i];
200: }
201: }
202: return super .getPropertyDefault(propertyId);
203: } // getPropertyDefault(String):Object
204:
205: /**
206: * Resets the component. The component can query the component manager
207: * about any features and properties that affect the operation of the
208: * component.
209: *
210: * @param manager The component manager.
211: *
212: * @throws XNIException Thrown by component on initialization error.
213: */
214: public void reset(XMLComponentManager manager)
215: throws XMLConfigurationException {
216: super .reset(manager);
217:
218: // features
219: fNamespaces = manager.getFeature(NAMESPACES);
220: fOverrideNamespaces = manager.getFeature(OVERRIDE_NAMESPACES);
221: fInsertNamespaces = manager.getFeature(INSERT_NAMESPACES);
222:
223: // get properties
224: fNamesElems = getNamesValue(String.valueOf(manager
225: .getProperty(NAMES_ELEMS)));
226: fNamesAttrs = getNamesValue(String.valueOf(manager
227: .getProperty(NAMES_ATTRS)));
228: fNamespacesURI = String.valueOf(manager
229: .getProperty(NAMESPACES_URI));
230:
231: // initialize state
232: fNamespaceContext.reset();
233:
234: } // reset(XMLComponentManager)
235:
236: //
237: // XMLDocumentHandler methods
238: //
239:
240: /** Start document. */
241: public void startDocument(XMLLocator locator, String encoding,
242: NamespaceContext nscontext, Augmentations augs)
243: throws XNIException {
244:
245: // perform default handling
246: // NOTE: using own namespace context
247: super .startDocument(locator, encoding, fNamespaceContext, augs);
248:
249: } // startDocument(XMLLocator,String,NamespaceContext,Augmentations)
250:
251: /** Start element. */
252: public void startElement(QName element, XMLAttributes attrs,
253: Augmentations augs) throws XNIException {
254:
255: // bind namespaces, if needed
256: if (fNamespaces) {
257: fNamespaceContext.pushContext();
258: bindNamespaces(element, attrs);
259:
260: int dcount = fNamespaceContext.getDeclaredPrefixCount();
261: if (fDocumentHandler != null && dcount > 0) {
262: try {
263: Class cls = fDocumentHandler.getClass();
264: Class[] types = { String.class, String.class };
265: Method method = cls.getMethod("startPrefixMapping",
266: types);
267: for (int i = 0; i < dcount; i++) {
268: String prefix = fNamespaceContext
269: .getDeclaredPrefixAt(i);
270: String uri = fNamespaceContext.getURI(prefix);
271: Object[] args = { prefix, uri };
272: method.invoke(fDocumentHandler, args);
273: }
274: } catch (NoSuchMethodException e) {
275: // ignore
276: } catch (InvocationTargetException e) {
277: // ignore
278: } catch (IllegalAccessException e) {
279: // ignore
280: }
281: }
282: }
283:
284: // perform default handling
285: super .startElement(element, attrs, augs);
286:
287: } // startElement(QName,XMLAttributes,Augmentations)
288:
289: /** Empty element. */
290: public void emptyElement(QName element, XMLAttributes attrs,
291: Augmentations augs) throws XNIException {
292:
293: // bind namespaces, if needed
294: if (fNamespaces) {
295: fNamespaceContext.pushContext();
296: bindNamespaces(element, attrs);
297:
298: int dcount = fNamespaceContext.getDeclaredPrefixCount();
299: if (fDocumentHandler != null && dcount > 0) {
300: try {
301: Class cls = fDocumentHandler.getClass();
302: Class[] types = { String.class, String.class };
303: Method method = cls.getMethod("startPrefixMapping",
304: types);
305: for (int i = 0; i < dcount; i++) {
306: String prefix = fNamespaceContext
307: .getDeclaredPrefixAt(i);
308: String uri = fNamespaceContext.getURI(prefix);
309: Object[] args = { prefix, uri };
310: method.invoke(fDocumentHandler, args);
311: }
312: } catch (NoSuchMethodException e) {
313: // ignore
314: } catch (InvocationTargetException e) {
315: // ignore
316: } catch (IllegalAccessException e) {
317: // ignore
318: }
319: }
320: }
321:
322: // perform default handling
323: super .emptyElement(element, attrs, augs);
324:
325: // pop context
326: if (fNamespaces) {
327: int dcount = fNamespaceContext.getDeclaredPrefixCount();
328: if (fDocumentHandler != null && dcount > 0) {
329: try {
330: Class cls = fDocumentHandler.getClass();
331: Class[] types = { String.class };
332: Method method = cls.getMethod("endPrefixMapping",
333: types);
334: for (int i = dcount - 1; i >= 0; i--) {
335: String prefix = fNamespaceContext
336: .getDeclaredPrefixAt(i);
337: Object[] args = { prefix };
338: method.invoke(fDocumentHandler, args);
339: }
340: } catch (NoSuchMethodException e) {
341: // ignore
342: } catch (InvocationTargetException e) {
343: // ignore
344: } catch (IllegalAccessException e) {
345: // ignore
346: }
347: }
348:
349: fNamespaceContext.popContext();
350: }
351:
352: } // startElement(QName,XMLAttributes,Augmentations)
353:
354: /** End element. */
355: public void endElement(QName element, Augmentations augs)
356: throws XNIException {
357:
358: // bind namespaces, if needed
359: if (fNamespaces) {
360: bindNamespaces(element, null);
361: }
362:
363: // perform default handling
364: super .endElement(element, augs);
365:
366: // pop context
367: if (fNamespaces) {
368: int dcount = fNamespaceContext.getDeclaredPrefixCount();
369: if (fDocumentHandler != null && dcount > 0) {
370: try {
371: Class cls = fDocumentHandler.getClass();
372: Class[] types = { String.class };
373: Method method = cls.getMethod("endPrefixMapping",
374: types);
375: for (int i = dcount - 1; i >= 0; i--) {
376: String prefix = fNamespaceContext
377: .getDeclaredPrefixAt(i);
378: Object[] args = { prefix };
379: method.invoke(fDocumentHandler, args);
380: }
381: } catch (NoSuchMethodException e) {
382: // ignore
383: } catch (InvocationTargetException e) {
384: // ignore
385: } catch (IllegalAccessException e) {
386: // ignore
387: }
388: }
389:
390: fNamespaceContext.popContext();
391: }
392:
393: } // endElement(QName,Augmentations)
394:
395: //
396: // Protected static methods
397: //
398:
399: /** Splits a qualified name. */
400: protected static void splitQName(QName qname) {
401: int index = qname.rawname.indexOf(':');
402: if (index != -1) {
403: qname.prefix = qname.rawname.substring(0, index);
404: qname.localpart = qname.rawname.substring(index + 1);
405: }
406: } // splitQName(QName)
407:
408: /**
409: * Converts HTML names string value to constant value.
410: *
411: * @see #NAMES_NO_CHANGE
412: * @see #NAMES_LOWERCASE
413: * @see #NAMES_UPPERCASE
414: */
415: protected static final short getNamesValue(String value) {
416: if (value.equals("lower")) {
417: return NAMES_LOWERCASE;
418: }
419: if (value.equals("upper")) {
420: return NAMES_UPPERCASE;
421: }
422: return NAMES_NO_CHANGE;
423: } // getNamesValue(String):short
424:
425: /** Modifies the given name based on the specified mode. */
426: protected static final String modifyName(String name, short mode) {
427: switch (mode) {
428: case NAMES_UPPERCASE:
429: return name.toUpperCase();
430: case NAMES_LOWERCASE:
431: return name.toLowerCase();
432: }
433: return name;
434: } // modifyName(String,short):String
435:
436: //
437: // Protected methods
438: //
439:
440: /** Binds namespaces. */
441: protected void bindNamespaces(QName element, XMLAttributes attrs) {
442:
443: // split element qname
444: splitQName(element);
445:
446: // declare namespace prefixes
447: int attrCount = attrs != null ? attrs.getLength() : 0;
448: for (int i = attrCount - 1; i >= 0; i--) {
449: attrs.getName(i, fQName);
450: String aname = fQName.rawname;
451: String ANAME = aname.toUpperCase();
452: if (ANAME.startsWith("XMLNS:") || ANAME.equals("XMLNS")) {
453: int anamelen = aname.length();
454:
455: // get parts
456: String aprefix = anamelen > 5 ? aname.substring(0, 5)
457: : null;
458: String alocal = anamelen > 5 ? aname.substring(6)
459: : aname;
460: String avalue = attrs.getValue(i);
461:
462: // re-case parts and set them back into attributes
463: if (anamelen > 5) {
464: aprefix = modifyName(aprefix, NAMES_LOWERCASE);
465: alocal = modifyName(alocal, fNamesElems);
466: aname = aprefix + ':' + alocal;
467: } else {
468: alocal = modifyName(alocal, NAMES_LOWERCASE);
469: aname = alocal;
470: }
471: fQName.setValues(aprefix, alocal, aname, null);
472: attrs.setName(i, fQName);
473:
474: // declare prefix
475: String prefix = alocal != aname ? alocal : "";
476: String uri = avalue.length() > 0 ? avalue : null;
477: if (fOverrideNamespaces
478: && prefix.equals(element.prefix)
479: && HTMLElements.getElement(element.localpart,
480: null) != null) {
481: uri = fNamespacesURI;
482: }
483: fNamespaceContext.declarePrefix(prefix, uri);
484: }
485: }
486:
487: // bind element
488: String prefix = element.prefix != null ? element.prefix : "";
489: element.uri = fNamespaceContext.getURI(prefix);
490: // REVISIT: The prefix of a qualified element name that is
491: // bound to a namespace is passed (as recent as
492: // Xerces 2.4.0) as "" for start elements and null
493: // for end elements. Why? One of them is a bug,
494: // clearly. -Ac
495: if (element.uri != null && element.prefix == null) {
496: element.prefix = "";
497: }
498:
499: // do we need to insert namespace bindings?
500: if (fInsertNamespaces
501: && HTMLElements.getElement(element.localpart, null) != null) {
502: if (element.prefix == null
503: || fNamespaceContext.getURI(element.prefix) == null) {
504: String xmlns = "xmlns"
505: + ((element.prefix != null) ? ":"
506: + element.prefix : "");
507: fQName.setValues(null, xmlns, xmlns, null);
508: attrs.addAttribute(fQName, "CDATA", fNamespacesURI);
509: bindNamespaces(element, attrs);
510: return;
511: }
512: }
513:
514: // bind attributes
515: attrCount = attrs != null ? attrs.getLength() : 0;
516: for (int i = 0; i < attrCount; i++) {
517: attrs.getName(i, fQName);
518: splitQName(fQName);
519: prefix = !fQName.rawname.equals("xmlns") ? (fQName.prefix != null ? fQName.prefix
520: : "")
521: : "xmlns";
522: // PATCH: Joseph Walton
523: if (!prefix.equals("")) {
524: fQName.uri = prefix.equals("xml") ? XML_URI
525: : fNamespaceContext.getURI(prefix);
526: }
527: // NOTE: You would think the xmlns namespace would be handled
528: // by NamespaceSupport but it's not. -Ac
529: if (prefix.equals("xmlns") && fQName.uri == null) {
530: fQName.uri = XMLNS_URI;
531: }
532: attrs.setName(i, fQName);
533: }
534:
535: } // bindNamespaces(QName,XMLAttributes)
536:
537: //
538: // Classes
539: //
540:
541: /**
542: * This namespace context object implements the old and new XNI
543: * <code>NamespaceContext</code> interface methods so that it can
544: * be used across all versions of Xerces2.
545: */
546: public static class NamespaceSupport implements NamespaceContext {
547:
548: //
549: // Data
550: //
551:
552: /** Top of the levels list. */
553: protected int fTop = 0;
554:
555: /** The levels of the entries. */
556: protected int[] fLevels = new int[10];
557:
558: /** The entries. */
559: protected Entry[] fEntries = new Entry[10];
560:
561: //
562: // Constructors
563: //
564:
565: /** Default constructor. */
566: public NamespaceSupport() {
567: pushContext();
568: declarePrefix("xml", NamespaceContext.XML_URI);
569: declarePrefix("xmlns", NamespaceContext.XMLNS_URI);
570: } // <init>()
571:
572: //
573: // NamespaceContext methods
574: //
575:
576: // since Xerces 2.0.0-beta2 (old XNI namespaces)
577:
578: /** Get URI. */
579: public String getURI(String prefix) {
580: for (int i = fLevels[fTop] - 1; i >= 0; i--) {
581: Entry entry = (Entry) fEntries[i];
582: if (entry.prefix.equals(prefix)) {
583: return entry.uri;
584: }
585: }
586: return null;
587: } // getURI(String):String
588:
589: /** Get declared prefix count. */
590: public int getDeclaredPrefixCount() {
591: return fLevels[fTop] - fLevels[fTop - 1];
592: } // getDeclaredPrefixCount():int
593:
594: /** Get declared prefix at. */
595: public String getDeclaredPrefixAt(int index) {
596: return fEntries[fLevels[fTop - 1] + index].prefix;
597: } // getDeclaredPrefixAt(int):String
598:
599: /** Get parent context. */
600: public NamespaceContext getParentContext() {
601: return this ;
602: } // getParentContext():NamespaceContext
603:
604: // since Xerces #.#.# (new XNI namespaces)
605:
606: /** Reset. */
607: public void reset() {
608: fLevels[fTop = 1] = fLevels[fTop - 1];
609: } // reset()
610:
611: /** Push context. */
612: public void pushContext() {
613: if (++fTop == fLevels.length) {
614: int[] iarray = new int[fLevels.length + 10];
615: System.arraycopy(fLevels, 0, iarray, 0, fLevels.length);
616: fLevels = iarray;
617: }
618: fLevels[fTop] = fLevels[fTop - 1];
619: } // pushContext()
620:
621: /** Pop context. */
622: public void popContext() {
623: if (fTop > 1) {
624: fTop--;
625: }
626: } // popContext()
627:
628: /** Declare prefix. */
629: public boolean declarePrefix(String prefix, String uri) {
630: int count = getDeclaredPrefixCount();
631: for (int i = 0; i < count; i++) {
632: String dprefix = getDeclaredPrefixAt(i);
633: if (dprefix.equals(prefix)) {
634: return false;
635: }
636: }
637: Entry entry = new Entry(prefix, uri);
638: if (fLevels[fTop] == fEntries.length) {
639: Entry[] earray = new Entry[fEntries.length + 10];
640: System.arraycopy(fEntries, 0, earray, 0,
641: fEntries.length);
642: fEntries = earray;
643: }
644: fEntries[fLevels[fTop]++] = entry;
645: return true;
646: } // declarePrefix(String,String):boolean
647:
648: /** Get prefix. */
649: public String getPrefix(String uri) {
650: for (int i = fLevels[fTop] - 1; i >= 0; i--) {
651: Entry entry = (Entry) fEntries[i];
652: if (entry.uri.equals(uri)) {
653: return entry.prefix;
654: }
655: }
656: return null;
657: } // getPrefix(String):String
658:
659: /** Get all prefixes. */
660: public Enumeration getAllPrefixes() {
661: Vector prefixes = new Vector();
662: for (int i = fLevels[1]; i < fLevels[fTop]; i++) {
663: String prefix = fEntries[i].prefix;
664: if (!prefixes.contains(prefix)) {
665: prefixes.addElement(prefix);
666: }
667: }
668: return prefixes.elements();
669: } // getAllPrefixes():Enumeration
670:
671: //
672: // Classes
673: //
674:
675: /** A namespace binding entry. */
676: static class Entry {
677:
678: //
679: // Data
680: //
681:
682: /** Prefix. */
683: public String prefix;
684:
685: /** URI. */
686: public String uri;
687:
688: //
689: // Constructors
690: //
691:
692: /** Constructs an entry. */
693: public Entry(String prefix, String uri) {
694: this .prefix = prefix;
695: this .uri = uri;
696: } // <init>(String,String)
697:
698: } // class Entry
699:
700: } // class NamespaceSupport
701:
702: } // class NamespaceBinder
|