History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/javax/swing/text/html/parser/Parser.java (1 of 18352 files). Show all

Unified · Split

java/pkg/javax/swing/text/html/parser/Parser.java+2315−0
@@ -0,0 +1,2315 @@
1+/*
2+ * Copyright (c) 1998, 2010, Oracle and/or its affiliates. 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. Oracle designates this
8+ * particular file as subject to the "Classpath" exception as provided
9+ * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+ * or visit www.oracle.com if you need additional information or have any
23+ * questions.
24+ */
25+
26+package javax.swing.text.html.parser;
27+
28+import javax.swing.text.SimpleAttributeSet;
29+import javax.swing.text.html.HTML;
30+import javax.swing.text.ChangedCharSetException;
31+import java.io.*;
32+import java.util.Hashtable;
33+import java.util.Properties;
34+import java.util.Vector;
35+import java.util.Enumeration;
36+import java.net.URL;
37+
38+import sun.misc.MessageUtils;
39+
40+/**
41+ * A simple DTD-driven HTML parser. The parser reads an
42+ * HTML file from an InputStream and calls various methods
43+ * (which should be overridden in a subclass) when tags and
44+ * data are encountered.
45+ * <p>
46+ * Unfortunately there are many badly implemented HTML parsers
47+ * out there, and as a result there are many badly formatted
48+ * HTML files. This parser attempts to parse most HTML files.
49+ * This means that the implementation sometimes deviates from
50+ * the SGML specification in favor of HTML.
51+ * <p>
52+ * The parser treats \r and \r\n as \n. Newlines after starttags
53+ * and before end tags are ignored just as specified in the SGML/HTML
54+ * specification.
55+ * <p>
56+ * The html spec does not specify how spaces are to be coalesced very well.
57+ * Specifically, the following scenarios are not discussed (note that a
58+ * space should be used here, but I am using &amp;nbsp to force the space to
59+ * be displayed):
60+ * <p>
61+ * '&lt;b>blah&nbsp;&lt;i>&nbsp;&lt;strike>&nbsp;foo' which can be treated as:
62+ * '&lt;b>blah&nbsp;&lt;i>&lt;strike>foo'
63+ * <p>as well as:
64+ * '&lt;p>&lt;a href="xx">&nbsp;&lt;em>Using&lt;/em>&lt;/a>&lt;/p>'
65+ * which appears to be treated as:
66+ * '&lt;p>&lt;a href="xx">&lt;em>Using&lt;/em>&lt;/a>&lt;/p>'
67+ * <p>
68+ * If <code>strict</code> is false, when a tag that breaks flow,
69+ * (<code>TagElement.breaksFlows</code>) or trailing whitespace is
70+ * encountered, all whitespace will be ignored until a non whitespace
71+ * character is encountered. This appears to give behavior closer to
72+ * the popular browsers.
73+ *
74+ * @see DTD
75+ * @see TagElement
76+ * @see SimpleAttributeSet
77+ * @author Arthur van Hoff
78+ * @author Sunita Mani
79+ */
80+public
81+class Parser implements DTDConstants {
82+
83+ private char text[] = new char[1024];
84+ private int textpos = 0;
85+ private TagElement last;
86+ private boolean space;
87+
88+ private char str[] = new char[128];
89+ private int strpos = 0;
90+
91+ protected DTD dtd = null;
92+
93+ private int ch;
94+ private int ln;
95+ private Reader in;
96+
97+ private Element recent;
98+ private TagStack stack;
99+ private boolean skipTag = false;
100+ private TagElement lastFormSent = null;
101+ private SimpleAttributeSet attributes = new SimpleAttributeSet();
102+
103+ // State for <html>, <head> and <body>. Since people like to slap
104+ // together HTML documents without thinking, occasionally they
105+ // have multiple instances of these tags. These booleans track
106+ // the first sightings of these tags so they can be safely ignored
107+ // by the parser if repeated.
108+ private boolean seenHtml = false;
109+ private boolean seenHead = false;
110+ private boolean seenBody = false;
111+
112+ /**
113+ * The html spec does not specify how spaces are coalesced very well.
114+ * If strict == false, ignoreSpace is used to try and mimic the behavior
115+ * of the popular browsers.
116+ * <p>
117+ * The problematic scenarios are:
118+ * '&lt;b>blah &lt;i> &lt;strike> foo' which can be treated as:
119+ * '&lt;b>blah &lt;i>&lt;strike>foo'
120+ * as well as:
121+ * '&lt;p>&lt;a href="xx"> &lt;em>Using&lt;/em>&lt;/a>&lt;/p>'
122+ * which appears to be treated as:
123+ * '&lt;p>&lt;a href="xx">&lt;em>Using&lt;/em>&lt;/a>&lt;/p>'
124+ * <p>
125+ * When a tag that breaks flow, or trailing whitespace is encountered
126+ * ignoreSpace is set to true. From then on, all whitespace will be
127+ * ignored.
128+ * ignoreSpace will be set back to false the first time a
129+ * non whitespace character is encountered. This appears to give
130+ * behavior closer to the popular browsers.
131+ */
132+ private boolean ignoreSpace;
133+
134+ /**
135+ * This flag determines whether or not the Parser will be strict
136+ * in enforcing SGML compatibility. If false, it will be lenient
137+ * with certain common classes of erroneous HTML constructs.
138+ * Strict or not, in either case an error will be recorded.
139+ *
140+ */
141+ protected boolean strict = false;
142+
143+
144+ /** Number of \r\n's encountered. */
145+ private int crlfCount;
146+ /** Number of \r's encountered. A \r\n will not increment this. */
147+ private int crCount;
148+ /** Number of \n's encountered. A \r\n will not increment this. */
149+ private int lfCount;
150+
151+ //
152+ // To correctly identify the start of a tag/comment/text we need two
153+ // ivars. Two are needed as handleText isn't invoked until the tag
154+ // after the text has been parsed, that is the parser parses the text,
155+ // then a tag, then invokes handleText followed by handleStart.
156+ //
157+ /** The start position of the current block. Block is overloaded here,
158+ * it really means the current start position for the current comment,
159+ * tag, text. Use getBlockStartPosition to access this. */
160+ private int currentBlockStartPos;
161+ /** Start position of the last block. */
162+ private int lastBlockStartPos;
163+
164+ /**
165+ * array for mapping numeric references in range
166+ * 130-159 to displayable Unicode characters.
167+ */
168+ private static final char[] cp1252Map = {
169+ 8218, // &#130;
170+ 402, // &#131;
171+ 8222, // &#132;
172+ 8230, // &#133;
173+ 8224, // &#134;
174+ 8225, // &#135;
175+ 710, // &#136;
176+ 8240, // &#137;
177+ 352, // &#138;
178+ 8249, // &#139;
179+ 338, // &#140;
180+ 141, // &#141;
181+ 142, // &#142;
182+ 143, // &#143;
183+ 144, // &#144;
184+ 8216, // &#145;
185+ 8217, // &#146;
186+ 8220, // &#147;
187+ 8221, // &#148;
188+ 8226, // &#149;
189+ 8211, // &#150;
190+ 8212, // &#151;
191+ 732, // &#152;
192+ 8482, // &#153;
193+ 353, // &#154;
194+ 8250, // &#155;
195+ 339, // &#156;
196+ 157, // &#157;
197+ 158, // &#158;
198+ 376 // &#159;
199+ };
200+
201+ public Parser(DTD dtd) {
202+ this.dtd = dtd;
203+ }
204+
205+
206+ /**
207+ * @return the line number of the line currently being parsed
208+ */
209+ protected int getCurrentLine() {
210+ return ln;
211+ }
212+
213+ /**
214+ * Returns the start position of the current block. Block is
215+ * overloaded here, it really means the current start position for
216+ * the current comment tag, text, block.... This is provided for
217+ * subclassers that wish to know the start of the current block when
218+ * called with one of the handleXXX methods.
219+ */
220+ int getBlockStartPosition() {
221+ return Math.max(0, lastBlockStartPos - 1);
222+ }
223+
224+ /**
225+ * Makes a TagElement.
226+ */
227+ protected TagElement makeTag(Element elem, boolean fictional) {
228+ return new TagElement(elem, fictional);
229+ }
230+
231+ protected TagElement makeTag(Element elem) {
232+ return makeTag(elem, false);
233+ }
234+
235+ protected SimpleAttributeSet getAttributes() {
236+ return attributes;
237+ }
238+
239+ protected void flushAttributes() {
240+ attributes.removeAttributes(attributes);
241+ }
242+
243+ /**
244+ * Called when PCDATA is encountered.
245+ */
246+ protected void handleText(char text[]) {
247+ }
248+
249+ /**
250+ * Called when an HTML title tag is encountered.
251+ */
252+ protected void handleTitle(char text[]) {
253+ // default behavior is to call handleText. Subclasses
254+ // can override if necessary.
255+ handleText(text);
256+ }
257+
258+ /**
259+ * Called when an HTML comment is encountered.
260+ */
261+ protected void handleComment(char text[]) {
262+ }
263+
264+ protected void handleEOFInComment() {
265+ // We've reached EOF. Our recovery strategy is to
266+ // see if we have more than one line in the comment;
267+ // if so, we pretend that the comment was an unterminated
268+ // single line comment, and reparse the lines after the
269+ // first line as normal HTML content.
270+
271+ int commentEndPos = strIndexOf('\n');
272+ if (commentEndPos >= 0) {
273+ handleComment(getChars(0, commentEndPos));
274+ try {
275+ in.close();
276+ in = new CharArrayReader(getChars(commentEndPos + 1));
277+ ch = '>';
278+ } catch (IOException e) {
279+ error("ioexception");
280+ }
281+
282+ resetStrBuffer();
283+ } else {
284+ // no newline, so signal an error
285+ error("eof.comment");
286+ }
287+ }
288+
289+ /**
290+ * Called when an empty tag is encountered.
291+ */
292+ protected void handleEmptyTag(TagElement tag) throws ChangedCharSetException {
293+ }
294+
295+ /**
296+ * Called when a start tag is encountered.
297+ */
298+ protected void handleStartTag(TagElement tag) {
299+ }
300+
301+ /**
302+ * Called when an end tag is encountered.
303+ */
304+ protected void handleEndTag(TagElement tag) {
305+ }
306+
307+ /**
308+ * An error has occurred.
309+ */
310+ protected void handleError(int ln, String msg) {
311+ /*
312+ Thread.dumpStack();
313+ System.out.println("**** " + stack);
314+ System.out.println("line " + ln + ": error: " + msg);
315+ System.out.println();
316+ */
317+ }
318+
319+ /**
320+ * Output text.
321+ */
322+ void handleText(TagElement tag) {
323+ if (tag.breaksFlow()) {
324+ space = false;
325+ if (!strict) {
326+ ignoreSpace = true;
327+ }
328+ }
329+ if (textpos == 0) {
330+ if ((!space) || (stack == null) || last.breaksFlow() ||
331+ !stack.advance(dtd.pcdata)) {
332+ last = tag;
333+ space = false;
334+ lastBlockStartPos = currentBlockStartPos;
335+ return;
336+ }
337+ }
338+ if (space) {
339+ if (!ignoreSpace) {
340+ // enlarge buffer if needed
341+ if (textpos + 1 > text.length) {
342+ char newtext[] = new char[text.length + 200];
343+ System.arraycopy(text, 0, newtext, 0, text.length);
344+ text = newtext;
345+ }
346+
347+ // output pending space
348+ text[textpos++] = ' ';
349+ if (!strict && !tag.getElement().isEmpty()) {
350+ ignoreSpace = true;
351+ }
352+ }
353+ space = false;
354+ }
355+ char newtext[] = new char[textpos];
356+ System.arraycopy(text, 0, newtext, 0, textpos);
357+ // Handles cases of bad html where the title tag
358+ // was getting lost when we did error recovery.
359+ if (tag.getElement().getName().equals("title")) {
360+ handleTitle(newtext);
361+ } else {
362+ handleText(newtext);
363+ }
364+ lastBlockStartPos = currentBlockStartPos;
365+ textpos = 0;
366+ last = tag;
367+ space = false;
368+ }
369+
370+ /**
371+ * Invoke the error handler.
372+ */
373+ protected void error(String err, String arg1, String arg2,
374+ String arg3) {
375+ handleError(ln, err + " " + arg1 + " " + arg2 + " " + arg3);
376+ }
377+
378+ protected void error(String err, String arg1, String arg2) {
379+ error(err, arg1, arg2, "?");
380+ }
381+ protected void error(String err, String arg1) {
382+ error(err, arg1, "?", "?");
383+ }
384+ protected void error(String err) {
385+ error(err, "?", "?", "?");
386+ }
387+
388+
389+ /**
390+ * Handle a start tag. The new tag is pushed
391+ * onto the tag stack. The attribute list is
392+ * checked for required attributes.
393+ */
394+ protected void startTag(TagElement tag) throws ChangedCharSetException {
395+ Element elem = tag.getElement();
396+
397+ // If the tag is an empty tag and texpos != 0
398+ // this implies that there is text before the
399+ // start tag that needs to be processed before
400+ // handling the tag.
401+ //
402+ if (!elem.isEmpty() ||
403+ ((last != null) && !last.breaksFlow()) ||
404+ (textpos != 0)) {
405+ handleText(tag);
406+ } else {
407+ // this variable gets updated in handleText().
408+ // Since in this case we do not call handleText()
409+ // we need to update it here.
410+ //
411+ last = tag;
412+ // Note that we should really check last.breakFlows before
413+ // assuming this should be false.
414+ space = false;
415+ }
416+ lastBlockStartPos = currentBlockStartPos;
417+
418+ // check required attributes
419+ for (AttributeList a = elem.atts ; a != null ; a = a.next) {
420+ if ((a.modifier == REQUIRED) &&
421+ ((attributes.isEmpty()) ||
422+ ((!attributes.isDefined(a.name)) &&
423+ (!attributes.isDefined(HTML.getAttributeKey(a.name)))))) {
424+ error("req.att ", a.getName(), elem.getName());
425+ }
426+ }
427+
428+ if (elem.isEmpty()) {
429+ handleEmptyTag(tag);
430+ /*
431+ } else if (elem.getName().equals("form")) {
432+ handleStartTag(tag);
433+ */
434+ } else {
435+ recent = elem;
436+ stack = new TagStack(tag, stack);
437+ handleStartTag(tag);
438+ }
439+ }
440+
441+ /**
442+ * Handle an end tag. The end tag is popped
443+ * from the tag stack.
444+ */
445+ protected void endTag(boolean omitted) {
446+ handleText(stack.tag);
447+
448+ if (omitted && !stack.elem.omitEnd()) {
449+ error("end.missing", stack.elem.getName());
450+ } else if (!stack.terminate()) {
451+ error("end.unexpected", stack.elem.getName());
452+ }
453+
454+ // handle the tag
455+ handleEndTag(stack.tag);
456+ stack = stack.next;
457+ recent = (stack != null) ? stack.elem : null;
458+ }
459+
460+
461+ boolean ignoreElement(Element elem) {
462+
463+ String stackElement = stack.elem.getName();
464+ String elemName = elem.getName();
465+ /* We ignore all elements that are not valid in the context of
466+ a table except <td>, <th> (these we handle in
467+ legalElementContext()) and #pcdata. We also ignore the
468+ <font> tag in the context of <ul> and <ol> We additonally
469+ ignore the <meta> and the <style> tag if the body tag has
470+ been seen. **/
471+ if ((elemName.equals("html") && seenHtml) ||
472+ (elemName.equals("head") && seenHead) ||
473+ (elemName.equals("body") && seenBody)) {
474+ return true;
475+ }
476+ if (elemName.equals("dt") || elemName.equals("dd")) {
477+ TagStack s = stack;
478+ while (s != null && !s.elem.getName().equals("dl")) {
479+ s = s.next;
480+ }
481+ if (s == null) {
482+ return true;
483+ }
484+ }
485+
486+ if (((stackElement.equals("table")) &&
487+ (!elemName.equals("#pcdata")) && (!elemName.equals("input"))) ||
488+ ((elemName.equals("font")) &&
489+ (stackElement.equals("ul") || stackElement.equals("ol"))) ||
490+ (elemName.equals("meta") && stack != null) ||
491+ (elemName.equals("style") && seenBody) ||
492+ (stackElement.equals("table") && elemName.equals("a"))) {
493+ return true;
494+ }
495+ return false;
496+ }
497+
498+
499+ /**
500+ * Marks the first time a tag has been seen in a document
501+ */
502+
503+ protected void markFirstTime(Element elem) {
504+ String elemName = elem.getName();
505+ if (elemName.equals("html")) {
506+ seenHtml = true;
507+ } else if (elemName.equals("head")) {
508+ seenHead = true;
509+ } else if (elemName.equals("body")) {
510+ if (buf.length == 1) {
511+ // Refer to note in definition of buf for details on this.
512+ char[] newBuf = new char[256];
513+
514+ newBuf[0] = buf[0];
515+ buf = newBuf;
516+ }
517+ seenBody = true;
518+ }
519+ }
520+
521+ /**
522+ * Create a legal content for an element.
523+ */
524+ boolean legalElementContext(Element elem) throws ChangedCharSetException {
525+
526+ // System.out.println("-- legalContext -- " + elem);
527+
528+ // Deal with the empty stack
529+ if (stack == null) {
530+ // System.out.println("-- stack is empty");
531+ if (elem != dtd.html) {
532+ // System.out.println("-- pushing html");
533+ startTag(makeTag(dtd.html, true));
534+ return legalElementContext(elem);
535+ }
536+ return true;
537+ }
538+
539+ // Is it allowed in the current context
540+ if (stack.advance(elem)) {
541+ // System.out.println("-- legal context");
542+ markFirstTime(elem);
543+ return true;
544+ }
545+ boolean insertTag = false;
546+
547+ // The use of all error recovery strategies are contingent
548+ // on the value of the strict property.
549+ //
550+ // These are commonly occuring errors. if insertTag is true,
551+ // then we want to adopt an error recovery strategy that
552+ // involves attempting to insert an additional tag to
553+ // legalize the context. The two errors addressed here
554+ // are:
555+ // 1) when a <td> or <th> is seen soon after a <table> tag.
556+ // In this case we insert a <tr>.
557+ // 2) when any other tag apart from a <tr> is seen
558+ // in the context of a <tr>. In this case we would
559+ // like to add a <td>. If a <tr> is seen within a
560+ // <tr> context, then we will close out the current
561+ // <tr>.
562+ //
563+ // This insertion strategy is handled later in the method.
564+ // The reason for checking this now, is that in other cases
565+ // we would like to apply other error recovery strategies for example
566+ // ignoring tags.
567+ //
568+ // In certain cases it is better to ignore a tag than try to
569+ // fix the situation. So the first test is to see if this
570+ // is what we need to do.
571+ //
572+ String stackElemName = stack.elem.getName();
573+ String elemName = elem.getName();
574+
575+
576+ if (!strict &&
577+ ((stackElemName.equals("table") && elemName.equals("td")) ||
578+ (stackElemName.equals("table") && elemName.equals("th")) ||
579+ (stackElemName.equals("tr") && !elemName.equals("tr")))){
580+ insertTag = true;
581+ }
582+
583+
584+ if (!strict && !insertTag && (stack.elem.getName() != elem.getName() ||
585+ elem.getName().equals("body"))) {
586+ if (skipTag = ignoreElement(elem)) {
587+ error("tag.ignore", elem.getName());
588+ return skipTag;
589+ }
590+ }
591+
592+ // Check for anything after the start of the table besides tr, td, th
593+ // or caption, and if those aren't there, insert the <tr> and call
594+ // legalElementContext again.
595+ if (!strict && stackElemName.equals("table") &&
596+ !elemName.equals("tr") && !elemName.equals("td") &&
597+ !elemName.equals("th") && !elemName.equals("caption")) {
598+ Element e = dtd.getElement("tr");
599+ TagElement t = makeTag(e, true);
600+ legalTagContext(t);
601+ startTag(t);
602+ error("start.missing", elem.getName());
603+ return legalElementContext(elem);
604+ }
605+
606+ // They try to find a legal context by checking if the current
607+ // tag is valid in an enclosing context. If so
608+ // close out the tags by outputing end tags and then
609+ // insert the curent tag. If the tags that are
610+ // being closed out do not have an optional end tag
611+ // specification in the DTD then an html error is
612+ // reported.
613+ //
614+ if (!insertTag && stack.terminate() && (!strict || stack.elem.omitEnd())) {
615+ for (TagStack s = stack.next ; s != null ; s = s.next) {
616+ if (s.advance(elem)) {
617+ while (stack != s) {
618+ endTag(true);
619+ }
620+ return true;
621+ }
622+ if (!s.terminate() || (strict && !s.elem.omitEnd())) {
623+ break;
624+ }
625+ }
626+ }
627+
628+ // Check if we know what tag is expected next.
629+ // If so insert the tag. Report an error if the
630+ // tag does not have its start tag spec in the DTD as optional.
631+ //
632+ Element next = stack.first();
633+ if (next != null && (!strict || next.omitStart()) &&
634+ !(next==dtd.head && elem==dtd.pcdata) ) {
635+ // System.out.println("-- omitting start tag: " + next);
636+ TagElement t = makeTag(next, true);
637+ legalTagContext(t);
638+ startTag(t);
639+ if (!next.omitStart()) {
640+ error("start.missing", elem.getName());
641+ }
642+ return legalElementContext(elem);
643+ }
644+
645+
646+ // Traverse the list of expected elements and determine if adding
647+ // any of these elements would make for a legal context.
648+ //
649+
650+ if (!strict) {
651+ ContentModel content = stack.contentModel();
652+ Vector<Element> elemVec = new Vector<Element>();
653+ if (content != null) {
654+ content.getElements(elemVec);
655+ for (Element e : elemVec) {
656+ // Ensure that this element has not been included as
657+ // part of the exclusions in the DTD.
658+ //
659+ if (stack.excluded(e.getIndex())) {
660+ continue;
661+ }
662+
663+ boolean reqAtts = false;
664+
665+ for (AttributeList a = e.getAttributes(); a != null ; a = a.next) {
666+ if (a.modifier == REQUIRED) {
667+ reqAtts = true;
668+ break;
669+ }
670+ }
671+ // Ensure that no tag that has required attributes
672+ // gets inserted.
673+ //
674+ if (reqAtts) {
675+ continue;
676+ }
677+
678+ ContentModel m = e.getContent();
679+ if (m != null && m.first(elem)) {
680+ // System.out.println("-- adding a legal tag: " + e);
681+ TagElement t = makeTag(e, true);
682+ legalTagContext(t);
683+ startTag(t);
684+ error("start.missing", e.getName());
685+ return legalElementContext(elem);
686+ }
687+ }
688+ }
689+ }
690+
691+ // Check if the stack can be terminated. If so add the appropriate
692+ // end tag. Report an error if the tag being ended does not have its
693+ // end tag spec in the DTD as optional.
694+ //
695+ if (stack.terminate() && (stack.elem != dtd.body) && (!strict || stack.elem.omitEnd())) {
696+ // System.out.println("-- omitting end tag: " + stack.elem);
697+ if (!stack.elem.omitEnd()) {
698+ error("end.missing", elem.getName());
699+ }
700+
701+ endTag(true);
702+ return legalElementContext(elem);
703+ }
704+
705+ // At this point we know that something is screwed up.
706+ return false;
707+ }
708+
709+ /**
710+ * Create a legal context for a tag.
711+ */
712+ void legalTagContext(TagElement tag) throws ChangedCharSetException {
713+ if (legalElementContext(tag.getElement())) {
714+ markFirstTime(tag.getElement());
715+ return;
716+ }
717+
718+ // Avoid putting a block tag in a flow tag.
719+ if (tag.breaksFlow() && (stack != null) && !stack.tag.breaksFlow()) {
720+ endTag(true);
721+ legalTagContext(tag);
722+ return;
723+ }
724+
725+ // Avoid putting something wierd in the head of the document.
726+ for (TagStack s = stack ; s != null ; s = s.next) {
727+ if (s.tag.getElement() == dtd.head) {
728+ while (stack != s) {
729+ endTag(true);
730+ }
731+ endTag(true);
732+ legalTagContext(tag);
733+ return;
734+ }
735+ }
736+
737+ // Everything failed
738+ error("tag.unexpected", tag.getElement().getName());
739+ }
740+
741+ /**
742+ * Error context. Something went wrong, make sure we are in
743+ * the document's body context
744+ */
745+ void errorContext() throws ChangedCharSetException {
746+ for (; (stack != null) && (stack.tag.getElement() != dtd.body) ; stack = stack.next) {
747+ handleEndTag(stack.tag);
748+ }
749+ if (stack == null) {
750+ legalElementContext(dtd.body);
751+ startTag(makeTag(dtd.body, true));
752+ }
753+ }
754+
755+ /**
756+ * Add a char to the string buffer.
757+ */
758+ void addString(int c) {
759+ if (strpos == str.length) {
760+ char newstr[] = new char[str.length + 128];
761+ System.arraycopy(str, 0, newstr, 0, str.length);
762+ str = newstr;
763+ }
764+ str[strpos++] = (char)c;
765+ }
766+
767+ /**
768+ * Get the string that's been accumulated.
769+ */
770+ String getString(int pos) {
771+ char newStr[] = new char[strpos - pos];
772+ System.arraycopy(str, pos, newStr, 0, strpos - pos);
773+ strpos = pos;
774+ return new String(newStr);
775+ }
776+
777+ char[] getChars(int pos) {
778+ char newStr[] = new char[strpos - pos];
779+ System.arraycopy(str, pos, newStr, 0, strpos - pos);
780+ strpos = pos;
781+ return newStr;
782+ }
783+
784+ char[] getChars(int pos, int endPos) {
785+ char newStr[] = new char[endPos - pos];
786+ System.arraycopy(str, pos, newStr, 0, endPos - pos);
787+ // REMIND: it's not clear whether this version should set strpos or not
788+ // strpos = pos;
789+ return newStr;
790+ }
791+
792+ void resetStrBuffer() {
793+ strpos = 0;
794+ }
795+
796+ int strIndexOf(char target) {
797+ for (int i = 0; i < strpos; i++) {
798+ if (str[i] == target) {
799+ return i;
800+ }
801+ }
802+
803+ return -1;
804+ }
805+
806+ /**
807+ * Skip space.
808+ * [5] 297:5
809+ */
810+ void skipSpace() throws IOException {
811+ while (true) {
812+ switch (ch) {
813+ case '\n':
814+ ln++;
815+ ch = readCh();
816+ lfCount++;
817+ break;
818+
819+ case '\r':
820+ ln++;
821+ if ((ch = readCh()) == '\n') {
822+ ch = readCh();
823+ crlfCount++;
824+ }
825+ else {
826+ crCount++;
827+ }
828+ break;
829+ case ' ':
830+ case '\t':
831+ ch = readCh();
832+ break;
833+
834+ default:
835+ return;
836+ }
837+ }
838+ }
839+
840+ /**
841+ * Parse identifier. Uppercase characters are folded
842+ * to lowercase when lower is true. Returns falsed if
843+ * no identifier is found. [55] 346:17
844+ */
845+ boolean parseIdentifier(boolean lower) throws IOException {
846+ switch (ch) {
847+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
848+ case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
849+ case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
850+ case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
851+ case 'Y': case 'Z':
852+ if (lower) {
853+ ch = 'a' + (ch - 'A');
854+ }
855+
856+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
857+ case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
858+ case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
859+ case 's': case 't': case 'u': case 'v': case 'w': case 'x':
860+ case 'y': case 'z':
861+ break;
862+
863+ default:
864+ return false;
865+ }
866+
867+ while (true) {
868+ addString(ch);
869+
870+ switch (ch = readCh()) {
871+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
872+ case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
873+ case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
874+ case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
875+ case 'Y': case 'Z':
876+ if (lower) {
877+ ch = 'a' + (ch - 'A');
878+ }
879+
880+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
881+ case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
882+ case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
883+ case 's': case 't': case 'u': case 'v': case 'w': case 'x':
884+ case 'y': case 'z':
885+
886+ case '0': case '1': case '2': case '3': case '4':
887+ case '5': case '6': case '7': case '8': case '9':
888+
889+ case '.': case '-':
890+
891+ case '_': // not officially allowed
892+ break;
893+
894+ default:
895+ return true;
896+ }
897+ }
898+ }
899+
900+ /**
901+ * Parse an entity reference. [59] 350:17
902+ */
903+ private char[] parseEntityReference() throws IOException {
904+ int pos = strpos;
905+
906+ if ((ch = readCh()) == '#') {
907+ int n = 0;
908+ ch = readCh();
909+ if ((ch >= '0') && (ch <= '9') ||
910+ ch == 'x' || ch == 'X') {
911+
912+ if ((ch >= '0') && (ch <= '9')) {
913+ // parse decimal reference
914+ while ((ch >= '0') && (ch <= '9')) {
915+ n = (n * 10) + ch - '0';
916+ ch = readCh();
917+ }
918+ } else {
919+ // parse hexadecimal reference
920+ ch = readCh();
921+ char lch = (char) Character.toLowerCase(ch);
922+ while ((lch >= '0') && (lch <= '9') ||
923+ (lch >= 'a') && (lch <= 'f')) {
924+ if (lch >= '0' && lch <= '9') {
925+ n = (n * 16) + lch - '0';
926+ } else {
927+ n = (n * 16) + lch - 'a' + 10;
928+ }
929+ ch = readCh();
930+ lch = (char) Character.toLowerCase(ch);
931+ }
932+ }
933+ switch (ch) {
934+ case '\n':
935+ ln++;
936+ ch = readCh();
937+ lfCount++;
938+ break;
939+
940+ case '\r':
941+ ln++;
942+ if ((ch = readCh()) == '\n') {
943+ ch = readCh();
944+ crlfCount++;
945+ }
946+ else {
947+ crCount++;
948+ }
949+ break;
950+
951+ case ';':
952+ ch = readCh();
953+ break;
954+ }
955+ char data[] = {mapNumericReference((char) n)};
956+ return data;
957+ }
958+ addString('#');
959+ if (!parseIdentifier(false)) {
960+ error("ident.expected");
961+ strpos = pos;
962+ char data[] = {'&', '#'};
963+ return data;
964+ }
965+ } else if (!parseIdentifier(false)) {
966+ char data[] = {'&'};
967+ return data;
968+ }
969+
970+ boolean semicolon = false;
971+
972+ switch (ch) {
973+ case '\n':
974+ ln++;
975+ ch = readCh();
976+ lfCount++;
977+ break;
978+
979+ case '\r':
980+ ln++;
981+ if ((ch = readCh()) == '\n') {
982+ ch = readCh();
983+ crlfCount++;
984+ }
985+ else {
986+ crCount++;
987+ }
988+ break;
989+
990+ case ';':
991+ semicolon = true;
992+
993+ ch = readCh();
994+ break;
995+ }
996+
997+ String nm = getString(pos);
998+ Entity ent = dtd.getEntity(nm);
999+
1000+ // entities are case sensitive - however if strict
1001+ // is false then we will try to make a match by
1002+ // converting the string to all lowercase.
1003+ //
1004+ if (!strict && (ent == null)) {
1005+ ent = dtd.getEntity(nm.toLowerCase());
1006+ }
1007+ if ((ent == null) || !ent.isGeneral()) {
1008+
1009+ if (nm.length() == 0) {
1010+ error("invalid.entref", nm);
1011+ return new char[0];
1012+ }
1013+ /* given that there is not a match restore the entity reference */
1014+ String str = "&" + nm + (semicolon ? ";" : "");
1015+
1016+ char b[] = new char[str.length()];
1017+ str.getChars(0, b.length, b, 0);
1018+ return b;
1019+ }
1020+ return ent.getData();
1021+ }
1022+
1023+ /**
1024+ * Converts numeric character reference to Unicode character.
1025+ *
1026+ * Normally the code in a reference should be always converted
1027+ * to the Unicode character with the same code, but due to
1028+ * wide usage of Cp1252 charset most browsers map numeric references
1029+ * in the range 130-159 (which are control chars in Unicode set)
1030+ * to displayable characters with other codes.
1031+ *
1032+ * @param c the code of numeric character reference.
1033+ * @return the character corresponding to the reference code.
1034+ */
1035+ private char mapNumericReference(char c) {
1036+ if (c < 130 || c > 159) {
1037+ return c;
1038+ }
1039+ return cp1252Map[c - 130];
1040+ }
1041+
1042+ /**
1043+ * Parse a comment. [92] 391:7
1044+ */
1045+ void parseComment() throws IOException {
1046+
1047+ while (true) {
1048+ int c = ch;
1049+ switch (c) {
1050+ case '-':
1051+ /** Presuming that the start string of a comment "<!--" has
1052+ already been parsed, the '-' character is valid only as
1053+ part of a comment termination and further more it must
1054+ be present in even numbers. Hence if strict is true, we
1055+ presume the comment has been terminated and return.
1056+ However if strict is false, then there is no even number
1057+ requirement and this character can appear anywhere in the
1058+ comment. The parser reads on until it sees the following
1059+ pattern: "-->" or "--!>".
1060+ **/
1061+ if (!strict && (strpos != 0) && (str[strpos - 1] == '-')) {
1062+ if ((ch = readCh()) == '>') {
1063+ return;
1064+ }
1065+ if (ch == '!') {
1066+ if ((ch = readCh()) == '>') {
1067+ return;
1068+ } else {
1069+ /* to account for extra read()'s that happened */
1070+ addString('-');
1071+ addString('!');
1072+ continue;
1073+ }
1074+ }
1075+ break;
1076+ }
1077+
1078+ if ((ch = readCh()) == '-') {
1079+ ch = readCh();
1080+ if (strict || ch == '>') {
1081+ return;
1082+ }
1083+ if (ch == '!') {
1084+ if ((ch = readCh()) == '>') {
1085+ return;
1086+ } else {
1087+ /* to account for extra read()'s that happened */
1088+ addString('-');
1089+ addString('!');
1090+ continue;
1091+ }
1092+ }
1093+ /* to account for the extra read() */
1094+ addString('-');
1095+ }
1096+ break;
1097+
1098+ case -1:
1099+ handleEOFInComment();
1100+ return;
1101+
1102+ case '\n':
1103+ ln++;
1104+ ch = readCh();
1105+ lfCount++;
1106+ break;
1107+
1108+ case '>':
1109+ ch = readCh();
1110+ break;
1111+
1112+ case '\r':
1113+ ln++;
1114+ if ((ch = readCh()) == '\n') {
1115+ ch = readCh();
1116+ crlfCount++;
1117+ }
1118+ else {
1119+ crCount++;
1120+ }
1121+ c = '\n';
1122+ break;
1123+ default:
1124+ ch = readCh();
1125+ break;
1126+ }
1127+
1128+ addString(c);
1129+ }
1130+ }
1131+
1132+ /**
1133+ * Parse literal content. [46] 343:1 and [47] 344:1
1134+ */
1135+ void parseLiteral(boolean replace) throws IOException {
1136+ while (true) {
1137+ int c = ch;
1138+ switch (c) {
1139+ case -1:
1140+ error("eof.literal", stack.elem.getName());
1141+ endTag(true);
1142+ return;
1143+
1144+ case '>':
1145+ ch = readCh();
1146+ int i = textpos - (stack.elem.name.length() + 2), j = 0;
1147+
1148+ // match end tag
1149+ if ((i >= 0) && (text[i++] == '<') && (text[i] == '/')) {
1150+ while ((++i < textpos) &&
1151+ (Character.toLowerCase(text[i]) == stack.elem.name.charAt(j++)));
1152+ if (i == textpos) {
1153+ textpos -= (stack.elem.name.length() + 2);
1154+ if ((textpos > 0) && (text[textpos-1] == '\n')) {
1155+ textpos--;
1156+ }
1157+ endTag(false);
1158+ return;
1159+ }
1160+ }
1161+ break;
1162+
1163+ case '&':
1164+ char data[] = parseEntityReference();
1165+ if (textpos + data.length > text.length) {
1166+ char newtext[] = new char[Math.max(textpos + data.length + 128, text.length * 2)];
1167+ System.arraycopy(text, 0, newtext, 0, text.length);
1168+ text = newtext;
1169+ }
1170+ System.arraycopy(data, 0, text, textpos, data.length);
1171+ textpos += data.length;
1172+ continue;
1173+
1174+ case '\n':
1175+ ln++;
1176+ ch = readCh();
1177+ lfCount++;
1178+ break;
1179+
1180+ case '\r':
1181+ ln++;
1182+ if ((ch = readCh()) == '\n') {
1183+ ch = readCh();
1184+ crlfCount++;
1185+ }
1186+ else {
1187+ crCount++;
1188+ }
1189+ c = '\n';
1190+ break;
1191+ default:
1192+ ch = readCh();
1193+ break;
1194+ }
1195+
1196+ // output character
1197+ if (textpos == text.length) {
1198+ char newtext[] = new char[text.length + 128];
1199+ System.arraycopy(text, 0, newtext, 0, text.length);
1200+ text = newtext;
1201+ }
1202+ text[textpos++] = (char)c;
1203+ }
1204+ }
1205+
1206+ /**
1207+ * Parse attribute value. [33] 331:1
1208+ */
1209+ String parseAttributeValue(boolean lower) throws IOException {
1210+ int delim = -1;
1211+
1212+ // Check for a delimiter
1213+ switch(ch) {
1214+ case '\'':
1215+ case '"':
1216+ delim = ch;
1217+ ch = readCh();
1218+ break;
1219+ }
1220+
1221+ // Parse the rest of the value
1222+ while (true) {
1223+ int c = ch;
1224+
1225+ switch (c) {
1226+ case '\n':
1227+ ln++;
1228+ ch = readCh();
1229+ lfCount++;
1230+ if (delim < 0) {
1231+ return getString(0);
1232+ }
1233+ break;
1234+
1235+ case '\r':
1236+ ln++;
1237+
1238+ if ((ch = readCh()) == '\n') {
1239+ ch = readCh();
1240+ crlfCount++;
1241+ }
1242+ else {
1243+ crCount++;
1244+ }
1245+ if (delim < 0) {
1246+ return getString(0);
1247+ }
1248+ break;
1249+
1250+ case '\t':
1251+ if (delim < 0)
1252+ c = ' ';
1253+ case ' ':
1254+ ch = readCh();
1255+ if (delim < 0) {
1256+ return getString(0);
1257+ }
1258+ break;
1259+
1260+ case '>':
1261+ case '<':
1262+ if (delim < 0) {
1263+ return getString(0);
1264+ }
1265+ ch = readCh();
1266+ break;
1267+
1268+ case '\'':
1269+ case '"':
1270+ ch = readCh();
1271+ if (c == delim) {
1272+ return getString(0);
1273+ } else if (delim == -1) {
1274+ error("attvalerr");
1275+ if (strict || ch == ' ') {
1276+ return getString(0);
1277+ } else {
1278+ continue;
1279+ }
1280+ }
1281+ break;
1282+
1283+ case '=':
1284+ if (delim < 0) {
1285+ /* In SGML a construct like <img src=/cgi-bin/foo?x=1>
1286+ is considered invalid since an = sign can only be contained
1287+ in an attributes value if the string is quoted.
1288+ */
1289+ error("attvalerr");
1290+ /* If strict is true then we return with the string we have thus far.
1291+ Otherwise we accept the = sign as part of the attribute's value and
1292+ process the rest of the img tag. */
1293+ if (strict) {
1294+ return getString(0);
1295+ }
1296+ }
1297+ ch = readCh();
1298+ break;
1299+
1300+ case '&':
1301+ if (strict && delim < 0) {
1302+ ch = readCh();
1303+ break;
1304+ }
1305+
1306+ char data[] = parseEntityReference();
1307+ for (int i = 0 ; i < data.length ; i++) {
1308+ c = data[i];
1309+ addString((lower && (c >= 'A') && (c <= 'Z')) ? 'a' + c - 'A' : c);
1310+ }
1311+ continue;
1312+
1313+ case -1:
1314+ return getString(0);
1315+
1316+ default:
1317+ if (lower && (c >= 'A') && (c <= 'Z')) {
1318+ c = 'a' + c - 'A';
1319+ }
1320+ ch = readCh();
1321+ break;
1322+ }
1323+ addString(c);
1324+ }
1325+ }
1326+
1327+
1328+ /**
1329+ * Parse attribute specification List. [31] 327:17
1330+ */
1331+ void parseAttributeSpecificationList(Element elem) throws IOException {
1332+
1333+ while (true) {
1334+ skipSpace();
1335+
1336+ switch (ch) {
1337+ case '/':
1338+ case '>':
1339+ case '<':
1340+ case -1:
1341+ return;
1342+
1343+ case '-':
1344+ if ((ch = readCh()) == '-') {
1345+ ch = readCh();
1346+ parseComment();
1347+ strpos = 0;
1348+ } else {
1349+ error("invalid.tagchar", "-", elem.getName());
1350+ ch = readCh();
1351+ }
1352+ continue;
1353+ }
1354+
1355+ AttributeList att;
1356+ String attname;
1357+ String attvalue;
1358+
1359+ if (parseIdentifier(true)) {
1360+ attname = getString(0);
1361+ skipSpace();
1362+ if (ch == '=') {
1363+ ch = readCh();
1364+ skipSpace();
1365+ att = elem.getAttribute(attname);
1366+// Bug ID 4102750
1367+// Load the NAME of an Attribute Case Sensitive
1368+// The case of the NAME must be intact
1369+// MG 021898
1370+ attvalue = parseAttributeValue((att != null) && (att.type != CDATA) && (att.type != NOTATION) && (att.type != NAME));
1371+// attvalue = parseAttributeValue((att != null) && (att.type != CDATA) && (att.type != NOTATION));
1372+ } else {
1373+ attvalue = attname;
1374+ att = elem.getAttributeByValue(attvalue);
1375+ if (att == null) {
1376+ att = elem.getAttribute(attname);
1377+ if (att != null) {
1378+ attvalue = att.getValue();
1379+ }
1380+ else {
1381+ // Make it null so that NULL_ATTRIBUTE_VALUE is
1382+ // used
1383+ attvalue = null;
1384+ }
1385+ }
1386+ }
1387+ } else if (!strict && ch == ',') { // allows for comma separated attribute-value pairs
1388+ ch = readCh();
1389+ continue;
1390+ } else if (!strict && ch == '"') { // allows for quoted attributes
1391+ ch = readCh();
1392+ skipSpace();
1393+ if (parseIdentifier(true)) {
1394+ attname = getString(0);
1395+ if (ch == '"') {
1396+ ch = readCh();
1397+ }
1398+ skipSpace();
1399+ if (ch == '=') {
1400+ ch = readCh();
1401+ skipSpace();
1402+ att = elem.getAttribute(attname);
1403+ attvalue = parseAttributeValue((att != null) &&
1404+ (att.type != CDATA) &&
1405+ (att.type != NOTATION));
1406+ } else {
1407+ attvalue = attname;
1408+ att = elem.getAttributeByValue(attvalue);
1409+ if (att == null) {
1410+ att = elem.getAttribute(attname);
1411+ if (att != null) {
1412+ attvalue = att.getValue();
1413+ }
1414+ }
1415+ }
1416+ } else {
1417+ char str[] = {(char)ch};
1418+ error("invalid.tagchar", new String(str), elem.getName());
1419+ ch = readCh();
1420+ continue;
1421+ }
1422+ } else if (!strict && (attributes.isEmpty()) && (ch == '=')) {
1423+ ch = readCh();
1424+ skipSpace();
1425+ attname = elem.getName();
1426+ att = elem.getAttribute(attname);
1427+ attvalue = parseAttributeValue((att != null) &&
1428+ (att.type != CDATA) &&
1429+ (att.type != NOTATION));
1430+ } else if (!strict && (ch == '=')) {
1431+ ch = readCh();
1432+ skipSpace();
1433+ attvalue = parseAttributeValue(true);
1434+ error("attvalerr");
1435+ return;
1436+ } else {
1437+ char str[] = {(char)ch};
1438+ error("invalid.tagchar", new String(str), elem.getName());
1439+ if (!strict) {
1440+ ch = readCh();
1441+ continue;
1442+ } else {
1443+ return;
1444+ }
1445+ }
1446+
1447+ if (att != null) {
1448+ attname = att.getName();
1449+ } else {
1450+ error("invalid.tagatt", attname, elem.getName());
1451+ }
1452+
1453+ // Check out the value
1454+ if (attributes.isDefined(attname)) {
1455+ error("multi.tagatt", attname, elem.getName());
1456+ }
1457+ if (attvalue == null) {
1458+ attvalue = ((att != null) && (att.value != null)) ? att.value :
1459+ HTML.NULL_ATTRIBUTE_VALUE;
1460+ } else if ((att != null) && (att.values != null) && !att.values.contains(attvalue)) {
1461+ error("invalid.tagattval", attname, elem.getName());
1462+ }
1463+ HTML.Attribute attkey = HTML.getAttributeKey(attname);
1464+ if (attkey == null) {
1465+ attributes.addAttribute(attname, attvalue);
1466+ } else {
1467+ attributes.addAttribute(attkey, attvalue);
1468+ }
1469+ }
1470+ }
1471+
1472+ /**
1473+ * Parses th Document Declaration Type markup declaration.
1474+ * Currently ignores it.
1475+ */
1476+ public String parseDTDMarkup() throws IOException {
1477+
1478+ StringBuilder strBuff = new StringBuilder();
1479+ ch = readCh();
1480+ while(true) {
1481+ switch (ch) {
1482+ case '>':
1483+ ch = readCh();
1484+ return strBuff.toString();
1485+ case -1:
1486+ error("invalid.markup");
1487+ return strBuff.toString();
1488+ case '\n':
1489+ ln++;
1490+ ch = readCh();
1491+ lfCount++;
1492+ break;
1493+ case '"':
1494+ ch = readCh();
1495+ break;
1496+ case '\r':
1497+ ln++;
1498+ if ((ch = readCh()) == '\n') {
1499+ ch = readCh();
1500+ crlfCount++;
1501+ }
1502+ else {
1503+ crCount++;
1504+ }
1505+ break;
1506+ default:
1507+ strBuff.append((char)(ch & 0xFF));
1508+ ch = readCh();
1509+ break;
1510+ }
1511+ }
1512+ }
1513+
1514+ /**
1515+ * Parse markup declarations.
1516+ * Currently only handles the Document Type Declaration markup.
1517+ * Returns true if it is a markup declaration false otherwise.
1518+ */
1519+ protected boolean parseMarkupDeclarations(StringBuffer strBuff) throws IOException {
1520+
1521+ /* Currently handles only the DOCTYPE */
1522+ if ((strBuff.length() == "DOCTYPE".length()) &&
1523+ (strBuff.toString().toUpperCase().equals("DOCTYPE"))) {
1524+ parseDTDMarkup();
1525+ return true;
1526+ }
1527+ return false;
1528+ }
1529+
1530+ /**
1531+ * Parse an invalid tag.
1532+ */
1533+ void parseInvalidTag() throws IOException {
1534+ // ignore all data upto the close bracket '>'
1535+ while (true) {
1536+ skipSpace();
1537+ switch (ch) {
1538+ case '>':
1539+ case -1:
1540+ ch = readCh();
1541+ return;
1542+ case '<':
1543+ return;
1544+ default:
1545+ ch = readCh();
1546+
1547+ }
1548+ }
1549+ }
1550+
1551+ /**
1552+ * Parse a start or end tag.
1553+ */
1554+ void parseTag() throws IOException {
1555+ Element elem;
1556+ boolean net = false;
1557+ boolean warned = false;
1558+ boolean unknown = false;
1559+
1560+ switch (ch = readCh()) {
1561+ case '!':
1562+ switch (ch = readCh()) {
1563+ case '-':
1564+ // Parse comment. [92] 391:7
1565+ while (true) {
1566+ if (ch == '-') {
1567+ if (!strict || ((ch = readCh()) == '-')) {
1568+ ch = readCh();
1569+ if (!strict && ch == '-') {
1570+ ch = readCh();
1571+ }
1572+ // send over any text you might see
1573+ // before parsing and sending the
1574+ // comment
1575+ if (textpos != 0) {
1576+ char newtext[] = new char[textpos];
1577+ System.arraycopy(text, 0, newtext, 0, textpos);
1578+ handleText(newtext);
1579+ lastBlockStartPos = currentBlockStartPos;
1580+ textpos = 0;
1581+ }
1582+ parseComment();
1583+ last = makeTag(dtd.getElement("comment"), true);
1584+ handleComment(getChars(0));
1585+ continue;
1586+ } else if (!warned) {
1587+ warned = true;
1588+ error("invalid.commentchar", "-");
1589+ }
1590+ }
1591+ skipSpace();
1592+ switch (ch) {
1593+ case '-':
1594+ continue;
1595+ case '>':
1596+ ch = readCh();
1597+ case -1:
1598+ return;
1599+ default:
1600+ ch = readCh();
1601+ if (!warned) {
1602+ warned = true;
1603+ error("invalid.commentchar",
1604+ String.valueOf((char)ch));
1605+ }
1606+ break;
1607+ }
1608+ }
1609+
1610+ default:
1611+ // deal with marked sections
1612+ StringBuffer strBuff = new StringBuffer();
1613+ while (true) {
1614+ strBuff.append((char)ch);
1615+ if (parseMarkupDeclarations(strBuff)) {
1616+ return;
1617+ }
1618+ switch(ch) {
1619+ case '>':
1620+ ch = readCh();
1621+ case -1:
1622+ error("invalid.markup");
1623+ return;
1624+ case '\n':
1625+ ln++;
1626+ ch = readCh();
1627+ lfCount++;
1628+ break;
1629+ case '\r':
1630+ ln++;
1631+ if ((ch = readCh()) == '\n') {
1632+ ch = readCh();
1633+ crlfCount++;
1634+ }
1635+ else {
1636+ crCount++;
1637+ }
1638+ break;
1639+
1640+ default:
1641+ ch = readCh();
1642+ break;
1643+ }
1644+ }
1645+ }
1646+
1647+ case '/':
1648+ // parse end tag [19] 317:4
1649+ switch (ch = readCh()) {
1650+ case '>':
1651+ ch = readCh();
1652+ case '<':
1653+ // empty end tag. either </> or </<
1654+ if (recent == null) {
1655+ error("invalid.shortend");
1656+ return;
1657+ }
1658+ elem = recent;
1659+ break;
1660+
1661+ default:
1662+ if (!parseIdentifier(true)) {
1663+ error("expected.endtagname");
1664+ return;
1665+ }
1666+ skipSpace();
1667+ switch (ch) {
1668+ case '>':
1669+ ch = readCh();
1670+ case '<':
1671+ break;
1672+
1673+ default:
1674+ error("expected", "'>'");
1675+ while ((ch != -1) && (ch != '\n') && (ch != '>')) {
1676+ ch = readCh();
1677+ }
1678+ if (ch == '>') {
1679+ ch = readCh();
1680+ }
1681+ break;
1682+ }
1683+ String elemStr = getString(0);
1684+ if (!dtd.elementExists(elemStr)) {
1685+ error("end.unrecognized", elemStr);
1686+ // Ignore RE before end tag
1687+ if ((textpos > 0) && (text[textpos-1] == '\n')) {
1688+ textpos--;
1689+ }
1690+ elem = dtd.getElement("unknown");
1691+ elem.name = elemStr;
1692+ unknown = true;
1693+ } else {
1694+ elem = dtd.getElement(elemStr);
1695+ }
1696+ break;
1697+ }
1698+
1699+
1700+ // If the stack is null, we're seeing end tags without any begin
1701+ // tags. Ignore them.
1702+
1703+ if (stack == null) {
1704+ error("end.extra.tag", elem.getName());
1705+ return;
1706+ }
1707+
1708+ // Ignore RE before end tag
1709+ if ((textpos > 0) && (text[textpos-1] == '\n')) {
1710+ // In a pre tag, if there are blank lines
1711+ // we do not want to remove the newline
1712+ // before the end tag. Hence this code.
1713+ //
1714+ if (stack.pre) {
1715+ if ((textpos > 1) && (text[textpos-2] != '\n')) {
1716+ textpos--;
1717+ }
1718+ } else {
1719+ textpos--;
1720+ }
1721+ }
1722+
1723+ // If the end tag is a form, since we did not put it
1724+ // on the tag stack, there is no corresponding start
1725+ // start tag to find. Hence do not touch the tag stack.
1726+ //
1727+
1728+ /*
1729+ if (!strict && elem.getName().equals("form")) {
1730+ if (lastFormSent != null) {
1731+ handleEndTag(lastFormSent);
1732+ return;
1733+ } else {
1734+ // do nothing.
1735+ return;
1736+ }
1737+ }
1738+ */
1739+
1740+ if (unknown) {
1741+ // we will not see a corresponding start tag
1742+ // on the the stack. If we are seeing an
1743+ // end tag, lets send this on as an empty
1744+ // tag with the end tag attribute set to
1745+ // true.
1746+ TagElement t = makeTag(elem);
1747+ handleText(t);
1748+ attributes.addAttribute(HTML.Attribute.ENDTAG, "true");
1749+ handleEmptyTag(makeTag(elem));
1750+ unknown = false;
1751+ return;
1752+ }
1753+
1754+ // find the corresponding start tag
1755+
1756+ // A commonly occuring error appears to be the insertion
1757+ // of extra end tags in a table. The intent here is ignore
1758+ // such extra end tags.
1759+ //
1760+ if (!strict) {
1761+ String stackElem = stack.elem.getName();
1762+
1763+ if (stackElem.equals("table")) {
1764+ // If it isnt a valid end tag ignore it and return
1765+ //
1766+ if (!elem.getName().equals(stackElem)) {
1767+ error("tag.ignore", elem.getName());
1768+ return;
1769+ }
1770+ }
1771+
1772+
1773+
1774+ if (stackElem.equals("tr") ||
1775+ stackElem.equals("td")) {
1776+ if ((!elem.getName().equals("table")) &&
1777+ (!elem.getName().equals(stackElem))) {
1778+ error("tag.ignore", elem.getName());
1779+ return;
1780+ }
1781+ }
1782+ }
1783+ TagStack sp = stack;
1784+
1785+ while ((sp != null) && (elem != sp.elem)) {
1786+ sp = sp.next;
1787+ }
1788+ if (sp == null) {
1789+ error("unmatched.endtag", elem.getName());
1790+ return;
1791+ }
1792+
1793+ // People put font ending tags in the darndest places.
1794+ // Don't close other contexts based on them being between
1795+ // a font tag and the corresponding end tag. Instead,
1796+ // ignore the end tag like it doesn't exist and allow the end
1797+ // of the document to close us out.
1798+ String elemName = elem.getName();
1799+ if (stack != sp &&
1800+ (elemName.equals("font") ||
1801+ elemName.equals("center"))) {
1802+
1803+ // Since closing out a center tag can have real wierd
1804+ // effects on the formatting, make sure that tags
1805+ // for which omitting an end tag is legimitate
1806+ // get closed out.
1807+ //
1808+ if (elemName.equals("center")) {
1809+ while(stack.elem.omitEnd() && stack != sp) {
1810+ endTag(true);
1811+ }
1812+ if (stack.elem == elem) {
1813+ endTag(false);
1814+ }
1815+ }
1816+ return;
1817+ }
1818+ // People do the same thing with center tags. In this
1819+ // case we would like to close off the center tag but
1820+ // not necessarily all enclosing tags.
1821+
1822+
1823+
1824+ // end tags
1825+ while (stack != sp) {
1826+ endTag(true);
1827+ }
1828+
1829+ endTag(false);
1830+ return;
1831+
1832+ case -1:
1833+ error("eof");
1834+ return;
1835+ }
1836+
1837+ // start tag [14] 314:1
1838+ if (!parseIdentifier(true)) {
1839+ elem = recent;
1840+ if ((ch != '>') || (elem == null)) {
1841+ error("expected.tagname");
1842+ return;
1843+ }
1844+ } else {
1845+ String elemStr = getString(0);
1846+
1847+ if (elemStr.equals("image")) {
1848+ elemStr = "img";
1849+ }
1850+
1851+ /* determine if this element is part of the dtd. */
1852+
1853+ if (!dtd.elementExists(elemStr)) {
1854+ // parseInvalidTag();
1855+ error("tag.unrecognized ", elemStr);
1856+ elem = dtd.getElement("unknown");
1857+ elem.name = elemStr;
1858+ unknown = true;
1859+ } else {
1860+ elem = dtd.getElement(elemStr);
1861+ }
1862+ }
1863+
1864+ // Parse attributes
1865+ parseAttributeSpecificationList(elem);
1866+
1867+ switch (ch) {
1868+ case '/':
1869+ net = true;
1870+ case '>':
1871+ ch = readCh();
1872+ if (ch == '>' && net) {
1873+ ch = readCh();
1874+ }
1875+ case '<':
1876+ break;
1877+
1878+ default:
1879+ error("expected", "'>'");
1880+ break;
1881+ }
1882+
1883+ if (!strict) {
1884+ if (elem.getName().equals("script")) {
1885+ error("javascript.unsupported");
1886+ }
1887+ }
1888+
1889+ // ignore RE after start tag
1890+ //
1891+ if (!elem.isEmpty()) {
1892+ if (ch == '\n') {
1893+ ln++;
1894+ lfCount++;
1895+ ch = readCh();
1896+ } else if (ch == '\r') {
1897+ ln++;
1898+ if ((ch = readCh()) == '\n') {
1899+ ch = readCh();
1900+ crlfCount++;
1901+ }
1902+ else {
1903+ crCount++;
1904+ }
1905+ }
1906+ }
1907+
1908+ // ensure a legal context for the tag
1909+ TagElement tag = makeTag(elem, false);
1910+
1911+
1912+ /** In dealing with forms, we have decided to treat
1913+ them as legal in any context. Also, even though
1914+ they do have a start and an end tag, we will
1915+ not put this tag on the stack. This is to deal
1916+ several pages in the web oasis that choose to
1917+ start and end forms in any possible location. **/
1918+
1919+ /*
1920+ if (!strict && elem.getName().equals("form")) {
1921+ if (lastFormSent == null) {
1922+ lastFormSent = tag;
1923+ } else {
1924+ handleEndTag(lastFormSent);
1925+ lastFormSent = tag;
1926+ }
1927+ } else {
1928+ */
1929+ // Smlly, if a tag is unknown, we will apply
1930+ // no legalTagContext logic to it.
1931+ //
1932+ if (!unknown) {
1933+ legalTagContext(tag);
1934+
1935+ // If skip tag is true, this implies that
1936+ // the tag was illegal and that the error
1937+ // recovery strategy adopted is to ignore
1938+ // the tag.
1939+ if (!strict && skipTag) {
1940+ skipTag = false;
1941+ return;
1942+ }
1943+ }
1944+ /*
1945+ }
1946+ */
1947+
1948+ startTag(tag);
1949+
1950+ if (!elem.isEmpty()) {
1951+ switch (elem.getType()) {
1952+ case CDATA:
1953+ parseLiteral(false);
1954+ break;
1955+ case RCDATA:
1956+ parseLiteral(true);
1957+ break;
1958+ default:
1959+ if (stack != null) {
1960+ stack.net = net;
1961+ }
1962+ break;
1963+ }
1964+ }
1965+ }
1966+
1967+ private static final String START_COMMENT = "<!--";
1968+ private static final String END_COMMENT = "-->";
1969+ private static final char[] SCRIPT_END_TAG = "</script>".toCharArray();
1970+ private static final char[] SCRIPT_END_TAG_UPPER_CASE =
1971+ "</SCRIPT>".toCharArray();
1972+
1973+ void parseScript() throws IOException {
1974+ char[] charsToAdd = new char[SCRIPT_END_TAG.length];
1975+
1976+ /* Here, ch should be the first character after <script> */
1977+ while (true) {
1978+ int i = 0;
1979+ while (i < SCRIPT_END_TAG.length
1980+ && (SCRIPT_END_TAG[i] == ch
1981+ || SCRIPT_END_TAG_UPPER_CASE[i] == ch)) {
1982+ charsToAdd[i] = (char) ch;
1983+ ch = readCh();
1984+ i++;
1985+ }
1986+ if (i == SCRIPT_END_TAG.length) {
1987+
1988+ /* '</script>' tag detected */
1989+ /* Here, ch == '>' */
1990+ ch = readCh();
1991+ /* Here, ch == the first character after </script> */
1992+ return;
1993+ } else {
1994+
1995+ /* To account for extra read()'s that happened */
1996+ for (int j = 0; j < i; j++) {
1997+ addString(charsToAdd[j]);
1998+ }
1999+
2000+ switch (ch) {
2001+ case -1:
2002+ error("eof.script");
2003+ return;
2004+ case '\n':
2005+ ln++;
2006+ ch = readCh();
2007+ lfCount++;
2008+ addString('\n');
2009+ break;
2010+ case '\r':
2011+ ln++;
2012+ if ((ch = readCh()) == '\n') {
2013+ ch = readCh();
2014+ crlfCount++;
2015+ } else {
2016+ crCount++;
2017+ }
2018+ addString('\n');
2019+ break;
2020+ default:
2021+ addString(ch);
2022+ ch = readCh();
2023+ break;
2024+ } // switch
2025+ }
2026+ } // while
2027+ }
2028+
2029+ /**
2030+ * Parse Content. [24] 320:1
2031+ */
2032+ void parseContent() throws IOException {
2033+ Thread curThread = Thread.currentThread();
2034+
2035+ for (;;) {
2036+ if (curThread.isInterrupted()) {
2037+ curThread.interrupt(); // resignal the interrupt
2038+ break;
2039+ }
2040+
2041+ int c = ch;
2042+ currentBlockStartPos = currentPosition;
2043+
2044+ if (recent == dtd.script) { // means: if after starting <script> tag
2045+
2046+ /* Here, ch has to be the first character after <script> */
2047+ parseScript();
2048+ last = makeTag(dtd.getElement("comment"), true);
2049+
2050+ /* Remove leading and trailing HTML comment declarations */
2051+ String str = new String(getChars(0)).trim();
2052+ int minLength = START_COMMENT.length() + END_COMMENT.length();
2053+ if (str.startsWith(START_COMMENT) && str.endsWith(END_COMMENT)
2054+ && str.length() >= (minLength)) {
2055+ str = str.substring(START_COMMENT.length(),
2056+ str.length() - END_COMMENT.length());
2057+ }
2058+
2059+ /* Handle resulting chars as comment */
2060+ handleComment(str.toCharArray());
2061+ endTag(false);
2062+ lastBlockStartPos = currentPosition;
2063+ } else {
2064+ switch (c) {
2065+ case '<':
2066+ parseTag();
2067+ lastBlockStartPos = currentPosition;
2068+ continue;
2069+
2070+ case '/':
2071+ ch = readCh();
2072+ if ((stack != null) && stack.net) {
2073+ // null end tag.
2074+ endTag(false);
2075+ continue;
2076+ }
2077+ break;
2078+
2079+ case -1:
2080+ return;
2081+
2082+ case '&':
2083+ if (textpos == 0) {
2084+ if (!legalElementContext(dtd.pcdata)) {
2085+ error("unexpected.pcdata");
2086+ }
2087+ if (last.breaksFlow()) {
2088+ space = false;
2089+ }
2090+ }
2091+ char data[] = parseEntityReference();
2092+ if (textpos + data.length + 1 > text.length) {
2093+ char newtext[] = new char[Math.max(textpos + data.length + 128, text.length * 2)];
2094+ System.arraycopy(text, 0, newtext, 0, text.length);
2095+ text = newtext;
2096+ }
2097+ if (space) {
2098+ space = false;
2099+ text[textpos++] = ' ';
2100+ }
2101+ System.arraycopy(data, 0, text, textpos, data.length);
2102+ textpos += data.length;
2103+ ignoreSpace = false;
2104+ continue;
2105+
2106+ case '\n':
2107+ ln++;
2108+ lfCount++;
2109+ ch = readCh();
2110+ if ((stack != null) && stack.pre) {
2111+ break;
2112+ }
2113+ if (textpos == 0) {
2114+ lastBlockStartPos = currentPosition;
2115+ }
2116+ if (!ignoreSpace) {
2117+ space = true;
2118+ }
2119+ continue;
2120+
2121+ case '\r':
2122+ ln++;
2123+ c = '\n';
2124+ if ((ch = readCh()) == '\n') {
2125+ ch = readCh();
2126+ crlfCount++;
2127+ }
2128+ else {
2129+ crCount++;
2130+ }
2131+ if ((stack != null) && stack.pre) {
2132+ break;
2133+ }
2134+ if (textpos == 0) {
2135+ lastBlockStartPos = currentPosition;
2136+ }
2137+ if (!ignoreSpace) {
2138+ space = true;
2139+ }
2140+ continue;
2141+
2142+
2143+ case '\t':
2144+ case ' ':
2145+ ch = readCh();
2146+ if ((stack != null) && stack.pre) {
2147+ break;
2148+ }
2149+ if (textpos == 0) {
2150+ lastBlockStartPos = currentPosition;
2151+ }
2152+ if (!ignoreSpace) {
2153+ space = true;
2154+ }
2155+ continue;
2156+
2157+ default:
2158+ if (textpos == 0) {
2159+ if (!legalElementContext(dtd.pcdata)) {
2160+ error("unexpected.pcdata");
2161+ }
2162+ if (last.breaksFlow()) {
2163+ space = false;
2164+ }
2165+ }
2166+ ch = readCh();
2167+ break;
2168+ }
2169+ }
2170+
2171+ // enlarge buffer if needed
2172+ if (textpos + 2 > text.length) {
2173+ char newtext[] = new char[text.length + 128];
2174+ System.arraycopy(text, 0, newtext, 0, text.length);
2175+ text = newtext;
2176+ }
2177+
2178+ // output pending space
2179+ if (space) {
2180+ if (textpos == 0) {
2181+ lastBlockStartPos--;
2182+ }
2183+ text[textpos++] = ' ';
2184+ space = false;
2185+ }
2186+ text[textpos++] = (char)c;
2187+ ignoreSpace = false;
2188+ }
2189+ }
2190+
2191+ /**
2192+ * Returns the end of line string. This will return the end of line
2193+ * string that has been encountered the most, one of \r, \n or \r\n.
2194+ */
2195+ String getEndOfLineString() {
2196+ if (crlfCount >= crCount) {
2197+ if (lfCount >= crlfCount) {
2198+ return "\n";
2199+ }
2200+ else {
2201+ return "\r\n";
2202+ }
2203+ }
2204+ else {
2205+ if (crCount > lfCount) {
2206+ return "\r";
2207+ }
2208+ else {
2209+ return "\n";
2210+ }
2211+ }
2212+ }
2213+
2214+ /**
2215+ * Parse an HTML stream, given a DTD.
2216+ */
2217+ public synchronized void parse(Reader in) throws IOException {
2218+ this.in = in;
2219+
2220+ this.ln = 1;
2221+
2222+ seenHtml = false;
2223+ seenHead = false;
2224+ seenBody = false;
2225+
2226+ crCount = lfCount = crlfCount = 0;
2227+
2228+ try {
2229+ ch = readCh();
2230+ text = new char[1024];
2231+ str = new char[128];
2232+
2233+ parseContent();
2234+ // NOTE: interruption may have occurred. Control flows out
2235+ // of here normally.
2236+ while (stack != null) {
2237+ endTag(true);
2238+ }
2239+ in.close();
2240+ } catch (IOException e) {
2241+ errorContext();
2242+ error("ioexception");
2243+ throw e;
2244+ } catch (Exception e) {
2245+ errorContext();
2246+ error("exception", e.getClass().getName(), e.getMessage());
2247+ e.printStackTrace();
2248+ } catch (ThreadDeath e) {
2249+ errorContext();
2250+ error("terminated");
2251+ e.printStackTrace();
2252+ throw e;
2253+ } finally {
2254+ for (; stack != null ; stack = stack.next) {
2255+ handleEndTag(stack.tag);
2256+ }
2257+
2258+ text = null;
2259+ str = null;
2260+ }
2261+
2262+ }
2263+
2264+
2265+ /*
2266+ * Input cache. This is much faster than calling down to a synchronized
2267+ * method of BufferedReader for each byte. Measurements done 5/30/97
2268+ * show that there's no point in having a bigger buffer: Increasing
2269+ * the buffer to 8192 had no measurable impact for a program discarding
2270+ * one character at a time (reading from an http URL to a local machine).
2271+ * NOTE: If the current encoding is bogus, and we read too much
2272+ * (past the content-type) we may suffer a MalformedInputException. For
2273+ * this reason the initial size is 1 and when the body is encountered the
2274+ * size is adjusted to 256.
2275+ */
2276+ private char buf[] = new char[1];
2277+ private int pos;
2278+ private int len;
2279+ /*
2280+ tracks position relative to the beginning of the
2281+ document.
2282+ */
2283+ private int currentPosition;
2284+
2285+
2286+ private final int readCh() throws IOException {
2287+
2288+ if (pos >= len) {
2289+
2290+ // This loop allows us to ignore interrupts if the flag
2291+ // says so
2292+ for (;;) {
2293+ try {
2294+ len = in.read(buf);
2295+ break;
2296+ } catch (InterruptedIOException ex) {
2297+ throw ex;
2298+ }
2299+ }
2300+
2301+ if (len <= 0) {
2302+ return -1; // eof
2303+ }
2304+ pos = 0;
2305+ }
2306+ ++currentPosition;
2307+
2308+ return buf[pos++];
2309+ }
2310+
2311+
2312+ protected int getCurrentPos() {
2313+ return currentPosition;
2314+ }
2315+}