blob: fb623981fccae89cbfec670bda7b93d9d5129463 [file] [log] [blame]
/*
* Copyright (c) 1998, 2000, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javax.swing.text.html.parser;
/**
* A content model state. This is basically a list of pointers to
* the BNF expression representing the model (the ContentModel).
* Each element in a DTD has a content model which describes the
* elements that may occur inside, and the order in which they can
* occur.
* <p>
* Each time a token is reduced a new state is created.
* <p>
* See Annex H on page 556 of the SGML handbook for more information.
*
* @see Parser
* @see DTD
* @see Element
* @see ContentModel
* @author Arthur van Hoff
*/
class ContentModelState {
ContentModel model;
long value;
ContentModelState next;
/**
* Create a content model state for a content model.
*/
public ContentModelState(ContentModel model) {
this(model, null, 0);
}
/**
* Create a content model state for a content model given the
* remaining state that needs to be reduce.
*/
ContentModelState(Object content, ContentModelState next) {
this(content, next, 0);
}
/**
* Create a content model state for a content model given the
* remaining state that needs to be reduce.
*/
ContentModelState(Object content, ContentModelState next, long value) {
this.model = (ContentModel)content;
this.next = next;
this.value = value;
}
/**
* Return the content model that is relevant to the current state.
*/
public ContentModel getModel() {
ContentModel m = model;
for (int i = 0; i < value; i++) {
if (m.next != null) {
m = m.next;
} else {
return null;
}
}
return m;
}
/**
* Check if the state can be terminated. That is there are no more
* tokens required in the input stream.
* @return true if the model can terminate without further input
*/
public boolean terminate() {
switch (model.type) {
case '+':
if ((value == 0) && !(model).empty()) {
return false;
}
case '*':
case '?':
return (next == null) || next.terminate();
case '|':
for (ContentModel m = (ContentModel)model.content ; m != null ; m = m.next) {
if (m.empty()) {
return (next == null) || next.terminate();
}
}
return false;
case '&': {
ContentModel m = (ContentModel)model.content;
for (int i = 0 ; m != null ; i++, m = m.next) {
if ((value & (1L << i)) == 0) {
if (!m.empty()) {
return false;
}
}
}
return (next == null) || next.terminate();
}
case ',': {
ContentModel m = (ContentModel)model.content;
for (int i = 0 ; i < value ; i++, m = m.next);
for (; (m != null) && m.empty() ; m = m.next);
if (m != null) {
return false;
}
return (next == null) || next.terminate();
}
default:
return false;
}
}
/**
* Check if the state can be terminated. That is there are no more
* tokens required in the input stream.
* @return the only possible element that can occur next
*/
public Element first() {
switch (model.type) {
case '*':
case '?':
case '|':
case '&':
return null;
case '+':
return model.first();
case ',': {
ContentModel m = (ContentModel)model.content;
for (int i = 0 ; i < value ; i++, m = m.next);
return m.first();
}
default:
return model.first();
}
}
/**
* Advance this state to a new state. An exception is thrown if the
* token is illegal at this point in the content model.
* @return next state after reducing a token
*/
public ContentModelState advance(Object token) {
switch (model.type) {
case '+':
if (model.first(token)) {
return new ContentModelState(model.content,
new ContentModelState(model, next, value + 1)).advance(token);
}
if (value != 0) {
if (next != null) {
return next.advance(token);
} else {
return null;
}
}
break;
case '*':
if (model.first(token)) {
return new ContentModelState(model.content, this).advance(token);
}
if (next != null) {
return next.advance(token);
} else {
return null;
}
case '?':
if (model.first(token)) {
return new ContentModelState(model.content, next).advance(token);
}
if (next != null) {
return next.advance(token);
} else {
return null;
}
case '|':
for (ContentModel m = (ContentModel)model.content ; m != null ; m = m.next) {
if (m.first(token)) {
return new ContentModelState(m, next).advance(token);
}
}
break;
case ',': {
ContentModel m = (ContentModel)model.content;
for (int i = 0 ; i < value ; i++, m = m.next);
if (m.first(token) || m.empty()) {
if (m.next == null) {
return new ContentModelState(m, next).advance(token);
} else {
return new ContentModelState(m,
new ContentModelState(model, next, value + 1)).advance(token);
}
}
break;
}
case '&': {
ContentModel m = (ContentModel)model.content;
boolean complete = true;
for (int i = 0 ; m != null ; i++, m = m.next) {
if ((value & (1L << i)) == 0) {
if (m.first(token)) {
return new ContentModelState(m,
new ContentModelState(model, next, value | (1L << i))).advance(token);
}
if (!m.empty()) {
complete = false;
}
}
}
if (complete) {
if (next != null) {
return next.advance(token);
} else {
return null;
}
}
break;
}
default:
if (model.content == token) {
if (next == null && (token instanceof Element) &&
((Element)token).content != null) {
return new ContentModelState(((Element)token).content);
}
return next;
}
// PENDING: Currently we don't correctly deal with optional start
// tags. This can most notably be seen with the 4.01 spec where
// TBODY's start and end tags are optional.
// Uncommenting this and the PENDING in ContentModel will
// correctly skip the omit tags, but the delegate is not notified.
// Some additional API needs to be added to track skipped tags,
// and this can then be added back.
/*
if ((model.content instanceof Element)) {
Element e = (Element)model.content;
if (e.omitStart() && e.content != null) {
return new ContentModelState(e.content, next).advance(
token);
}
}
*/
}
// We used to throw this exception at this point. However, it
// was determined that throwing this exception was more expensive
// than returning null, and we could not justify to ourselves why
// it was necessary to throw an exception, rather than simply
// returning null. I'm leaving it in a commented out state so
// that it can be easily restored if the situation ever arises.
//
// throw new IllegalArgumentException("invalid token: " + token);
return null;
}
}