parser/html/javasrc/HtmlAttributes.java

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

Integrate suggestion from review to improve consistency with existing code.

     1 /*
     2  * Copyright (c) 2007 Henri Sivonen
     3  * Copyright (c) 2008-2011 Mozilla Foundation
     4  *
     5  * Permission is hereby granted, free of charge, to any person obtaining a 
     6  * copy of this software and associated documentation files (the "Software"), 
     7  * to deal in the Software without restriction, including without limitation 
     8  * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
     9  * and/or sell copies of the Software, and to permit persons to whom the 
    10  * Software is furnished to do so, subject to the following conditions:
    11  *
    12  * The above copyright notice and this permission notice shall be included in 
    13  * all copies or substantial portions of the Software.
    14  *
    15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
    16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
    17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
    18  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
    19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
    20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
    21  * DEALINGS IN THE SOFTWARE.
    22  */
    24 package nu.validator.htmlparser.impl;
    26 import nu.validator.htmlparser.annotation.Auto;
    27 import nu.validator.htmlparser.annotation.IdType;
    28 import nu.validator.htmlparser.annotation.Local;
    29 import nu.validator.htmlparser.annotation.NsUri;
    30 import nu.validator.htmlparser.annotation.Prefix;
    31 import nu.validator.htmlparser.annotation.QName;
    32 import nu.validator.htmlparser.common.Interner;
    33 import nu.validator.htmlparser.common.XmlViolationPolicy;
    35 import org.xml.sax.Attributes;
    36 import org.xml.sax.SAXException;
    38 /**
    39  * Be careful with this class. QName is the name in from HTML tokenization.
    40  * Otherwise, please refer to the interface doc.
    41  * 
    42  * @version $Id: AttributesImpl.java 206 2008-03-20 14:09:29Z hsivonen $
    43  * @author hsivonen
    44  */
    45 public final class HtmlAttributes implements Attributes {
    47     // [NOCPP[
    49     private static final AttributeName[] EMPTY_ATTRIBUTENAMES = new AttributeName[0];
    51     private static final String[] EMPTY_STRINGS = new String[0];
    53     // ]NOCPP]
    55     public static final HtmlAttributes EMPTY_ATTRIBUTES = new HtmlAttributes(
    56             AttributeName.HTML);
    58     private int mode;
    60     private int length;
    62     private @Auto AttributeName[] names;
    64     private @Auto String[] values; // XXX perhaps make this @NoLength?
    66     // [NOCPP[
    68     private String idValue;
    70     private int xmlnsLength;
    72     private AttributeName[] xmlnsNames;
    74     private String[] xmlnsValues;
    76     // ]NOCPP]
    78     public HtmlAttributes(int mode) {
    79         this.mode = mode;
    80         this.length = 0;
    81         /*
    82          * The length of 5 covers covers 98.3% of elements
    83          * according to Hixie
    84          */
    85         this.names = new AttributeName[5];
    86         this.values = new String[5];
    88         // [NOCPP[
    90         this.idValue = null;
    92         this.xmlnsLength = 0;
    94         this.xmlnsNames = HtmlAttributes.EMPTY_ATTRIBUTENAMES;
    96         this.xmlnsValues = HtmlAttributes.EMPTY_STRINGS;
    98         // ]NOCPP]
    99     }
   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     */
   115     void destructor() {
   116         clear(0);
   117     }
   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     }
   134     /**
   135      * Only use with static argument.
   136      * 
   137      * @see org.xml.sax.Attributes#getValue(java.lang.String)
   138      */
   139     public String getValue(AttributeName name) {
   140         int index = getIndex(name);
   141         if (index == -1) {
   142             return null;
   143         } else {
   144             return getValueNoBoundsCheck(index);
   145         }
   146     }
   148     public int getLength() {
   149         return length;
   150     }
   152     /**
   153      * Variant of <code>getLocalName(int index)</code> without bounds check.
   154      * @param index a valid attribute index
   155      * @return the local name at index
   156      */
   157     public @Local String getLocalNameNoBoundsCheck(int index) {
   158         // CPPONLY: assert index < length && index >= 0: "Index out of bounds";
   159         return names[index].getLocal(mode);
   160     }
   162     /**
   163      * Variant of <code>getURI(int index)</code> without bounds check.
   164      * @param index a valid attribute index
   165      * @return the namespace URI at index
   166      */
   167     public @NsUri String getURINoBoundsCheck(int index) {
   168         // CPPONLY: assert index < length && index >= 0: "Index out of bounds";
   169         return names[index].getUri(mode);
   170     }
   172     /**
   173      * Variant of <code>getPrefix(int index)</code> without bounds check.
   174      * @param index a valid attribute index
   175      * @return the namespace prefix at index
   176      */
   177     public @Prefix String getPrefixNoBoundsCheck(int index) {
   178         // CPPONLY: assert index < length && index >= 0: "Index out of bounds";
   179         return names[index].getPrefix(mode);
   180     }
   182     /**
   183      * Variant of <code>getValue(int index)</code> without bounds check.
   184      * @param index a valid attribute index
   185      * @return the attribute value at index
   186      */
   187     public String getValueNoBoundsCheck(int index) {
   188         // CPPONLY: assert index < length && index >= 0: "Index out of bounds";
   189         return values[index];
   190     }
   192     /**
   193      * Variant of <code>getAttributeName(int index)</code> without bounds check.
   194      * @param index a valid attribute index
   195      * @return the attribute name at index
   196      */
   197     public AttributeName getAttributeNameNoBoundsCheck(int index) {
   198         // CPPONLY: assert index < length && index >= 0: "Index out of bounds";
   199         return names[index];
   200     }
   202     // [NOCPP[
   204     /**
   205      * Variant of <code>getQName(int index)</code> without bounds check.
   206      * @param index a valid attribute index
   207      * @return the QName at index
   208      */
   209     public @QName String getQNameNoBoundsCheck(int index) {
   210         return names[index].getQName(mode);
   211     }
   213     /**
   214      * Variant of <code>getType(int index)</code> without bounds check.
   215      * @param index a valid attribute index
   216      * @return the attribute type at index
   217      */
   218     public @IdType String getTypeNoBoundsCheck(int index) {
   219         return (names[index] == AttributeName.ID) ? "ID" : "CDATA";
   220     }
   222     public int getIndex(String qName) {
   223         for (int i = 0; i < length; i++) {
   224             if (names[i].getQName(mode).equals(qName)) {
   225                 return i;
   226             }
   227         }
   228         return -1;
   229     }
   231     public int getIndex(String uri, String localName) {
   232         for (int i = 0; i < length; i++) {
   233             if (names[i].getLocal(mode).equals(localName)
   234                     && names[i].getUri(mode).equals(uri)) {
   235                 return i;
   236             }
   237         }
   238         return -1;
   239     }
   241     public @IdType String getType(String qName) {
   242         int index = getIndex(qName);
   243         if (index == -1) {
   244             return null;
   245         } else {
   246             return getType(index);
   247         }
   248     }
   250     public @IdType String getType(String uri, String localName) {
   251         int index = getIndex(uri, localName);
   252         if (index == -1) {
   253             return null;
   254         } else {
   255             return getType(index);
   256         }
   257     }
   259     public String getValue(String qName) {
   260         int index = getIndex(qName);
   261         if (index == -1) {
   262             return null;
   263         } else {
   264             return getValue(index);
   265         }
   266     }
   268     public String getValue(String uri, String localName) {
   269         int index = getIndex(uri, localName);
   270         if (index == -1) {
   271             return null;
   272         } else {
   273             return getValue(index);
   274         }
   275     }
   277     public @Local String getLocalName(int index) {
   278         if (index < length && index >= 0) {
   279             return names[index].getLocal(mode);
   280         } else {
   281             return null;
   282         }
   283     }
   285     public @QName String getQName(int index) {
   286         if (index < length && index >= 0) {
   287             return names[index].getQName(mode);
   288         } else {
   289             return null;
   290         }
   291     }
   293     public @IdType String getType(int index) {
   294         if (index < length && index >= 0) {
   295             return (names[index] == AttributeName.ID) ? "ID" : "CDATA";
   296         } else {
   297             return null;
   298         }
   299     }
   301     public AttributeName getAttributeName(int index) {
   302         if (index < length && index >= 0) {
   303             return names[index];
   304         } else {
   305             return null;
   306         }
   307     }
   309     public @NsUri String getURI(int index) {
   310         if (index < length && index >= 0) {
   311             return names[index].getUri(mode);
   312         } else {
   313             return null;
   314         }
   315     }
   317     public @Prefix String getPrefix(int index) {
   318         if (index < length && index >= 0) {
   319             return names[index].getPrefix(mode);
   320         } else {
   321             return null;
   322         }
   323     }
   325     public String getValue(int index) {
   326         if (index < length && index >= 0) {
   327             return values[index];
   328         } else {
   329             return null;
   330         }
   331     }
   333     public String getId() {
   334         return idValue;
   335     }
   337     public int getXmlnsLength() {
   338         return xmlnsLength;
   339     }
   341     public @Local String getXmlnsLocalName(int index) {
   342         if (index < xmlnsLength && index >= 0) {
   343             return xmlnsNames[index].getLocal(mode);
   344         } else {
   345             return null;
   346         }
   347     }
   349     public @NsUri String getXmlnsURI(int index) {
   350         if (index < xmlnsLength && index >= 0) {
   351             return xmlnsNames[index].getUri(mode);
   352         } else {
   353             return null;
   354         }
   355     }
   357     public String getXmlnsValue(int index) {
   358         if (index < xmlnsLength && index >= 0) {
   359             return xmlnsValues[index];
   360         } else {
   361             return null;
   362         }
   363     }
   365     public int getXmlnsIndex(AttributeName name) {
   366         for (int i = 0; i < xmlnsLength; i++) {
   367             if (xmlnsNames[i] == name) {
   368                 return i;
   369             }
   370         }
   371         return -1;
   372     }
   374     public String getXmlnsValue(AttributeName name) {
   375         int index = getXmlnsIndex(name);
   376         if (index == -1) {
   377             return null;
   378         } else {
   379             return getXmlnsValue(index);
   380         }
   381     }
   383     public AttributeName getXmlnsAttributeName(int index) {
   384         if (index < xmlnsLength && index >= 0) {
   385             return xmlnsNames[index];
   386         } else {
   387             return null;
   388         }
   389     }
   391     // ]NOCPP]
   393     void addAttribute(AttributeName name, String value
   394             // [NOCPP[
   395             , XmlViolationPolicy xmlnsPolicy
   396     // ]NOCPP]        
   397     ) throws SAXException {
   398         // [NOCPP[
   399         if (name == AttributeName.ID) {
   400             idValue = value;
   401         }
   403         if (name.isXmlns()) {
   404             if (xmlnsNames.length == xmlnsLength) {
   405                 int newLen = xmlnsLength == 0 ? 2 : xmlnsLength << 1;
   406                 AttributeName[] newNames = new AttributeName[newLen];
   407                 System.arraycopy(xmlnsNames, 0, newNames, 0, xmlnsNames.length);
   408                 xmlnsNames = newNames;
   409                 String[] newValues = new String[newLen];
   410                 System.arraycopy(xmlnsValues, 0, newValues, 0, xmlnsValues.length);
   411                 xmlnsValues = newValues;
   412             }
   413             xmlnsNames[xmlnsLength] = name;
   414             xmlnsValues[xmlnsLength] = value;
   415             xmlnsLength++;
   416             switch (xmlnsPolicy) {
   417                 case FATAL:
   418                     // this is ugly
   419                     throw new SAXException("Saw an xmlns attribute.");
   420                 case ALTER_INFOSET:
   421                     return;
   422                 case ALLOW:
   423                     // fall through
   424             }
   425         }
   427         // ]NOCPP]
   429         if (names.length == length) {
   430             int newLen = length << 1; // The first growth covers virtually
   431             // 100% of elements according to
   432             // Hixie
   433             AttributeName[] newNames = new AttributeName[newLen];
   434             System.arraycopy(names, 0, newNames, 0, names.length);
   435             names = newNames;
   436             String[] newValues = new String[newLen];
   437             System.arraycopy(values, 0, newValues, 0, values.length);
   438             values = newValues;
   439         }
   440         names[length] = name;
   441         values[length] = value;
   442         length++;
   443     }
   445     void clear(int m) {
   446         for (int i = 0; i < length; i++) {
   447             names[i].release();
   448             names[i] = null;
   449             Portability.releaseString(values[i]);
   450             values[i] = null;
   451         }
   452         length = 0;
   453         mode = m;
   454         // [NOCPP[
   455         idValue = null;
   456         for (int i = 0; i < xmlnsLength; i++) {
   457             xmlnsNames[i] = null;
   458             xmlnsValues[i] = null;
   459         }
   460         xmlnsLength = 0;
   461         // ]NOCPP]
   462     }
   464     /**
   465      * This is used in C++ to release special <code>isindex</code>
   466      * attribute values whose ownership is not transferred.
   467      */
   468     void releaseValue(int i) {
   469         Portability.releaseString(values[i]);        
   470     }
   472     /**
   473      * This is only used for <code>AttributeName</code> ownership transfer
   474      * in the isindex case to avoid freeing custom names twice in C++.
   475      */
   476     void clearWithoutReleasingContents() {
   477         for (int i = 0; i < length; i++) {
   478             names[i] = null;
   479             values[i] = null;
   480         }
   481         length = 0;
   482     }
   484     boolean contains(AttributeName name) {
   485         for (int i = 0; i < length; i++) {
   486             if (name.equalsAnother(names[i])) {
   487                 return true;
   488             }
   489         }
   490         // [NOCPP[
   491         for (int i = 0; i < xmlnsLength; i++) {
   492             if (name.equalsAnother(xmlnsNames[i])) {
   493                 return true;
   494             }
   495         }
   496         // ]NOCPP]
   497         return false;
   498     }
   500     public void adjustForMath() {
   501         mode = AttributeName.MATHML;
   502     }
   504     public void adjustForSvg() {
   505         mode = AttributeName.SVG;
   506     }
   508     public HtmlAttributes cloneAttributes(Interner interner)
   509             throws SAXException {
   510         assert (length == 0
   511                 // [NOCPP[
   512                 && xmlnsLength == 0
   513                 // ]NOCPP]
   514                 )
   515                 || mode == 0 || mode == 3;
   516         HtmlAttributes clone = new HtmlAttributes(0);
   517         for (int i = 0; i < length; i++) {
   518             clone.addAttribute(names[i].cloneAttributeName(interner),
   519                     Portability.newStringFromString(values[i])
   520                     // [NOCPP[
   521                     , XmlViolationPolicy.ALLOW
   522                     // ]NOCPP]
   523             );
   524         }
   525         // [NOCPP[
   526         for (int i = 0; i < xmlnsLength; i++) {
   527             clone.addAttribute(xmlnsNames[i], xmlnsValues[i],
   528                     XmlViolationPolicy.ALLOW);
   529         }
   530         // ]NOCPP]
   531         return clone; // XXX!!!
   532     }
   534     public boolean equalsAnother(HtmlAttributes other) {
   535         assert mode == 0 || mode == 3 : "Trying to compare attributes in foreign content.";
   536         int otherLength = other.getLength();
   537         if (length != otherLength) {
   538             return false;
   539         }
   540         for (int i = 0; i < length; i++) {
   541             // Work around the limitations of C++
   542             boolean found = false;
   543             // The comparing just the local names is OK, since these attribute
   544             // holders are both supposed to belong to HTML formatting elements
   545             @Local String ownLocal = names[i].getLocal(AttributeName.HTML);
   546             for (int j = 0; j < otherLength; j++) {
   547                 if (ownLocal == other.names[j].getLocal(AttributeName.HTML)) {
   548                     found = true;
   549                     if (!Portability.stringEqualsString(values[i], other.values[j])) {
   550                         return false;
   551                     }
   552                 }
   553             }
   554             if (!found) {
   555                 return false;
   556             }
   557         }
   558         return true;
   559     }
   561     // [NOCPP[
   563     void processNonNcNames(TreeBuilder<?> treeBuilder, XmlViolationPolicy namePolicy) throws SAXException {
   564         for (int i = 0; i < length; i++) {
   565             AttributeName attName = names[i];
   566             if (!attName.isNcName(mode)) {
   567                 String name = attName.getLocal(mode);
   568                 switch (namePolicy) {
   569                     case ALTER_INFOSET:
   570                         names[i] = AttributeName.create(NCName.escapeName(name));
   571                         // fall through
   572                     case ALLOW:
   573                         if (attName != AttributeName.XML_LANG) {
   574                             treeBuilder.warn("Attribute \u201C" + name + "\u201D is not serializable as XML 1.0.");
   575                         }
   576                         break;
   577                     case FATAL:
   578                         treeBuilder.fatal("Attribute \u201C" + name + "\u201D is not serializable as XML 1.0.");
   579                         break;
   580                 }
   581             }
   582         }
   583     }
   585     public void merge(HtmlAttributes attributes) throws SAXException {
   586         int len = attributes.getLength();
   587         for (int i = 0; i < len; i++) {
   588             AttributeName name = attributes.getAttributeNameNoBoundsCheck(i);
   589             if (!contains(name)) {
   590                 addAttribute(name, attributes.getValueNoBoundsCheck(i), XmlViolationPolicy.ALLOW);
   591             }
   592         }
   593     }
   596     // ]NOCPP]
   598 }

mercurial