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