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
26package javax.swing.text.html.parser;
27
28import javax.swing.text.SimpleAttributeSet;
29import javax.swing.text.html.HTML;
30import javax.swing.text.ChangedCharSetException;
31import java.io.*;
32import java.util.Hashtable;
33import java.util.Properties;
34import java.util.Vector;
35import java.util.Enumeration;
36import java.net.URL;
37
38import 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 */
80public
81class 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}