EMMA Coverage Report (generated Mon Nov 01 16:48:29 PDT 2010)
[all classes][com.google.caja.parser.html]

COVERAGE SUMMARY FOR SOURCE FILE [Html5ElementStack.java]

nameclass, %method, %block, %line, %
Html5ElementStack.java100% (2/2)90%  (27/30)94%  (997/1064)92%  (217.8/237)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class Html5ElementStack100% (1/1)92%  (22/24)94%  (923/985)92%  (204.8/222)
<static initializer> 100% (1/1)100% (12/12)100% (3/3)
Html5ElementStack (Document, boolean, MessageQueue): void 100% (1/1)100% (34/34)100% (10/10)
access$000 (Html5ElementStack): CajaTreeBuilder 100% (1/1)100% (3/3)100% (1/1)
access$100 (Html5ElementStack): MessageQueue 100% (1/1)100% (3/3)100% (1/1)
appendNormalized (Node, Node, DocumentFragment): Node 100% (1/1)100% (62/62)100% (10/10)
builderRootElement (): Element 100% (1/1)100% (4/4)100% (1/1)
canonicalAttributeName (String): String 0%   (0/1)0%   (0/3)0%   (0/1)
canonicalElementName (String): String 100% (1/1)100% (3/3)100% (1/1)
canonicalizeName (String): String 100% (1/1)100% (9/9)100% (3/3)
checkName (String): boolean 100% (1/1)100% (12/12)100% (4/4)
finish (FilePosition): void 100% (1/1)100% (8/8)100% (3/3)
fixBrokenEntities (String, FilePosition): String 100% (1/1)100% (84/84)100% (17/17)
getAssociatedAttrs (HtmlAttributes): List 100% (1/1)100% (11/11)100% (3/3)
getDocument (): Document 0%   (0/1)0%   (0/3)0%   (0/1)
getRootElement (): DocumentFragment 100% (1/1)98%  (144/147)94%  (33/35)
hasSpecifiedAttributes (Element): boolean 100% (1/1)93%  (27/29)97%  (5.8/6)
maybeCreateAttribute (String, AttrStub): Attr 100% (1/1)100% (41/41)100% (5/5)
maybeCreateAttributeNs (String, String, AttrStub): Attr 100% (1/1)100% (42/42)100% (5/5)
needsNamespaceFixup (): boolean 100% (1/1)100% (3/3)100% (1/1)
open (boolean): void 100% (1/1)83%  (30/36)80%  (8/10)
processComment (Token): void 100% (1/1)84%  (48/57)77%  (10/13)
processTag (Token, Token, List): void 100% (1/1)90%  (274/304)92%  (65/71)
processText (Token): void 100% (1/1)91%  (62/68)88%  (14/16)
toHtmlAttributes (List, HtmlAttributes): HtmlAttributes 100% (1/1)100% (7/7)100% (2/2)
     
class Html5ElementStack$1100% (1/1)83%  (5/6)94%  (74/79)87%  (13/15)
Html5ElementStack$1 (Html5ElementStack): void 100% (1/1)100% (6/6)100% (1/1)
error (SAXParseException): void 100% (1/1)100% (5/5)100% (2/2)
errorMessage (SAXParseException): String 100% (1/1)100% (9/9)100% (1/1)
fatalError (SAXParseException): void 0%   (0/1)0%   (0/5)0%   (0/2)
report (MessageLevel, SAXParseException): void 100% (1/1)100% (49/49)100% (7/7)
warning (SAXParseException): void 100% (1/1)100% (5/5)100% (2/2)

