/* * Copyright (c) 2007 Henri Sivonen * Copyright (c) 2007-2015 Mozilla Foundation * Portions of comments Copyright 2004-2008 Apple Computer, Inc., Mozilla * Foundation, and Opera Software ASA. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ /* * The comments following this one that use the same comment syntax as this * comment are quotes from the WHATWG HTML 5 spec as of 27 June 2007 * amended as of June 28 2007. * That document came with this statement: * "© Copyright 2004-2007 Apple Computer, Inc., Mozilla Foundation, and * Opera Software ASA. You are granted a license to use, reproduce and * create derivative works of this document." */ package nu.validator.htmlparser.impl; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import org.xml.sax.ErrorHandler; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import nu.validator.htmlparser.annotation.Auto; import nu.validator.htmlparser.annotation.Const; import nu.validator.htmlparser.annotation.IdType; import nu.validator.htmlparser.annotation.Inline; import nu.validator.htmlparser.annotation.Literal; import nu.validator.htmlparser.annotation.Local; import nu.validator.htmlparser.annotation.NoLength; import nu.validator.htmlparser.annotation.NsUri; import nu.validator.htmlparser.common.DoctypeExpectation; import nu.validator.htmlparser.common.DocumentMode; import nu.validator.htmlparser.common.DocumentModeHandler; import nu.validator.htmlparser.common.Interner; import nu.validator.htmlparser.common.TokenHandler; import nu.validator.htmlparser.common.XmlViolationPolicy; public abstract class TreeBuilder<T> implements TokenHandler, TreeBuilderState<T> { /** * Array version of U+FFFD. */ private static final @NoLength char[] REPLACEMENT_CHARACTER = { '\uFFFD' }; // Start dispatch groups final static int OTHER = 0; final static int A = 1; final static int BASE = 2; final static int BODY = 3; final static int BR = 4; final static int BUTTON = 5; final static int CAPTION = 6; final static int COL = 7; final static int COLGROUP = 8; final static int FORM = 9; final static int FRAME = 10; final static int FRAMESET = 11; final static int IMAGE = 12; final static int INPUT = 13; final static int ISINDEX = 14; final static int LI = 15; final static int LINK_OR_BASEFONT_OR_BGSOUND = 16; final static int MATH = 17; final static int META = 18; final static int SVG = 19; final static int HEAD = 20; final static int HR = 22; final static int HTML = 23; final static int NOBR = 24; final static int NOFRAMES = 25; final static int NOSCRIPT = 26; final static int OPTGROUP = 27; final static int OPTION = 28; final static int P = 29; final static int PLAINTEXT = 30; final static int SCRIPT = 31; final static int SELECT = 32; final static int STYLE = 33; final static int TABLE = 34; final static int TEXTAREA = 35; final static int TITLE = 36; final static int TR = 37; final static int XMP = 38; final static int TBODY_OR_THEAD_OR_TFOOT = 39; final static int TD_OR_TH = 40; final static int DD_OR_DT = 41; final static int H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6 = 42; final static int MARQUEE_OR_APPLET = 43; final static int PRE_OR_LISTING = 44; final static int B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U = 45; final static int UL_OR_OL_OR_DL = 46; final static int IFRAME = 47; final static int EMBED = 48; final static int AREA_OR_WBR = 49; final static int DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU = 50; final static int ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY = 51; final static int RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR = 52; final static int RB_OR_RTC = 53; final static int PARAM_OR_SOURCE_OR_TRACK = 55; final static int MGLYPH_OR_MALIGNMARK = 56; final static int MI_MO_MN_MS_MTEXT = 57; final static int ANNOTATION_XML = 58; final static int FOREIGNOBJECT_OR_DESC = 59; final static int NOEMBED = 60; final static int FIELDSET = 61; final static int OUTPUT = 62; final static int OBJECT = 63; final static int FONT = 64; final static int KEYGEN = 65; final static int MENUITEM = 66; final static int TEMPLATE = 67; final static int IMG = 68; final static int RT_OR_RP = 69; // start insertion modes private static final int IN_ROW = 0; private static final int IN_TABLE_BODY = 1; private static final int IN_TABLE = 2; private static final int IN_CAPTION = 3; private static final int IN_CELL = 4; private static final int FRAMESET_OK = 5; private static final int IN_BODY = 6; private static final int IN_HEAD = 7; private static final int IN_HEAD_NOSCRIPT = 8; // no fall-through private static final int IN_COLUMN_GROUP = 9; // no fall-through private static final int IN_SELECT_IN_TABLE = 10; private static final int IN_SELECT = 11; // no fall-through private static final int AFTER_BODY = 12; // no fall-through private static final int IN_FRAMESET = 13; private static final int AFTER_FRAMESET = 14; // no fall-through private static final int INITIAL = 15; // could add fall-through private static final int BEFORE_HTML = 16; // could add fall-through private static final int BEFORE_HEAD = 17; // no fall-through private static final int AFTER_HEAD = 18; // no fall-through private static final int AFTER_AFTER_BODY = 19; // no fall-through private static final int AFTER_AFTER_FRAMESET = 20; // no fall-through private static final int TEXT = 21; private static final int IN_TEMPLATE = 22; // start charset states private static final int CHARSET_INITIAL = 0; private static final int CHARSET_C = 1; private static final int CHARSET_H = 2; private static final int CHARSET_A = 3; private static final int CHARSET_R = 4; private static final int CHARSET_S = 5; private static final int CHARSET_E = 6; private static final int CHARSET_T = 7; private static final int CHARSET_EQUALS = 8; private static final int CHARSET_SINGLE_QUOTED = 9; private static final int CHARSET_DOUBLE_QUOTED = 10; private static final int CHARSET_UNQUOTED = 11; // end pseudo enums // [NOCPP[ private final static String[] HTML4_PUBLIC_IDS = { "-//W3C//DTD HTML 4.0 Frameset//EN", "-//W3C//DTD HTML 4.0 Transitional//EN", "-//W3C//DTD HTML 4.0//EN", "-//W3C//DTD HTML 4.01 Frameset//EN", "-//W3C//DTD HTML 4.01 Transitional//EN", "-//W3C//DTD HTML 4.01//EN" }; // ]NOCPP] @Literal private final static String[] QUIRKY_PUBLIC_IDS = { "+//silmaril//dtd html pro v0r11 19970101//", "-//advasoft ltd//dtd html 3.0 aswedit + extensions//", "-//as//dtd html 3.0 aswedit + extensions//", "-//ietf//dtd html 2.0 level 1//", "-//ietf//dtd html 2.0 level 2//", "-//ietf//dtd html 2.0 strict level 1//", "-//ietf//dtd html 2.0 strict level 2//", "-//ietf//dtd html 2.0 strict//", "-//ietf//dtd html 2.0//", "-//ietf//dtd html 2.1e//", "-//ietf//dtd html 3.0//", "-//ietf//dtd html 3.2 final//", "-//ietf//dtd html 3.2//", "-//ietf//dtd html 3//", "-//ietf//dtd html level 0//", "-//ietf//dtd html level 1//", "-//ietf//dtd html level 2//", "-//ietf//dtd html level 3//", "-//ietf//dtd html strict level 0//", "-//ietf//dtd html strict level 1//", "-//ietf//dtd html strict level 2//", "-//ietf//dtd html strict level 3//", "-//ietf//dtd html strict//", "-//ietf//dtd html//", "-//metrius//dtd metrius presentational//", "-//microsoft//dtd internet explorer 2.0 html strict//", "-//microsoft//dtd internet explorer 2.0 html//", "-//microsoft//dtd internet explorer 2.0 tables//", "-//microsoft//dtd internet explorer 3.0 html strict//", "-//microsoft//dtd internet explorer 3.0 html//", "-//microsoft//dtd internet explorer 3.0 tables//", "-//netscape comm. corp.//dtd html//", "-//netscape comm. corp.//dtd strict html//", "-//o'reilly and associates//dtd html 2.0//", "-//o'reilly and associates//dtd html extended 1.0//", "-//o'reilly and associates//dtd html extended relaxed 1.0//", "-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//", "-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//", "-//spyglass//dtd html 2.0 extended//", "-//sq//dtd html 2.0 hotmetal + extensions//", "-//sun microsystems corp.//dtd hotjava html//", "-//sun microsystems corp.//dtd hotjava strict html//", "-//w3c//dtd html 3 1995-03-24//", "-//w3c//dtd html 3.2 draft//", "-//w3c//dtd html 3.2 final//", "-//w3c//dtd html 3.2//", "-//w3c//dtd html 3.2s draft//", "-//w3c//dtd html 4.0 frameset//", "-//w3c//dtd html 4.0 transitional//", "-//w3c//dtd html experimental 19960712//", "-//w3c//dtd html experimental 970421//", "-//w3c//dtd w3 html//", "-//w3o//dtd w3 html 3.0//", "-//webtechs//dtd mozilla html 2.0//", "-//webtechs//dtd mozilla html//" }; private static final int NOT_FOUND_ON_STACK = Integer.MAX_VALUE; // [NOCPP[ private static final @Local String HTML_LOCAL = "html"; // ]NOCPP] private int mode = INITIAL; private int originalMode = INITIAL; /** * Used only when moving back to IN_BODY. */ private boolean framesetOk = true; protected Tokenizer tokenizer; // [NOCPP[ protected ErrorHandler errorHandler; private DocumentModeHandler documentModeHandler; private DoctypeExpectation doctypeExpectation = DoctypeExpectation.HTML; private LocatorImpl firstCommentLocation; // ]NOCPP] private boolean scriptingEnabled = false; private boolean needToDropLF; // [NOCPP[ private boolean wantingComments; // ]NOCPP] private boolean fragment; private @Local String contextName; private @NsUri String contextNamespace; private T contextNode; /** * Stack of template insertion modes */ private @Auto int[] templateModeStack; /** * Current template mode stack pointer. */ private int templateModePtr = -1; private @Auto StackNode<T>[] stack; private int currentPtr = -1; private @Auto StackNode<T>[] listOfActiveFormattingElements; private int listPtr = -1; private T formPointer; private T headPointer; /** * Used to work around Gecko limitations. Not used in Java. */ private T deepTreeSurrogateParent; protected @Auto char[] charBuffer; protected int charBufferLen = 0; private boolean quirks = false; private boolean isSrcdocDocument = false; // [NOCPP[ private boolean reportingDoctype = true; private XmlViolationPolicy namePolicy = XmlViolationPolicy.ALTER_INFOSET; private final Map<String, LocatorImpl> idLocations = new HashMap<String, LocatorImpl>(); private boolean html4; // ]NOCPP] protected TreeBuilder() { fragment = false; } /** * Reports an condition that would make the infoset incompatible with XML * 1.0 as fatal. * * @throws SAXException * @throws SAXParseException */ protected void fatal() throws SAXException { } // [NOCPP[ protected final void fatal(Exception e) throws SAXException { SAXParseException spe = new SAXParseException(e.getMessage(), tokenizer, e); if (errorHandler != null) { errorHandler.fatalError(spe); } throw spe; } final void fatal(String s) throws SAXException { SAXParseException spe = new SAXParseException(s, tokenizer); if (errorHandler != null) { errorHandler.fatalError(spe); } throw spe; } /** * Reports a Parse Error. * * @param message * the message * @throws SAXException */ final void err(String message) throws SAXException { if (errorHandler == null) { return; } errNoCheck(message); } /** * Reports a Parse Error without checking if an error handler is present. * * @param message * the message * @throws SAXException */ final void errNoCheck(String message) throws SAXException { SAXParseException spe = new SAXParseException(message, tokenizer); errorHandler.error(spe); } private void errListUnclosedStartTags(int eltPos) throws SAXException { if (currentPtr != -1) { for (int i = currentPtr; i > eltPos; i--) { reportUnclosedElementNameAndLocation(i); } } } /** * Reports the name and location of an unclosed element. * * @throws SAXException */ private final void reportUnclosedElementNameAndLocation(int pos) throws SAXException { StackNode<T> node = stack[pos]; if (node.isOptionalEndTag()) { return; } TaintableLocatorImpl locator = node.getLocator(); if (locator.isTainted()) { return; } locator.markTainted(); SAXParseException spe = new SAXParseException( "Unclosed element \u201C" + node.popName + "\u201D.", locator); errorHandler.error(spe); } /** * Reports a warning * * @param message * the message * @throws SAXException */ final void warn(String message) throws SAXException { if (errorHandler == null) { return; } SAXParseException spe = new SAXParseException(message, tokenizer); errorHandler.warning(spe); } /** * Reports a warning with an explicit locator * * @param message * the message * @throws SAXException */ final void warn(String message, Locator locator) throws SAXException { if (errorHandler == null) { return; } SAXParseException spe = new SAXParseException(message, locator); errorHandler.warning(spe); } // ]NOCPP] @SuppressWarnings("unchecked") public final void startTokenization(Tokenizer self) throws SAXException { tokenizer = self; stack = new StackNode[64]; templateModeStack = new int[64]; listOfActiveFormattingElements = new StackNode[64]; needToDropLF = false; originalMode = INITIAL; templateModePtr = -1; currentPtr = -1; listPtr = -1; formPointer = null; headPointer = null; deepTreeSurrogateParent = null; // [NOCPP[ html4 = false; idLocations.clear(); wantingComments = wantsComments(); firstCommentLocation = null; // ]NOCPP] start(fragment); charBufferLen = 0; charBuffer = null; framesetOk = true; if (fragment) { T elt; if (contextNode != null) { elt = contextNode; } else { elt = createHtmlElementSetAsRoot(tokenizer.emptyAttributes()); } // When the context node is not in the HTML namespace, contrary // to the spec, the first node on the stack is not set to "html" // in the HTML namespace. Instead, it is set to a node that has // the characteristics of the appropriate "adjusted current node". // This way, there is no need to perform "adjusted current node" // checks during tree construction. Instead, it's sufficient to // just look at the current node. However, this also means that it // is not safe to treat "html" in the HTML namespace as a sentinel // that ends stack popping. Instead, stack popping loops that are // meant not to pop the first element on the stack need to check // for currentPos becoming zero. if (contextNamespace == "http://www.w3.org/2000/svg") { ElementName elementName = ElementName.SVG; if ("title" == contextName || "desc" == contextName || "foreignObject" == contextName) { // These elements are all alike and we don't care about // the exact name. elementName = ElementName.FOREIGNOBJECT; } // This is the SVG variant of the StackNode constructor. StackNode<T> node = new StackNode<T>(elementName, elementName.camelCaseName, elt // [NOCPP[ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) // ]NOCPP] ); currentPtr++; stack[currentPtr] = node; tokenizer.setStateAndEndTagExpectation(Tokenizer.DATA, contextName); // The frameset-ok flag is set even though <frameset> never // ends up being allowed as HTML frameset in the fragment case. mode = FRAMESET_OK; } else if (contextNamespace == "http://www.w3.org/1998/Math/MathML") { ElementName elementName = ElementName.MATH; if ("mi" == contextName || "mo" == contextName || "mn" == contextName || "ms" == contextName || "mtext" == contextName) { // These elements are all alike and we don't care about // the exact name. elementName = ElementName.MTEXT; } else if ("annotation-xml" == contextName) { elementName = ElementName.ANNOTATION_XML; // Blink does not check the encoding attribute of the // annotation-xml element innerHTML is being set on. // Let's do the same at least until // https://www.w3.org/Bugs/Public/show_bug.cgi?id=26783 // is resolved. } // This is the MathML variant of the StackNode constructor. StackNode<T> node = new StackNode<T>(elementName, elt, elementName.name, false // [NOCPP[ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) // ]NOCPP] ); currentPtr++; stack[currentPtr] = node; tokenizer.setStateAndEndTagExpectation(Tokenizer.DATA, contextName); // The frameset-ok flag is set even though <frameset> never // ends up being allowed as HTML frameset in the fragment case. mode = FRAMESET_OK; } else { // html StackNode<T> node = new StackNode<T>(ElementName.HTML, elt // [NOCPP[ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) // ]NOCPP] ); currentPtr++; stack[currentPtr] = node; if ("template" == contextName) { pushTemplateMode(IN_TEMPLATE); } resetTheInsertionMode(); formPointer = getFormPointerForContext(contextNode); if ("title" == contextName || "textarea" == contextName) { tokenizer.setStateAndEndTagExpectation(Tokenizer.RCDATA, contextName); } else if ("style" == contextName || "xmp" == contextName || "iframe" == contextName || "noembed" == contextName || "noframes" == contextName || (scriptingEnabled && "noscript" == contextName)) { tokenizer.setStateAndEndTagExpectation(Tokenizer.RAWTEXT, contextName); } else if ("plaintext" == contextName) { tokenizer.setStateAndEndTagExpectation(Tokenizer.PLAINTEXT, contextName); } else if ("script" == contextName) { tokenizer.setStateAndEndTagExpectation( Tokenizer.SCRIPT_DATA, contextName); } else { tokenizer.setStateAndEndTagExpectation(Tokenizer.DATA, contextName); } } contextName = null; contextNode = null; } else { mode = INITIAL; // If we are viewing XML source, put a foreign element permanently // on the stack so that cdataSectionAllowed() returns true. // CPPONLY: if (tokenizer.isViewingXmlSource()) { // CPPONLY: T elt = createElement("http://www.w3.org/2000/svg", // CPPONLY: "svg", // CPPONLY: tokenizer.emptyAttributes(), null); // CPPONLY: StackNode<T> node = new StackNode<T>(ElementName.SVG, // CPPONLY: "svg", // CPPONLY: elt); // CPPONLY: currentPtr++; // CPPONLY: stack[currentPtr] = node; // CPPONLY: } } } public final void doctype(@Local String name, String publicIdentifier, String systemIdentifier, boolean forceQuirks) throws SAXException { needToDropLF = false; if (!isInForeign() && mode == INITIAL) { // [NOCPP[ if (reportingDoctype) { // ]NOCPP] String emptyString = Portability.newEmptyString(); appendDoctypeToDocument(name == null ? "" : name, publicIdentifier == null ? emptyString : publicIdentifier, systemIdentifier == null ? emptyString : systemIdentifier); Portability.releaseString(emptyString); // [NOCPP[ } switch (doctypeExpectation) { case HTML: // ]NOCPP] if (isQuirky(name, publicIdentifier, systemIdentifier, forceQuirks)) { errQuirkyDoctype(); documentModeInternal(DocumentMode.QUIRKS_MODE, publicIdentifier, systemIdentifier, false); } else if (isAlmostStandards(publicIdentifier, systemIdentifier)) { // [NOCPP[ if (firstCommentLocation != null) { warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.", firstCommentLocation); } // ]NOCPP] errAlmostStandardsDoctype(); documentModeInternal( DocumentMode.ALMOST_STANDARDS_MODE, publicIdentifier, systemIdentifier, false); } else { // [NOCPP[ if (firstCommentLocation != null) { warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.", firstCommentLocation); } if ((Portability.literalEqualsString( "-//W3C//DTD HTML 4.0//EN", publicIdentifier) && (systemIdentifier == null || Portability.literalEqualsString( "http://www.w3.org/TR/REC-html40/strict.dtd", systemIdentifier))) || (Portability.literalEqualsString( "-//W3C//DTD HTML 4.01//EN", publicIdentifier) && (systemIdentifier == null || Portability.literalEqualsString( "http://www.w3.org/TR/html4/strict.dtd", systemIdentifier))) || (Portability.literalEqualsString( "-//W3C//DTD XHTML 1.0 Strict//EN", publicIdentifier) && Portability.literalEqualsString( "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd", systemIdentifier)) || (Portability.literalEqualsString( "-//W3C//DTD XHTML 1.1//EN", publicIdentifier) && Portability.literalEqualsString( "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd", systemIdentifier)) ) { warn("Obsolete doctype. Expected \u201C<!DOCTYPE html>\u201D."); } else if (!((systemIdentifier == null || Portability.literalEqualsString( "about:legacy-compat", systemIdentifier)) && publicIdentifier == null)) { err("Legacy doctype. Expected \u201C<!DOCTYPE html>\u201D."); } // ]NOCPP] documentModeInternal(DocumentMode.STANDARDS_MODE, publicIdentifier, systemIdentifier, false); } // [NOCPP[ break; case HTML401_STRICT: html4 = true; tokenizer.turnOnAdditionalHtml4Errors(); if (isQuirky(name, publicIdentifier, systemIdentifier, forceQuirks)) { err("Quirky doctype. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u201D."); documentModeInternal(DocumentMode.QUIRKS_MODE, publicIdentifier, systemIdentifier, true); } else if (isAlmostStandards(publicIdentifier, systemIdentifier)) { if (firstCommentLocation != null) { warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.", firstCommentLocation); } err("Almost standards mode doctype. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u201D."); documentModeInternal( DocumentMode.ALMOST_STANDARDS_MODE, publicIdentifier, systemIdentifier, true); } else { if (firstCommentLocation != null) { warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.", firstCommentLocation); } if ("-//W3C//DTD HTML 4.01//EN".equals(publicIdentifier)) { if (!"http://www.w3.org/TR/html4/strict.dtd".equals(systemIdentifier)) { warn("The doctype did not contain the system identifier prescribed by the HTML 4.01 specification. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u201D."); } } else { err("The doctype was not the HTML 4.01 Strict doctype. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u201D."); } documentModeInternal(DocumentMode.STANDARDS_MODE, publicIdentifier, systemIdentifier, true); } break; case HTML401_TRANSITIONAL: html4 = true; tokenizer.turnOnAdditionalHtml4Errors(); if (isQuirky(name, publicIdentifier, systemIdentifier, forceQuirks)) { err("Quirky doctype. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\u201D."); documentModeInternal(DocumentMode.QUIRKS_MODE, publicIdentifier, systemIdentifier, true); } else if (isAlmostStandards(publicIdentifier, systemIdentifier)) { if (firstCommentLocation != null) { warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.", firstCommentLocation); } if ("-//W3C//DTD HTML 4.01 Transitional//EN".equals(publicIdentifier) && systemIdentifier != null) { if (!"http://www.w3.org/TR/html4/loose.dtd".equals(systemIdentifier)) { warn("The doctype did not contain the system identifier prescribed by the HTML 4.01 specification. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\u201D."); } } else { err("The doctype was not a non-quirky HTML 4.01 Transitional doctype. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\u201D."); } documentModeInternal( DocumentMode.ALMOST_STANDARDS_MODE, publicIdentifier, systemIdentifier, true); } else { if (firstCommentLocation != null) { warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.", firstCommentLocation); } err("The doctype was not the HTML 4.01 Transitional doctype. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\u201D."); documentModeInternal(DocumentMode.STANDARDS_MODE, publicIdentifier, systemIdentifier, true); } break; case AUTO: html4 = isHtml4Doctype(publicIdentifier); if (html4) { tokenizer.turnOnAdditionalHtml4Errors(); } if (isQuirky(name, publicIdentifier, systemIdentifier, forceQuirks)) { err("Quirky doctype. Expected e.g. \u201C<!DOCTYPE html>\u201D."); documentModeInternal(DocumentMode.QUIRKS_MODE, publicIdentifier, systemIdentifier, html4); } else if (isAlmostStandards(publicIdentifier, systemIdentifier)) { if (firstCommentLocation != null) { warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.", firstCommentLocation); } if ("-//W3C//DTD HTML 4.01 Transitional//EN".equals(publicIdentifier)) { if (!"http://www.w3.org/TR/html4/loose.dtd".equals(systemIdentifier)) { warn("The doctype did not contain the system identifier prescribed by the HTML 4.01 specification. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\u201D."); } } else { err("Almost standards mode doctype. Expected e.g. \u201C<!DOCTYPE html>\u201D."); } documentModeInternal( DocumentMode.ALMOST_STANDARDS_MODE, publicIdentifier, systemIdentifier, html4); } else { if (firstCommentLocation != null) { warn("Comments seen before doctype. Internet Explorer will go into the quirks mode.", firstCommentLocation); } if ("-//W3C//DTD HTML 4.01//EN".equals(publicIdentifier)) { if (!"http://www.w3.org/TR/html4/strict.dtd".equals(systemIdentifier)) { warn("The doctype did not contain the system identifier prescribed by the HTML 4.01 specification. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u201D."); } } else if ("-//W3C//DTD XHTML 1.0 Strict//EN".equals(publicIdentifier)) { if (!"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd".equals(systemIdentifier)) { warn("The doctype did not contain the system identifier prescribed by the XHTML 1.0 specification. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\u201D."); } } else if ("//W3C//DTD XHTML 1.1//EN".equals(publicIdentifier)) { if (!"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd".equals(systemIdentifier)) { warn("The doctype did not contain the system identifier prescribed by the XHTML 1.1 specification. Expected \u201C<!DOCTYPE HTML PUBLIC \"//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\u201D."); } } else if (!((systemIdentifier == null || Portability.literalEqualsString( "about:legacy-compat", systemIdentifier)) && publicIdentifier == null)) { err("Unexpected doctype. Expected, e.g., \u201C<!DOCTYPE html>\u201D."); } documentModeInternal(DocumentMode.STANDARDS_MODE, publicIdentifier, systemIdentifier, html4); } break; case NO_DOCTYPE_ERRORS: if (isQuirky(name, publicIdentifier, systemIdentifier, forceQuirks)) { documentModeInternal(DocumentMode.QUIRKS_MODE, publicIdentifier, systemIdentifier, false); } else if (isAlmostStandards(publicIdentifier, systemIdentifier)) { documentModeInternal( DocumentMode.ALMOST_STANDARDS_MODE, publicIdentifier, systemIdentifier, false); } else { documentModeInternal(DocumentMode.STANDARDS_MODE, publicIdentifier, systemIdentifier, false); } break; } // ]NOCPP] /* * * Then, switch to the root element mode of the tree construction * stage. */ mode = BEFORE_HTML; return; } /* * A DOCTYPE token Parse error. */ errStrayDoctype(); /* * Ignore the token. */ return; } // [NOCPP[ private boolean isHtml4Doctype(String publicIdentifier) { if (publicIdentifier != null && (Arrays.binarySearch(TreeBuilder.HTML4_PUBLIC_IDS, publicIdentifier) > -1)) { return true; } return false; } // ]NOCPP] public final void comment(@NoLength char[] buf, int start, int length) throws SAXException { needToDropLF = false; // [NOCPP[ if (firstCommentLocation == null) { firstCommentLocation = new LocatorImpl(tokenizer); } if (!wantingComments) { return; } // ]NOCPP] if (!isInForeign()) { switch (mode) { case INITIAL: case BEFORE_HTML: case AFTER_AFTER_BODY: case AFTER_AFTER_FRAMESET: /* * A comment token Append a Comment node to the Document * object with the data attribute set to the data given in * the comment token. */ appendCommentToDocument(buf, start, length); return; case AFTER_BODY: /* * A comment token Append a Comment node to the first * element in the stack of open elements (the html element), * with the data attribute set to the data given in the * comment token. */ flushCharacters(); appendComment(stack[0].node, buf, start, length); return; default: break; } } /* * A comment token Append a Comment node to the current node with the * data attribute set to the data given in the comment token. */ flushCharacters(); appendComment(stack[currentPtr].node, buf, start, length); return; } /** * @see nu.validator.htmlparser.common.TokenHandler#characters(char[], int, * int) */ public final void characters(@Const @NoLength char[] buf, int start, int length) throws SAXException { // Note: Can't attach error messages to EOF in C++ yet // CPPONLY: if (tokenizer.isViewingXmlSource()) { // CPPONLY: return; // CPPONLY: } if (needToDropLF) { needToDropLF = false; if (buf[start] == '\n') { start++; length--; if (length == 0) { return; } } } // optimize the most common case switch (mode) { case IN_BODY: case IN_CELL: case IN_CAPTION: if (!isInForeignButNotHtmlOrMathTextIntegrationPoint()) { reconstructTheActiveFormattingElements(); } // fall through case TEXT: accumulateCharacters(buf, start, length); return; case IN_TABLE: case IN_TABLE_BODY: case IN_ROW: accumulateCharactersForced(buf, start, length); return; default: int end = start + length; charactersloop: for (int i = start; i < end; i++) { switch (buf[i]) { case ' ': case '\t': case '\n': case '\r': case '\u000C': /* * A character token that is one of one of U+0009 * CHARACTER TABULATION, U+000A LINE FEED (LF), * U+000C FORM FEED (FF), or U+0020 SPACE */ switch (mode) { case INITIAL: case BEFORE_HTML: case BEFORE_HEAD: /* * Ignore the token. */ start = i + 1; continue; case IN_HEAD: case IN_HEAD_NOSCRIPT: case AFTER_HEAD: case IN_COLUMN_GROUP: case IN_FRAMESET: case AFTER_FRAMESET: /* * Append the character to the current node. */ continue; case FRAMESET_OK: case IN_TEMPLATE: case IN_BODY: case IN_CELL: case IN_CAPTION: if (start < i) { accumulateCharacters(buf, start, i - start); start = i; } /* * Reconstruct the active formatting * elements, if any. */ if (!isInForeignButNotHtmlOrMathTextIntegrationPoint()) { flushCharacters(); reconstructTheActiveFormattingElements(); } /* * Append the token's character to the * current node. */ break charactersloop; case IN_SELECT: case IN_SELECT_IN_TABLE: break charactersloop; case IN_TABLE: case IN_TABLE_BODY: case IN_ROW: accumulateCharactersForced(buf, i, 1); start = i + 1; continue; case AFTER_BODY: case AFTER_AFTER_BODY: case AFTER_AFTER_FRAMESET: if (start < i) { accumulateCharacters(buf, start, i - start); start = i; } /* * Reconstruct the active formatting * elements, if any. */ flushCharacters(); reconstructTheActiveFormattingElements(); /* * Append the token's character to the * current node. */ continue; } default: /* * A character token that is not one of one of * U+0009 CHARACTER TABULATION, U+000A LINE FEED * (LF), U+000C FORM FEED (FF), or U+0020 SPACE */ switch (mode) { case INITIAL: /* * Parse error. */ // [NOCPP[ switch (doctypeExpectation) { case AUTO: err("Non-space characters found without seeing a doctype first. Expected e.g. \u201C<!DOCTYPE html>\u201D."); break; case HTML: // XXX figure out a way to report this in the Gecko View Source case err("Non-space characters found without seeing a doctype first. Expected \u201C<!DOCTYPE html>\u201D."); break; case HTML401_STRICT: err("Non-space characters found without seeing a doctype first. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u201D."); break; case HTML401_TRANSITIONAL: err("Non-space characters found without seeing a doctype first. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\u201D."); break; case NO_DOCTYPE_ERRORS: } // ]NOCPP] /* * * Set the document to quirks mode. */ documentModeInternal( DocumentMode.QUIRKS_MODE, null, null, false); /* * Then, switch to the root element mode of * the tree construction stage */ mode = BEFORE_HTML; /* * and reprocess the current token. */ i--; continue; case BEFORE_HTML: /* * Create an HTMLElement node with the tag * name html, in the HTML namespace. Append * it to the Document object. */ // No need to flush characters here, // because there's nothing to flush. appendHtmlElementToDocumentAndPush(); /* Switch to the main mode */ mode = BEFORE_HEAD; /* * reprocess the current token. */ i--; continue; case BEFORE_HEAD: if (start < i) { accumulateCharacters(buf, start, i - start); start = i; } /* * /Act as if a start tag token with the tag * name "head" and no attributes had been * seen, */ flushCharacters(); appendToCurrentNodeAndPushHeadElement(HtmlAttributes.EMPTY_ATTRIBUTES); mode = IN_HEAD; /* * then reprocess the current token. * * This will result in an empty head element * being generated, with the current token * being reprocessed in the "after head" * insertion mode. */ i--; continue; case IN_HEAD: if (start < i) { accumulateCharacters(buf, start, i - start); start = i; } /* * Act as if an end tag token with the tag * name "head" had been seen, */ flushCharacters(); pop(); mode = AFTER_HEAD; /* * and reprocess the current token. */ i--; continue; case IN_HEAD_NOSCRIPT: if (start < i) { accumulateCharacters(buf, start, i - start); start = i; } /* * Parse error. Act as if an end tag with * the tag name "noscript" had been seen */ errNonSpaceInNoscriptInHead(); flushCharacters(); pop(); mode = IN_HEAD; /* * and reprocess the current token. */ i--; continue; case AFTER_HEAD: if (start < i) { accumulateCharacters(buf, start, i - start); start = i; } /* * Act as if a start tag token with the tag * name "body" and no attributes had been * seen, */ flushCharacters(); appendToCurrentNodeAndPushBodyElement(); mode = FRAMESET_OK; /* * and then reprocess the current token. */ i--; continue; case FRAMESET_OK: framesetOk = false; mode = IN_BODY; i--; continue; case IN_TEMPLATE: case IN_BODY: case IN_CELL: case IN_CAPTION: if (start < i) { accumulateCharacters(buf, start, i - start); start = i; } /* * Reconstruct the active formatting * elements, if any. */ if (!isInForeignButNotHtmlOrMathTextIntegrationPoint()) { flushCharacters(); reconstructTheActiveFormattingElements(); } /* * Append the token's character to the * current node. */ break charactersloop; case IN_TABLE: case IN_TABLE_BODY: case IN_ROW: accumulateCharactersForced(buf, i, 1); start = i + 1; continue; case IN_COLUMN_GROUP: if (start < i) { accumulateCharacters(buf, start, i - start); start = i; } /* * Act as if an end tag with the tag name * "colgroup" had been seen, and then, if * that token wasn't ignored, reprocess the * current token. */ if (currentPtr == 0 || stack[currentPtr].getGroup() == TreeBuilder.TEMPLATE) { errNonSpaceInColgroupInFragment(); start = i + 1; continue; } flushCharacters(); pop(); mode = IN_TABLE; i--; continue; case IN_SELECT: case IN_SELECT_IN_TABLE: break charactersloop; case AFTER_BODY: errNonSpaceAfterBody(); fatal(); mode = framesetOk ? FRAMESET_OK : IN_BODY; i--; continue; case IN_FRAMESET: if (start < i) { accumulateCharacters(buf, start, i - start); // start index is adjusted below. } /* * Parse error. */ errNonSpaceInFrameset(); /* * Ignore the token. */ start = i + 1; continue; case AFTER_FRAMESET: if (start < i) { accumulateCharacters(buf, start, i - start); // start index is adjusted below. } /* * Parse error. */ errNonSpaceAfterFrameset(); /* * Ignore the token. */ start = i + 1; continue; case AFTER_AFTER_BODY: /* * Parse error. */ errNonSpaceInTrailer(); /* * Switch back to the main mode and * reprocess the token. */ mode = framesetOk ? FRAMESET_OK : IN_BODY; i--; continue; case AFTER_AFTER_FRAMESET: if (start < i) { accumulateCharacters(buf, start, i - start); // start index is adjusted below. } /* * Parse error. */ errNonSpaceInTrailer(); /* * Ignore the token. */ start = i + 1; continue; } } } if (start < end) { accumulateCharacters(buf, start, end - start); } } } /** * @see nu.validator.htmlparser.common.TokenHandler#zeroOriginatingReplacementCharacter() */ public void zeroOriginatingReplacementCharacter() throws SAXException { if (mode == TEXT) { accumulateCharacters(REPLACEMENT_CHARACTER, 0, 1); return; } if (currentPtr >= 0) { if (isSpecialParentInForeign(stack[currentPtr])) { return; } accumulateCharacters(REPLACEMENT_CHARACTER, 0, 1); } } public final void eof() throws SAXException { flushCharacters(); // Note: Can't attach error messages to EOF in C++ yet eofloop: for (;;) { switch (mode) { case INITIAL: /* * Parse error. */ // [NOCPP[ switch (doctypeExpectation) { case AUTO: err("End of file seen without seeing a doctype first. Expected e.g. \u201C<!DOCTYPE html>\u201D."); break; case HTML: err("End of file seen without seeing a doctype first. Expected \u201C<!DOCTYPE html>\u201D."); break; case HTML401_STRICT: err("End of file seen without seeing a doctype first. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u201D."); break; case HTML401_TRANSITIONAL: err("End of file seen without seeing a doctype first. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\u201D."); break; case NO_DOCTYPE_ERRORS: } // ]NOCPP] /* * * Set the document to quirks mode. */ documentModeInternal(DocumentMode.QUIRKS_MODE, null, null, false); /* * Then, switch to the root element mode of the tree * construction stage */ mode = BEFORE_HTML; /* * and reprocess the current token. */ continue; case BEFORE_HTML: /* * Create an HTMLElement node with the tag name html, in the * HTML namespace. Append it to the Document object. */ appendHtmlElementToDocumentAndPush(); // XXX application cache manifest /* Switch to the main mode */ mode = BEFORE_HEAD; /* * reprocess the current token. */ continue; case BEFORE_HEAD: appendToCurrentNodeAndPushHeadElement(HtmlAttributes.EMPTY_ATTRIBUTES); mode = IN_HEAD; continue; case IN_HEAD: // [NOCPP[ if (errorHandler != null && currentPtr > 1) { errEofWithUnclosedElements(); } // ]NOCPP] while (currentPtr > 0) { popOnEof(); } mode = AFTER_HEAD; continue; case IN_HEAD_NOSCRIPT: // [NOCPP[ errEofWithUnclosedElements(); // ]NOCPP] while (currentPtr > 1) { popOnEof(); } mode = IN_HEAD; continue; case AFTER_HEAD: appendToCurrentNodeAndPushBodyElement(); mode = IN_BODY; continue; case IN_TABLE_BODY: case IN_ROW: case IN_TABLE: case IN_SELECT_IN_TABLE: case IN_SELECT: case IN_COLUMN_GROUP: case FRAMESET_OK: case IN_CAPTION: case IN_CELL: case IN_BODY: // [NOCPP[ // i > 0 to stop in time in the foreign fragment case. openelementloop: for (int i = currentPtr; i > 0; i--) { int group = stack[i].getGroup(); switch (group) { case DD_OR_DT: case LI: case P: case TBODY_OR_THEAD_OR_TFOOT: case TD_OR_TH: case BODY: case HTML: break; default: errEofWithUnclosedElements(); break openelementloop; } } // ]NOCPP] if (isTemplateModeStackEmpty()) { break eofloop; } // fall through to IN_TEMPLATE case IN_TEMPLATE: int eltPos = findLast("template"); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { assert fragment; break eofloop; } if (errorHandler != null) { errUnclosedElements(eltPos, "template"); } while (currentPtr >= eltPos) { pop(); } clearTheListOfActiveFormattingElementsUpToTheLastMarker(); popTemplateMode(); resetTheInsertionMode(); // Reprocess token. continue; case TEXT: // [NOCPP[ if (errorHandler != null) { errNoCheck("End of file seen when expecting text or an end tag."); errListUnclosedStartTags(0); } // ]NOCPP] // XXX mark script as already executed if (originalMode == AFTER_HEAD) { popOnEof(); } popOnEof(); mode = originalMode; continue; case IN_FRAMESET: // [NOCPP[ if (errorHandler != null && currentPtr > 0) { errEofWithUnclosedElements(); } // ]NOCPP] break eofloop; case AFTER_BODY: case AFTER_FRAMESET: case AFTER_AFTER_BODY: case AFTER_AFTER_FRAMESET: default: // [NOCPP[ if (currentPtr == 0) { // This silliness is here to poison // buggy compiler optimizations in // GWT System.currentTimeMillis(); } // ]NOCPP] break eofloop; } } while (currentPtr > 0) { popOnEof(); } if (!fragment) { popOnEof(); } /* Stop parsing. */ } /** * @see nu.validator.htmlparser.common.TokenHandler#endTokenization() */ public final void endTokenization() throws SAXException { formPointer = null; headPointer = null; deepTreeSurrogateParent = null; templateModeStack = null; if (stack != null) { while (currentPtr > -1) { stack[currentPtr].release(); currentPtr--; } stack = null; } if (listOfActiveFormattingElements != null) { while (listPtr > -1) { if (listOfActiveFormattingElements[listPtr] != null) { listOfActiveFormattingElements[listPtr].release(); } listPtr--; } listOfActiveFormattingElements = null; } // [NOCPP[ idLocations.clear(); // ]NOCPP] charBuffer = null; end(); } public final void startTag(ElementName elementName, HtmlAttributes attributes, boolean selfClosing) throws SAXException { flushCharacters(); // [NOCPP[ if (errorHandler != null) { // ID uniqueness @IdType String id = attributes.getId(); if (id != null) { LocatorImpl oldLoc = idLocations.get(id); if (oldLoc != null) { err("Duplicate ID \u201C" + id + "\u201D."); errorHandler.warning(new SAXParseException( "The first occurrence of ID \u201C" + id + "\u201D was here.", oldLoc)); } else { idLocations.put(id, new LocatorImpl(tokenizer)); } } } // ]NOCPP] int eltPos; needToDropLF = false; starttagloop: for (;;) { int group = elementName.getGroup(); @Local String name = elementName.name; if (isInForeign()) { StackNode<T> currentNode = stack[currentPtr]; @NsUri String currNs = currentNode.ns; if (!(currentNode.isHtmlIntegrationPoint() || (currNs == "http://www.w3.org/1998/Math/MathML" && ((currentNode.getGroup() == MI_MO_MN_MS_MTEXT && group != MGLYPH_OR_MALIGNMARK) || (currentNode.getGroup() == ANNOTATION_XML && group == SVG))))) { switch (group) { case B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U: case DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU: case BODY: case BR: case RUBY_OR_SPAN_OR_SUB_OR_SUP_OR_VAR: case DD_OR_DT: case UL_OR_OL_OR_DL: case EMBED: case IMG: case H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6: case HEAD: case HR: case LI: case META: case NOBR: case P: case PRE_OR_LISTING: case TABLE: case FONT: // re-check FONT to deal with the special case if (!(group == FONT && !(attributes.contains(AttributeName.COLOR) || attributes.contains(AttributeName.FACE) || attributes.contains(AttributeName.SIZE)))) { errHtmlStartTagInForeignContext(name); if (!fragment) { while (!isSpecialParentInForeign(stack[currentPtr])) { pop(); } continue starttagloop; } // else fall thru } // else fall thru default: if ("http://www.w3.org/2000/svg" == currNs) { attributes.adjustForSvg(); if (selfClosing) { appendVoidElementToCurrentMayFosterSVG( elementName, attributes); selfClosing = false; } else { appendToCurrentNodeAndPushElementMayFosterSVG( elementName, attributes); } attributes = null; // CPP break starttagloop; } else { attributes.adjustForMath(); if (selfClosing) { appendVoidElementToCurrentMayFosterMathML( elementName, attributes); selfClosing = false; } else { appendToCurrentNodeAndPushElementMayFosterMathML( elementName, attributes); } attributes = null; // CPP break starttagloop; } } // switch } // foreignObject / annotation-xml } switch (mode) { case IN_TEMPLATE: switch (group) { case COL: popTemplateMode(); pushTemplateMode(IN_COLUMN_GROUP); mode = IN_COLUMN_GROUP; // Reprocess token. continue; case CAPTION: case COLGROUP: case TBODY_OR_THEAD_OR_TFOOT: popTemplateMode(); pushTemplateMode(IN_TABLE); mode = IN_TABLE; // Reprocess token. continue; case TR: popTemplateMode(); pushTemplateMode(IN_TABLE_BODY); mode = IN_TABLE_BODY; // Reprocess token. continue; case TD_OR_TH: popTemplateMode(); pushTemplateMode(IN_ROW); mode = IN_ROW; // Reprocess token. continue; case META: checkMetaCharset(attributes); appendVoidElementToCurrentMayFoster( elementName, attributes); selfClosing = false; attributes = null; // CPP break starttagloop; case TITLE: startTagTitleInHead(elementName, attributes); attributes = null; // CPP break starttagloop; case BASE: case LINK_OR_BASEFONT_OR_BGSOUND: appendVoidElementToCurrentMayFoster( elementName, attributes); selfClosing = false; attributes = null; // CPP break starttagloop; case SCRIPT: startTagScriptInHead(elementName, attributes); attributes = null; // CPP break starttagloop; case NOFRAMES: case STYLE: startTagGenericRawText(elementName, attributes); attributes = null; // CPP break starttagloop; case TEMPLATE: startTagTemplateInHead(elementName, attributes); attributes = null; // CPP break starttagloop; default: popTemplateMode(); pushTemplateMode(IN_BODY); mode = IN_BODY; // Reprocess token. continue; } case IN_ROW: switch (group) { case TD_OR_TH: clearStackBackTo(findLastOrRoot(TreeBuilder.TR)); appendToCurrentNodeAndPushElement( elementName, attributes); mode = IN_CELL; insertMarker(); attributes = null; // CPP break starttagloop; case CAPTION: case COL: case COLGROUP: case TBODY_OR_THEAD_OR_TFOOT: case TR: eltPos = findLastOrRoot(TreeBuilder.TR); if (eltPos == 0) { assert fragment || isTemplateContents(); errNoTableRowToClose(); break starttagloop; } clearStackBackTo(eltPos); pop(); mode = IN_TABLE_BODY; continue; default: // fall through to IN_TABLE } case IN_TABLE_BODY: switch (group) { case TR: clearStackBackTo(findLastInTableScopeOrRootTemplateTbodyTheadTfoot()); appendToCurrentNodeAndPushElement( elementName, attributes); mode = IN_ROW; attributes = null; // CPP break starttagloop; case TD_OR_TH: errStartTagInTableBody(name); clearStackBackTo(findLastInTableScopeOrRootTemplateTbodyTheadTfoot()); appendToCurrentNodeAndPushElement( ElementName.TR, HtmlAttributes.EMPTY_ATTRIBUTES); mode = IN_ROW; continue; case CAPTION: case COL: case COLGROUP: case TBODY_OR_THEAD_OR_TFOOT: eltPos = findLastInTableScopeOrRootTemplateTbodyTheadTfoot(); if (eltPos == 0 || stack[eltPos].getGroup() == TEMPLATE) { assert fragment || isTemplateContents(); errStrayStartTag(name); break starttagloop; } else { clearStackBackTo(eltPos); pop(); mode = IN_TABLE; continue; } default: // fall through to IN_TABLE } case IN_TABLE: intableloop: for (;;) { switch (group) { case CAPTION: clearStackBackTo(findLastOrRoot(TreeBuilder.TABLE)); insertMarker(); appendToCurrentNodeAndPushElement( elementName, attributes); mode = IN_CAPTION; attributes = null; // CPP break starttagloop; case COLGROUP: clearStackBackTo(findLastOrRoot(TreeBuilder.TABLE)); appendToCurrentNodeAndPushElement( elementName, attributes); mode = IN_COLUMN_GROUP; attributes = null; // CPP break starttagloop; case COL: clearStackBackTo(findLastOrRoot(TreeBuilder.TABLE)); appendToCurrentNodeAndPushElement( ElementName.COLGROUP, HtmlAttributes.EMPTY_ATTRIBUTES); mode = IN_COLUMN_GROUP; continue starttagloop; case TBODY_OR_THEAD_OR_TFOOT: clearStackBackTo(findLastOrRoot(TreeBuilder.TABLE)); appendToCurrentNodeAndPushElement( elementName, attributes); mode = IN_TABLE_BODY; attributes = null; // CPP break starttagloop; case TR: case TD_OR_TH: clearStackBackTo(findLastOrRoot(TreeBuilder.TABLE)); appendToCurrentNodeAndPushElement( ElementName.TBODY, HtmlAttributes.EMPTY_ATTRIBUTES); mode = IN_TABLE_BODY; continue starttagloop; case TEMPLATE: // fall through to IN_HEAD break intableloop; case TABLE: errTableSeenWhileTableOpen(); eltPos = findLastInTableScope(name); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { assert fragment || isTemplateContents(); break starttagloop; } generateImpliedEndTags(); if (errorHandler != null && !isCurrent("table")) { errNoCheckUnclosedElementsOnStack(); } while (currentPtr >= eltPos) { pop(); } resetTheInsertionMode(); continue starttagloop; case SCRIPT: // XXX need to manage much more stuff // here if // supporting // document.write() appendToCurrentNodeAndPushElement( elementName, attributes); originalMode = mode; mode = TEXT; tokenizer.setStateAndEndTagExpectation( Tokenizer.SCRIPT_DATA, elementName); attributes = null; // CPP break starttagloop; case STYLE: appendToCurrentNodeAndPushElement( elementName, attributes); originalMode = mode; mode = TEXT; tokenizer.setStateAndEndTagExpectation( Tokenizer.RAWTEXT, elementName); attributes = null; // CPP break starttagloop; case INPUT: errStartTagInTable(name); if (!Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( "hidden", attributes.getValue(AttributeName.TYPE))) { break intableloop; } appendVoidElementToCurrent( name, attributes, formPointer); selfClosing = false; attributes = null; // CPP break starttagloop; case FORM: if (formPointer != null || isTemplateContents()) { errFormWhenFormOpen(); break starttagloop; } else { errStartTagInTable(name); appendVoidFormToCurrent(attributes); attributes = null; // CPP break starttagloop; } default: errStartTagInTable(name); // fall through to IN_BODY break intableloop; } } case IN_CAPTION: switch (group) { case CAPTION: case COL: case COLGROUP: case TBODY_OR_THEAD_OR_TFOOT: case TR: case TD_OR_TH: errStrayStartTag(name); eltPos = findLastInTableScope("caption"); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { break starttagloop; } generateImpliedEndTags(); if (errorHandler != null && currentPtr != eltPos) { errNoCheckUnclosedElementsOnStack(); } while (currentPtr >= eltPos) { pop(); } clearTheListOfActiveFormattingElementsUpToTheLastMarker(); mode = IN_TABLE; continue; default: // fall through to IN_BODY } case IN_CELL: switch (group) { case CAPTION: case COL: case COLGROUP: case TBODY_OR_THEAD_OR_TFOOT: case TR: case TD_OR_TH: eltPos = findLastInTableScopeTdTh(); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { errNoCellToClose(); break starttagloop; } else { closeTheCell(eltPos); continue; } default: // fall through to IN_BODY } case FRAMESET_OK: switch (group) { case FRAMESET: if (mode == FRAMESET_OK) { if (currentPtr == 0 || stack[1].getGroup() != BODY) { assert fragment || isTemplateContents(); errStrayStartTag(name); break starttagloop; } else { errFramesetStart(); detachFromParent(stack[1].node); while (currentPtr > 0) { pop(); } appendToCurrentNodeAndPushElement( elementName, attributes); mode = IN_FRAMESET; attributes = null; // CPP break starttagloop; } } else { errStrayStartTag(name); break starttagloop; } // NOT falling through! case PRE_OR_LISTING: case LI: case DD_OR_DT: case BUTTON: case MARQUEE_OR_APPLET: case OBJECT: case TABLE: case AREA_OR_WBR: case BR: case EMBED: case IMG: case INPUT: case KEYGEN: case HR: case TEXTAREA: case XMP: case IFRAME: case SELECT: if (mode == FRAMESET_OK && !(group == INPUT && Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( "hidden", attributes.getValue(AttributeName.TYPE)))) { framesetOk = false; mode = IN_BODY; } // fall through to IN_BODY default: // fall through to IN_BODY } case IN_BODY: inbodyloop: for (;;) { switch (group) { case HTML: errStrayStartTag(name); if (!fragment && !isTemplateContents()) { addAttributesToHtml(attributes); attributes = null; // CPP } break starttagloop; case BASE: case LINK_OR_BASEFONT_OR_BGSOUND: case META: case STYLE: case SCRIPT: case TITLE: case TEMPLATE: // Fall through to IN_HEAD break inbodyloop; case BODY: if (currentPtr == 0 || stack[1].getGroup() != BODY || isTemplateContents()) { assert fragment || isTemplateContents(); errStrayStartTag(name); break starttagloop; } errFooSeenWhenFooOpen(name); framesetOk = false; if (mode == FRAMESET_OK) { mode = IN_BODY; } if (addAttributesToBody(attributes)) { attributes = null; // CPP } break starttagloop; case P: case DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU: case UL_OR_OL_OR_DL: case ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY: implicitlyCloseP(); appendToCurrentNodeAndPushElementMayFoster( elementName, attributes); attributes = null; // CPP break starttagloop; case H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6: implicitlyCloseP(); if (stack[currentPtr].getGroup() == H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6) { errHeadingWhenHeadingOpen(); pop(); } appendToCurrentNodeAndPushElementMayFoster( elementName, attributes); attributes = null; // CPP break starttagloop; case FIELDSET: implicitlyCloseP(); appendToCurrentNodeAndPushElementMayFoster( elementName, attributes, formPointer); attributes = null; // CPP break starttagloop; case PRE_OR_LISTING: implicitlyCloseP(); appendToCurrentNodeAndPushElementMayFoster( elementName, attributes); needToDropLF = true; attributes = null; // CPP break starttagloop; case FORM: if (formPointer != null && !isTemplateContents()) { errFormWhenFormOpen(); break starttagloop; } else { implicitlyCloseP(); appendToCurrentNodeAndPushFormElementMayFoster(attributes); attributes = null; // CPP break starttagloop; } case LI: case DD_OR_DT: eltPos = currentPtr; for (;;) { StackNode<T> node = stack[eltPos]; // weak // ref if (node.getGroup() == group) { // LI or // DD_OR_DT generateImpliedEndTagsExceptFor(node.name); if (errorHandler != null && eltPos != currentPtr) { errUnclosedElementsImplied(eltPos, name); } while (currentPtr >= eltPos) { pop(); } break; } else if (eltPos == 0 || (node.isSpecial() && (node.ns != "http://www.w3.org/1999/xhtml" || (node.name != "p" && node.name != "address" && node.name != "div")))) { break; } eltPos--; } implicitlyCloseP(); appendToCurrentNodeAndPushElementMayFoster( elementName, attributes); attributes = null; // CPP break starttagloop; case PLAINTEXT: implicitlyCloseP(); appendToCurrentNodeAndPushElementMayFoster( elementName, attributes); tokenizer.setStateAndEndTagExpectation( Tokenizer.PLAINTEXT, elementName); attributes = null; // CPP break starttagloop; case A: int activeAPos = findInListOfActiveFormattingElementsContainsBetweenEndAndLastMarker("a"); if (activeAPos != -1) { errFooSeenWhenFooOpen(name); StackNode<T> activeA = listOfActiveFormattingElements[activeAPos]; activeA.retain(); adoptionAgencyEndTag("a"); removeFromStack(activeA); activeAPos = findInListOfActiveFormattingElements(activeA); if (activeAPos != -1) { removeFromListOfActiveFormattingElements(activeAPos); } activeA.release(); } reconstructTheActiveFormattingElements(); appendToCurrentNodeAndPushFormattingElementMayFoster( elementName, attributes); attributes = null; // CPP break starttagloop; case B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U: case FONT: reconstructTheActiveFormattingElements(); maybeForgetEarlierDuplicateFormattingElement(elementName.name, attributes); appendToCurrentNodeAndPushFormattingElementMayFoster( elementName, attributes); attributes = null; // CPP break starttagloop; case NOBR: reconstructTheActiveFormattingElements(); if (TreeBuilder.NOT_FOUND_ON_STACK != findLastInScope("nobr")) { errFooSeenWhenFooOpen(name); adoptionAgencyEndTag("nobr"); reconstructTheActiveFormattingElements(); } appendToCurrentNodeAndPushFormattingElementMayFoster( elementName, attributes); attributes = null; // CPP break starttagloop; case BUTTON: eltPos = findLastInScope(name); if (eltPos != TreeBuilder.NOT_FOUND_ON_STACK) { errFooSeenWhenFooOpen(name); generateImpliedEndTags(); if (errorHandler != null && !isCurrent(name)) { errUnclosedElementsImplied(eltPos, name); } while (currentPtr >= eltPos) { pop(); } continue starttagloop; } else { reconstructTheActiveFormattingElements(); appendToCurrentNodeAndPushElementMayFoster( elementName, attributes, formPointer); attributes = null; // CPP break starttagloop; } case OBJECT: reconstructTheActiveFormattingElements(); appendToCurrentNodeAndPushElementMayFoster( elementName, attributes, formPointer); insertMarker(); attributes = null; // CPP break starttagloop; case MARQUEE_OR_APPLET: reconstructTheActiveFormattingElements(); appendToCurrentNodeAndPushElementMayFoster( elementName, attributes); insertMarker(); attributes = null; // CPP break starttagloop; case TABLE: // The only quirk. Blame Hixie and // Acid2. if (!quirks) { implicitlyCloseP(); } appendToCurrentNodeAndPushElementMayFoster( elementName, attributes); mode = IN_TABLE; attributes = null; // CPP break starttagloop; case BR: case EMBED: case AREA_OR_WBR: reconstructTheActiveFormattingElements(); // FALL THROUGH to PARAM_OR_SOURCE_OR_TRACK // CPPONLY: case MENUITEM: case PARAM_OR_SOURCE_OR_TRACK: appendVoidElementToCurrentMayFoster( elementName, attributes); selfClosing = false; attributes = null; // CPP break starttagloop; case HR: implicitlyCloseP(); appendVoidElementToCurrentMayFoster( elementName, attributes); selfClosing = false; attributes = null; // CPP break starttagloop; case IMAGE: errImage(); elementName = ElementName.IMG; continue starttagloop; case IMG: case KEYGEN: case INPUT: reconstructTheActiveFormattingElements(); appendVoidElementToCurrentMayFoster( name, attributes, formPointer); selfClosing = false; attributes = null; // CPP break starttagloop; case ISINDEX: errIsindex(); if (formPointer != null && !isTemplateContents()) { break starttagloop; } implicitlyCloseP(); HtmlAttributes formAttrs = new HtmlAttributes(0); int actionIndex = attributes.getIndex(AttributeName.ACTION); if (actionIndex > -1) { formAttrs.addAttribute( AttributeName.ACTION, attributes.getValueNoBoundsCheck(actionIndex) // [NOCPP[ , XmlViolationPolicy.ALLOW // ]NOCPP] ); } appendToCurrentNodeAndPushFormElementMayFoster(formAttrs); appendVoidElementToCurrentMayFoster( ElementName.HR, HtmlAttributes.EMPTY_ATTRIBUTES); appendToCurrentNodeAndPushElementMayFoster( ElementName.LABEL, HtmlAttributes.EMPTY_ATTRIBUTES); int promptIndex = attributes.getIndex(AttributeName.PROMPT); if (promptIndex > -1) { @Auto char[] prompt = Portability.newCharArrayFromString(attributes.getValueNoBoundsCheck(promptIndex)); appendCharacters(stack[currentPtr].node, prompt, 0, prompt.length); } else { appendIsindexPrompt(stack[currentPtr].node); } HtmlAttributes inputAttributes = new HtmlAttributes( 0); inputAttributes.addAttribute( AttributeName.NAME, Portability.newStringFromLiteral("isindex") // [NOCPP[ , XmlViolationPolicy.ALLOW // ]NOCPP] ); for (int i = 0; i < attributes.getLength(); i++) { AttributeName attributeQName = attributes.getAttributeNameNoBoundsCheck(i); if (AttributeName.NAME == attributeQName || AttributeName.PROMPT == attributeQName) { attributes.releaseValue(i); } else if (AttributeName.ACTION != attributeQName) { inputAttributes.addAttribute( attributeQName, attributes.getValueNoBoundsCheck(i) // [NOCPP[ , XmlViolationPolicy.ALLOW // ]NOCPP] ); } } attributes.clearWithoutReleasingContents(); appendVoidElementToCurrentMayFoster( "input", inputAttributes, formPointer); pop(); // label appendVoidElementToCurrentMayFoster( ElementName.HR, HtmlAttributes.EMPTY_ATTRIBUTES); pop(); // form if (!isTemplateContents()) { formPointer = null; } selfClosing = false; // Portability.delete(formAttrs); // Portability.delete(inputAttributes); // Don't delete attributes, they are deleted // later break starttagloop; case TEXTAREA: appendToCurrentNodeAndPushElementMayFoster( elementName, attributes, formPointer); tokenizer.setStateAndEndTagExpectation( Tokenizer.RCDATA, elementName); originalMode = mode; mode = TEXT; needToDropLF = true; attributes = null; // CPP break starttagloop; case XMP: implicitlyCloseP(); reconstructTheActiveFormattingElements(); appendToCurrentNodeAndPushElementMayFoster( elementName, attributes); originalMode = mode; mode = TEXT; tokenizer.setStateAndEndTagExpectation( Tokenizer.RAWTEXT, elementName); attributes = null; // CPP break starttagloop; case NOSCRIPT: if (!scriptingEnabled) { reconstructTheActiveFormattingElements(); appendToCurrentNodeAndPushElementMayFoster( elementName, attributes); attributes = null; // CPP break starttagloop; } else { // fall through } case NOFRAMES: case IFRAME: case NOEMBED: startTagGenericRawText(elementName, attributes); attributes = null; // CPP break starttagloop; case SELECT: reconstructTheActiveFormattingElements(); appendToCurrentNodeAndPushElementMayFoster( elementName, attributes, formPointer); switch (mode) { case IN_TABLE: case IN_CAPTION: case IN_COLUMN_GROUP: case IN_TABLE_BODY: case IN_ROW: case IN_CELL: mode = IN_SELECT_IN_TABLE; break; default: mode = IN_SELECT; break; } attributes = null; // CPP break starttagloop; case OPTGROUP: case OPTION: if (isCurrent("option")) { pop(); } reconstructTheActiveFormattingElements(); appendToCurrentNodeAndPushElementMayFoster( elementName, attributes); attributes = null; // CPP break starttagloop; case RB_OR_RTC: eltPos = findLastInScope("ruby"); if (eltPos != NOT_FOUND_ON_STACK) { generateImpliedEndTags(); } if (eltPos != currentPtr) { if (eltPos == NOT_FOUND_ON_STACK) { errStartTagSeenWithoutRuby(name); } else { errUnclosedChildrenInRuby(); } } appendToCurrentNodeAndPushElementMayFoster( elementName, attributes); attributes = null; // CPP break starttagloop; case RT_OR_RP: eltPos = findLastInScope("ruby"); if (eltPos != NOT_FOUND_ON_STACK) { generateImpliedEndTagsExceptFor("rtc"); } if (eltPos != currentPtr) { if (!isCurrent("rtc")) { if (eltPos == NOT_FOUND_ON_STACK) { errStartTagSeenWithoutRuby(name); } else { errUnclosedChildrenInRuby(); } } } appendToCurrentNodeAndPushElementMayFoster( elementName, attributes); attributes = null; // CPP break starttagloop; case MATH: reconstructTheActiveFormattingElements(); attributes.adjustForMath(); if (selfClosing) { appendVoidElementToCurrentMayFosterMathML( elementName, attributes); selfClosing = false; } else { appendToCurrentNodeAndPushElementMayFosterMathML( elementName, attributes); } attributes = null; // CPP break starttagloop; case SVG: reconstructTheActiveFormattingElements(); attributes.adjustForSvg(); if (selfClosing) { appendVoidElementToCurrentMayFosterSVG( elementName, attributes); selfClosing = false; } else { appendToCurrentNodeAndPushElementMayFosterSVG( elementName, attributes); } attributes = null; // CPP break starttagloop; case CAPTION: case COL: case COLGROUP: case TBODY_OR_THEAD_OR_TFOOT: case TR: case TD_OR_TH: case FRAME: case FRAMESET: case HEAD: errStrayStartTag(name); break starttagloop; case OUTPUT: reconstructTheActiveFormattingElements(); appendToCurrentNodeAndPushElementMayFoster( elementName, attributes, formPointer); attributes = null; // CPP break starttagloop; default: reconstructTheActiveFormattingElements(); appendToCurrentNodeAndPushElementMayFoster( elementName, attributes); attributes = null; // CPP break starttagloop; } } case IN_HEAD: inheadloop: for (;;) { switch (group) { case HTML: errStrayStartTag(name); if (!fragment && !isTemplateContents()) { addAttributesToHtml(attributes); attributes = null; // CPP } break starttagloop; case BASE: case LINK_OR_BASEFONT_OR_BGSOUND: appendVoidElementToCurrentMayFoster( elementName, attributes); selfClosing = false; attributes = null; // CPP break starttagloop; case META: // Fall through to IN_HEAD_NOSCRIPT break inheadloop; case TITLE: startTagTitleInHead(elementName, attributes); attributes = null; // CPP break starttagloop; case NOSCRIPT: if (scriptingEnabled) { appendToCurrentNodeAndPushElement( elementName, attributes); originalMode = mode; mode = TEXT; tokenizer.setStateAndEndTagExpectation( Tokenizer.RAWTEXT, elementName); } else { appendToCurrentNodeAndPushElementMayFoster( elementName, attributes); mode = IN_HEAD_NOSCRIPT; } attributes = null; // CPP break starttagloop; case SCRIPT: startTagScriptInHead(elementName, attributes); attributes = null; // CPP break starttagloop; case STYLE: case NOFRAMES: startTagGenericRawText(elementName, attributes); attributes = null; // CPP break starttagloop; case HEAD: /* Parse error. */ errFooSeenWhenFooOpen(name); /* Ignore the token. */ break starttagloop; case TEMPLATE: startTagTemplateInHead(elementName, attributes); attributes = null; // CPP break starttagloop; default: pop(); mode = AFTER_HEAD; continue starttagloop; } } case IN_HEAD_NOSCRIPT: switch (group) { case HTML: // XXX did Hixie really mean to omit "base" // here? errStrayStartTag(name); if (!fragment && !isTemplateContents()) { addAttributesToHtml(attributes); attributes = null; // CPP } break starttagloop; case LINK_OR_BASEFONT_OR_BGSOUND: appendVoidElementToCurrentMayFoster( elementName, attributes); selfClosing = false; attributes = null; // CPP break starttagloop; case META: checkMetaCharset(attributes); appendVoidElementToCurrentMayFoster( elementName, attributes); selfClosing = false; attributes = null; // CPP break starttagloop; case STYLE: case NOFRAMES: appendToCurrentNodeAndPushElement( elementName, attributes); originalMode = mode; mode = TEXT; tokenizer.setStateAndEndTagExpectation( Tokenizer.RAWTEXT, elementName); attributes = null; // CPP break starttagloop; case HEAD: errFooSeenWhenFooOpen(name); break starttagloop; case NOSCRIPT: errFooSeenWhenFooOpen(name); break starttagloop; default: errBadStartTagInHead(name); pop(); mode = IN_HEAD; continue; } case IN_COLUMN_GROUP: switch (group) { case HTML: errStrayStartTag(name); if (!fragment && !isTemplateContents()) { addAttributesToHtml(attributes); attributes = null; // CPP } break starttagloop; case COL: appendVoidElementToCurrentMayFoster( elementName, attributes); selfClosing = false; attributes = null; // CPP break starttagloop; case TEMPLATE: startTagTemplateInHead(elementName, attributes); attributes = null; // CPP break starttagloop; default: if (currentPtr == 0 || stack[currentPtr].getGroup() == TEMPLATE) { assert fragment || isTemplateContents(); errGarbageInColgroup(); break starttagloop; } pop(); mode = IN_TABLE; continue; } case IN_SELECT_IN_TABLE: switch (group) { case CAPTION: case TBODY_OR_THEAD_OR_TFOOT: case TR: case TD_OR_TH: case TABLE: errStartTagWithSelectOpen(name); eltPos = findLastInTableScope("select"); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { assert fragment; break starttagloop; // http://www.w3.org/Bugs/Public/show_bug.cgi?id=8375 } while (currentPtr >= eltPos) { pop(); } resetTheInsertionMode(); continue; default: // fall through to IN_SELECT } case IN_SELECT: switch (group) { case HTML: errStrayStartTag(name); if (!fragment) { addAttributesToHtml(attributes); attributes = null; // CPP } break starttagloop; case OPTION: if (isCurrent("option")) { pop(); } appendToCurrentNodeAndPushElement( elementName, attributes); attributes = null; // CPP break starttagloop; case OPTGROUP: if (isCurrent("option")) { pop(); } if (isCurrent("optgroup")) { pop(); } appendToCurrentNodeAndPushElement( elementName, attributes); attributes = null; // CPP break starttagloop; case SELECT: errStartSelectWhereEndSelectExpected(); eltPos = findLastInTableScope(name); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { assert fragment; errNoSelectInTableScope(); break starttagloop; } else { while (currentPtr >= eltPos) { pop(); } resetTheInsertionMode(); break starttagloop; } case INPUT: case TEXTAREA: case KEYGEN: errStartTagWithSelectOpen(name); eltPos = findLastInTableScope("select"); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { assert fragment; break starttagloop; } while (currentPtr >= eltPos) { pop(); } resetTheInsertionMode(); continue; case SCRIPT: startTagScriptInHead(elementName, attributes); attributes = null; // CPP break starttagloop; case TEMPLATE: startTagTemplateInHead(elementName, attributes); attributes = null; // CPP break starttagloop; default: errStrayStartTag(name); break starttagloop; } case AFTER_BODY: switch (group) { case HTML: errStrayStartTag(name); if (!fragment && !isTemplateContents()) { addAttributesToHtml(attributes); attributes = null; // CPP } break starttagloop; default: errStrayStartTag(name); mode = framesetOk ? FRAMESET_OK : IN_BODY; continue; } case IN_FRAMESET: switch (group) { case FRAMESET: appendToCurrentNodeAndPushElement( elementName, attributes); attributes = null; // CPP break starttagloop; case FRAME: appendVoidElementToCurrentMayFoster( elementName, attributes); selfClosing = false; attributes = null; // CPP break starttagloop; default: // fall through to AFTER_FRAMESET } case AFTER_FRAMESET: switch (group) { case HTML: errStrayStartTag(name); if (!fragment && !isTemplateContents()) { addAttributesToHtml(attributes); attributes = null; // CPP } break starttagloop; case NOFRAMES: appendToCurrentNodeAndPushElement( elementName, attributes); originalMode = mode; mode = TEXT; tokenizer.setStateAndEndTagExpectation( Tokenizer.RAWTEXT, elementName); attributes = null; // CPP break starttagloop; default: errStrayStartTag(name); break starttagloop; } case INITIAL: /* * Parse error. */ // [NOCPP[ switch (doctypeExpectation) { case AUTO: err("Start tag seen without seeing a doctype first. Expected e.g. \u201C<!DOCTYPE html>\u201D."); break; case HTML: // ]NOCPP] errStartTagWithoutDoctype(); // [NOCPP[ break; case HTML401_STRICT: err("Start tag seen without seeing a doctype first. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u201D."); break; case HTML401_TRANSITIONAL: err("Start tag seen without seeing a doctype first. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\u201D."); break; case NO_DOCTYPE_ERRORS: } // ]NOCPP] /* * * Set the document to quirks mode. */ documentModeInternal(DocumentMode.QUIRKS_MODE, null, null, false); /* * Then, switch to the root element mode of the tree * construction stage */ mode = BEFORE_HTML; /* * and reprocess the current token. */ continue; case BEFORE_HTML: switch (group) { case HTML: // optimize error check and streaming SAX by // hoisting // "html" handling here. if (attributes == HtmlAttributes.EMPTY_ATTRIBUTES) { // This has the right magic side effect // that // it // makes attributes in SAX Tree mutable. appendHtmlElementToDocumentAndPush(); } else { appendHtmlElementToDocumentAndPush(attributes); } // XXX application cache should fire here mode = BEFORE_HEAD; attributes = null; // CPP break starttagloop; default: /* * Create an HTMLElement node with the tag name * html, in the HTML namespace. Append it to the * Document object. */ appendHtmlElementToDocumentAndPush(); /* Switch to the main mode */ mode = BEFORE_HEAD; /* * reprocess the current token. */ continue; } case BEFORE_HEAD: switch (group) { case HTML: errStrayStartTag(name); if (!fragment && !isTemplateContents()) { addAttributesToHtml(attributes); attributes = null; // CPP } break starttagloop; case HEAD: /* * A start tag whose tag name is "head" * * Create an element for the token. * * Set the head element pointer to this new element * node. * * Append the new element to the current node and * push it onto the stack of open elements. */ appendToCurrentNodeAndPushHeadElement(attributes); /* * Change the insertion mode to "in head". */ mode = IN_HEAD; attributes = null; // CPP break starttagloop; default: /* * Any other start tag token * * Act as if a start tag token with the tag name * "head" and no attributes had been seen, */ appendToCurrentNodeAndPushHeadElement(HtmlAttributes.EMPTY_ATTRIBUTES); mode = IN_HEAD; /* * then reprocess the current token. * * This will result in an empty head element being * generated, with the current token being * reprocessed in the "after head" insertion mode. */ continue; } case AFTER_HEAD: switch (group) { case HTML: errStrayStartTag(name); if (!fragment && !isTemplateContents()) { addAttributesToHtml(attributes); attributes = null; // CPP } break starttagloop; case BODY: if (attributes.getLength() == 0) { // This has the right magic side effect // that // it // makes attributes in SAX Tree mutable. appendToCurrentNodeAndPushBodyElement(); } else { appendToCurrentNodeAndPushBodyElement(attributes); } framesetOk = false; mode = IN_BODY; attributes = null; // CPP break starttagloop; case FRAMESET: appendToCurrentNodeAndPushElement( elementName, attributes); mode = IN_FRAMESET; attributes = null; // CPP break starttagloop; case TEMPLATE: errFooBetweenHeadAndBody(name); pushHeadPointerOntoStack(); StackNode<T> headOnStack = stack[currentPtr]; startTagTemplateInHead(elementName, attributes); removeFromStack(headOnStack); attributes = null; // CPP break starttagloop; case BASE: case LINK_OR_BASEFONT_OR_BGSOUND: errFooBetweenHeadAndBody(name); pushHeadPointerOntoStack(); appendVoidElementToCurrentMayFoster( elementName, attributes); selfClosing = false; pop(); // head attributes = null; // CPP break starttagloop; case META: errFooBetweenHeadAndBody(name); checkMetaCharset(attributes); pushHeadPointerOntoStack(); appendVoidElementToCurrentMayFoster( elementName, attributes); selfClosing = false; pop(); // head attributes = null; // CPP break starttagloop; case SCRIPT: errFooBetweenHeadAndBody(name); pushHeadPointerOntoStack(); appendToCurrentNodeAndPushElement( elementName, attributes); originalMode = mode; mode = TEXT; tokenizer.setStateAndEndTagExpectation( Tokenizer.SCRIPT_DATA, elementName); attributes = null; // CPP break starttagloop; case STYLE: case NOFRAMES: errFooBetweenHeadAndBody(name); pushHeadPointerOntoStack(); appendToCurrentNodeAndPushElement( elementName, attributes); originalMode = mode; mode = TEXT; tokenizer.setStateAndEndTagExpectation( Tokenizer.RAWTEXT, elementName); attributes = null; // CPP break starttagloop; case TITLE: errFooBetweenHeadAndBody(name); pushHeadPointerOntoStack(); appendToCurrentNodeAndPushElement( elementName, attributes); originalMode = mode; mode = TEXT; tokenizer.setStateAndEndTagExpectation( Tokenizer.RCDATA, elementName); attributes = null; // CPP break starttagloop; case HEAD: errStrayStartTag(name); break starttagloop; default: appendToCurrentNodeAndPushBodyElement(); mode = FRAMESET_OK; continue; } case AFTER_AFTER_BODY: switch (group) { case HTML: errStrayStartTag(name); if (!fragment && !isTemplateContents()) { addAttributesToHtml(attributes); attributes = null; // CPP } break starttagloop; default: errStrayStartTag(name); fatal(); mode = framesetOk ? FRAMESET_OK : IN_BODY; continue; } case AFTER_AFTER_FRAMESET: switch (group) { case HTML: errStrayStartTag(name); if (!fragment && !isTemplateContents()) { addAttributesToHtml(attributes); attributes = null; // CPP } break starttagloop; case NOFRAMES: startTagGenericRawText(elementName, attributes); attributes = null; // CPP break starttagloop; default: errStrayStartTag(name); break starttagloop; } case TEXT: assert false; break starttagloop; // Avoid infinite loop if the assertion // fails } } if (selfClosing) { errSelfClosing(); } // CPPONLY: if (mBuilder == null && attributes != HtmlAttributes.EMPTY_ATTRIBUTES) { // CPPONLY: Portability.delete(attributes); // CPPONLY: } } private void startTagTitleInHead(ElementName elementName, HtmlAttributes attributes) throws SAXException { appendToCurrentNodeAndPushElementMayFoster(elementName, attributes); originalMode = mode; mode = TEXT; tokenizer.setStateAndEndTagExpectation(Tokenizer.RCDATA, elementName); } private void startTagGenericRawText(ElementName elementName, HtmlAttributes attributes) throws SAXException { appendToCurrentNodeAndPushElementMayFoster(elementName, attributes); originalMode = mode; mode = TEXT; tokenizer.setStateAndEndTagExpectation(Tokenizer.RAWTEXT, elementName); } private void startTagScriptInHead(ElementName elementName, HtmlAttributes attributes) throws SAXException { // XXX need to manage much more stuff here if supporting document.write() appendToCurrentNodeAndPushElementMayFoster(elementName, attributes); originalMode = mode; mode = TEXT; tokenizer.setStateAndEndTagExpectation(Tokenizer.SCRIPT_DATA, elementName); } private void startTagTemplateInHead(ElementName elementName, HtmlAttributes attributes) throws SAXException { appendToCurrentNodeAndPushElement(elementName, attributes); insertMarker(); framesetOk = false; originalMode = mode; mode = IN_TEMPLATE; pushTemplateMode(IN_TEMPLATE); } private boolean isTemplateContents() { return TreeBuilder.NOT_FOUND_ON_STACK != findLast("template"); } private boolean isTemplateModeStackEmpty() { return templateModePtr == -1; } private boolean isSpecialParentInForeign(StackNode<T> stackNode) { @NsUri String ns = stackNode.ns; return ("http://www.w3.org/1999/xhtml" == ns) || (stackNode.isHtmlIntegrationPoint()) || (("http://www.w3.org/1998/Math/MathML" == ns) && (stackNode.getGroup() == MI_MO_MN_MS_MTEXT)); } /** * * <p> * C++ memory note: The return value must be released. * * @return * @throws SAXException * @throws StopSniffingException */ public static String extractCharsetFromContent(String attributeValue // CPPONLY: , TreeBuilder tb ) { // This is a bit ugly. Converting the string to char array in order to // make the portability layer smaller. int charsetState = CHARSET_INITIAL; int start = -1; int end = -1; @Auto char[] buffer = Portability.newCharArrayFromString(attributeValue); charsetloop: for (int i = 0; i < buffer.length; i++) { char c = buffer[i]; switch (charsetState) { case CHARSET_INITIAL: switch (c) { case 'c': case 'C': charsetState = CHARSET_C; continue; default: continue; } case CHARSET_C: switch (c) { case 'h': case 'H': charsetState = CHARSET_H; continue; default: charsetState = CHARSET_INITIAL; continue; } case CHARSET_H: switch (c) { case 'a': case 'A': charsetState = CHARSET_A; continue; default: charsetState = CHARSET_INITIAL; continue; } case CHARSET_A: switch (c) { case 'r': case 'R': charsetState = CHARSET_R; continue; default: charsetState = CHARSET_INITIAL; continue; } case CHARSET_R: switch (c) { case 's': case 'S': charsetState = CHARSET_S; continue; default: charsetState = CHARSET_INITIAL; continue; } case CHARSET_S: switch (c) { case 'e': case 'E': charsetState = CHARSET_E; continue; default: charsetState = CHARSET_INITIAL; continue; } case CHARSET_E: switch (c) { case 't': case 'T': charsetState = CHARSET_T; continue; default: charsetState = CHARSET_INITIAL; continue; } case CHARSET_T: switch (c) { case '\t': case '\n': case '\u000C': case '\r': case ' ': continue; case '=': charsetState = CHARSET_EQUALS; continue; default: return null; } case CHARSET_EQUALS: switch (c) { case '\t': case '\n': case '\u000C': case '\r': case ' ': continue; case '\'': start = i + 1; charsetState = CHARSET_SINGLE_QUOTED; continue; case '\"': start = i + 1; charsetState = CHARSET_DOUBLE_QUOTED; continue; default: start = i; charsetState = CHARSET_UNQUOTED; continue; } case CHARSET_SINGLE_QUOTED: switch (c) { case '\'': end = i; break charsetloop; default: continue; } case CHARSET_DOUBLE_QUOTED: switch (c) { case '\"': end = i; break charsetloop; default: continue; } case CHARSET_UNQUOTED: switch (c) { case '\t': case '\n': case '\u000C': case '\r': case ' ': case ';': end = i; break charsetloop; default: continue; } } } String charset = null; if (start != -1) { if (end == -1) { end = buffer.length; } charset = Portability.newStringFromBuffer(buffer, start, end - start // CPPONLY: , tb ); } return charset; } private void checkMetaCharset(HtmlAttributes attributes) throws SAXException { String charset = attributes.getValue(AttributeName.CHARSET); if (charset != null) { if (tokenizer.internalEncodingDeclaration(charset)) { requestSuspension(); return; } return; } if (!Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( "content-type", attributes.getValue(AttributeName.HTTP_EQUIV))) { return; } String content = attributes.getValue(AttributeName.CONTENT); if (content != null) { String extract = TreeBuilder.extractCharsetFromContent(content // CPPONLY: , this ); // remember not to return early without releasing the string if (extract != null) { if (tokenizer.internalEncodingDeclaration(extract)) { requestSuspension(); } } Portability.releaseString(extract); } } public final void endTag(ElementName elementName) throws SAXException { flushCharacters(); needToDropLF = false; int eltPos; int group = elementName.getGroup(); @Local String name = elementName.name; endtagloop: for (;;) { if (isInForeign()) { if (stack[currentPtr].name != name) { if (currentPtr == 0) { errStrayEndTag(name); } else { errEndTagDidNotMatchCurrentOpenElement(name, stack[currentPtr].popName); } } eltPos = currentPtr; for (;;) { if (eltPos == 0) { assert fragment: "We can get this close to the root of the stack in foreign content only in the fragment case."; break endtagloop; } if (stack[eltPos].name == name) { while (currentPtr >= eltPos) { pop(); } break endtagloop; } if (stack[--eltPos].ns == "http://www.w3.org/1999/xhtml") { break; } } } switch (mode) { case IN_TEMPLATE: switch (group) { case TEMPLATE: // fall through to IN_HEAD break; default: errStrayEndTag(name); break endtagloop; } case IN_ROW: switch (group) { case TR: eltPos = findLastOrRoot(TreeBuilder.TR); if (eltPos == 0) { assert fragment || isTemplateContents(); errNoTableRowToClose(); break endtagloop; } clearStackBackTo(eltPos); pop(); mode = IN_TABLE_BODY; break endtagloop; case TABLE: eltPos = findLastOrRoot(TreeBuilder.TR); if (eltPos == 0) { assert fragment || isTemplateContents(); errNoTableRowToClose(); break endtagloop; } clearStackBackTo(eltPos); pop(); mode = IN_TABLE_BODY; continue; case TBODY_OR_THEAD_OR_TFOOT: if (findLastInTableScope(name) == TreeBuilder.NOT_FOUND_ON_STACK) { errStrayEndTag(name); break endtagloop; } eltPos = findLastOrRoot(TreeBuilder.TR); if (eltPos == 0) { assert fragment || isTemplateContents(); errNoTableRowToClose(); break endtagloop; } clearStackBackTo(eltPos); pop(); mode = IN_TABLE_BODY; continue; case BODY: case CAPTION: case COL: case COLGROUP: case HTML: case TD_OR_TH: errStrayEndTag(name); break endtagloop; default: // fall through to IN_TABLE } case IN_TABLE_BODY: switch (group) { case TBODY_OR_THEAD_OR_TFOOT: eltPos = findLastOrRoot(name); if (eltPos == 0) { errStrayEndTag(name); break endtagloop; } clearStackBackTo(eltPos); pop(); mode = IN_TABLE; break endtagloop; case TABLE: eltPos = findLastInTableScopeOrRootTemplateTbodyTheadTfoot(); if (eltPos == 0 || stack[eltPos].getGroup() == TEMPLATE) { assert fragment || isTemplateContents(); errStrayEndTag(name); break endtagloop; } clearStackBackTo(eltPos); pop(); mode = IN_TABLE; continue; case BODY: case CAPTION: case COL: case COLGROUP: case HTML: case TD_OR_TH: case TR: errStrayEndTag(name); break endtagloop; default: // fall through to IN_TABLE } case IN_TABLE: switch (group) { case TABLE: eltPos = findLast("table"); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { assert fragment || isTemplateContents(); errStrayEndTag(name); break endtagloop; } while (currentPtr >= eltPos) { pop(); } resetTheInsertionMode(); break endtagloop; case BODY: case CAPTION: case COL: case COLGROUP: case HTML: case TBODY_OR_THEAD_OR_TFOOT: case TD_OR_TH: case TR: errStrayEndTag(name); break endtagloop; case TEMPLATE: // fall through to IN_HEAD break; default: errStrayEndTag(name); // fall through to IN_BODY } case IN_CAPTION: switch (group) { case CAPTION: eltPos = findLastInTableScope("caption"); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { break endtagloop; } generateImpliedEndTags(); if (errorHandler != null && currentPtr != eltPos) { errUnclosedElements(eltPos, name); } while (currentPtr >= eltPos) { pop(); } clearTheListOfActiveFormattingElementsUpToTheLastMarker(); mode = IN_TABLE; break endtagloop; case TABLE: errTableClosedWhileCaptionOpen(); eltPos = findLastInTableScope("caption"); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { break endtagloop; } generateImpliedEndTags(); if (errorHandler != null && currentPtr != eltPos) { errUnclosedElements(eltPos, name); } while (currentPtr >= eltPos) { pop(); } clearTheListOfActiveFormattingElementsUpToTheLastMarker(); mode = IN_TABLE; continue; case BODY: case COL: case COLGROUP: case HTML: case TBODY_OR_THEAD_OR_TFOOT: case TD_OR_TH: case TR: errStrayEndTag(name); break endtagloop; default: // fall through to IN_BODY } case IN_CELL: switch (group) { case TD_OR_TH: eltPos = findLastInTableScope(name); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { errStrayEndTag(name); break endtagloop; } generateImpliedEndTags(); if (errorHandler != null && !isCurrent(name)) { errUnclosedElements(eltPos, name); } while (currentPtr >= eltPos) { pop(); } clearTheListOfActiveFormattingElementsUpToTheLastMarker(); mode = IN_ROW; break endtagloop; case TABLE: case TBODY_OR_THEAD_OR_TFOOT: case TR: if (findLastInTableScope(name) == TreeBuilder.NOT_FOUND_ON_STACK) { assert name == "tbody" || name == "tfoot" || name == "thead" || fragment || isTemplateContents(); errStrayEndTag(name); break endtagloop; } closeTheCell(findLastInTableScopeTdTh()); continue; case BODY: case CAPTION: case COL: case COLGROUP: case HTML: errStrayEndTag(name); break endtagloop; default: // fall through to IN_BODY } case FRAMESET_OK: case IN_BODY: switch (group) { case BODY: if (!isSecondOnStackBody()) { assert fragment || isTemplateContents(); errStrayEndTag(name); break endtagloop; } assert currentPtr >= 1; if (errorHandler != null) { uncloseloop1: for (int i = 2; i <= currentPtr; i++) { switch (stack[i].getGroup()) { case DD_OR_DT: case LI: case OPTGROUP: case OPTION: // is this possible? case P: case RB_OR_RTC: case RT_OR_RP: case TD_OR_TH: case TBODY_OR_THEAD_OR_TFOOT: break; default: errEndWithUnclosedElements(name); break uncloseloop1; } } } mode = AFTER_BODY; break endtagloop; case HTML: if (!isSecondOnStackBody()) { assert fragment || isTemplateContents(); errStrayEndTag(name); break endtagloop; } if (errorHandler != null) { uncloseloop2: for (int i = 0; i <= currentPtr; i++) { switch (stack[i].getGroup()) { case DD_OR_DT: case LI: case P: case RB_OR_RTC: case RT_OR_RP: case TBODY_OR_THEAD_OR_TFOOT: case TD_OR_TH: case BODY: case HTML: break; default: errEndWithUnclosedElements(name); break uncloseloop2; } } } mode = AFTER_BODY; continue; case DIV_OR_BLOCKQUOTE_OR_CENTER_OR_MENU: case UL_OR_OL_OR_DL: case PRE_OR_LISTING: case FIELDSET: case BUTTON: case ADDRESS_OR_ARTICLE_OR_ASIDE_OR_DETAILS_OR_DIR_OR_FIGCAPTION_OR_FIGURE_OR_FOOTER_OR_HEADER_OR_HGROUP_OR_MAIN_OR_NAV_OR_SECTION_OR_SUMMARY: eltPos = findLastInScope(name); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { errStrayEndTag(name); } else { generateImpliedEndTags(); if (errorHandler != null && !isCurrent(name)) { errUnclosedElements(eltPos, name); } while (currentPtr >= eltPos) { pop(); } } break endtagloop; case FORM: if (!isTemplateContents()) { if (formPointer == null) { errStrayEndTag(name); break endtagloop; } formPointer = null; eltPos = findLastInScope(name); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { errStrayEndTag(name); break endtagloop; } generateImpliedEndTags(); if (errorHandler != null && !isCurrent(name)) { errUnclosedElements(eltPos, name); } removeFromStack(eltPos); break endtagloop; } else { eltPos = findLastInScope(name); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { errStrayEndTag(name); break endtagloop; } generateImpliedEndTags(); if (errorHandler != null && !isCurrent(name)) { errUnclosedElements(eltPos, name); } while (currentPtr >= eltPos) { pop(); } break endtagloop; } case P: eltPos = findLastInButtonScope("p"); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { errNoElementToCloseButEndTagSeen("p"); // XXX Can the 'in foreign' case happen anymore? if (isInForeign()) { errHtmlStartTagInForeignContext(name); // Check for currentPtr for the fragment // case. while (currentPtr >= 0 && stack[currentPtr].ns != "http://www.w3.org/1999/xhtml") { pop(); } } appendVoidElementToCurrentMayFoster( elementName, HtmlAttributes.EMPTY_ATTRIBUTES); break endtagloop; } generateImpliedEndTagsExceptFor("p"); assert eltPos != TreeBuilder.NOT_FOUND_ON_STACK; if (errorHandler != null && eltPos != currentPtr) { errUnclosedElements(eltPos, name); } while (currentPtr >= eltPos) { pop(); } break endtagloop; case LI: eltPos = findLastInListScope(name); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { errNoElementToCloseButEndTagSeen(name); } else { generateImpliedEndTagsExceptFor(name); if (errorHandler != null && eltPos != currentPtr) { errUnclosedElements(eltPos, name); } while (currentPtr >= eltPos) { pop(); } } break endtagloop; case DD_OR_DT: eltPos = findLastInScope(name); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { errNoElementToCloseButEndTagSeen(name); } else { generateImpliedEndTagsExceptFor(name); if (errorHandler != null && eltPos != currentPtr) { errUnclosedElements(eltPos, name); } while (currentPtr >= eltPos) { pop(); } } break endtagloop; case H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6: eltPos = findLastInScopeHn(); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { errStrayEndTag(name); } else { generateImpliedEndTags(); if (errorHandler != null && !isCurrent(name)) { errUnclosedElements(eltPos, name); } while (currentPtr >= eltPos) { pop(); } } break endtagloop; case OBJECT: case MARQUEE_OR_APPLET: eltPos = findLastInScope(name); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { errStrayEndTag(name); } else { generateImpliedEndTags(); if (errorHandler != null && !isCurrent(name)) { errUnclosedElements(eltPos, name); } while (currentPtr >= eltPos) { pop(); } clearTheListOfActiveFormattingElementsUpToTheLastMarker(); } break endtagloop; case BR: errEndTagBr(); if (isInForeign()) { // XXX can this happen anymore? errHtmlStartTagInForeignContext(name); // Check for currentPtr for the fragment // case. while (currentPtr >= 0 && stack[currentPtr].ns != "http://www.w3.org/1999/xhtml") { pop(); } } reconstructTheActiveFormattingElements(); appendVoidElementToCurrentMayFoster( elementName, HtmlAttributes.EMPTY_ATTRIBUTES); break endtagloop; case TEMPLATE: // fall through to IN_HEAD; break; case AREA_OR_WBR: // CPPONLY: case MENUITEM: case PARAM_OR_SOURCE_OR_TRACK: case EMBED: case IMG: case IMAGE: case INPUT: case KEYGEN: // XXX?? case HR: case ISINDEX: case IFRAME: case NOEMBED: // XXX??? case NOFRAMES: // XXX?? case SELECT: case TABLE: case TEXTAREA: // XXX?? errStrayEndTag(name); break endtagloop; case NOSCRIPT: if (scriptingEnabled) { errStrayEndTag(name); break endtagloop; } else { // fall through } case A: case B_OR_BIG_OR_CODE_OR_EM_OR_I_OR_S_OR_SMALL_OR_STRIKE_OR_STRONG_OR_TT_OR_U: case FONT: case NOBR: if (adoptionAgencyEndTag(name)) { break endtagloop; } // else handle like any other tag default: if (isCurrent(name)) { pop(); break endtagloop; } eltPos = currentPtr; for (;;) { StackNode<T> node = stack[eltPos]; if (node.ns == "http://www.w3.org/1999/xhtml" && node.name == name) { generateImpliedEndTags(); if (errorHandler != null && !isCurrent(name)) { errUnclosedElements(eltPos, name); } while (currentPtr >= eltPos) { pop(); } break endtagloop; } else if (eltPos == 0 || node.isSpecial()) { errStrayEndTag(name); break endtagloop; } eltPos--; } } case IN_HEAD: switch (group) { case HEAD: pop(); mode = AFTER_HEAD; break endtagloop; case BR: case HTML: case BODY: pop(); mode = AFTER_HEAD; continue; case TEMPLATE: endTagTemplateInHead(); break endtagloop; default: errStrayEndTag(name); break endtagloop; } case IN_HEAD_NOSCRIPT: switch (group) { case NOSCRIPT: pop(); mode = IN_HEAD; break endtagloop; case BR: errStrayEndTag(name); pop(); mode = IN_HEAD; continue; default: errStrayEndTag(name); break endtagloop; } case IN_COLUMN_GROUP: switch (group) { case COLGROUP: if (currentPtr == 0 || stack[currentPtr].getGroup() == TreeBuilder.TEMPLATE) { assert fragment || isTemplateContents(); errGarbageInColgroup(); break endtagloop; } pop(); mode = IN_TABLE; break endtagloop; case COL: errStrayEndTag(name); break endtagloop; case TEMPLATE: endTagTemplateInHead(); break endtagloop; default: if (currentPtr == 0 || stack[currentPtr].getGroup() == TreeBuilder.TEMPLATE) { assert fragment || isTemplateContents(); errGarbageInColgroup(); break endtagloop; } pop(); mode = IN_TABLE; continue; } case IN_SELECT_IN_TABLE: switch (group) { case CAPTION: case TABLE: case TBODY_OR_THEAD_OR_TFOOT: case TR: case TD_OR_TH: errEndTagSeenWithSelectOpen(name); if (findLastInTableScope(name) != TreeBuilder.NOT_FOUND_ON_STACK) { eltPos = findLastInTableScope("select"); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { assert fragment; break endtagloop; // http://www.w3.org/Bugs/Public/show_bug.cgi?id=8375 } while (currentPtr >= eltPos) { pop(); } resetTheInsertionMode(); continue; } else { break endtagloop; } default: // fall through to IN_SELECT } case IN_SELECT: switch (group) { case OPTION: if (isCurrent("option")) { pop(); break endtagloop; } else { errStrayEndTag(name); break endtagloop; } case OPTGROUP: if (isCurrent("option") && "optgroup" == stack[currentPtr - 1].name) { pop(); } if (isCurrent("optgroup")) { pop(); } else { errStrayEndTag(name); } break endtagloop; case SELECT: eltPos = findLastInTableScope("select"); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { assert fragment; errStrayEndTag(name); break endtagloop; } while (currentPtr >= eltPos) { pop(); } resetTheInsertionMode(); break endtagloop; case TEMPLATE: endTagTemplateInHead(); break endtagloop; default: errStrayEndTag(name); break endtagloop; } case AFTER_BODY: switch (group) { case HTML: if (fragment) { errStrayEndTag(name); break endtagloop; } else { mode = AFTER_AFTER_BODY; break endtagloop; } default: errEndTagAfterBody(); mode = framesetOk ? FRAMESET_OK : IN_BODY; continue; } case IN_FRAMESET: switch (group) { case FRAMESET: if (currentPtr == 0) { assert fragment; errStrayEndTag(name); break endtagloop; } pop(); if ((!fragment) && !isCurrent("frameset")) { mode = AFTER_FRAMESET; } break endtagloop; default: errStrayEndTag(name); break endtagloop; } case AFTER_FRAMESET: switch (group) { case HTML: mode = AFTER_AFTER_FRAMESET; break endtagloop; default: errStrayEndTag(name); break endtagloop; } case INITIAL: /* * Parse error. */ // [NOCPP[ switch (doctypeExpectation) { case AUTO: err("End tag seen without seeing a doctype first. Expected e.g. \u201C<!DOCTYPE html>\u201D."); break; case HTML: // ]NOCPP] errEndTagSeenWithoutDoctype(); // [NOCPP[ break; case HTML401_STRICT: err("End tag seen without seeing a doctype first. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u201D."); break; case HTML401_TRANSITIONAL: err("End tag seen without seeing a doctype first. Expected \u201C<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\u201D."); break; case NO_DOCTYPE_ERRORS: } // ]NOCPP] /* * * Set the document to quirks mode. */ documentModeInternal(DocumentMode.QUIRKS_MODE, null, null, false); /* * Then, switch to the root element mode of the tree * construction stage */ mode = BEFORE_HTML; /* * and reprocess the current token. */ continue; case BEFORE_HTML: switch (group) { case HEAD: case BR: case HTML: case BODY: /* * Create an HTMLElement node with the tag name * html, in the HTML namespace. Append it to the * Document object. */ appendHtmlElementToDocumentAndPush(); /* Switch to the main mode */ mode = BEFORE_HEAD; /* * reprocess the current token. */ continue; default: errStrayEndTag(name); break endtagloop; } case BEFORE_HEAD: switch (group) { case HEAD: case BR: case HTML: case BODY: appendToCurrentNodeAndPushHeadElement(HtmlAttributes.EMPTY_ATTRIBUTES); mode = IN_HEAD; continue; default: errStrayEndTag(name); break endtagloop; } case AFTER_HEAD: switch (group) { case TEMPLATE: endTagTemplateInHead(); break endtagloop; case HTML: case BODY: case BR: appendToCurrentNodeAndPushBodyElement(); mode = FRAMESET_OK; continue; default: errStrayEndTag(name); break endtagloop; } case AFTER_AFTER_BODY: errStrayEndTag(name); mode = framesetOk ? FRAMESET_OK : IN_BODY; continue; case AFTER_AFTER_FRAMESET: errStrayEndTag(name); break endtagloop; case TEXT: // XXX need to manage insertion point here pop(); if (originalMode == AFTER_HEAD) { silentPop(); } mode = originalMode; break endtagloop; } } // endtagloop } private void endTagTemplateInHead() throws SAXException { int eltPos = findLast("template"); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { errStrayEndTag("template"); return; } generateImpliedEndTags(); if (errorHandler != null && !isCurrent("template")) { errUnclosedElements(eltPos, "template"); } while (currentPtr >= eltPos) { pop(); } clearTheListOfActiveFormattingElementsUpToTheLastMarker(); popTemplateMode(); resetTheInsertionMode(); } private int findLastInTableScopeOrRootTemplateTbodyTheadTfoot() { for (int i = currentPtr; i > 0; i--) { if (stack[i].getGroup() == TreeBuilder.TBODY_OR_THEAD_OR_TFOOT || stack[i].getGroup() == TreeBuilder.TEMPLATE) { return i; } } return 0; } private int findLast(@Local String name) { for (int i = currentPtr; i > 0; i--) { if (stack[i].ns == "http://www.w3.org/1999/xhtml" && stack[i].name == name) { return i; } } return TreeBuilder.NOT_FOUND_ON_STACK; } private int findLastInTableScope(@Local String name) { for (int i = currentPtr; i > 0; i--) { if (stack[i].ns == "http://www.w3.org/1999/xhtml") { if (stack[i].name == name) { return i; } else if (stack[i].name == "table" || stack[i].name == "template") { return TreeBuilder.NOT_FOUND_ON_STACK; } } } return TreeBuilder.NOT_FOUND_ON_STACK; } private int findLastInButtonScope(@Local String name) { for (int i = currentPtr; i > 0; i--) { if (stack[i].ns == "http://www.w3.org/1999/xhtml") { if (stack[i].name == name) { return i; } else if (stack[i].name == "button") { return TreeBuilder.NOT_FOUND_ON_STACK; } } if (stack[i].isScoping()) { return TreeBuilder.NOT_FOUND_ON_STACK; } } return TreeBuilder.NOT_FOUND_ON_STACK; } private int findLastInScope(@Local String name) { for (int i = currentPtr; i > 0; i--) { if (stack[i].ns == "http://www.w3.org/1999/xhtml" && stack[i].name == name) { return i; } else if (stack[i].isScoping()) { return TreeBuilder.NOT_FOUND_ON_STACK; } } return TreeBuilder.NOT_FOUND_ON_STACK; } private int findLastInListScope(@Local String name) { for (int i = currentPtr; i > 0; i--) { if (stack[i].ns == "http://www.w3.org/1999/xhtml") { if (stack[i].name == name) { return i; } else if (stack[i].name == "ul" || stack[i].name == "ol") { return TreeBuilder.NOT_FOUND_ON_STACK; } } if (stack[i].isScoping()) { return TreeBuilder.NOT_FOUND_ON_STACK; } } return TreeBuilder.NOT_FOUND_ON_STACK; } private int findLastInScopeHn() { for (int i = currentPtr; i > 0; i--) { if (stack[i].getGroup() == TreeBuilder.H1_OR_H2_OR_H3_OR_H4_OR_H5_OR_H6) { return i; } else if (stack[i].isScoping()) { return TreeBuilder.NOT_FOUND_ON_STACK; } } return TreeBuilder.NOT_FOUND_ON_STACK; } private void generateImpliedEndTagsExceptFor(@Local String name) throws SAXException { for (;;) { StackNode<T> node = stack[currentPtr]; switch (node.getGroup()) { case P: case LI: case DD_OR_DT: case OPTION: case OPTGROUP: case RB_OR_RTC: case RT_OR_RP: if (node.ns == "http://www.w3.org/1999/xhtml" && node.name == name) { return; } pop(); continue; default: return; } } } private void generateImpliedEndTags() throws SAXException { for (;;) { switch (stack[currentPtr].getGroup()) { case P: case LI: case DD_OR_DT: case OPTION: case OPTGROUP: case RB_OR_RTC: case RT_OR_RP: pop(); continue; default: return; } } } private boolean isSecondOnStackBody() { return currentPtr >= 1 && stack[1].getGroup() == TreeBuilder.BODY; } private void documentModeInternal(DocumentMode m, String publicIdentifier, String systemIdentifier, boolean html4SpecificAdditionalErrorChecks) throws SAXException { if (isSrcdocDocument) { // Srcdoc documents are always rendered in standards mode. quirks = false; if (documentModeHandler != null) { documentModeHandler.documentMode( DocumentMode.STANDARDS_MODE // [NOCPP[ , null, null, false // ]NOCPP] ); } return; } quirks = (m == DocumentMode.QUIRKS_MODE); if (documentModeHandler != null) { documentModeHandler.documentMode( m // [NOCPP[ , publicIdentifier, systemIdentifier, html4SpecificAdditionalErrorChecks // ]NOCPP] ); } // [NOCPP[ documentMode(m, publicIdentifier, systemIdentifier, html4SpecificAdditionalErrorChecks); // ]NOCPP] } private boolean isAlmostStandards(String publicIdentifier, String systemIdentifier) { if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( "-//w3c//dtd xhtml 1.0 transitional//en", publicIdentifier)) { return true; } if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( "-//w3c//dtd xhtml 1.0 frameset//en", publicIdentifier)) { return true; } if (systemIdentifier != null) { if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( "-//w3c//dtd html 4.01 transitional//en", publicIdentifier)) { return true; } if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( "-//w3c//dtd html 4.01 frameset//en", publicIdentifier)) { return true; } } return false; } private boolean isQuirky(@Local String name, String publicIdentifier, String systemIdentifier, boolean forceQuirks) { if (forceQuirks) { return true; } if (name != HTML_LOCAL) { return true; } if (publicIdentifier != null) { for (int i = 0; i < TreeBuilder.QUIRKY_PUBLIC_IDS.length; i++) { if (Portability.lowerCaseLiteralIsPrefixOfIgnoreAsciiCaseString( TreeBuilder.QUIRKY_PUBLIC_IDS[i], publicIdentifier)) { return true; } } if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( "-//w3o//dtd w3 html strict 3.0//en//", publicIdentifier) || Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( "-/w3c/dtd html 4.0 transitional/en", publicIdentifier) || Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( "html", publicIdentifier)) { return true; } } if (systemIdentifier == null) { if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( "-//w3c//dtd html 4.01 transitional//en", publicIdentifier)) { return true; } else if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( "-//w3c//dtd html 4.01 frameset//en", publicIdentifier)) { return true; } } else if (Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd", systemIdentifier)) { return true; } return false; } private void closeTheCell(int eltPos) throws SAXException { generateImpliedEndTags(); if (errorHandler != null && eltPos != currentPtr) { errUnclosedElementsCell(eltPos); } while (currentPtr >= eltPos) { pop(); } clearTheListOfActiveFormattingElementsUpToTheLastMarker(); mode = IN_ROW; return; } private int findLastInTableScopeTdTh() { for (int i = currentPtr; i > 0; i--) { @Local String name = stack[i].name; if (stack[i].ns == "http://www.w3.org/1999/xhtml") { if ("td" == name || "th" == name) { return i; } else if (name == "table" || name == "template") { return TreeBuilder.NOT_FOUND_ON_STACK; } } } return TreeBuilder.NOT_FOUND_ON_STACK; } private void clearStackBackTo(int eltPos) throws SAXException { int eltGroup = stack[eltPos].getGroup(); while (currentPtr > eltPos) { // > not >= intentional if (stack[currentPtr].ns == "http://www.w3.org/1999/xhtml" && stack[currentPtr].getGroup() == TEMPLATE && (eltGroup == TABLE || eltGroup == TBODY_OR_THEAD_OR_TFOOT|| eltGroup == TR || eltPos == 0)) { return; } pop(); } } private void resetTheInsertionMode() { StackNode<T> node; @Local String name; @NsUri String ns; for (int i = currentPtr; i >= 0; i--) { node = stack[i]; name = node.name; ns = node.ns; if (i == 0) { if (!(contextNamespace == "http://www.w3.org/1999/xhtml" && (contextName == "td" || contextName == "th"))) { if (fragment) { // Make sure we are parsing a fragment otherwise the context element doesn't make sense. name = contextName; ns = contextNamespace; } } else { mode = framesetOk ? FRAMESET_OK : IN_BODY; // XXX from Hixie's email return; } } if ("select" == name) { int ancestorIndex = i; while (ancestorIndex > 0) { StackNode<T> ancestor = stack[ancestorIndex--]; if ("http://www.w3.org/1999/xhtml" == ancestor.ns) { if ("template" == ancestor.name) { break; } if ("table" == ancestor.name) { mode = IN_SELECT_IN_TABLE; return; } } } mode = IN_SELECT; return; } else if ("td" == name || "th" == name) { mode = IN_CELL; return; } else if ("tr" == name) { mode = IN_ROW; return; } else if ("tbody" == name || "thead" == name || "tfoot" == name) { mode = IN_TABLE_BODY; return; } else if ("caption" == name) { mode = IN_CAPTION; return; } else if ("colgroup" == name) { mode = IN_COLUMN_GROUP; return; } else if ("table" == name) { mode = IN_TABLE; return; } else if ("http://www.w3.org/1999/xhtml" != ns) { mode = framesetOk ? FRAMESET_OK : IN_BODY; return; } else if ("template" == name) { assert templateModePtr >= 0; mode = templateModeStack[templateModePtr]; return; } else if ("head" == name) { if (name == contextName) { mode = framesetOk ? FRAMESET_OK : IN_BODY; // really } else { mode = IN_HEAD; } return; } else if ("body" == name) { mode = framesetOk ? FRAMESET_OK : IN_BODY; return; } else if ("frameset" == name) { // TODO: Fragment case. Add error reporting. mode = IN_FRAMESET; return; } else if ("html" == name) { if (headPointer == null) { // TODO: Fragment case. Add error reporting. mode = BEFORE_HEAD; } else { mode = AFTER_HEAD; } return; } else if (i == 0) { mode = framesetOk ? FRAMESET_OK : IN_BODY; return; } } } /** * @throws SAXException * */ private void implicitlyCloseP() throws SAXException { int eltPos = findLastInButtonScope("p"); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { return; } generateImpliedEndTagsExceptFor("p"); if (errorHandler != null && eltPos != currentPtr) { errUnclosedElementsImplied(eltPos, "p"); } while (currentPtr >= eltPos) { pop(); } } private boolean debugOnlyClearLastStackSlot() { stack[currentPtr] = null; return true; } private boolean debugOnlyClearLastListSlot() { listOfActiveFormattingElements[listPtr] = null; return true; } private void pushTemplateMode(int mode) { templateModePtr++; if (templateModePtr == templateModeStack.length) { int[] newStack = new int[templateModeStack.length + 64]; System.arraycopy(templateModeStack, 0, newStack, 0, templateModeStack.length); templateModeStack = newStack; } templateModeStack[templateModePtr] = mode; } @SuppressWarnings("unchecked") private void push(StackNode<T> node) throws SAXException { currentPtr++; if (currentPtr == stack.length) { StackNode<T>[] newStack = new StackNode[stack.length + 64]; System.arraycopy(stack, 0, newStack, 0, stack.length); stack = newStack; } stack[currentPtr] = node; elementPushed(node.ns, node.popName, node.node); } @SuppressWarnings("unchecked") private void silentPush(StackNode<T> node) throws SAXException { currentPtr++; if (currentPtr == stack.length) { StackNode<T>[] newStack = new StackNode[stack.length + 64]; System.arraycopy(stack, 0, newStack, 0, stack.length); stack = newStack; } stack[currentPtr] = node; } @SuppressWarnings("unchecked") private void append(StackNode<T> node) { listPtr++; if (listPtr == listOfActiveFormattingElements.length) { StackNode<T>[] newList = new StackNode[listOfActiveFormattingElements.length + 64]; System.arraycopy(listOfActiveFormattingElements, 0, newList, 0, listOfActiveFormattingElements.length); listOfActiveFormattingElements = newList; } listOfActiveFormattingElements[listPtr] = node; } @Inline private void insertMarker() { append(null); } private void clearTheListOfActiveFormattingElementsUpToTheLastMarker() { while (listPtr > -1) { if (listOfActiveFormattingElements[listPtr] == null) { --listPtr; return; } listOfActiveFormattingElements[listPtr].release(); --listPtr; } } @Inline private boolean isCurrent(@Local String name) { return stack[currentPtr].ns == "http://www.w3.org/1999/xhtml" && name == stack[currentPtr].name; } private void removeFromStack(int pos) throws SAXException { if (currentPtr == pos) { pop(); } else { fatal(); stack[pos].release(); System.arraycopy(stack, pos + 1, stack, pos, currentPtr - pos); assert debugOnlyClearLastStackSlot(); currentPtr--; } } private void removeFromStack(StackNode<T> node) throws SAXException { if (stack[currentPtr] == node) { pop(); } else { int pos = currentPtr - 1; while (pos >= 0 && stack[pos] != node) { pos--; } if (pos == -1) { // dead code? return; } fatal(); node.release(); System.arraycopy(stack, pos + 1, stack, pos, currentPtr - pos); currentPtr--; } } private void removeFromListOfActiveFormattingElements(int pos) { assert listOfActiveFormattingElements[pos] != null; listOfActiveFormattingElements[pos].release(); if (pos == listPtr) { assert debugOnlyClearLastListSlot(); listPtr--; return; } assert pos < listPtr; System.arraycopy(listOfActiveFormattingElements, pos + 1, listOfActiveFormattingElements, pos, listPtr - pos); assert debugOnlyClearLastListSlot(); listPtr--; } /** * Adoption agency algorithm. * * @param name subject as described in the specified algorithm. * @return Returns true if the algorithm has completed and there is nothing remaining to * be done. Returns false if the algorithm needs to "act as described in the 'any other * end tag' entry" as described in the specified algorithm. * @throws SAXException */ private boolean adoptionAgencyEndTag(@Local String name) throws SAXException { // This check intends to ensure that for properly nested tags, closing tags will match // against the stack instead of the listOfActiveFormattingElements. if (stack[currentPtr].ns == "http://www.w3.org/1999/xhtml" && stack[currentPtr].name == name && findInListOfActiveFormattingElements(stack[currentPtr]) == -1) { // If the current element matches the name but isn't on the list of active // formatting elements, then it is possible that the list was mangled by the Noah's Ark // clause. In this case, we want to match the end tag against the stack instead of // proceeding with the AAA algorithm that may match against the list of // active formatting elements (and possibly mangle the tree in unexpected ways). pop(); return true; } // If you crash around here, perhaps some stack node variable claimed to // be a weak ref isn't. for (int i = 0; i < 8; ++i) { int formattingEltListPos = listPtr; while (formattingEltListPos > -1) { StackNode<T> listNode = listOfActiveFormattingElements[formattingEltListPos]; // weak ref if (listNode == null) { formattingEltListPos = -1; break; } else if (listNode.name == name) { break; } formattingEltListPos--; } if (formattingEltListPos == -1) { return false; } // this *looks* like a weak ref to the list of formatting elements StackNode<T> formattingElt = listOfActiveFormattingElements[formattingEltListPos]; int formattingEltStackPos = currentPtr; boolean inScope = true; while (formattingEltStackPos > -1) { StackNode<T> node = stack[formattingEltStackPos]; // weak ref if (node == formattingElt) { break; } else if (node.isScoping()) { inScope = false; } formattingEltStackPos--; } if (formattingEltStackPos == -1) { errNoElementToCloseButEndTagSeen(name); removeFromListOfActiveFormattingElements(formattingEltListPos); return true; } if (!inScope) { errNoElementToCloseButEndTagSeen(name); return true; } // stackPos now points to the formatting element and it is in scope if (formattingEltStackPos != currentPtr) { errEndTagViolatesNestingRules(name); } int furthestBlockPos = formattingEltStackPos + 1; while (furthestBlockPos <= currentPtr) { StackNode<T> node = stack[furthestBlockPos]; // weak ref assert furthestBlockPos > 0: "How is formattingEltStackPos + 1 not > 0?"; if (node.isSpecial()) { break; } furthestBlockPos++; } if (furthestBlockPos > currentPtr) { // no furthest block while (currentPtr >= formattingEltStackPos) { pop(); } removeFromListOfActiveFormattingElements(formattingEltListPos); return true; } StackNode<T> commonAncestor = stack[formattingEltStackPos - 1]; // weak ref StackNode<T> furthestBlock = stack[furthestBlockPos]; // weak ref // detachFromParent(furthestBlock.node); XXX AAA CHANGE int bookmark = formattingEltListPos; int nodePos = furthestBlockPos; StackNode<T> lastNode = furthestBlock; // weak ref int j = 0; for (;;) { ++j; nodePos--; if (nodePos == formattingEltStackPos) { break; } StackNode<T> node = stack[nodePos]; // weak ref int nodeListPos = findInListOfActiveFormattingElements(node); if (j > 3 && nodeListPos != -1) { removeFromListOfActiveFormattingElements(nodeListPos); // Adjust the indices into the list to account // for the removal of nodeListPos. if (nodeListPos <= formattingEltListPos) { formattingEltListPos--; } if (nodeListPos <= bookmark) { bookmark--; } // Update position to reflect removal from list. nodeListPos = -1; } if (nodeListPos == -1) { assert formattingEltStackPos < nodePos; assert bookmark < nodePos; assert furthestBlockPos > nodePos; removeFromStack(nodePos); // node is now a bad pointer in C++ furthestBlockPos--; continue; } // now node is both on stack and in the list if (nodePos == furthestBlockPos) { bookmark = nodeListPos + 1; } // if (hasChildren(node.node)) { XXX AAA CHANGE assert node == listOfActiveFormattingElements[nodeListPos]; assert node == stack[nodePos]; T clone = createElement("http://www.w3.org/1999/xhtml", node.name, node.attributes.cloneAttributes(null), commonAncestor.node); StackNode<T> newNode = new StackNode<T>(node.getFlags(), node.ns, node.name, clone, node.popName, node.attributes // [NOCPP[ , node.getLocator() // ]NOCPP] ); // creation ownership goes to stack node.dropAttributes(); // adopt ownership to newNode stack[nodePos] = newNode; newNode.retain(); // retain for list listOfActiveFormattingElements[nodeListPos] = newNode; node.release(); // release from stack node.release(); // release from list node = newNode; // } XXX AAA CHANGE detachFromParent(lastNode.node); appendElement(lastNode.node, node.node); lastNode = node; } if (commonAncestor.isFosterParenting()) { fatal(); detachFromParent(lastNode.node); insertIntoFosterParent(lastNode.node); } else { detachFromParent(lastNode.node); appendElement(lastNode.node, commonAncestor.node); } T clone = createElement("http://www.w3.org/1999/xhtml", formattingElt.name, formattingElt.attributes.cloneAttributes(null), furthestBlock.node); StackNode<T> formattingClone = new StackNode<T>( formattingElt.getFlags(), formattingElt.ns, formattingElt.name, clone, formattingElt.popName, formattingElt.attributes // [NOCPP[ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) // ]NOCPP] ); // Ownership transfers to stack below formattingElt.dropAttributes(); // transfer ownership to // formattingClone appendChildrenToNewParent(furthestBlock.node, clone); appendElement(clone, furthestBlock.node); removeFromListOfActiveFormattingElements(formattingEltListPos); insertIntoListOfActiveFormattingElements(formattingClone, bookmark); assert formattingEltStackPos < furthestBlockPos; removeFromStack(formattingEltStackPos); // furthestBlockPos is now off by one and points to the slot after // it insertIntoStack(formattingClone, furthestBlockPos); } return true; } private void insertIntoStack(StackNode<T> node, int position) throws SAXException { assert currentPtr + 1 < stack.length; assert position <= currentPtr + 1; if (position == currentPtr + 1) { push(node); } else { System.arraycopy(stack, position, stack, position + 1, (currentPtr - position) + 1); currentPtr++; stack[position] = node; } } private void insertIntoListOfActiveFormattingElements( StackNode<T> formattingClone, int bookmark) { formattingClone.retain(); assert listPtr + 1 < listOfActiveFormattingElements.length; if (bookmark <= listPtr) { System.arraycopy(listOfActiveFormattingElements, bookmark, listOfActiveFormattingElements, bookmark + 1, (listPtr - bookmark) + 1); } listPtr++; listOfActiveFormattingElements[bookmark] = formattingClone; } private int findInListOfActiveFormattingElements(StackNode<T> node) { for (int i = listPtr; i >= 0; i--) { if (node == listOfActiveFormattingElements[i]) { return i; } } return -1; } private int findInListOfActiveFormattingElementsContainsBetweenEndAndLastMarker( @Local String name) { for (int i = listPtr; i >= 0; i--) { StackNode<T> node = listOfActiveFormattingElements[i]; if (node == null) { return -1; } else if (node.name == name) { return i; } } return -1; } private void maybeForgetEarlierDuplicateFormattingElement( @Local String name, HtmlAttributes attributes) throws SAXException { int candidate = -1; int count = 0; for (int i = listPtr; i >= 0; i--) { StackNode<T> node = listOfActiveFormattingElements[i]; if (node == null) { break; } if (node.name == name && node.attributes.equalsAnother(attributes)) { candidate = i; ++count; } } if (count >= 3) { removeFromListOfActiveFormattingElements(candidate); } } private int findLastOrRoot(@Local String name) { for (int i = currentPtr; i > 0; i--) { if (stack[i].ns == "http://www.w3.org/1999/xhtml" && stack[i].name == name) { return i; } } return 0; } private int findLastOrRoot(int group) { for (int i = currentPtr; i > 0; i--) { if (stack[i].getGroup() == group) { return i; } } return 0; } /** * Attempt to add attribute to the body element. * @param attributes the attributes * @return <code>true</code> iff the attributes were added * @throws SAXException */ private boolean addAttributesToBody(HtmlAttributes attributes) throws SAXException { // [NOCPP[ checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); // ]NOCPP] if (currentPtr >= 1) { StackNode<T> body = stack[1]; if (body.getGroup() == TreeBuilder.BODY) { addAttributesToElement(body.node, attributes); return true; } } return false; } private void addAttributesToHtml(HtmlAttributes attributes) throws SAXException { // [NOCPP[ checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); // ]NOCPP] addAttributesToElement(stack[0].node, attributes); } private void pushHeadPointerOntoStack() throws SAXException { assert headPointer != null; assert mode == AFTER_HEAD; fatal(); silentPush(new StackNode<T>(ElementName.HEAD, headPointer // [NOCPP[ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) // ]NOCPP] )); } /** * @throws SAXException * */ private void reconstructTheActiveFormattingElements() throws SAXException { if (listPtr == -1) { return; } StackNode<T> mostRecent = listOfActiveFormattingElements[listPtr]; if (mostRecent == null || isInStack(mostRecent)) { return; } int entryPos = listPtr; for (;;) { entryPos--; if (entryPos == -1) { break; } if (listOfActiveFormattingElements[entryPos] == null) { break; } if (isInStack(listOfActiveFormattingElements[entryPos])) { break; } } while (entryPos < listPtr) { entryPos++; StackNode<T> entry = listOfActiveFormattingElements[entryPos]; StackNode<T> currentNode = stack[currentPtr]; T clone; if (currentNode.isFosterParenting()) { clone = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", entry.name, entry.attributes.cloneAttributes(null)); } else { clone = createElement("http://www.w3.org/1999/xhtml", entry.name, entry.attributes.cloneAttributes(null), currentNode.node); appendElement(clone, currentNode.node); } StackNode<T> entryClone = new StackNode<T>(entry.getFlags(), entry.ns, entry.name, clone, entry.popName, entry.attributes // [NOCPP[ , entry.getLocator() // ]NOCPP] ); entry.dropAttributes(); // transfer ownership to entryClone push(entryClone); // stack takes ownership of the local variable listOfActiveFormattingElements[entryPos] = entryClone; // overwriting the old entry on the list, so release & retain entry.release(); entryClone.retain(); } } private void insertIntoFosterParent(T child) throws SAXException { int tablePos = findLastOrRoot(TreeBuilder.TABLE); int templatePos = findLastOrRoot(TreeBuilder.TEMPLATE); if (templatePos >= tablePos) { appendElement(child, stack[templatePos].node); return; } StackNode<T> node = stack[tablePos]; insertFosterParentedChild(child, node.node, stack[tablePos - 1].node); } private T createAndInsertFosterParentedElement(@NsUri String ns, @Local String name, HtmlAttributes attributes) throws SAXException { return createAndInsertFosterParentedElement(ns, name, attributes, null); } private T createAndInsertFosterParentedElement(@NsUri String ns, @Local String name, HtmlAttributes attributes, T form) throws SAXException { int tablePos = findLastOrRoot(TreeBuilder.TABLE); int templatePos = findLastOrRoot(TreeBuilder.TEMPLATE); if (templatePos >= tablePos) { T child = createElement(ns, name, attributes, form, stack[templatePos].node); appendElement(child, stack[templatePos].node); return child; } StackNode<T> node = stack[tablePos]; return createAndInsertFosterParentedElement(ns, name, attributes, form, node.node, stack[tablePos - 1].node); } private boolean isInStack(StackNode<T> node) { for (int i = currentPtr; i >= 0; i--) { if (stack[i] == node) { return true; } } return false; } private void popTemplateMode() { templateModePtr--; } private void pop() throws SAXException { StackNode<T> node = stack[currentPtr]; assert debugOnlyClearLastStackSlot(); currentPtr--; elementPopped(node.ns, node.popName, node.node); node.release(); } private void silentPop() throws SAXException { StackNode<T> node = stack[currentPtr]; assert debugOnlyClearLastStackSlot(); currentPtr--; node.release(); } private void popOnEof() throws SAXException { StackNode<T> node = stack[currentPtr]; assert debugOnlyClearLastStackSlot(); currentPtr--; markMalformedIfScript(node.node); elementPopped(node.ns, node.popName, node.node); node.release(); } // [NOCPP[ private void checkAttributes(HtmlAttributes attributes, @NsUri String ns) throws SAXException { if (errorHandler != null) { int len = attributes.getXmlnsLength(); for (int i = 0; i < len; i++) { AttributeName name = attributes.getXmlnsAttributeName(i); if (name == AttributeName.XMLNS) { if (html4) { err("Attribute \u201Cxmlns\u201D not allowed here. (HTML4-only error.)"); } else { String xmlns = attributes.getXmlnsValue(i); if (!ns.equals(xmlns)) { err("Bad value \u201C" + xmlns + "\u201D for the attribute \u201Cxmlns\u201D (only \u201C" + ns + "\u201D permitted here)."); switch (namePolicy) { case ALTER_INFOSET: // fall through case ALLOW: warn("Attribute \u201Cxmlns\u201D is not serializable as XML 1.0."); break; case FATAL: fatal("Attribute \u201Cxmlns\u201D is not serializable as XML 1.0."); break; } } } } else if (ns != "http://www.w3.org/1999/xhtml" && name == AttributeName.XMLNS_XLINK) { String xmlns = attributes.getXmlnsValue(i); if (!"http://www.w3.org/1999/xlink".equals(xmlns)) { err("Bad value \u201C" + xmlns + "\u201D for the attribute \u201Cxmlns:link\u201D (only \u201Chttp://www.w3.org/1999/xlink\u201D permitted here)."); switch (namePolicy) { case ALTER_INFOSET: // fall through case ALLOW: warn("Attribute \u201Cxmlns:xlink\u201D with a value other than \u201Chttp://www.w3.org/1999/xlink\u201D is not serializable as XML 1.0 without changing document semantics."); break; case FATAL: fatal("Attribute \u201Cxmlns:xlink\u201D with a value other than \u201Chttp://www.w3.org/1999/xlink\u201D is not serializable as XML 1.0 without changing document semantics."); break; } } } else { err("Attribute \u201C" + attributes.getXmlnsLocalName(i) + "\u201D not allowed here."); switch (namePolicy) { case ALTER_INFOSET: // fall through case ALLOW: warn("Attribute with the local name \u201C" + attributes.getXmlnsLocalName(i) + "\u201D is not serializable as XML 1.0."); break; case FATAL: fatal("Attribute with the local name \u201C" + attributes.getXmlnsLocalName(i) + "\u201D is not serializable as XML 1.0."); break; } } } } attributes.processNonNcNames(this, namePolicy); } private String checkPopName(@Local String name) throws SAXException { if (NCName.isNCName(name)) { return name; } else { switch (namePolicy) { case ALLOW: warn("Element name \u201C" + name + "\u201D cannot be represented as XML 1.0."); return name; case ALTER_INFOSET: warn("Element name \u201C" + name + "\u201D cannot be represented as XML 1.0."); return NCName.escapeName(name); case FATAL: fatal("Element name \u201C" + name + "\u201D cannot be represented as XML 1.0."); } } return null; // keep compiler happy } // ]NOCPP] private void appendHtmlElementToDocumentAndPush(HtmlAttributes attributes) throws SAXException { // [NOCPP[ checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); // ]NOCPP] T elt = createHtmlElementSetAsRoot(attributes); StackNode<T> node = new StackNode<T>(ElementName.HTML, elt // [NOCPP[ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) // ]NOCPP] ); push(node); } private void appendHtmlElementToDocumentAndPush() throws SAXException { appendHtmlElementToDocumentAndPush(tokenizer.emptyAttributes()); } private void appendToCurrentNodeAndPushHeadElement(HtmlAttributes attributes) throws SAXException { // [NOCPP[ checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); // ]NOCPP] T currentNode = stack[currentPtr].node; T elt = createElement("http://www.w3.org/1999/xhtml", "head", attributes, currentNode); appendElement(elt, currentNode); headPointer = elt; StackNode<T> node = new StackNode<T>(ElementName.HEAD, elt // [NOCPP[ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) // ]NOCPP] ); push(node); } private void appendToCurrentNodeAndPushBodyElement(HtmlAttributes attributes) throws SAXException { appendToCurrentNodeAndPushElement(ElementName.BODY, attributes); } private void appendToCurrentNodeAndPushBodyElement() throws SAXException { appendToCurrentNodeAndPushBodyElement(tokenizer.emptyAttributes()); } private void appendToCurrentNodeAndPushFormElementMayFoster( HtmlAttributes attributes) throws SAXException { // [NOCPP[ checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); // ]NOCPP] T elt; StackNode<T> current = stack[currentPtr]; if (current.isFosterParenting()) { fatal(); elt = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", "form", attributes); } else { elt = createElement("http://www.w3.org/1999/xhtml", "form", attributes, current.node); appendElement(elt, current.node); } if (!isTemplateContents()) { formPointer = elt; } StackNode<T> node = new StackNode<T>(ElementName.FORM, elt // [NOCPP[ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) // ]NOCPP] ); push(node); } private void appendToCurrentNodeAndPushFormattingElementMayFoster( ElementName elementName, HtmlAttributes attributes) throws SAXException { // [NOCPP[ checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); // ]NOCPP] // This method can't be called for custom elements HtmlAttributes clone = attributes.cloneAttributes(null); // Attributes must not be read after calling createElement, because // createElement may delete attributes in C++. T elt; StackNode<T> current = stack[currentPtr]; if (current.isFosterParenting()) { fatal(); elt = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", elementName.name, attributes); } else { elt = createElement("http://www.w3.org/1999/xhtml", elementName.name, attributes, current.node); appendElement(elt, current.node); } StackNode<T> node = new StackNode<T>(elementName, elt, clone // [NOCPP[ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) // ]NOCPP] ); push(node); append(node); node.retain(); // append doesn't retain itself } private void appendToCurrentNodeAndPushElement(ElementName elementName, HtmlAttributes attributes) throws SAXException { // [NOCPP[ checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); // ]NOCPP] // This method can't be called for custom elements T currentNode = stack[currentPtr].node; T elt = createElement("http://www.w3.org/1999/xhtml", elementName.name, attributes, currentNode); appendElement(elt, currentNode); if (ElementName.TEMPLATE == elementName) { elt = getDocumentFragmentForTemplate(elt); } StackNode<T> node = new StackNode<T>(elementName, elt // [NOCPP[ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) // ]NOCPP] ); push(node); } private void appendToCurrentNodeAndPushElementMayFoster(ElementName elementName, HtmlAttributes attributes) throws SAXException { @Local String popName = elementName.name; // [NOCPP[ checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); if (elementName.isCustom()) { popName = checkPopName(popName); } // ]NOCPP] T elt; StackNode<T> current = stack[currentPtr]; if (current.isFosterParenting()) { fatal(); elt = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", popName, attributes); } else { elt = createElement("http://www.w3.org/1999/xhtml", popName, attributes, current.node); appendElement(elt, current.node); } StackNode<T> node = new StackNode<T>(elementName, elt, popName // [NOCPP[ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) // ]NOCPP] ); push(node); } private void appendToCurrentNodeAndPushElementMayFosterMathML( ElementName elementName, HtmlAttributes attributes) throws SAXException { @Local String popName = elementName.name; // [NOCPP[ checkAttributes(attributes, "http://www.w3.org/1998/Math/MathML"); if (elementName.isCustom()) { popName = checkPopName(popName); } // ]NOCPP] boolean markAsHtmlIntegrationPoint = false; if (ElementName.ANNOTATION_XML == elementName && annotationXmlEncodingPermitsHtml(attributes)) { markAsHtmlIntegrationPoint = true; } // Attributes must not be read after calling createElement(), since // createElement may delete the object in C++. T elt; StackNode<T> current = stack[currentPtr]; if (current.isFosterParenting()) { fatal(); elt = createAndInsertFosterParentedElement("http://www.w3.org/1998/Math/MathML", popName, attributes); } else { elt = createElement("http://www.w3.org/1998/Math/MathML", popName, attributes, current.node); appendElement(elt, current.node); } StackNode<T> node = new StackNode<T>(elementName, elt, popName, markAsHtmlIntegrationPoint // [NOCPP[ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) // ]NOCPP] ); push(node); } // [NOCPP[ T getDocumentFragmentForTemplate(T template) { return template; } T getFormPointerForContext(T context) { return null; } // ]NOCPP] private boolean annotationXmlEncodingPermitsHtml(HtmlAttributes attributes) { String encoding = attributes.getValue(AttributeName.ENCODING); if (encoding == null) { return false; } return Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( "application/xhtml+xml", encoding) || Portability.lowerCaseLiteralEqualsIgnoreAsciiCaseString( "text/html", encoding); } private void appendToCurrentNodeAndPushElementMayFosterSVG( ElementName elementName, HtmlAttributes attributes) throws SAXException { @Local String popName = elementName.camelCaseName; // [NOCPP[ checkAttributes(attributes, "http://www.w3.org/2000/svg"); if (elementName.isCustom()) { popName = checkPopName(popName); } // ]NOCPP] T elt; StackNode<T> current = stack[currentPtr]; if (current.isFosterParenting()) { fatal(); elt = createAndInsertFosterParentedElement("http://www.w3.org/2000/svg", popName, attributes); } else { elt = createElement("http://www.w3.org/2000/svg", popName, attributes, current.node); appendElement(elt, current.node); } StackNode<T> node = new StackNode<T>(elementName, popName, elt // [NOCPP[ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) // ]NOCPP] ); push(node); } private void appendToCurrentNodeAndPushElementMayFoster(ElementName elementName, HtmlAttributes attributes, T form) throws SAXException { // [NOCPP[ checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); // ]NOCPP] // Can't be called for custom elements T elt; T formOwner = form == null || fragment || isTemplateContents() ? null : form; StackNode<T> current = stack[currentPtr]; if (current.isFosterParenting()) { fatal(); elt = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", elementName.name, attributes, formOwner); } else { elt = createElement("http://www.w3.org/1999/xhtml", elementName.name, attributes, formOwner, current.node); appendElement(elt, current.node); } StackNode<T> node = new StackNode<T>(elementName, elt // [NOCPP[ , errorHandler == null ? null : new TaintableLocatorImpl(tokenizer) // ]NOCPP] ); push(node); } private void appendVoidElementToCurrentMayFoster( @Local String name, HtmlAttributes attributes, T form) throws SAXException { // [NOCPP[ checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); // ]NOCPP] // Can't be called for custom elements T elt; T formOwner = form == null || fragment || isTemplateContents() ? null : form; StackNode<T> current = stack[currentPtr]; if (current.isFosterParenting()) { fatal(); elt = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", name, attributes, formOwner); } else { elt = createElement("http://www.w3.org/1999/xhtml", name, attributes, formOwner, current.node); appendElement(elt, current.node); } elementPushed("http://www.w3.org/1999/xhtml", name, elt); elementPopped("http://www.w3.org/1999/xhtml", name, elt); } private void appendVoidElementToCurrentMayFoster( ElementName elementName, HtmlAttributes attributes) throws SAXException { @Local String popName = elementName.name; // [NOCPP[ checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); if (elementName.isCustom()) { popName = checkPopName(popName); } // ]NOCPP] T elt; StackNode<T> current = stack[currentPtr]; if (current.isFosterParenting()) { fatal(); elt = createAndInsertFosterParentedElement("http://www.w3.org/1999/xhtml", popName, attributes); } else { elt = createElement("http://www.w3.org/1999/xhtml", popName, attributes, current.node); appendElement(elt, current.node); } elementPushed("http://www.w3.org/1999/xhtml", popName, elt); elementPopped("http://www.w3.org/1999/xhtml", popName, elt); } private void appendVoidElementToCurrentMayFosterSVG( ElementName elementName, HtmlAttributes attributes) throws SAXException { @Local String popName = elementName.camelCaseName; // [NOCPP[ checkAttributes(attributes, "http://www.w3.org/2000/svg"); if (elementName.isCustom()) { popName = checkPopName(popName); } // ]NOCPP] T elt; StackNode<T> current = stack[currentPtr]; if (current.isFosterParenting()) { fatal(); elt = createAndInsertFosterParentedElement("http://www.w3.org/2000/svg", popName, attributes); } else { elt = createElement("http://www.w3.org/2000/svg", popName, attributes, current.node); appendElement(elt, current.node); } elementPushed("http://www.w3.org/2000/svg", popName, elt); elementPopped("http://www.w3.org/2000/svg", popName, elt); } private void appendVoidElementToCurrentMayFosterMathML( ElementName elementName, HtmlAttributes attributes) throws SAXException { @Local String popName = elementName.name; // [NOCPP[ checkAttributes(attributes, "http://www.w3.org/1998/Math/MathML"); if (elementName.isCustom()) { popName = checkPopName(popName); } // ]NOCPP] T elt; StackNode<T> current = stack[currentPtr]; if (current.isFosterParenting()) { fatal(); elt = createAndInsertFosterParentedElement("http://www.w3.org/1998/Math/MathML", popName, attributes); } else { elt = createElement("http://www.w3.org/1998/Math/MathML", popName, attributes, current.node); appendElement(elt, current.node); } elementPushed("http://www.w3.org/1998/Math/MathML", popName, elt); elementPopped("http://www.w3.org/1998/Math/MathML", popName, elt); } private void appendVoidElementToCurrent( @Local String name, HtmlAttributes attributes, T form) throws SAXException { // [NOCPP[ checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); // ]NOCPP] // Can't be called for custom elements T currentNode = stack[currentPtr].node; T elt = createElement("http://www.w3.org/1999/xhtml", name, attributes, form == null || fragment || isTemplateContents() ? null : form, currentNode); appendElement(elt, currentNode); elementPushed("http://www.w3.org/1999/xhtml", name, elt); elementPopped("http://www.w3.org/1999/xhtml", name, elt); } private void appendVoidFormToCurrent(HtmlAttributes attributes) throws SAXException { // [NOCPP[ checkAttributes(attributes, "http://www.w3.org/1999/xhtml"); // ]NOCPP] T currentNode = stack[currentPtr].node; T elt = createElement("http://www.w3.org/1999/xhtml", "form", attributes, currentNode); formPointer = elt; // ownership transferred to form pointer appendElement(elt, currentNode); elementPushed("http://www.w3.org/1999/xhtml", "form", elt); elementPopped("http://www.w3.org/1999/xhtml", "form", elt); } // [NOCPP[ private final void accumulateCharactersForced(@Const @NoLength char[] buf, int start, int length) throws SAXException { System.arraycopy(buf, start, charBuffer, charBufferLen, length); charBufferLen += length; } @Override public void ensureBufferSpace(int inputLength) throws SAXException { // TODO: Unify Tokenizer.strBuf and TreeBuilder.charBuffer so that // this method becomes unnecessary. int worstCase = charBufferLen + inputLength; if (charBuffer == null) { // Add an arbitrary small value to avoid immediate reallocation // once there are a few characters in the buffer. charBuffer = new char[worstCase + 128]; } else if (worstCase > charBuffer.length) { // HotSpot reportedly allocates memory with 8-byte accuracy, so // there's no point in trying to do math here to avoid slop. // Maybe we should add some small constant to worstCase here // but not doing that without profiling. In C++ with jemalloc, // the corresponding method should do math to round up here // to avoid slop. char[] newBuf = new char[worstCase]; System.arraycopy(charBuffer, 0, newBuf, 0, charBufferLen); charBuffer = newBuf; } } // ]NOCPP] protected void accumulateCharacters(@Const @NoLength char[] buf, int start, int length) throws SAXException { appendCharacters(stack[currentPtr].node, buf, start, length); } // ------------------------------- // protected final void requestSuspension() { tokenizer.requestSuspension(); } protected abstract T createElement(@NsUri String ns, @Local String name, HtmlAttributes attributes, T intendedParent) throws SAXException; protected T createElement(@NsUri String ns, @Local String name, HtmlAttributes attributes, T form, T intendedParent) throws SAXException { return createElement("http://www.w3.org/1999/xhtml", name, attributes, intendedParent); } protected abstract T createHtmlElementSetAsRoot(HtmlAttributes attributes) throws SAXException; protected abstract void detachFromParent(T element) throws SAXException; protected abstract boolean hasChildren(T element) throws SAXException; protected abstract void appendElement(T child, T newParent) throws SAXException; protected abstract void appendChildrenToNewParent(T oldParent, T newParent) throws SAXException; protected abstract void insertFosterParentedChild(T child, T table, T stackParent) throws SAXException; // We don't generate CPP code for this method because it is not used in generated CPP // code. Instead, the form owner version of this method is called with a null form owner. // [NOCPP[ protected abstract T createAndInsertFosterParentedElement(@NsUri String ns, @Local String name, HtmlAttributes attributes, T table, T stackParent) throws SAXException; // ]NOCPP] protected T createAndInsertFosterParentedElement(@NsUri String ns, @Local String name, HtmlAttributes attributes, T form, T table, T stackParent) throws SAXException { return createAndInsertFosterParentedElement(ns, name, attributes, table, stackParent); }; protected abstract void insertFosterParentedCharacters( @NoLength char[] buf, int start, int length, T table, T stackParent) throws SAXException; protected abstract void appendCharacters(T parent, @NoLength char[] buf, int start, int length) throws SAXException; protected abstract void appendIsindexPrompt(T parent) throws SAXException; protected abstract void appendComment(T parent, @NoLength char[] buf, int start, int length) throws SAXException; protected abstract void appendCommentToDocument(@NoLength char[] buf, int start, int length) throws SAXException; protected abstract void addAttributesToElement(T element, HtmlAttributes attributes) throws SAXException; protected void markMalformedIfScript(T elt) throws SAXException { } protected void start(boolean fragmentMode) throws SAXException { } protected void end() throws SAXException { } protected void appendDoctypeToDocument(@Local String name, String publicIdentifier, String systemIdentifier) throws SAXException { } protected void elementPushed(@NsUri String ns, @Local String name, T node) throws SAXException { } protected void elementPopped(@NsUri String ns, @Local String name, T node) throws SAXException { } // [NOCPP[ protected void documentMode(DocumentMode m, String publicIdentifier, String systemIdentifier, boolean html4SpecificAdditionalErrorChecks) throws SAXException { } /** * @see nu.validator.htmlparser.common.TokenHandler#wantsComments() */ public boolean wantsComments() { return wantingComments; } public void setIgnoringComments(boolean ignoreComments) { wantingComments = !ignoreComments; } /** * Sets the errorHandler. * * @param errorHandler * the errorHandler to set */ public final void setErrorHandler(ErrorHandler errorHandler) { this.errorHandler = errorHandler; } /** * Returns the errorHandler. * * @return the errorHandler */ public ErrorHandler getErrorHandler() { return errorHandler; } /** * The argument MUST be an interned string or <code>null</code>. * * @param context */ public final void setFragmentContext(@Local String context) { this.contextName = context; this.contextNamespace = "http://www.w3.org/1999/xhtml"; this.contextNode = null; this.fragment = (contextName != null); this.quirks = false; } // ]NOCPP] /** * @see nu.validator.htmlparser.common.TokenHandler#cdataSectionAllowed() */ @Inline public boolean cdataSectionAllowed() throws SAXException { return isInForeign(); } private boolean isInForeign() { return currentPtr >= 0 && stack[currentPtr].ns != "http://www.w3.org/1999/xhtml"; } private boolean isInForeignButNotHtmlOrMathTextIntegrationPoint() { if (currentPtr < 0) { return false; } return !isSpecialParentInForeign(stack[currentPtr]); } /** * The argument MUST be an interned string or <code>null</code>. * * @param context */ public final void setFragmentContext(@Local String context, @NsUri String ns, T node, boolean quirks) { // [NOCPP[ if (!((context == null && ns == null) || "http://www.w3.org/1999/xhtml" == ns || "http://www.w3.org/2000/svg" == ns || "http://www.w3.org/1998/Math/MathML" == ns)) { throw new IllegalArgumentException( "The namespace must be the HTML, SVG or MathML namespace (or null when the local name is null). Got: " + ns); } // ]NOCPP] this.contextName = context; this.contextNamespace = ns; this.contextNode = node; this.fragment = (contextName != null); this.quirks = quirks; } protected final T currentNode() { return stack[currentPtr].node; } /** * Returns the scriptingEnabled. * * @return the scriptingEnabled */ public boolean isScriptingEnabled() { return scriptingEnabled; } /** * Sets the scriptingEnabled. * * @param scriptingEnabled * the scriptingEnabled to set */ public void setScriptingEnabled(boolean scriptingEnabled) { this.scriptingEnabled = scriptingEnabled; } public void setIsSrcdocDocument(boolean isSrcdocDocument) { this.isSrcdocDocument = isSrcdocDocument; } // [NOCPP[ /** * Sets the doctypeExpectation. * * @param doctypeExpectation * the doctypeExpectation to set */ public void setDoctypeExpectation(DoctypeExpectation doctypeExpectation) { this.doctypeExpectation = doctypeExpectation; } public void setNamePolicy(XmlViolationPolicy namePolicy) { this.namePolicy = namePolicy; } /** * Sets the documentModeHandler. * * @param documentModeHandler * the documentModeHandler to set */ public void setDocumentModeHandler(DocumentModeHandler documentModeHandler) { this.documentModeHandler = documentModeHandler; } /** * Sets the reportingDoctype. * * @param reportingDoctype * the reportingDoctype to set */ public void setReportingDoctype(boolean reportingDoctype) { this.reportingDoctype = reportingDoctype; } // ]NOCPP] /** * Flushes the pending characters. Public for document.write use cases only. * @throws SAXException */ public final void flushCharacters() throws SAXException { if (charBufferLen > 0) { if ((mode == IN_TABLE || mode == IN_TABLE_BODY || mode == IN_ROW) && charBufferContainsNonWhitespace()) { errNonSpaceInTable(); reconstructTheActiveFormattingElements(); if (!stack[currentPtr].isFosterParenting()) { // reconstructing gave us a new current node appendCharacters(currentNode(), charBuffer, 0, charBufferLen); charBufferLen = 0; return; } int tablePos = findLastOrRoot(TreeBuilder.TABLE); int templatePos = findLastOrRoot(TreeBuilder.TEMPLATE); if (templatePos >= tablePos) { appendCharacters(stack[templatePos].node, charBuffer, 0, charBufferLen); charBufferLen = 0; return; } StackNode<T> tableElt = stack[tablePos]; insertFosterParentedCharacters(charBuffer, 0, charBufferLen, tableElt.node, stack[tablePos - 1].node); charBufferLen = 0; return; } appendCharacters(currentNode(), charBuffer, 0, charBufferLen); charBufferLen = 0; } } private boolean charBufferContainsNonWhitespace() { for (int i = 0; i < charBufferLen; i++) { switch (charBuffer[i]) { case ' ': case '\t': case '\n': case '\r': case '\u000C': continue; default: return true; } } return false; } /** * Creates a comparable snapshot of the tree builder state. Snapshot * creation is only supported immediately after a script end tag has been * processed. In C++ the caller is responsible for calling * <code>delete</code> on the returned object. * * @return a snapshot. * @throws SAXException */ @SuppressWarnings("unchecked") public TreeBuilderState<T> newSnapshot() throws SAXException { StackNode<T>[] listCopy = new StackNode[listPtr + 1]; for (int i = 0; i < listCopy.length; i++) { StackNode<T> node = listOfActiveFormattingElements[i]; if (node != null) { StackNode<T> newNode = new StackNode<T>(node.getFlags(), node.ns, node.name, node.node, node.popName, node.attributes.cloneAttributes(null) // [NOCPP[ , node.getLocator() // ]NOCPP] ); listCopy[i] = newNode; } else { listCopy[i] = null; } } StackNode<T>[] stackCopy = new StackNode[currentPtr + 1]; for (int i = 0; i < stackCopy.length; i++) { StackNode<T> node = stack[i]; int listIndex = findInListOfActiveFormattingElements(node); if (listIndex == -1) { StackNode<T> newNode = new StackNode<T>(node.getFlags(), node.ns, node.name, node.node, node.popName, null // [NOCPP[ , node.getLocator() // ]NOCPP] ); stackCopy[i] = newNode; } else { stackCopy[i] = listCopy[listIndex]; stackCopy[i].retain(); } } int[] templateModeStackCopy = new int[templateModePtr + 1]; System.arraycopy(templateModeStack, 0, templateModeStackCopy, 0, templateModeStackCopy.length); return new StateSnapshot<T>(stackCopy, listCopy, templateModeStackCopy, formPointer, headPointer, deepTreeSurrogateParent, mode, originalMode, framesetOk, needToDropLF, quirks); } public boolean snapshotMatches(TreeBuilderState<T> snapshot) { StackNode<T>[] stackCopy = snapshot.getStack(); int stackLen = snapshot.getStackLength(); StackNode<T>[] listCopy = snapshot.getListOfActiveFormattingElements(); int listLen = snapshot.getListOfActiveFormattingElementsLength(); int[] templateModeStackCopy = snapshot.getTemplateModeStack(); int templateModeStackLen = snapshot.getTemplateModeStackLength(); if (stackLen != currentPtr + 1 || listLen != listPtr + 1 || templateModeStackLen != templateModePtr + 1 || formPointer != snapshot.getFormPointer() || headPointer != snapshot.getHeadPointer() || deepTreeSurrogateParent != snapshot.getDeepTreeSurrogateParent() || mode != snapshot.getMode() || originalMode != snapshot.getOriginalMode() || framesetOk != snapshot.isFramesetOk() || needToDropLF != snapshot.isNeedToDropLF() || quirks != snapshot.isQuirks()) { // maybe just assert quirks return false; } for (int i = listLen - 1; i >= 0; i--) { if (listCopy[i] == null && listOfActiveFormattingElements[i] == null) { continue; } else if (listCopy[i] == null || listOfActiveFormattingElements[i] == null) { return false; } if (listCopy[i].node != listOfActiveFormattingElements[i].node) { return false; // it's possible that this condition is overly // strict } } for (int i = stackLen - 1; i >= 0; i--) { if (stackCopy[i].node != stack[i].node) { return false; } } for (int i = templateModeStackLen - 1; i >=0; i--) { if (templateModeStackCopy[i] != templateModeStack[i]) { return false; } } return true; } @SuppressWarnings("unchecked") public void loadState( TreeBuilderState<T> snapshot, Interner interner) throws SAXException { StackNode<T>[] stackCopy = snapshot.getStack(); int stackLen = snapshot.getStackLength(); StackNode<T>[] listCopy = snapshot.getListOfActiveFormattingElements(); int listLen = snapshot.getListOfActiveFormattingElementsLength(); int[] templateModeStackCopy = snapshot.getTemplateModeStack(); int templateModeStackLen = snapshot.getTemplateModeStackLength(); for (int i = 0; i <= listPtr; i++) { if (listOfActiveFormattingElements[i] != null) { listOfActiveFormattingElements[i].release(); } } if (listOfActiveFormattingElements.length < listLen) { listOfActiveFormattingElements = new StackNode[listLen]; } listPtr = listLen - 1; for (int i = 0; i <= currentPtr; i++) { stack[i].release(); } if (stack.length < stackLen) { stack = new StackNode[stackLen]; } currentPtr = stackLen - 1; if (templateModeStack.length < templateModeStackLen) { templateModeStack = new int[templateModeStackLen]; } templateModePtr = templateModeStackLen - 1; for (int i = 0; i < listLen; i++) { StackNode<T> node = listCopy[i]; if (node != null) { StackNode<T> newNode = new StackNode<T>(node.getFlags(), node.ns, Portability.newLocalFromLocal(node.name, interner), node.node, Portability.newLocalFromLocal(node.popName, interner), node.attributes.cloneAttributes(null) // [NOCPP[ , node.getLocator() // ]NOCPP] ); listOfActiveFormattingElements[i] = newNode; } else { listOfActiveFormattingElements[i] = null; } } for (int i = 0; i < stackLen; i++) { StackNode<T> node = stackCopy[i]; int listIndex = findInArray(node, listCopy); if (listIndex == -1) { StackNode<T> newNode = new StackNode<T>(node.getFlags(), node.ns, Portability.newLocalFromLocal(node.name, interner), node.node, Portability.newLocalFromLocal(node.popName, interner), null // [NOCPP[ , node.getLocator() // ]NOCPP] ); stack[i] = newNode; } else { stack[i] = listOfActiveFormattingElements[listIndex]; stack[i].retain(); } } System.arraycopy(templateModeStackCopy, 0, templateModeStack, 0, templateModeStackLen); formPointer = snapshot.getFormPointer(); headPointer = snapshot.getHeadPointer(); deepTreeSurrogateParent = snapshot.getDeepTreeSurrogateParent(); mode = snapshot.getMode(); originalMode = snapshot.getOriginalMode(); framesetOk = snapshot.isFramesetOk(); needToDropLF = snapshot.isNeedToDropLF(); quirks = snapshot.isQuirks(); } private int findInArray(StackNode<T> node, StackNode<T>[] arr) { for (int i = listPtr; i >= 0; i--) { if (node == arr[i]) { return i; } } return -1; } /** * @see nu.validator.htmlparser.impl.TreeBuilderState#getFormPointer() */ public T getFormPointer() { return formPointer; } /** * Returns the headPointer. * * @return the headPointer */ public T getHeadPointer() { return headPointer; } /** * Returns the deepTreeSurrogateParent. * * @return the deepTreeSurrogateParent */ public T getDeepTreeSurrogateParent() { return deepTreeSurrogateParent; } /** * @see nu.validator.htmlparser.impl.TreeBuilderState#getListOfActiveFormattingElements() */ public StackNode<T>[] getListOfActiveFormattingElements() { return listOfActiveFormattingElements; } /** * @see nu.validator.htmlparser.impl.TreeBuilderState#getStack() */ public StackNode<T>[] getStack() { return stack; } /** * @see nu.validator.htmlparser.impl.TreeBuilderState#getTemplateModeStack() */ public int[] getTemplateModeStack() { return templateModeStack; } /** * Returns the mode. * * @return the mode */ public int getMode() { return mode; } /** * Returns the originalMode. * * @return the originalMode */ public int getOriginalMode() { return originalMode; } /** * Returns the framesetOk. * * @return the framesetOk */ public boolean isFramesetOk() { return framesetOk; } /** * Returns the needToDropLF. * * @return the needToDropLF */ public boolean isNeedToDropLF() { return needToDropLF; } /** * Returns the quirks. * * @return the quirks */ public boolean isQuirks() { return quirks; } /** * @see nu.validator.htmlparser.impl.TreeBuilderState#getListOfActiveFormattingElementsLength() */ public int getListOfActiveFormattingElementsLength() { return listPtr + 1; } /** * @see nu.validator.htmlparser.impl.TreeBuilderState#getStackLength() */ public int getStackLength() { return currentPtr + 1; } /** * @see nu.validator.htmlparser.impl.TreeBuilderState#getTemplateModeStackLength() */ public int getTemplateModeStackLength() { return templateModePtr + 1; } /** * Reports a stray start tag. * @param name the name of the stray tag * * @throws SAXException */ private void errStrayStartTag(@Local String name) throws SAXException { err("Stray start tag \u201C" + name + "\u201D."); } /** * Reports a stray end tag. * @param name the name of the stray tag * * @throws SAXException */ private void errStrayEndTag(@Local String name) throws SAXException { err("Stray end tag \u201C" + name + "\u201D."); } /** * Reports a state when elements expected to be closed were not. * * @param eltPos the position of the start tag on the stack of the element * being closed. * @param name the name of the end tag * * @throws SAXException */ private void errUnclosedElements(int eltPos, @Local String name) throws SAXException { errNoCheck("End tag \u201C" + name + "\u201D seen, but there were open elements."); errListUnclosedStartTags(eltPos); } /** * Reports a state when elements expected to be closed ahead of an implied * end tag but were not. * * @param eltPos the position of the start tag on the stack of the element * being closed. * @param name the name of the end tag * * @throws SAXException */ private void errUnclosedElementsImplied(int eltPos, String name) throws SAXException { errNoCheck("End tag \u201C" + name + "\u201D implied, but there were open elements."); errListUnclosedStartTags(eltPos); } /** * Reports a state when elements expected to be closed ahead of an implied * table cell close. * * @param eltPos the position of the start tag on the stack of the element * being closed. * @throws SAXException */ private void errUnclosedElementsCell(int eltPos) throws SAXException { errNoCheck("A table cell was implicitly closed, but there were open elements."); errListUnclosedStartTags(eltPos); } private void errStrayDoctype() throws SAXException { err("Stray doctype."); } private void errAlmostStandardsDoctype() throws SAXException { if (!isSrcdocDocument) { err("Almost standards mode doctype. Expected \u201C<!DOCTYPE html>\u201D."); } } private void errQuirkyDoctype() throws SAXException { if (!isSrcdocDocument) { err("Quirky doctype. Expected \u201C<!DOCTYPE html>\u201D."); } } private void errNonSpaceInTrailer() throws SAXException { err("Non-space character in page trailer."); } private void errNonSpaceAfterFrameset() throws SAXException { err("Non-space after \u201Cframeset\u201D."); } private void errNonSpaceInFrameset() throws SAXException { err("Non-space in \u201Cframeset\u201D."); } private void errNonSpaceAfterBody() throws SAXException { err("Non-space character after body."); } private void errNonSpaceInColgroupInFragment() throws SAXException { err("Non-space in \u201Ccolgroup\u201D when parsing fragment."); } private void errNonSpaceInNoscriptInHead() throws SAXException { err("Non-space character inside \u201Cnoscript\u201D inside \u201Chead\u201D."); } private void errFooBetweenHeadAndBody(@Local String name) throws SAXException { if (errorHandler == null) { return; } errNoCheck("\u201C" + name + "\u201D element between \u201Chead\u201D and \u201Cbody\u201D."); } private void errStartTagWithoutDoctype() throws SAXException { if (!isSrcdocDocument) { err("Start tag seen without seeing a doctype first. Expected \u201C<!DOCTYPE html>\u201D."); } } private void errNoSelectInTableScope() throws SAXException { err("No \u201Cselect\u201D in table scope."); } private void errStartSelectWhereEndSelectExpected() throws SAXException { err("\u201Cselect\u201D start tag where end tag expected."); } private void errStartTagWithSelectOpen(@Local String name) throws SAXException { if (errorHandler == null) { return; } errNoCheck("\u201C" + name + "\u201D start tag with \u201Cselect\u201D open."); } private void errBadStartTagInHead(@Local String name) throws SAXException { if (errorHandler == null) { return; } errNoCheck("Bad start tag in \u201C" + name + "\u201D in \u201Chead\u201D."); } private void errImage() throws SAXException { err("Saw a start tag \u201Cimage\u201D."); } private void errIsindex() throws SAXException { err("\u201Cisindex\u201D seen."); } private void errFooSeenWhenFooOpen(@Local String name) throws SAXException { if (errorHandler == null) { return; } errNoCheck("An \u201C" + name + "\u201D start tag seen but an element of the same type was already open."); } private void errHeadingWhenHeadingOpen() throws SAXException { err("Heading cannot be a child of another heading."); } private void errFramesetStart() throws SAXException { err("\u201Cframeset\u201D start tag seen."); } private void errNoCellToClose() throws SAXException { err("No cell to close."); } private void errStartTagInTable(@Local String name) throws SAXException { if (errorHandler == null) { return; } errNoCheck("Start tag \u201C" + name + "\u201D seen in \u201Ctable\u201D."); } private void errFormWhenFormOpen() throws SAXException { err("Saw a \u201Cform\u201D start tag, but there was already an active \u201Cform\u201D element. Nested forms are not allowed. Ignoring the tag."); } private void errTableSeenWhileTableOpen() throws SAXException { err("Start tag for \u201Ctable\u201D seen but the previous \u201Ctable\u201D is still open."); } private void errStartTagInTableBody(@Local String name) throws SAXException { if (errorHandler == null) { return; } errNoCheck("\u201C" + name + "\u201D start tag in table body."); } private void errEndTagSeenWithoutDoctype() throws SAXException { if (!isSrcdocDocument) { err("End tag seen without seeing a doctype first. Expected \u201C<!DOCTYPE html>\u201D."); } } private void errEndTagAfterBody() throws SAXException { err("Saw an end tag after \u201Cbody\u201D had been closed."); } private void errEndTagSeenWithSelectOpen(@Local String name) throws SAXException { if (errorHandler == null) { return; } errNoCheck("\u201C" + name + "\u201D end tag with \u201Cselect\u201D open."); } private void errGarbageInColgroup() throws SAXException { err("Garbage in \u201Ccolgroup\u201D fragment."); } private void errEndTagBr() throws SAXException { err("End tag \u201Cbr\u201D."); } private void errNoElementToCloseButEndTagSeen(@Local String name) throws SAXException { if (errorHandler == null) { return; } errNoCheck("No \u201C" + name + "\u201D element in scope but a \u201C" + name + "\u201D end tag seen."); } private void errHtmlStartTagInForeignContext(@Local String name) throws SAXException { if (errorHandler == null) { return; } errNoCheck("HTML start tag \u201C" + name + "\u201D in a foreign namespace context."); } private void errTableClosedWhileCaptionOpen() throws SAXException { err("\u201Ctable\u201D closed but \u201Ccaption\u201D was still open."); } private void errNoTableRowToClose() throws SAXException { err("No table row to close."); } private void errNonSpaceInTable() throws SAXException { err("Misplaced non-space characters insided a table."); } private void errUnclosedChildrenInRuby() throws SAXException { if (errorHandler == null) { return; } errNoCheck("Unclosed children in \u201Cruby\u201D."); } private void errStartTagSeenWithoutRuby(@Local String name) throws SAXException { if (errorHandler == null) { return; } errNoCheck("Start tag \u201C" + name + "\u201D seen without a \u201Cruby\u201D element being open."); } private void errSelfClosing() throws SAXException { if (errorHandler == null) { return; } errNoCheck("Self-closing syntax (\u201C/>\u201D) used on a non-void HTML element. Ignoring the slash and treating as a start tag."); } private void errNoCheckUnclosedElementsOnStack() throws SAXException { errNoCheck("Unclosed elements on stack."); } private void errEndTagDidNotMatchCurrentOpenElement(@Local String name, @Local String currOpenName) throws SAXException { if (errorHandler == null) { return; } errNoCheck("End tag \u201C" + name + "\u201D did not match the name of the current open element (\u201C" + currOpenName + "\u201D)."); } private void errEndTagViolatesNestingRules(@Local String name) throws SAXException { if (errorHandler == null) { return; } errNoCheck("End tag \u201C" + name + "\u201D violates nesting rules."); } private void errEofWithUnclosedElements() throws SAXException { if (errorHandler == null) { return; } errNoCheck("End of file seen and there were open elements."); // just report all remaining unclosed elements errListUnclosedStartTags(0); } /** * Reports arriving at/near end of document with unclosed elements remaining. * * @param message * the message * @throws SAXException */ private void errEndWithUnclosedElements(@Local String name) throws SAXException { if (errorHandler == null) { return; } errNoCheck("End tag for \u201C" + name + "\u201D seen, but there were unclosed elements."); // just report all remaining unclosed elements errListUnclosedStartTags(0); } }