blob: 42444452a93c42d7cb29b9069e8932c1b57d8bd9 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1998-2000 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package javax.swing.text.html.parser;
27
28/**
29 * A content model state. This is basically a list of pointers to
30 * the BNF expression representing the model (the ContentModel).
31 * Each element in a DTD has a content model which describes the
32 * elements that may occur inside, and the order in which they can
33 * occur.
34 * <p>
35 * Each time a token is reduced a new state is created.
36 * <p>
37 * See Annex H on page 556 of the SGML handbook for more information.
38 *
39 * @see Parser
40 * @see DTD
41 * @see Element
42 * @see ContentModel
43 * @author Arthur van Hoff
44 */
45class ContentModelState {
46 ContentModel model;
47 long value;
48 ContentModelState next;
49
50 /**
51 * Create a content model state for a content model.
52 */
53 public ContentModelState(ContentModel model) {
54 this(model, null, 0);
55 }
56
57 /**
58 * Create a content model state for a content model given the
59 * remaining state that needs to be reduce.
60 */
61 ContentModelState(Object content, ContentModelState next) {
62 this(content, next, 0);
63 }
64
65 /**
66 * Create a content model state for a content model given the
67 * remaining state that needs to be reduce.
68 */
69 ContentModelState(Object content, ContentModelState next, long value) {
70 this.model = (ContentModel)content;
71 this.next = next;
72 this.value = value;
73 }
74
75 /**
76 * Return the content model that is relevant to the current state.
77 */
78 public ContentModel getModel() {
79 ContentModel m = model;
80 for (int i = 0; i < value; i++) {
81 if (m.next != null) {
82 m = m.next;
83 } else {
84 return null;
85 }
86 }
87 return m;
88 }
89
90 /**
91 * Check if the state can be terminated. That is there are no more
92 * tokens required in the input stream.
93 * @return true if the model can terminate without further input
94 */
95 public boolean terminate() {
96 switch (model.type) {
97 case '+':
98 if ((value == 0) && !(model).empty()) {
99 return false;
100 }
101 case '*':
102 case '?':
103 return (next == null) || next.terminate();
104
105 case '|':
106 for (ContentModel m = (ContentModel)model.content ; m != null ; m = m.next) {
107 if (m.empty()) {
108 return (next == null) || next.terminate();
109 }
110 }
111 return false;
112
113 case '&': {
114 ContentModel m = (ContentModel)model.content;
115
116 for (int i = 0 ; m != null ; i++, m = m.next) {
117 if ((value & (1L << i)) == 0) {
118 if (!m.empty()) {
119 return false;
120 }
121 }
122 }
123 return (next == null) || next.terminate();
124 }
125
126 case ',': {
127 ContentModel m = (ContentModel)model.content;
128 for (int i = 0 ; i < value ; i++, m = m.next);
129
130 for (; (m != null) && m.empty() ; m = m.next);
131 if (m != null) {
132 return false;
133 }
134 return (next == null) || next.terminate();
135 }
136
137 default:
138 return false;
139 }
140 }
141
142 /**
143 * Check if the state can be terminated. That is there are no more
144 * tokens required in the input stream.
145 * @return the only possible element that can occur next
146 */
147 public Element first() {
148 switch (model.type) {
149 case '*':
150 case '?':
151 case '|':
152 case '&':
153 return null;
154
155 case '+':
156 return model.first();
157
158 case ',': {
159 ContentModel m = (ContentModel)model.content;
160 for (int i = 0 ; i < value ; i++, m = m.next);
161 return m.first();
162 }
163
164 default:
165 return model.first();
166 }
167 }
168
169 /**
170 * Advance this state to a new state. An exception is thrown if the
171 * token is illegal at this point in the content model.
172 * @return next state after reducing a token
173 */
174 public ContentModelState advance(Object token) {
175 switch (model.type) {
176 case '+':
177 if (model.first(token)) {
178 return new ContentModelState(model.content,
179 new ContentModelState(model, next, value + 1)).advance(token);
180 }
181 if (value != 0) {
182 if (next != null) {
183 return next.advance(token);
184 } else {
185 return null;
186 }
187 }
188 break;
189
190 case '*':
191 if (model.first(token)) {
192 return new ContentModelState(model.content, this).advance(token);
193 }
194 if (next != null) {
195 return next.advance(token);
196 } else {
197 return null;
198 }
199
200 case '?':
201 if (model.first(token)) {
202 return new ContentModelState(model.content, next).advance(token);
203 }
204 if (next != null) {
205 return next.advance(token);
206 } else {
207 return null;
208 }
209
210 case '|':
211 for (ContentModel m = (ContentModel)model.content ; m != null ; m = m.next) {
212 if (m.first(token)) {
213 return new ContentModelState(m, next).advance(token);
214 }
215 }
216 break;
217
218 case ',': {
219 ContentModel m = (ContentModel)model.content;
220 for (int i = 0 ; i < value ; i++, m = m.next);
221
222 if (m.first(token) || m.empty()) {
223 if (m.next == null) {
224 return new ContentModelState(m, next).advance(token);
225 } else {
226 return new ContentModelState(m,
227 new ContentModelState(model, next, value + 1)).advance(token);
228 }
229 }
230 break;
231 }
232
233 case '&': {
234 ContentModel m = (ContentModel)model.content;
235 boolean complete = true;
236
237 for (int i = 0 ; m != null ; i++, m = m.next) {
238 if ((value & (1L << i)) == 0) {
239 if (m.first(token)) {
240 return new ContentModelState(m,
241 new ContentModelState(model, next, value | (1L << i))).advance(token);
242 }
243 if (!m.empty()) {
244 complete = false;
245 }
246 }
247 }
248 if (complete) {
249 if (next != null) {
250 return next.advance(token);
251 } else {
252 return null;
253 }
254 }
255 break;
256 }
257
258 default:
259 if (model.content == token) {
260 if (next == null && (token instanceof Element) &&
261 ((Element)token).content != null) {
262 return new ContentModelState(((Element)token).content);
263 }
264 return next;
265 }
266 // PENDING: Currently we don't correctly deal with optional start
267 // tags. This can most notably be seen with the 4.01 spec where
268 // TBODY's start and end tags are optional.
269 // Uncommenting this and the PENDING in ContentModel will
270 // correctly skip the omit tags, but the delegate is not notified.
271 // Some additional API needs to be added to track skipped tags,
272 // and this can then be added back.
273/*
274 if ((model.content instanceof Element)) {
275 Element e = (Element)model.content;
276
277 if (e.omitStart() && e.content != null) {
278 return new ContentModelState(e.content, next).advance(
279 token);
280 }
281 }
282*/
283 }
284
285 // We used to throw this exception at this point. However, it
286 // was determined that throwing this exception was more expensive
287 // than returning null, and we could not justify to ourselves why
288 // it was necessary to throw an exception, rather than simply
289 // returning null. I'm leaving it in a commented out state so
290 // that it can be easily restored if the situation ever arises.
291 //
292 // throw new IllegalArgumentException("invalid token: " + token);
293 return null;
294 }
295}