1// Copyright (C) 2007 Google Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// 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
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14 
15package com.google.caja.parser.html;
16 
17import com.google.caja.SomethingWidgyHappenedError;
18import com.google.caja.lexer.FilePosition;
19import com.google.caja.lexer.HtmlEntities;
20import com.google.caja.lexer.HtmlTokenType;
21import com.google.caja.lexer.Token;
22import com.google.caja.reporting.Message;
23import com.google.caja.reporting.MessageLevel;
24import com.google.caja.reporting.MessagePart;
25import com.google.caja.reporting.MessageQueue;
26import com.google.caja.reporting.MessageType;
27import com.google.caja.util.Lists;
28import com.google.caja.util.Maps;
29import com.google.caja.util.Strings;
30 
31import java.util.Collections;
32import java.util.List;
33import java.util.Map;
34import java.util.WeakHashMap;
35import java.util.logging.Level;
36import java.util.logging.Logger;
37import java.util.regex.Matcher;
38import java.util.regex.Pattern;
39 
40import org.w3c.dom.Attr;
41import org.w3c.dom.DOMException;
42import org.w3c.dom.Document;
43import org.w3c.dom.DocumentFragment;
44import org.w3c.dom.Element;
45import org.w3c.dom.NamedNodeMap;
46import org.w3c.dom.Node;
47import org.w3c.dom.Text;
48import org.xml.sax.ErrorHandler;
49import org.xml.sax.SAXException;
50import org.xml.sax.SAXParseException;
51 
52import nu.validator.htmlparser.common.DoctypeExpectation;
53import nu.validator.htmlparser.common.XmlViolationPolicy;
54import nu.validator.htmlparser.impl.AttributeName;
55import nu.validator.htmlparser.impl.ElementName;
56import nu.validator.htmlparser.impl.HtmlAttributes;
57import nu.validator.htmlparser.impl.Tokenizer;
58 
59/**
60 * A bridge between DomParser and html5lib which translates
61 * {@code Token<HtmlTokenType>}s into SAX style events which are fed to the
62 * TreeBuilder.  The TreeBuilder responds by issuing {@code createElementNS}
63 * commands which are used to build a {@link DocumentFragment}.
64 *
65 * @author mikesamuel@gmail.com
66 */
67public class Html5ElementStack implements OpenElementStack {
68  public static final Logger logger = Logger.getLogger(
69      Html5ElementStack.class.getName());
70  private final CajaTreeBuilder builder;
71  private final char[] charBuf = new char[1024];
72  private final MessageQueue mq;
73  private final Document doc;
74  private final boolean needsDebugData;
75  private final Map<String, ElementName> elNames = Maps.newHashMap();
76  private boolean isFragment;
77  private boolean needsNamespaceFixup;
78  private boolean topLevelHtmlFromInput = false;
79  private boolean processingFirstTag = true;
80 
81  /**
82   * @param doc The document being processed.
83   * @param needsDebugData see {@link DomParser#setNeedsDebugData(boolean)}
84   * @param queue will receive error messages from html5lib.
85   */
86  Html5ElementStack(Document doc, boolean needsDebugData, MessageQueue queue) {
87    this.doc = doc;
88    this.needsDebugData = needsDebugData;
89    this.mq = queue;
90    builder = new CajaTreeBuilder(doc, needsDebugData, mq);
91  }
92 
93  public final Document getDocument() { return doc; }
94 
95  public boolean needsNamespaceFixup() { return needsNamespaceFixup; }
96 
97  /** {@inheritDoc} */
98  public void open(boolean isFragment) {
99    this.isFragment = isFragment;
100    if (isFragment) {
101      builder.setFragmentContext(null);
102    }
103    builder.setDoctypeExpectation(DoctypeExpectation.NO_DOCTYPE_ERRORS);
104    try {
105      builder.startTokenization(new Tokenizer(builder));
106    } catch (SAXException ex) {
107      throw new SomethingWidgyHappenedError(ex);
108    }
109    builder.setErrorHandler(
110        new ErrorHandler() {
111          private FilePosition lastPos;
112          private String lastMessage;
113 
114          public void error(SAXParseException ex) {
115            // htmlparser is a bit strident, so we lower it's warnings to
116            // MessageLevel.LINT.
117            report(MessageLevel.LINT, ex);
118          }
119          public void fatalError(SAXParseException ex) {
120            report(MessageLevel.FATAL_ERROR, ex);
121          }
122          public void warning(SAXParseException ex) {
123            report(MessageLevel.LINT, ex);
124          }
125 
126          private void report(MessageLevel level, SAXParseException ex) {
127            String message = errorMessage(ex);
128            FilePosition pos = builder.getErrorLocation();
129            if (message.equals(lastMessage) && pos.equals(lastPos)) { return; }
130            lastMessage = message;
131            lastPos = pos;
132            mq.getMessages().add(new Message(
133                DomParserMessageType.GENERIC_SAX_ERROR, level, pos,
134                MessagePart.Factory.valueOf(message)));
135          }
136 
137          private String errorMessage(SAXParseException ex) {
138            // Don't ask.
139            return ex.getMessage()
140                .replace('\u201c', '\'').replace('\u201d', '\'');
141          }
142        });
143  }
144 
145  /** {@inheritDoc} */
146  public void finish(FilePosition endOfFile) {
147    if (CajaTreeBuilder.DEBUG) {
148      System.err.println("finish(" + endOfFile + ")");
149    }
150    builder.finish(endOfFile);
151    if (CajaTreeBuilder.DEBUG) {
152      System.err.println("closeUnclosedNodes");
153    }
154    builder.closeUnclosedNodes();
155  }
156 
157  public static String canonicalizeName(String name) {
158    if (name.indexOf(':') >= 0) {  // Do not case-normalize embedded XML.
159      return name;
160    } else {
161      // Forces LANG=C like behavior.
162      return Strings.toLowerCase(name);
163    }
164  }
165 
166  static String canonicalElementName(String elementName) {
167    return canonicalizeName(elementName);
168  }
169 
170  static String canonicalAttributeName(String attributeName) {
171    return canonicalizeName(attributeName);
172  }
173 
174  /** {@inheritDoc} */
175  public DocumentFragment getRootElement() {
176    // libHtmlParser always produces a document with html, head, and body tags
177    // which we usually don't want, so unroll it.
178 
179    // If we can't throw away the head element, and the body header, then we
180    // return the entire document.  Otherwise, we return a document fragment
181    // consisting of the contents of the body.
182 
183    Element root = builder.getRootElement();
184    DocumentFragment result = doc.createDocumentFragment();
185    if (needsDebugData) {
186      Nodes.setFilePositionFor(result, builder.getFragmentBounds());
187    }
188 
189    final Node first = root.getFirstChild();
190 
191    if (!isFragment || topLevelHtmlFromInput) {
192      result.appendChild(root);
193      return result;
194    }
195 
196    // If disposing of the html, body, or head elements would lose info don't
197    // do it, so look for attributes.
198    boolean tagsBesidesHeadBodyFrameset = false;
199    boolean topLevelTagsWithAttributes = hasSpecifiedAttributes(root);
200 
201    for (Node child = first; child != null; child = child.getNextSibling()) {
202      if (child instanceof Element) {
203        Element el = (Element) child;
204        String tagName = el.getTagName();
205        if (!("head".equals(tagName) || "body".equals(tagName)
206              || "frameset".equals(tagName))) {
207          tagsBesidesHeadBodyFrameset = true;
208          break;
209        }
210        if (!topLevelTagsWithAttributes
211            && hasSpecifiedAttributes(el)
212            // framesets, unlike body elements, are never created out of whole
213            // cloth, so we do not behave differently when there is a frameset
214            // with attributes.
215            && !"frameset".equals(tagName)) {
216          topLevelTagsWithAttributes = true;
217        }
218      }
219    }
220 
221    // topLevelTagsWithAttributes is true in the following cases
222    //   <html xml:lang="en">...</html>
223    //   <html><body bgcolor=white>...</body></html>
224    // tagsBesidesHeadAndBody is true for
225    //   <html><frameset>...</frameset></html>
226    if (tagsBesidesHeadBodyFrameset || topLevelTagsWithAttributes) {
227      // Merging the body and head would lose info.
228      result.appendChild(root);
229      return result;
230    }
231 
232    // Merge the body and head into a fragment.
233    // Convert
234    // <html>
235    //   <head>
236    //     <link rel=stylesheet ...>
237    //   </head>
238    //   <body>
239    //     <p>Hello World</p.
240    //   </body>
241    // </html>
242    // to
243    // #fragment
244    //   <link rel=stylesheet ...>
245    //   <p>Hello World</p.
246 
247    Node pending = null;
248    for (Node child = first; child != null; child = child.getNextSibling()) {
249      if (child instanceof Element) {
250        String tagName = ((Element) child).getTagName();
251        if ("head".equals(tagName) || "body".equals(tagName)) {
252          // Shallow descent
253          for (Node grandchild = child.getFirstChild(); grandchild != null;
254               grandchild = grandchild.getNextSibling()) {
255            pending = appendNormalized(pending, grandchild, result);
256          }
257        } else {  // reached for framesets
258          pending = child;
259        }
260      } else {
261        pending = appendNormalized(pending, child, result);
262      }
263    }
264    if (pending != null) { result.appendChild(pending); }
265 
266    return result;
267  }
268 
269  private static boolean hasSpecifiedAttributes(Element el) {
270    NamedNodeMap attrs = el.getAttributes();
271    for (int i = 0, n = attrs.getLength(); i < n; ++i) {
272      Attr a = (Attr) attrs.item(i);
273      if (el.hasAttributeNS(a.getNamespaceURI(), a.getLocalName())) {
274        return true;
275      }
276    }
277    return false;
278  }
279 
280  /**
281   * Given one or two nodes, see if the two can be combined.
282   * If two are passed in, they might be combined into one and returned, or
283   * the first will be appended to parent, and the other returned.
284   */
285  private Node appendNormalized(
286      Node pending, Node current, DocumentFragment parent) {
287    if (pending == null) { return current; }
288    if (pending.getNodeType() != Node.TEXT_NODE
289        || current.getNodeType() != Node.TEXT_NODE) {
290      parent.appendChild(pending);
291      return current;
292    }
293    Text a = (Text) pending, b = (Text) current;
294    Text combined = doc.createTextNode(a.getTextContent() + b.getTextContent());
295    if (needsDebugData) {
296      Nodes.setFilePositionFor(
297          combined,
298          FilePosition.span(
299              Nodes.getFilePositionFor(a),
300              Nodes.getFilePositionFor(b)));
301      Nodes.setRawText(combined, Nodes.getRawText(a) + Nodes.getRawText(b));
302    }
303    return combined;
304  }
305 
306  /**
307   * Records the fact that a tag has been seen, updating internal state
308   *
309   * @param start the token of the beginning of the tag, so {@code "<p"} for a
310   *   paragraph start, {@code "</p"} for an end tag.
311   * @param end the token of the beginning of the tag, so {@code ">"} for a
312   *   paragraph start, {@code "/>"} for an unary break tag.
313   * @param attrStubs the attributes for the element.
314   */
315  public void processTag(Token<HtmlTokenType> start, Token<HtmlTokenType> end,
316                         List<AttrStub> attrStubs) {
317    if (CajaTreeBuilder.DEBUG) {
318      System.err.println("processTag(" + start + ", " + end + ")");
319    }
320    boolean isEndTag = CajaTreeBuilder.isEndTag(start.text);
321    String tagName = start.text.substring(isEndTag ? 2 : 1);
322    boolean isHtml = checkName(tagName);
323    if (isHtml) { tagName = Strings.toLowerCase(tagName); }
324 
325    HtmlAttributes htmlAttrs = new HtmlAttributes(AttributeName.HTML);
326    List<Attr> attrs = Lists.newArrayList();
327    if (!attrStubs.isEmpty()) {
328      for (AttrStub as : attrStubs) {
329        String qname = as.nameTok.text;
330        Attr attrNode;
331        boolean isAttrHtml;
332        try {
333          String name;
334          if ("xmlns".equals(qname)) {
335            if (!Namespaces.HTML_NAMESPACE_URI.equals(as.value)) {
336              // We do not allow overriding of the default namespace when
337              // parsing HTML.
338              mq.addMessage(
339                  MessageType.CANNOT_OVERRIDE_DEFAULT_NAMESPACE_IN_HTML,
340                  as.nameTok.pos);
341            }
342            continue;
343          } else {
344            isAttrHtml = isHtml && checkName(qname);
345            if (isAttrHtml) {
346              name = Strings.toLowerCase(qname);
347              attrNode = maybeCreateAttributeNs(Namespaces.HTML_NAMESPACE_URI,
348                                                name, as);
349              if (attrNode == null) {
350                // Ignore this attribute.
351                continue;
352              }
353            } else {
354              name = AttributeNameFixup.fixupNameFromQname(qname);
355              attrNode = maybeCreateAttribute(name, as);
356              if (attrNode == null) {
357                // Ignore this attribute.
358                continue;
359              }
360            }
361          }
362          attrNode.setValue(as.value);
363          if (needsDebugData) {
364            Nodes.setFilePositionFor(attrNode, as.nameTok.pos);
365            Nodes.setFilePositionForValue(attrNode, as.valueTok.pos);
366            Nodes.setRawValue(attrNode, as.valueTok.text);
367          }
368          attrs.add(attrNode);
369          try {
370            htmlAttrs.addAttribute(
371                AttributeName.nameByString(name),
372                as.value, XmlViolationPolicy.ALLOW);
373          } catch (SAXException ex) {
374            if (CajaTreeBuilder.DEBUG) { ex.printStackTrace(); }
375          }
376        } catch (DOMException ex) {
377          ex.printStackTrace();
378          mq.addMessage(
379              MessageType.INVALID_IDENTIFIER, MessageLevel.WARNING,
380              as.nameTok.pos,
381              MessagePart.Factory.valueOf(as.nameTok.text));
382        }
383      }
384    }
385    ElementName elName = elNames.get(tagName);
386    if (elName == null) {
387      elName = ElementName.elementNameByString(tagName);
388      // Store element names because the underlying tree builder compares them
389      // using ==.
390      elNames.put(tagName, elName);
391    }
392    if (processingFirstTag && elName == ElementName.HTML && !isEndTag) {
393      // Indicate to fragment-retrieval code that the top-level
394      // <html> element came from the input, and wasn't synthesized
395      // by the underlying parser implementation.
396      topLevelHtmlFromInput = true;
397    }
398    processingFirstTag = false;
399    try {
400      if (builder.needsDebugData) {
401        if (isEndTag) {
402          // Version 1.2.1 of the TreeBuilder has a bug where it does not
403          // generate element popped events for body and head elements.
404          if (elName == ElementName.HTML) {
405            Token<HtmlTokenType> tok= Token.instance(
406                "", HtmlTokenType.TAGEND, FilePosition.startOf(start.pos));
407            if (!builder.wasOpened("frameset")) {
408              builder.setTokenContext(tok, tok);
409              if (!builder.wasOpened("body")) {
410                if (!builder.wasOpened("head")) {
411                  builder.startTag(
412                      ElementName.HEAD, HtmlAttributes.EMPTY_ATTRIBUTES, false);
413                  builder.endTag(ElementName.HEAD);
414                }
415                builder.headClosed();
416                builder.startTag(
417                    ElementName.BODY, HtmlAttributes.EMPTY_ATTRIBUTES, false);
418                builder.endTag(ElementName.BODY);
419              }
420              builder.bodyClosed();
421            }
422          }
423        }
424        builder.setTokenContext(start, end);
425      }
426      if (isEndTag) {
427        builder.endTag(elName);
428        if (builder.needsDebugData) {
429          // Make sure that implicit body and head tag are marked as ending
430          // before the </html> tag.
431          if (elName == ElementName.BODY) {
432            builder.bodyClosed();
433          } else if (elName == ElementName.HEAD) {
434            builder.headClosed();
435          }
436        }
437      } else {
438        builder.startTag(elName, toHtmlAttributes(attrs, htmlAttrs),
439                         end.text.equals("/>"));
440      }
441    } catch (SAXException ex) {
442      throw new SomethingWidgyHappenedError(ex);
443    }
444  }
445 
446  /**
447   * Adds the given comment node to the DOM.
448   */
449  public void processComment(Token<HtmlTokenType> commentToken) {
450    String text = commentToken.text.substring(
451        "<!--".length(), commentToken.text.lastIndexOf("--"));
452    commentToken = Token.instance(text, commentToken.type, commentToken.pos);
453    char[] chars;
454    int n = text.length();
455    if (n <= charBuf.length) {
456      chars = charBuf;
457      text.getChars(0, n, chars, 0);
458    } else {
459      chars = text.toCharArray();
460    }
461    builder.setTokenContext(commentToken, commentToken);
462    try {
463      builder.comment(chars, 0, n);
464    } catch (SAXException ex) {
465      throw new RuntimeException(ex);
466    }
467  }
468 
469  private boolean checkName(String qname) {
470    if (qname.indexOf(':', 1) < 0) {
471      return true;
472    } else {
473      needsNamespaceFixup = true;
474      return false;
475    }
476  }
477 
478  private static final Map<HtmlAttributes, List<Attr>> HTML_ASSOCIATED_ATTRS
479      = new WeakHashMap<HtmlAttributes, List<Attr>>();
480  private static HtmlAttributes toHtmlAttributes(
481      List<Attr> attrs, HtmlAttributes blank) {
482    HTML_ASSOCIATED_ATTRS.put(blank, attrs);
483    return blank;
484  }
485 
486  static List<Attr> getAssociatedAttrs(HtmlAttributes attrs) {
487    List<Attr> attrList = HTML_ASSOCIATED_ATTRS.get(attrs);
488    if (attrList == null) { attrList = Collections.emptyList(); }
489    return attrList;
490  }
491 
492  /**
493   * Adds the given text node to the DOM.
494   */
495  public void processText(Token<HtmlTokenType> textToken) {
496    if (CajaTreeBuilder.DEBUG) {
497      System.err.println(
498          "processText(\""
499          + textToken.text.replaceAll("\r\n?|\n", "\\n") + "\")");
500    }
501    // htmlparser doesn't recognize \r as whitespace.
502    String text = textToken.text.replaceAll("\r\n?", "\n");
503    if (textToken.type == HtmlTokenType.TEXT) {
504      text = fixBrokenEntities(text, textToken.pos);
505    }
506    if (text.equals(textToken.text)) {
507      textToken = Token.instance(text, textToken.type, textToken.pos);
508    }
509    char[] chars;
510    int n = text.length();
511    if (n <= charBuf.length) {
512      chars = charBuf;
513      text.getChars(0, n, chars, 0);
514    } else {
515      chars = text.toCharArray();
516    }
517    builder.setTokenContext(textToken, textToken);
518    try {
519      builder.characters(chars, 0, n);
520    } catch (SAXException ex) {
521      throw new SomethingWidgyHappenedError(ex);
522    }
523  }
524 
525  /**
526   * Matches possible HTML entities that lack a closing semicolon.
527   */
528  private static final Pattern BROKEN_ENTITY = Pattern.compile(
529      ""
530      + "&(?:"
531        // A named entity.
532        + "[A-Za-z][0-9A-Za-z]{1,11}(?![;0-9A-Za-z])"
533        // A numeric entity.
534        + "|#(?:"
535          // A decimal entity
536          + "[0-9]{1,7}(?![;0-9])"
537          // A hexadecimal entity.
538          + "|[Xx][0-9A-Fa-f]{1,6}(?![;0-9A-Fa-f])"
539        + ")"
540      + ")"
541      );
542  public String fixBrokenEntities(String rawText, FilePosition fp) {
543    int amp = rawText.indexOf('&');
544    if (amp >= 0) {
545      Matcher m = BROKEN_ENTITY.matcher(rawText);
546      if (m.find(amp)) {
547        StringBuilder sb = new StringBuilder(rawText.length() + 16);
548        int pos = 0;
549        do {
550          String entity = m.group();
551          if (entity.charAt(1) == '#'
552              || HtmlEntities.isEntityName(entity.substring(1))) {
553            sb.append(rawText, pos, m.end()).append(';');
554            pos = m.end();
555            if (needsDebugData) {
556              mq.addMessage(
557                  MessageType.MALFORMED_HTML_ENTITY, fp,
558                  MessagePart.Factory.valueOf(entity));
559            }
560          }
561        } while (m.find());
562        if (pos != 0) {
563          sb.append(rawText, pos, rawText.length());
564          return sb.toString();
565        }
566      }
567    }
568    return rawText;
569  }
570 
571  /**
572   * Creates a w3c dom attribute.
573   *
574   * @param attrName Attribute name.
575   * @param as The attribute stub.
576   * @return A w3c attribute if the attribute name is valid, null otherwise.
577   */
578  public Attr maybeCreateAttribute(String attrName, AttrStub as) {
579    try {
580      return doc.createAttribute(attrName);
581    } catch (DOMException e) {
582      // Ignore DOMException's like INVALID_CHARACTER_ERR since its an html
583      // document.
584      mq.addMessage(DomParserMessageType.IGNORING_TOKEN, as.nameTok.pos,
585                    MessagePart.Factory.valueOf("'" + as.nameTok.text + "'"));
586      logger.log(Level.FINE, "Ignoring DOMException in maybeCreateAttribute",
587                 e);
588      return null;
589    }
590  }
591 
592  /**
593   * Creates a w3c dom attribute in the given namespace.
594   *
595   * @param nsUri The namespace uri to use.
596   * @param attrName Attribute name.
597   * @param as The attribute stub.
598   * @return A w3c attribute if the attribute name is valid, null otherwise.
599   */
600  public Attr maybeCreateAttributeNs(String nsUri, String attrName,
601                                     AttrStub as) {
602    try {
603      return doc.createAttributeNS(nsUri, attrName);
604    } catch (DOMException e) {
605      // Ignore DOMException's like INVALID_CHARACTER_ERR since its an html
606      // document.
607      mq.addMessage(DomParserMessageType.IGNORING_TOKEN, as.nameTok.pos,
608                    MessagePart.Factory.valueOf("'" + as.nameTok.text + "'"));
609      logger.log(Level.FINE, "Ignoring DOMException in maybeCreateAttributeNs",
610                 e);
611      return null;
612    }
613  }
614 
615  // For testing.
616  Element builderRootElement() {
617    return builder.getRootElement();
618  }
619}

[all classes][com.google.caja.parser.html]
EMMA 2.0.5312 (C) Vladimir Roubtsov