mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 00:01:50 +00:00
6363 lines
265 KiB
Java
6363 lines
265 KiB
Java
/*
|
|
* Copyright (c) 2007 Henri Sivonen
|
|
* Copyright (c) 2007-2011 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 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;
|
|
|
|
import org.xml.sax.ErrorHandler;
|
|
import org.xml.sax.Locator;
|
|
import org.xml.sax.SAXException;
|
|
import org.xml.sax.SAXParseException;
|
|
|
|
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 RT_OR_RP = 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_OR_LABEL = 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;
|
|
|
|
// 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 = new char[1024];
|
|
framesetOk = true;
|
|
if (fragment) {
|
|
T elt;
|
|
if (contextNode != null) {
|
|
elt = contextNode;
|
|
} else {
|
|
elt = createHtmlElementSetAsRoot(tokenizer.emptyAttributes());
|
|
}
|
|
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());
|
|
// 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[
|
|
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:
|
|
errHtmlStartTagInForeignContext(name);
|
|
while (!isSpecialParentInForeign(stack[currentPtr])) {
|
|
pop();
|
|
}
|
|
continue starttagloop;
|
|
case FONT:
|
|
if (attributes.contains(AttributeName.COLOR)
|
|
|| attributes.contains(AttributeName.FACE)
|
|
|| attributes.contains(AttributeName.SIZE)) {
|
|
errHtmlStartTagInForeignContext(name);
|
|
while (!isSpecialParentInForeign(stack[currentPtr])) {
|
|
pop();
|
|
}
|
|
continue starttagloop;
|
|
}
|
|
// 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();
|
|
// XXX is the next if dead code?
|
|
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 (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 RT_OR_RP:
|
|
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 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_OR_LABEL:
|
|
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) {
|
|
// 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);
|
|
}
|
|
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);
|
|
// 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) {
|
|
errEndTagDidNotMatchCurrentOpenElement(name, stack[currentPtr].popName);
|
|
}
|
|
eltPos = currentPtr;
|
|
for (;;) {
|
|
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 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 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);
|
|
while (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()) {
|
|
errHtmlStartTagInForeignContext(name);
|
|
while (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 (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 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 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 || eltGroup == HTML)) {
|
|
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
|
|
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));
|
|
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));
|
|
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];
|
|
T clone = createElement("http://www.w3.org/1999/xhtml", entry.name,
|
|
entry.attributes.cloneAttributes(null));
|
|
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
|
|
StackNode<T> currentNode = stack[currentPtr];
|
|
if (currentNode.isFosterParenting()) {
|
|
insertIntoFosterParent(clone);
|
|
} else {
|
|
appendElement(clone, currentNode.node);
|
|
}
|
|
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 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 elt = createElement("http://www.w3.org/1999/xhtml", "head",
|
|
attributes);
|
|
appendElement(elt, stack[currentPtr].node);
|
|
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 = createElement("http://www.w3.org/1999/xhtml", "form",
|
|
attributes);
|
|
|
|
if (!isTemplateContents()) {
|
|
formPointer = elt;
|
|
}
|
|
|
|
StackNode<T> current = stack[currentPtr];
|
|
if (current.isFosterParenting()) {
|
|
fatal();
|
|
insertIntoFosterParent(elt);
|
|
} else {
|
|
appendElement(elt, current.node);
|
|
}
|
|
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 = createElement("http://www.w3.org/1999/xhtml", elementName.name, attributes);
|
|
StackNode<T> current = stack[currentPtr];
|
|
if (current.isFosterParenting()) {
|
|
fatal();
|
|
insertIntoFosterParent(elt);
|
|
} else {
|
|
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 elt = createElement("http://www.w3.org/1999/xhtml", elementName.name, attributes);
|
|
appendElement(elt, stack[currentPtr].node);
|
|
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 = createElement("http://www.w3.org/1999/xhtml", popName, attributes);
|
|
StackNode<T> current = stack[currentPtr];
|
|
if (current.isFosterParenting()) {
|
|
fatal();
|
|
insertIntoFosterParent(elt);
|
|
} else {
|
|
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 = createElement("http://www.w3.org/1998/Math/MathML", popName,
|
|
attributes);
|
|
StackNode<T> current = stack[currentPtr];
|
|
if (current.isFosterParenting()) {
|
|
fatal();
|
|
insertIntoFosterParent(elt);
|
|
} else {
|
|
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 = createElement("http://www.w3.org/2000/svg", popName, attributes);
|
|
StackNode<T> current = stack[currentPtr];
|
|
if (current.isFosterParenting()) {
|
|
fatal();
|
|
insertIntoFosterParent(elt);
|
|
} else {
|
|
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 = createElement("http://www.w3.org/1999/xhtml", elementName.name, attributes,
|
|
form == null || fragment || isTemplateContents() ? null : form);
|
|
StackNode<T> current = stack[currentPtr];
|
|
if (current.isFosterParenting()) {
|
|
fatal();
|
|
insertIntoFosterParent(elt);
|
|
} else {
|
|
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 = createElement("http://www.w3.org/1999/xhtml", name, attributes,
|
|
form == null || fragment || isTemplateContents() ? null : form);
|
|
StackNode<T> current = stack[currentPtr];
|
|
if (current.isFosterParenting()) {
|
|
fatal();
|
|
insertIntoFosterParent(elt);
|
|
} else {
|
|
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 = createElement("http://www.w3.org/1999/xhtml", popName, attributes);
|
|
StackNode<T> current = stack[currentPtr];
|
|
if (current.isFosterParenting()) {
|
|
fatal();
|
|
insertIntoFosterParent(elt);
|
|
} else {
|
|
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 = createElement("http://www.w3.org/2000/svg", popName, attributes);
|
|
StackNode<T> current = stack[currentPtr];
|
|
if (current.isFosterParenting()) {
|
|
fatal();
|
|
insertIntoFosterParent(elt);
|
|
} else {
|
|
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 = createElement("http://www.w3.org/1998/Math/MathML", popName, attributes);
|
|
StackNode<T> current = stack[currentPtr];
|
|
if (current.isFosterParenting()) {
|
|
fatal();
|
|
insertIntoFosterParent(elt);
|
|
} else {
|
|
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 elt = createElement("http://www.w3.org/1999/xhtml", name, attributes,
|
|
form == null || fragment || isTemplateContents() ? null : form);
|
|
StackNode<T> current = stack[currentPtr];
|
|
appendElement(elt, current.node);
|
|
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 elt = createElement("http://www.w3.org/1999/xhtml", "form",
|
|
attributes);
|
|
formPointer = elt;
|
|
// ownership transferred to form pointer
|
|
StackNode<T> current = stack[currentPtr];
|
|
appendElement(elt, current.node);
|
|
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 {
|
|
int newLen = charBufferLen + length;
|
|
if (newLen > charBuffer.length) {
|
|
char[] newBuf = new char[newLen];
|
|
System.arraycopy(charBuffer, 0, newBuf, 0, charBufferLen);
|
|
charBuffer = newBuf;
|
|
}
|
|
System.arraycopy(buf, start, charBuffer, charBufferLen, length);
|
|
charBufferLen = newLen;
|
|
}
|
|
|
|
// ]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) throws SAXException;
|
|
|
|
protected T createElement(@NsUri String ns, @Local String name,
|
|
HtmlAttributes attributes, T form) throws SAXException {
|
|
return createElement("http://www.w3.org/1999/xhtml", name, attributes);
|
|
}
|
|
|
|
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;
|
|
|
|
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) {
|
|
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);
|
|
}
|
|
}
|