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