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    }