001 /*
002 * Copyright (c) 2007 Henri Sivonen
003 * Copyright (c) 2008-2009 Mozilla Foundation
004 *
005 * Permission is hereby granted, free of charge, to any person obtaining a
006 * copy of this software and associated documentation files (the "Software"),
007 * to deal in the Software without restriction, including without limitation
008 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
009 * and/or sell copies of the Software, and to permit persons to whom the
010 * Software is furnished to do so, subject to the following conditions:
011 *
012 * The above copyright notice and this permission notice shall be included in
013 * all copies or substantial portions of the Software.
014 *
015 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
016 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
017 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
018 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
019 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
020 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
021 * DEALINGS IN THE SOFTWARE.
022 */
023
024 package nu.validator.htmlparser.impl;
025
026 import nu.validator.htmlparser.annotation.Auto;
027 import nu.validator.htmlparser.annotation.IdType;
028 import nu.validator.htmlparser.annotation.Local;
029 import nu.validator.htmlparser.annotation.NsUri;
030 import nu.validator.htmlparser.annotation.Prefix;
031 import nu.validator.htmlparser.annotation.QName;
032 import nu.validator.htmlparser.common.Interner;
033 import nu.validator.htmlparser.common.XmlViolationPolicy;
034
035 import org.xml.sax.Attributes;
036 import org.xml.sax.SAXException;
037
038 /**
039 * Be careful with this class. QName is the name in from HTML tokenization.
040 * Otherwise, please refer to the interface doc.
041 *
042 * @version $Id: AttributesImpl.java 206 2008-03-20 14:09:29Z hsivonen $
043 * @author hsivonen
044 */
045 public final class HtmlAttributes implements Attributes {
046
047 // [NOCPP[
048
049 private static final AttributeName[] EMPTY_ATTRIBUTENAMES = new AttributeName[0];
050
051 private static final String[] EMPTY_STRINGS = new String[0];
052
053 // ]NOCPP]
054
055 public static final HtmlAttributes EMPTY_ATTRIBUTES = new HtmlAttributes(
056 AttributeName.HTML);
057
058 private int mode;
059
060 private int length;
061
062 private @Auto AttributeName[] names;
063
064 private @Auto String[] values; // XXX perhaps make this @NoLength?
065
066 // [NOCPP[
067
068 private String idValue;
069
070 private int xmlnsLength;
071
072 private AttributeName[] xmlnsNames;
073
074 private String[] xmlnsValues;
075
076 // ]NOCPP]
077
078 public HtmlAttributes(int mode) {
079 this.mode = mode;
080 this.length = 0;
081 /*
082 * The length of 5 covers covers 98.3% of elements
083 * according to Hixie
084 */
085 this.names = new AttributeName[5];
086 this.values = new String[5];
087
088 // [NOCPP[
089
090 this.idValue = null;
091
092 this.xmlnsLength = 0;
093
094 this.xmlnsNames = HtmlAttributes.EMPTY_ATTRIBUTENAMES;
095
096 this.xmlnsValues = HtmlAttributes.EMPTY_STRINGS;
097
098 // ]NOCPP]
099 }
100 /*
101 public HtmlAttributes(HtmlAttributes other) {
102 this.mode = other.mode;
103 this.length = other.length;
104 this.names = new AttributeName[other.length];
105 this.values = new String[other.length];
106 // [NOCPP[
107 this.idValue = other.idValue;
108 this.xmlnsLength = other.xmlnsLength;
109 this.xmlnsNames = new AttributeName[other.xmlnsLength];
110 this.xmlnsValues = new String[other.xmlnsLength];
111 // ]NOCPP]
112 }
113 */
114
115 void destructor() {
116 clear(0);
117 }
118
119 /**
120 * Only use with a static argument
121 *
122 * @param name
123 * @return
124 */
125 public int getIndex(AttributeName name) {
126 for (int i = 0; i < length; i++) {
127 if (names[i] == name) {
128 return i;
129 }
130 }
131 return -1;
132 }
133
134 // [NOCPP[
135
136 public int getIndex(String qName) {
137 for (int i = 0; i < length; i++) {
138 if (names[i].getQName(mode).equals(qName)) {
139 return i;
140 }
141 }
142 return -1;
143 }
144
145 public int getIndex(String uri, String localName) {
146 for (int i = 0; i < length; i++) {
147 if (names[i].getLocal(mode).equals(localName)
148 && names[i].getUri(mode).equals(uri)) {
149 return i;
150 }
151 }
152 return -1;
153 }
154
155 public @IdType String getType(String qName) {
156 int index = getIndex(qName);
157 if (index == -1) {
158 return null;
159 } else {
160 return getType(index);
161 }
162 }
163
164 public @IdType String getType(String uri, String localName) {
165 int index = getIndex(uri, localName);
166 if (index == -1) {
167 return null;
168 } else {
169 return getType(index);
170 }
171 }
172
173 public String getValue(String qName) {
174 int index = getIndex(qName);
175 if (index == -1) {
176 return null;
177 } else {
178 return getValue(index);
179 }
180 }
181
182 public String getValue(String uri, String localName) {
183 int index = getIndex(uri, localName);
184 if (index == -1) {
185 return null;
186 } else {
187 return getValue(index);
188 }
189 }
190
191 // ]NOCPP]
192
193 public int getLength() {
194 return length;
195 }
196
197 public @Local String getLocalName(int index) {
198 if (index < length && index >= 0) {
199 return names[index].getLocal(mode);
200 } else {
201 return null;
202 }
203 }
204
205 // [NOCPP[
206
207 public @QName String getQName(int index) {
208 if (index < length && index >= 0) {
209 return names[index].getQName(mode);
210 } else {
211 return null;
212 }
213 }
214
215 public @IdType String getType(int index) {
216 if (index < length && index >= 0) {
217 return names[index].getType(mode);
218 } else {
219 return null;
220 }
221 }
222
223 // ]NOCPP]
224
225 public AttributeName getAttributeName(int index) {
226 if (index < length && index >= 0) {
227 return names[index];
228 } else {
229 return null;
230 }
231 }
232
233 public @NsUri String getURI(int index) {
234 if (index < length && index >= 0) {
235 return names[index].getUri(mode);
236 } else {
237 return null;
238 }
239 }
240
241 public @Prefix String getPrefix(int index) {
242 if (index < length && index >= 0) {
243 return names[index].getPrefix(mode);
244 } else {
245 return null;
246 }
247 }
248
249 public String getValue(int index) {
250 if (index < length && index >= 0) {
251 return values[index];
252 } else {
253 return null;
254 }
255 }
256
257 /**
258 * Only use with static argument.
259 *
260 * @see org.xml.sax.Attributes#getValue(java.lang.String)
261 */
262 public String getValue(AttributeName name) {
263 int index = getIndex(name);
264 if (index == -1) {
265 return null;
266 } else {
267 return getValue(index);
268 }
269 }
270
271 // [NOCPP[
272
273 public String getId() {
274 return idValue;
275 }
276
277 public int getXmlnsLength() {
278 return xmlnsLength;
279 }
280
281 public @Local String getXmlnsLocalName(int index) {
282 if (index < xmlnsLength && index >= 0) {
283 return xmlnsNames[index].getLocal(mode);
284 } else {
285 return null;
286 }
287 }
288
289 public @NsUri String getXmlnsURI(int index) {
290 if (index < xmlnsLength && index >= 0) {
291 return xmlnsNames[index].getUri(mode);
292 } else {
293 return null;
294 }
295 }
296
297 public String getXmlnsValue(int index) {
298 if (index < xmlnsLength && index >= 0) {
299 return xmlnsValues[index];
300 } else {
301 return null;
302 }
303 }
304
305 public int getXmlnsIndex(AttributeName name) {
306 for (int i = 0; i < xmlnsLength; i++) {
307 if (xmlnsNames[i] == name) {
308 return i;
309 }
310 }
311 return -1;
312 }
313
314 public String getXmlnsValue(AttributeName name) {
315 int index = getXmlnsIndex(name);
316 if (index == -1) {
317 return null;
318 } else {
319 return getXmlnsValue(index);
320 }
321 }
322
323 public AttributeName getXmlnsAttributeName(int index) {
324 if (index < xmlnsLength && index >= 0) {
325 return xmlnsNames[index];
326 } else {
327 return null;
328 }
329 }
330
331 // ]NOCPP]
332
333 void addAttribute(AttributeName name, String value
334 // [NOCPP[
335 , XmlViolationPolicy xmlnsPolicy
336 // ]NOCPP]
337 ) throws SAXException {
338 // [NOCPP[
339 if (name == AttributeName.ID) {
340 idValue = value;
341 }
342
343 if (name.isXmlns()) {
344 if (xmlnsNames.length == xmlnsLength) {
345 int newLen = xmlnsLength == 0 ? 2 : xmlnsLength << 1;
346 AttributeName[] newNames = new AttributeName[newLen];
347 System.arraycopy(xmlnsNames, 0, newNames, 0, xmlnsNames.length);
348 xmlnsNames = newNames;
349 String[] newValues = new String[newLen];
350 System.arraycopy(xmlnsValues, 0, newValues, 0, xmlnsValues.length);
351 xmlnsValues = newValues;
352 }
353 xmlnsNames[xmlnsLength] = name;
354 xmlnsValues[xmlnsLength] = value;
355 xmlnsLength++;
356 switch (xmlnsPolicy) {
357 case FATAL:
358 // this is ugly
359 throw new SAXException("Saw an xmlns attribute.");
360 case ALTER_INFOSET:
361 return;
362 case ALLOW:
363 // fall through
364 }
365 }
366
367 // ]NOCPP]
368
369 if (names.length == length) {
370 int newLen = length << 1; // The first growth covers virtually
371 // 100% of elements according to
372 // Hixie
373 AttributeName[] newNames = new AttributeName[newLen];
374 System.arraycopy(names, 0, newNames, 0, names.length);
375 names = newNames;
376 String[] newValues = new String[newLen];
377 System.arraycopy(values, 0, newValues, 0, values.length);
378 values = newValues;
379 }
380 names[length] = name;
381 values[length] = value;
382 length++;
383 }
384
385 void clear(int m) {
386 for (int i = 0; i < length; i++) {
387 names[i].release();
388 names[i] = null;
389 Portability.releaseString(values[i]);
390 values[i] = null;
391 }
392 length = 0;
393 mode = m;
394 // [NOCPP[
395 idValue = null;
396 for (int i = 0; i < xmlnsLength; i++) {
397 xmlnsNames[i] = null;
398 xmlnsValues[i] = null;
399 }
400 xmlnsLength = 0;
401 // ]NOCPP]
402 }
403
404 /**
405 * This is used in C++ to release special <code>isindex</code>
406 * attribute values whose ownership is not transferred.
407 */
408 void releaseValue(int i) {
409 Portability.releaseString(values[i]);
410 }
411
412 /**
413 * This is only used for <code>AttributeName</code> ownership transfer
414 * in the isindex case to avoid freeing custom names twice in C++.
415 */
416 void clearWithoutReleasingContents() {
417 for (int i = 0; i < length; i++) {
418 names[i] = null;
419 values[i] = null;
420 }
421 length = 0;
422 }
423
424 boolean contains(AttributeName name) {
425 for (int i = 0; i < length; i++) {
426 if (name.equalsAnother(names[i])) {
427 return true;
428 }
429 }
430 // [NOCPP[
431 for (int i = 0; i < xmlnsLength; i++) {
432 if (name.equalsAnother(xmlnsNames[i])) {
433 return true;
434 }
435 }
436 // ]NOCPP]
437 return false;
438 }
439
440 public void adjustForMath() {
441 mode = AttributeName.MATHML;
442 }
443
444 public void adjustForSvg() {
445 mode = AttributeName.SVG;
446 }
447
448 public HtmlAttributes cloneAttributes(Interner interner) throws SAXException {
449 assert (length == 0 && xmlnsLength == 0) || mode == 0 || mode == 3;
450 HtmlAttributes clone = new HtmlAttributes(0);
451 for (int i = 0; i < length; i++) {
452 clone.addAttribute(names[i].cloneAttributeName(interner), Portability.newStringFromString(values[i])
453 // [NOCPP[
454 , XmlViolationPolicy.ALLOW
455 // ]NOCPP]
456 );
457 }
458 // [NOCPP[
459 for (int i = 0; i < xmlnsLength; i++) {
460 clone.addAttribute(xmlnsNames[i],
461 xmlnsValues[i], XmlViolationPolicy.ALLOW);
462 }
463 // ]NOCPP]
464 return clone; // XXX!!!
465 }
466
467 public boolean equalsAnother(HtmlAttributes other) {
468 assert mode == 0 || mode == 3 : "Trying to compare attributes in foreign content.";
469 int otherLength = other.getLength();
470 if (length != otherLength) {
471 return false;
472 }
473 for (int i = 0; i < length; i++) {
474 // Work around the limitations of C++
475 boolean found = false;
476 // The comparing just the local names is OK, since these attribute
477 // holders are both supposed to belong to HTML formatting elements
478 @Local String ownLocal = names[i].getLocal(AttributeName.HTML);
479 for (int j = 0; j < otherLength; j++) {
480 if (ownLocal == other.names[j].getLocal(AttributeName.HTML)) {
481 found = true;
482 if (!Portability.stringEqualsString(values[i], other.values[j])) {
483 return false;
484 }
485 }
486 }
487 if (!found) {
488 return false;
489 }
490 }
491 return true;
492 }
493
494 // [NOCPP[
495
496 void processNonNcNames(TreeBuilder<?> treeBuilder, XmlViolationPolicy namePolicy) throws SAXException {
497 for (int i = 0; i < length; i++) {
498 AttributeName attName = names[i];
499 if (!attName.isNcName(mode)) {
500 String name = attName.getLocal(mode);
501 switch (namePolicy) {
502 case ALTER_INFOSET:
503 names[i] = AttributeName.create(NCName.escapeName(name));
504 // fall through
505 case ALLOW:
506 if (attName != AttributeName.XML_LANG) {
507 treeBuilder.warn("Attribute \u201C" + name + "\u201D is not serializable as XML 1.0.");
508 }
509 break;
510 case FATAL:
511 treeBuilder.fatal("Attribute \u201C" + name + "\u201D is not serializable as XML 1.0.");
512 break;
513 }
514 }
515 }
516 }
517
518 public void merge(HtmlAttributes attributes) throws SAXException {
519 int len = attributes.getLength();
520 for (int i = 0; i < len; i++) {
521 AttributeName name = attributes.getAttributeName(i);
522 if (!contains(name)) {
523 addAttribute(name, attributes.getValue(i), XmlViolationPolicy.ALLOW);
524 }
525 }
526 }
527
528
529 // ]NOCPP]
530
531 }