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