1. /*
  2. * @(#)CSSParser.java 1.4 00/02/02
  3. *
  4. * Copyright 1999, 2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package javax.swing.text.html;
  11. import java.io.*;
  12. /**
  13. * A CSS parser. This works by way of a delegate that implements the
  14. * CSSParserCallback interface. The delegate is notified of the following
  15. * events:
  16. * <ul>
  17. * <li>Import statement: <code>handleImport</code>
  18. * <li>Selectors <code>handleSelector</code>. This is invoked for each
  19. * string. For example if the Reader contained p, bar , a {}, the delegate
  20. * would be notified 4 times, for 'p,' 'bar' ',' and 'a'.
  21. * <li>When a rule starts, <code>startRule</code>
  22. * <li>Properties in the rule via the <code>handleProperty</code>. This
  23. * is invoked one per property/value key, eg font size: foo;, would
  24. * cause the delegate to be notified once with a value of 'font size'.
  25. * <li>Values in the rule via the <code>handleValue</code>, this is notified
  26. * for the total value.
  27. * <li>When a rule ends, <code>endRule</code>
  28. * </ul>
  29. * This will parse much more than CSS 1, and loosely implements the
  30. * recommendation for <i>Forward-compatible parsing</i> in section
  31. * 7.1 of the CSS spec found at:
  32. * <a href=http://www.w3.org/TR/REC-CSS1>http://www.w3.org/TR/REC-CSS1</a>.
  33. * If an error results in parsing, a RuntimeException will be thrown.
  34. *
  35. * @author Scott Violet
  36. * @version 1.4 02/02/00
  37. */
  38. class CSSParser {
  39. // Parsing something like the following:
  40. // (@rule | ruleset | block)*
  41. //
  42. // @rule (block | identifier)*; (block with {} ends @rule)
  43. // block matching [] () {} (that is, [()] is a block, [(){}{[]}]
  44. // is a block, ()[] is two blocks)
  45. // identifier "*" | '*' | anything but a [](){} and whitespace
  46. //
  47. // ruleset selector decblock
  48. // selector (identifier | (block, except block '{}') )*
  49. // declblock declaration* block*
  50. // declaration (identifier* stopping when identifier ends with :)
  51. // (identifier* stopping when identifier ends with ;)
  52. //
  53. // comments /* */ can appear any where, and are stripped.
  54. // identifier - letters, digits, dashes and escaped characters
  55. // block starts with { ends with matching }, () [] and {} always occur
  56. // in matching pairs, '' and "" also occur in pairs, except " may be
  57. // Indicates the type of token being parsed.
  58. private static final int IDENTIFIER = 1;
  59. private static final int BRACKET_OPEN = 2;
  60. private static final int BRACKET_CLOSE = 3;
  61. private static final int BRACE_OPEN = 4;
  62. private static final int BRACE_CLOSE = 5;
  63. private static final int PAREN_OPEN = 6;
  64. private static final int PAREN_CLOSE = 7;
  65. private static final int END = -1;
  66. private static final char[] charMapping = { 0, 0, '[', ']', '{', '}', '(',
  67. ')', 0};
  68. /** Set to true if one character has been read ahead. */
  69. private boolean didPushChar;
  70. /** The read ahead character. */
  71. private int pushedChar;
  72. /** Temporary place to hold identifiers. */
  73. private StringBuffer unitBuffer;
  74. /** Used to indicate blocks. */
  75. private int[] unitStack;
  76. /** Number of valid blocks. */
  77. private int stackCount;
  78. /** Holds the incoming CSS rules. */
  79. private Reader reader;
  80. /** Set to true when the first non @ rule is encountered. */
  81. private boolean encounteredRuleSet;
  82. /** Notified of state. */
  83. private CSSParserCallback callback;
  84. /** nextToken() inserts the string here. */
  85. private char[] tokenBuffer;
  86. /** Current number of chars in tokenBufferLength. */
  87. private int tokenBufferLength;
  88. /** Set to true if any whitespace is read. */
  89. private boolean readWS;
  90. // The delegate interface.
  91. static interface CSSParserCallback {
  92. /** Called when an @import is encountered. */
  93. void handleImport(String importString);
  94. // There is currently no way to distinguish between '"foo,"' and
  95. // 'foo,'. But this generally isn't valid CSS. If it becomes
  96. // a problem, handleSelector will have to be told if the string is
  97. // quoted.
  98. void handleSelector(String selector);
  99. void startRule();
  100. // Property names are mapped to lower case before being passed to
  101. // the delegate.
  102. void handleProperty(String property);
  103. void handleValue(String value);
  104. void endRule();
  105. }
  106. CSSParser() {
  107. unitStack = new int[2];
  108. tokenBuffer = new char[80];
  109. unitBuffer = new StringBuffer();
  110. }
  111. void parse(Reader reader, CSSParserCallback callback,
  112. boolean inRule) throws IOException {
  113. this.callback = callback;
  114. stackCount = tokenBufferLength = 0;
  115. this.reader = reader;
  116. encounteredRuleSet = false;
  117. try {
  118. if (inRule) {
  119. parseDeclarationBlock();
  120. }
  121. else {
  122. while (getNextStatement());
  123. }
  124. } finally {
  125. callback = null;
  126. reader = null;
  127. }
  128. }
  129. /**
  130. * Gets the next statement, returning false if the end is reached. A
  131. * statement is either an @rule, or a ruleset.
  132. */
  133. private boolean getNextStatement() throws IOException {
  134. unitBuffer.setLength(0);
  135. int token = nextToken((char)0);
  136. switch (token) {
  137. case IDENTIFIER:
  138. if (tokenBufferLength > 0) {
  139. if (tokenBuffer[0] == '@') {
  140. parseAtRule();
  141. }
  142. else {
  143. encounteredRuleSet = true;
  144. parseRuleSet();
  145. }
  146. }
  147. return true;
  148. case BRACKET_OPEN:
  149. case BRACE_OPEN:
  150. case PAREN_OPEN:
  151. parseTillClosed(token);
  152. return true;
  153. case BRACKET_CLOSE:
  154. case BRACE_CLOSE:
  155. case PAREN_CLOSE:
  156. // Shouldn't happen...
  157. throw new RuntimeException("Unexpected top level block close");
  158. case END:
  159. return false;
  160. }
  161. return true;
  162. }
  163. /**
  164. * Parses an @ rule, stopping at a matching brace pair, or ;.
  165. */
  166. private void parseAtRule() throws IOException {
  167. // PENDING: make this more effecient.
  168. boolean done = false;
  169. boolean isImport = (tokenBufferLength == 7 &&
  170. tokenBuffer[0] == '@' && tokenBuffer[1] == 'i' &&
  171. tokenBuffer[2] == 'm' && tokenBuffer[3] == 'p' &&
  172. tokenBuffer[4] == 'o' && tokenBuffer[5] == 'r' &&
  173. tokenBuffer[6] == 't');
  174. unitBuffer.setLength(0);
  175. while (!done) {
  176. int nextToken = nextToken(';');
  177. switch (nextToken) {
  178. case IDENTIFIER:
  179. if (tokenBufferLength > 0 &&
  180. tokenBuffer[tokenBufferLength - 1] == ';') {
  181. --tokenBufferLength;
  182. done = true;
  183. }
  184. if (tokenBufferLength > 0) {
  185. if (unitBuffer.length() > 0 && readWS) {
  186. unitBuffer.append(' ');
  187. }
  188. unitBuffer.append(tokenBuffer, 0, tokenBufferLength);
  189. }
  190. break;
  191. case BRACE_OPEN:
  192. if (unitBuffer.length() > 0 && readWS) {
  193. unitBuffer.append(' ');
  194. }
  195. unitBuffer.append(charMapping[nextToken]);
  196. parseTillClosed(nextToken);
  197. done = true;
  198. // Skip a tailing ';', not really to spec.
  199. {
  200. int nextChar = readWS();
  201. if (nextChar != -1 && nextChar != ';') {
  202. pushChar(nextChar);
  203. }
  204. }
  205. break;
  206. case BRACKET_OPEN: case PAREN_OPEN:
  207. unitBuffer.append(charMapping[nextToken]);
  208. parseTillClosed(nextToken);
  209. break;
  210. case BRACKET_CLOSE: case BRACE_CLOSE: case PAREN_CLOSE:
  211. throw new RuntimeException("Unexpected close in @ rule");
  212. case END:
  213. done = true;
  214. break;
  215. }
  216. }
  217. if (isImport && !encounteredRuleSet) {
  218. callback.handleImport(unitBuffer.toString());
  219. }
  220. }
  221. /**
  222. * Parses the next rule set, which is a selector followed by a
  223. * declaration block.
  224. */
  225. private void parseRuleSet() throws IOException {
  226. if (parseSelectors()) {
  227. callback.startRule();
  228. parseDeclarationBlock();
  229. callback.endRule();
  230. }
  231. }
  232. /**
  233. * Parses a set of selectors, returning false if the end of the stream
  234. * is reached.
  235. */
  236. private boolean parseSelectors() throws IOException {
  237. // Parse the selectors
  238. int nextToken;
  239. if (tokenBufferLength > 0) {
  240. callback.handleSelector(new String(tokenBuffer, 0,
  241. tokenBufferLength));
  242. }
  243. unitBuffer.setLength(0);
  244. for (;;) {
  245. while ((nextToken = nextToken((char)0)) == IDENTIFIER) {
  246. if (tokenBufferLength > 0) {
  247. callback.handleSelector(new String(tokenBuffer, 0,
  248. tokenBufferLength));
  249. }
  250. }
  251. switch (nextToken) {
  252. case BRACE_OPEN:
  253. return true;
  254. case BRACKET_OPEN: case PAREN_OPEN:
  255. parseTillClosed(nextToken);
  256. // Not too sure about this, how we handle this isn't very
  257. // well spec'd.
  258. unitBuffer.setLength(0);
  259. break;
  260. case BRACKET_CLOSE: case BRACE_CLOSE: case PAREN_CLOSE:
  261. throw new RuntimeException("Unexpected block close in selector");
  262. case END:
  263. // Prematurely hit end.
  264. return false;
  265. }
  266. }
  267. }
  268. /**
  269. * Parses a declaration block. Which a number of declarations followed
  270. * by a })].
  271. */
  272. private void parseDeclarationBlock() throws IOException {
  273. for (;;) {
  274. int token = parseDeclaration();
  275. switch (token) {
  276. case END: case BRACE_CLOSE:
  277. return;
  278. case BRACKET_CLOSE: case PAREN_CLOSE:
  279. // Bail
  280. throw new RuntimeException("Unexpected close in declaration block");
  281. case IDENTIFIER:
  282. break;
  283. }
  284. }
  285. }
  286. /**
  287. * Parses a single declaration, which is an identifier a : and another
  288. * identifier. This returns the last token seen.
  289. */
  290. // identifier+: identifier* ;|}
  291. private int parseDeclaration() throws IOException {
  292. int token;
  293. if ((token = parseIdentifiers(':', false)) != IDENTIFIER) {
  294. return token;
  295. }
  296. // Make the property name to lowercase
  297. for (int counter = unitBuffer.length() - 1; counter >= 0; counter--) {
  298. unitBuffer.setCharAt(counter, Character.toLowerCase
  299. (unitBuffer.charAt(counter)));
  300. }
  301. callback.handleProperty(unitBuffer.toString());
  302. token = parseIdentifiers(';', true);
  303. callback.handleValue(unitBuffer.toString());
  304. return token;
  305. }
  306. /**
  307. * Parses identifiers until <code>extraChar</code> is encountered,
  308. * returning the ending token, which will be IDENTIFIER if extraChar
  309. * is found.
  310. */
  311. private int parseIdentifiers(char extraChar,
  312. boolean wantsBlocks) throws IOException {
  313. int nextToken;
  314. int ubl;
  315. unitBuffer.setLength(0);
  316. for (;;) {
  317. nextToken = nextToken(extraChar);
  318. switch (nextToken) {
  319. case IDENTIFIER:
  320. if (tokenBufferLength > 0) {
  321. if (tokenBuffer[tokenBufferLength - 1] == extraChar) {
  322. if (--tokenBufferLength > 0) {
  323. if (readWS && unitBuffer.length() > 0) {
  324. unitBuffer.append(' ');
  325. }
  326. unitBuffer.append(tokenBuffer, 0,
  327. tokenBufferLength);
  328. }
  329. return IDENTIFIER;
  330. }
  331. if (readWS && unitBuffer.length() > 0) {
  332. unitBuffer.append(' ');
  333. }
  334. unitBuffer.append(tokenBuffer, 0, tokenBufferLength);
  335. }
  336. break;
  337. case BRACKET_OPEN:
  338. case BRACE_OPEN:
  339. case PAREN_OPEN:
  340. ubl = unitBuffer.length();
  341. if (wantsBlocks) {
  342. unitBuffer.append(charMapping[nextToken]);
  343. }
  344. parseTillClosed(nextToken);
  345. if (!wantsBlocks) {
  346. unitBuffer.setLength(ubl);
  347. }
  348. break;
  349. case BRACE_CLOSE:
  350. // No need to throw for these two, we return token and
  351. // caller can do whatever.
  352. case BRACKET_CLOSE:
  353. case PAREN_CLOSE:
  354. case END:
  355. // Hit the end
  356. return nextToken;
  357. }
  358. }
  359. }
  360. /**
  361. * Parses till a matching block close is encountered. This is only
  362. * appropriate to be called at the top level (no nesting).
  363. */
  364. private void parseTillClosed(int openToken) throws IOException {
  365. int nextToken;
  366. boolean done = false;
  367. startBlock(openToken);
  368. while (!done) {
  369. nextToken = nextToken((char)0);
  370. switch (nextToken) {
  371. case IDENTIFIER:
  372. if (unitBuffer.length() > 0 && readWS) {
  373. unitBuffer.append(' ');
  374. }
  375. if (tokenBufferLength > 0) {
  376. unitBuffer.append(tokenBuffer, 0, tokenBufferLength);
  377. }
  378. break;
  379. case BRACKET_OPEN: case BRACE_OPEN: case PAREN_OPEN:
  380. if (unitBuffer.length() > 0 && readWS) {
  381. unitBuffer.append(' ');
  382. }
  383. unitBuffer.append(charMapping[nextToken]);
  384. startBlock(nextToken);
  385. break;
  386. case BRACKET_CLOSE: case BRACE_CLOSE: case PAREN_CLOSE:
  387. if (unitBuffer.length() > 0 && readWS) {
  388. unitBuffer.append(' ');
  389. }
  390. unitBuffer.append(charMapping[nextToken]);
  391. endBlock(nextToken);
  392. if (!inBlock()) {
  393. done = true;
  394. }
  395. break;
  396. case END:
  397. // Prematurely hit end.
  398. throw new RuntimeException("Unclosed block");
  399. }
  400. }
  401. }
  402. /**
  403. * Fetches the next token.
  404. */
  405. private int nextToken(char idChar) throws IOException {
  406. readWS = false;
  407. int nextChar = readWS();
  408. switch (nextChar) {
  409. case '\'':
  410. readTill('\'');
  411. if (tokenBufferLength > 0) {
  412. tokenBufferLength--;
  413. }
  414. return IDENTIFIER;
  415. case '"':
  416. readTill('"');
  417. if (tokenBufferLength > 0) {
  418. tokenBufferLength--;
  419. }
  420. return IDENTIFIER;
  421. case '[':
  422. return BRACKET_OPEN;
  423. case ']':
  424. return BRACKET_CLOSE;
  425. case '{':
  426. return BRACE_OPEN;
  427. case '}':
  428. return BRACE_CLOSE;
  429. case '(':
  430. return PAREN_OPEN;
  431. case ')':
  432. return PAREN_CLOSE;
  433. case -1:
  434. return END;
  435. default:
  436. pushChar(nextChar);
  437. getIdentifier(idChar);
  438. return IDENTIFIER;
  439. }
  440. }
  441. /**
  442. * Gets an identifier, returning true if the length of the string is greater than 0,
  443. * stopping when <code>stopChar</code>, whitespace, or one of {}()[] is
  444. * hit.
  445. */
  446. // NOTE: this could be combined with readTill, as they contain somewhat
  447. // similiar functionality.
  448. private boolean getIdentifier(char stopChar) throws IOException {
  449. boolean lastWasEscape = false;
  450. boolean done = false;
  451. int escapeCount = 0;
  452. int escapeChar = 0;
  453. int nextChar;
  454. int intStopChar = (int)stopChar;
  455. // 1 for '\', 2 for valid escape char [0-9a-fA-F], 3 for
  456. // stop character (white space, ()[]{}) 0 otherwise
  457. short type;
  458. int escapeOffset = 0;
  459. tokenBufferLength = 0;
  460. while (!done) {
  461. nextChar = readChar();
  462. switch (nextChar) {
  463. case '\\':
  464. type = 1;
  465. break;
  466. case '0': case '1': case '2': case '3': case '4': case '5':
  467. case '6': case '7': case '8': case '9':
  468. type = 2;
  469. escapeOffset = nextChar - '0';
  470. break;
  471. case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
  472. type = 2;
  473. escapeOffset = nextChar - 'a' + 10;
  474. break;
  475. case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
  476. type = 2;
  477. escapeOffset = nextChar - 'A' + 10;
  478. break;
  479. case '\'': case '"': case '[': case ']': case '{': case '}':
  480. case '(': case ')':
  481. case ' ': case '\n': case '\t': case '\r':
  482. type = 3;
  483. break;
  484. case '/':
  485. type = 4;
  486. break;
  487. case -1:
  488. // Reached the end
  489. done = true;
  490. type = 0;
  491. break;
  492. default:
  493. type = 0;
  494. break;
  495. }
  496. if (lastWasEscape) {
  497. if (type == 2) {
  498. // Continue with escape.
  499. escapeChar = escapeChar * 16 + escapeOffset;
  500. if (++escapeCount == 4) {
  501. lastWasEscape = false;
  502. append((char)escapeChar);
  503. }
  504. }
  505. else {
  506. // no longer escaped
  507. lastWasEscape = false;
  508. if (escapeCount > 0) {
  509. append((char)escapeChar);
  510. // Make this simpler, reprocess the character.
  511. pushChar(nextChar);
  512. }
  513. else if (!done) {
  514. append((char)nextChar);
  515. }
  516. }
  517. }
  518. else if (!done) {
  519. if (type == 1) {
  520. lastWasEscape = true;
  521. escapeChar = escapeCount = 0;
  522. }
  523. else if (type == 3) {
  524. done = true;
  525. pushChar(nextChar);
  526. }
  527. else if (type == 4) {
  528. // Potential comment
  529. nextChar = readChar();
  530. if (nextChar == '*') {
  531. done = true;
  532. readComment();
  533. readWS = true;
  534. }
  535. else {
  536. append('/');
  537. if (nextChar == -1) {
  538. done = true;
  539. }
  540. else {
  541. pushChar(nextChar);
  542. }
  543. }
  544. }
  545. else {
  546. append((char)nextChar);
  547. if (nextChar == intStopChar) {
  548. done = true;
  549. }
  550. }
  551. }
  552. }
  553. return (tokenBufferLength > 0);
  554. }
  555. /**
  556. * Reads till a <code>stopChar</code> is encountered, escaping characters
  557. * as necessary.
  558. */
  559. private void readTill(char stopChar) throws IOException {
  560. boolean lastWasEscape = false;
  561. int escapeCount = 0;
  562. int escapeChar = 0;
  563. int nextChar;
  564. boolean done = false;
  565. int intStopChar = (int)stopChar;
  566. // 1 for '\', 2 for valid escape char [0-9a-fA-F], 0 otherwise
  567. short type;
  568. int escapeOffset = 0;
  569. tokenBufferLength = 0;
  570. while (!done) {
  571. nextChar = readChar();
  572. switch (nextChar) {
  573. case '\\':
  574. type = 1;
  575. break;
  576. case '0': case '1': case '2': case '3': case '4':case '5':
  577. case '6': case '7': case '8': case '9':
  578. type = 2;
  579. escapeOffset = nextChar - '0';
  580. break;
  581. case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
  582. type = 2;
  583. escapeOffset = nextChar - 'a' + 10;
  584. break;
  585. case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
  586. type = 2;
  587. escapeOffset = nextChar - 'A' + 10;
  588. break;
  589. case -1:
  590. // Prematurely reached the end!
  591. throw new RuntimeException("Unclosed " + stopChar);
  592. default:
  593. type = 0;
  594. break;
  595. }
  596. if (lastWasEscape) {
  597. if (type == 2) {
  598. // Continue with escape.
  599. escapeChar = escapeChar * 16 + escapeOffset;
  600. if (++escapeCount == 4) {
  601. lastWasEscape = false;
  602. append((char)escapeChar);
  603. }
  604. }
  605. else {
  606. // no longer escaped
  607. if (escapeCount > 0) {
  608. append((char)escapeChar);
  609. if (type == 1) {
  610. lastWasEscape = true;
  611. escapeChar = escapeCount = 0;
  612. }
  613. else {
  614. if (nextChar == intStopChar) {
  615. done = true;
  616. }
  617. append((char)nextChar);
  618. lastWasEscape = false;
  619. }
  620. }
  621. else {
  622. append((char)nextChar);
  623. lastWasEscape = false;
  624. }
  625. }
  626. }
  627. else if (type == 1) {
  628. lastWasEscape = true;
  629. escapeChar = escapeCount = 0;
  630. }
  631. else {
  632. if (nextChar == intStopChar) {
  633. done = true;
  634. }
  635. append((char)nextChar);
  636. }
  637. }
  638. }
  639. private void append(char character) {
  640. if (tokenBufferLength == tokenBuffer.length) {
  641. char[] newBuffer = new char[tokenBuffer.length * 2];
  642. System.arraycopy(tokenBuffer, 0, newBuffer, 0, tokenBuffer.length);
  643. tokenBuffer = newBuffer;
  644. }
  645. tokenBuffer[tokenBufferLength++] = character;
  646. }
  647. /**
  648. * Parses a comment block.
  649. */
  650. private void readComment() throws IOException {
  651. int nextChar;
  652. for(;;) {
  653. nextChar = readChar();
  654. switch (nextChar) {
  655. case -1:
  656. throw new RuntimeException("Unclosed comment");
  657. case '*':
  658. nextChar = readChar();
  659. if (nextChar == '/') {
  660. return;
  661. }
  662. else if (nextChar == -1) {
  663. throw new RuntimeException("Unclosed comment");
  664. }
  665. else {
  666. pushChar(nextChar);
  667. }
  668. break;
  669. default:
  670. break;
  671. }
  672. }
  673. }
  674. /**
  675. * Called when a block start is encountered ({[.
  676. */
  677. private void startBlock(int startToken) {
  678. if (stackCount == unitStack.length) {
  679. int[] newUS = new int[stackCount * 2];
  680. System.arraycopy(unitStack, 0, newUS, 0, stackCount);
  681. unitStack = newUS;
  682. }
  683. unitStack[stackCount++] = startToken;
  684. }
  685. /**
  686. * Called when an end block is encountered )]}
  687. */
  688. private void endBlock(int endToken) {
  689. int startToken;
  690. switch (endToken) {
  691. case BRACKET_CLOSE:
  692. startToken = BRACKET_OPEN;
  693. break;
  694. case BRACE_CLOSE:
  695. startToken = BRACE_OPEN;
  696. break;
  697. case PAREN_CLOSE:
  698. startToken = PAREN_OPEN;
  699. break;
  700. default:
  701. // Will never happen.
  702. startToken = -1;
  703. break;
  704. }
  705. if (stackCount > 0 && unitStack[stackCount - 1] == startToken) {
  706. stackCount--;
  707. }
  708. else {
  709. // Invalid state, should do something.
  710. throw new RuntimeException("Unmatched block");
  711. }
  712. }
  713. /**
  714. * @return true if currently in a block.
  715. */
  716. private boolean inBlock() {
  717. return (stackCount > 0);
  718. }
  719. /**
  720. * Skips any white space, returning the character after the white space.
  721. */
  722. private int readWS() throws IOException {
  723. int nextChar;
  724. while ((nextChar = readChar()) != -1 &&
  725. Character.isWhitespace((char)nextChar)) {
  726. readWS = true;
  727. }
  728. return nextChar;
  729. }
  730. /**
  731. * Reads a character from the stream.
  732. */
  733. private int readChar() throws IOException {
  734. if (didPushChar) {
  735. didPushChar = false;
  736. return pushedChar;
  737. }
  738. return reader.read();
  739. // Uncomment the following to do case insensitive parsing.
  740. /*
  741. if (retValue != -1) {
  742. return (int)Character.toLowerCase((char)retValue);
  743. }
  744. return retValue;
  745. */
  746. }
  747. /**
  748. * Supports one character look ahead, this will throw if called twice
  749. * in a row.
  750. */
  751. private void pushChar(int tempChar) {
  752. if (didPushChar) {
  753. // Should never happen.
  754. throw new RuntimeException("Can not handle look ahead of more than one character");
  755. }
  756. didPushChar = true;
  757. pushedChar = tempChar;
  758. }
  759. }