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 }