View Javadoc
1   /*
2    * Copyright 2005 Philipp Erlacher
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.exolab.castor.xml.parsing;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.exolab.castor.xml.AttributeSet;
20  import org.exolab.castor.xml.util.AttributeSetImpl;
21  import org.xml.sax.AttributeList;
22  import org.xml.sax.Attributes;
23  import org.xml.sax.SAXException;
24  
25  /**
26   * A helper class that takes SAX v1 AttributeList or SAX v2 attributes and
27   * converts those into Castor's internal {@link AttributeSet} representation.
28   * 
29   * @author <a href="mailto:philipp DOT erlacher AT gmail DOT com">Philipp
30   *         Erlacher</a>
31   * 
32   * @since 1.3.2
33   */
34  public class AttributeSetBuilder {
35  
36      /**
37       * The built-in XML prefix used for xml:space, xml:lang and, as the XML 1.0
38       * Namespaces document specifies, are reserved for use by XML and XML
39       * related specs.
40       **/
41      private static final String XML_PREFIX = "xml";
42  
43      /**
44       * Attribute name for default namespace declaration
45       **/
46      private static final String XMLNS = "xmlns";
47  
48      /**
49       * Attribute prefix for prefixed namespace declaration.
50       **/
51      private final static String XMLNS_PREFIX = "xmlns:";
52  
53      /**
54       * Length of the XMLNS prefix.
55       */
56      private final static int XMLNS_PREFIX_LENGTH = XMLNS_PREFIX.length();
57  
58      /**
59       * Tool class to deal with XML name spaces.
60       */
61      private NamespaceHandling _namespaceHandling = null;
62  
63      /**
64       * Creates an instance of this class.
65       * 
66       * @param namespaceHandling
67       *            Instance of a tool class to handle XML name spaces.
68       */
69      public AttributeSetBuilder(NamespaceHandling namespaceHandling) {
70          super();
71          _namespaceHandling = namespaceHandling;
72      }
73  
74      /**
75       * Prepares a reusable {@link AttributeSet} object instance.
76       * 
77       * @param atts
78       *            Attributes to determine the length of the reusable attribute
79       *            object, can be null
80       */
81      private AttributeSetImpl prepareAttributeSetImpl(Attributes atts) {
82              if (atts != null) {
83                  return new AttributeSetImpl(atts.getLength());
84              }
85              return new AttributeSetImpl();
86      }
87  
88      /**
89       * Processes the attributes and XML name space declarations found in the
90       * given {@link Attributes} instance. XML namespace declarations are added
91       * to the set of name spaces in scope.
92       * 
93       * @return AttributeSet,
94       * @throws SAXException
95       *             If a name space associated with the prefix could not be
96       *             resolved.
97       */
98      public AttributeSet getAttributeSet(Attributes atts) throws SAXException {
99          AttributeSetImpl attributeSet = prepareAttributeSetImpl(atts);
100         return processAttributes(atts, attributeSet);
101     }
102 
103     /**
104      * Processes the attributes and XML name space declarations found in the
105      * given SAX Attributes. The global {@link AttributeSet} is cleared and
106      * updated with the attributes. XML name space declarations are added to the
107      * set of name spaces in scope.
108      * 
109      * @param atts
110      *            the Attributes to process (can be null).
111      **/
112     private AttributeSet processAttributes(Attributes atts, AttributeSetImpl attributeSet) {
113         // -- process attributes
114 
115         if (atts == null || atts.getLength() == 0) {
116             return attributeSet;
117         }
118 
119         boolean hasQNameAtts = false;
120         // -- look for any potential namespace declarations
121         // -- in case namespace processing was disable
122         // -- on the parser
123         for (int i = 0; i < atts.getLength(); i++) {
124             String attName = atts.getQName(i);
125             if (StringUtils.isNotEmpty(attName)) {
126                 if (!attName.equals(XMLNS) && !attName.startsWith(XMLNS_PREFIX)) {
127                     // -- check for prefix
128                     if (attName.indexOf(':') < 0) {
129                     	attributeSet.setAttribute(attName, atts.getValue(i), atts
130                                 .getURI(i));
131                     } else
132                         hasQNameAtts = true;
133                 }
134             } else {
135                 // -- if attName is null or empty, just process as a normal
136                 // -- attribute
137                 attName = atts.getLocalName(i);
138                 if (!XMLNS.equals(attName)) {
139                 	attributeSet.setAttribute(attName, atts.getValue(i), atts
140                             .getURI(i));
141                 }
142             }
143         }
144 
145         // return if there are no qualified name attributes
146         if (!hasQNameAtts) {
147             return attributeSet;
148         }
149         // -- if we found any qName-only atts, process those
150         for (int i = 0; i < atts.getLength(); i++) {
151             String attName = atts.getQName(i);
152             if (StringUtils.isNotEmpty(attName)) {
153                 // -- process any non-namespace qName atts
154                 if ((!attName.equals(XMLNS))
155                         && (!attName.startsWith(XMLNS_PREFIX))) {
156                     int idx = attName.indexOf(':');
157                     if (idx >= 0) {
158                         String prefix = attName.substring(0, idx);
159                         attName = attName.substring(idx + 1);
160                         String nsURI = atts.getURI(i);
161                         if (StringUtils.isEmpty(nsURI)) {
162                             nsURI = _namespaceHandling.getNamespaceURI(prefix);
163                         }
164                         attributeSet.setAttribute(attName, atts.getValue(i), nsURI);
165                     }
166                 }
167             }
168             // -- else skip already processed in previous loop
169         }
170         return attributeSet;
171     }
172 
173     /**
174      * Processes the attributes and XML name space declarations found in the SAX
175      * v1 {@link AttributeList}. XML name space declarations are added to the
176      * set of XML name spaces in scope.
177      * 
178      * 
179      * @return AttributeSet An internal representation of XML attributes.
180      * @throws SAXException
181      *             If the XML name space associated with the prefix could not be
182      *             resolved.
183      */
184     public AttributeSet getAttributeSet(AttributeList atts) throws SAXException {
185     	AttributeSetImpl attributeSet = prepareAttributeSetImpl(atts);
186         return processAttributeList(atts, attributeSet);
187     }
188 
189     /**
190      * Processes the attributes and XML name space declarations found in the
191      * given SAX v1 AttributeList. The global AttributeSet is cleared and
192      * updated with the attribute data. XML name space declarations are added to
193      * the set of XML name spaces in scope.
194      * 
195      * @deprecated
196      * @param atts
197      *            the {@link AttributeList} to process (can be null)
198      **/
199     private AttributeSet processAttributeList(AttributeList atts, AttributeSetImpl attributeSet) throws SAXException {
200         if (atts == null || atts.getLength() == 0)
201             return attributeSet;
202 
203         // -- process all namespaces first
204         int attCount = 0;
205         boolean[] validAtts = new boolean[atts.getLength()];
206         for (int i = 0; i < validAtts.length; i++) {
207             String attName = atts.getName(i);
208             if (attName.equals(XMLNS)) {
209                 _namespaceHandling.addDefaultNamespace(atts.getValue(i));
210             } else if (attName.startsWith(XMLNS_PREFIX)) {
211                 String prefix = attName.substring(XMLNS_PREFIX_LENGTH);
212                 _namespaceHandling.addNamespace(prefix, atts.getValue(i));
213             } else {
214                 validAtts[i] = true;
215                 ++attCount;
216             }
217         }
218         // -- process validAtts...if any exist
219         for (int i = 0; i < validAtts.length; i++) {
220             if (!validAtts[i])
221                 continue;
222             String namespace = null;
223             String attName = atts.getName(i);
224             int idx = attName.indexOf(':');
225             if (idx > 0) {
226                 String prefix = attName.substring(0, idx);
227                 if (!prefix.equals(XML_PREFIX)) {
228                     attName = attName.substring(idx + 1);
229                     namespace = _namespaceHandling.getNamespaceURI(prefix);
230                     if (namespace == null) {
231                         String error = "The namespace associated with "
232                                 + "the prefix '" + prefix
233                                 + "' could not be resolved.";
234                         throw new SAXException(error);
235 
236                     }
237                 }
238             }
239             attributeSet.setAttribute(attName, atts.getValue(i), namespace);
240         }
241         return attributeSet;
242     }
243 
244     /**
245      * Prepares a reusable AttributeSet object.
246      * 
247      * @deprecated
248      * @param atts
249      *            {@link Attributes} to determine the length of the reusable
250      *            {@link AttributeSet} object (can be null).
251      */
252     private AttributeSetImpl prepareAttributeSetImpl(AttributeList atts) {
253     	if (atts == null) {
254     		return new AttributeSetImpl();
255     	}
256     	return new AttributeSetImpl(atts.getLength());
257     }
258 }