001 /*
002 * Copyright 1999-2000 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025 package javax.swing.text.html;
026
027 import java.io.*;
028
029 /**
030 * A CSS parser. This works by way of a delegate that implements the
031 * CSSParserCallback interface. The delegate is notified of the following
032 * events:
033 * <ul>
034 * <li>Import statement: <code>handleImport</code>
035 * <li>Selectors <code>handleSelector</code>. This is invoked for each
036 * string. For example if the Reader contained p, bar , a {}, the delegate
037 * would be notified 4 times, for 'p,' 'bar' ',' and 'a'.
038 * <li>When a rule starts, <code>startRule</code>
039 * <li>Properties in the rule via the <code>handleProperty</code>. This
040 * is invoked one per property/value key, eg font size: foo;, would
041 * cause the delegate to be notified once with a value of 'font size'.
042 * <li>Values in the rule via the <code>handleValue</code>, this is notified
043 * for the total value.
044 * <li>When a rule ends, <code>endRule</code>
045 * </ul>
046 * This will parse much more than CSS 1, and loosely implements the
047 * recommendation for <i>Forward-compatible parsing</i> in section
048 * 7.1 of the CSS spec found at:
049 * <a href=http://www.w3.org/TR/REC-CSS1>http://www.w3.org/TR/REC-CSS1</a>.
050 * If an error results in parsing, a RuntimeException will be thrown.
051 * <p>
052 * This will preserve case. If the callback wishes to treat certain poritions
053 * case insensitively (such as selectors), it should use toLowerCase, or
054 * something similar.
055 *
056 * @author Scott Violet
057 * @version 1.15 05/05/07
058 */
059 class CSSParser {
060 // Parsing something like the following:
061 // (@rule | ruleset | block)*
062 //
063 // @rule (block | identifier)*; (block with {} ends @rule)
064 // block matching [] () {} (that is, [()] is a block, [(){}{[]}]
065 // is a block, ()[] is two blocks)
066 // identifier "*" | '*' | anything but a [](){} and whitespace
067 //
068 // ruleset selector decblock
069 // selector (identifier | (block, except block '{}') )*
070 // declblock declaration* block*
071 // declaration (identifier* stopping when identifier ends with :)
072 // (identifier* stopping when identifier ends with ;)
073 //
074 // comments /* */ can appear any where, and are stripped.
075
076 // identifier - letters, digits, dashes and escaped characters
077 // block starts with { ends with matching }, () [] and {} always occur
078 // in matching pairs, '' and "" also occur in pairs, except " may be
079
080 // Indicates the type of token being parsed.
081 private static final int IDENTIFIER = 1;
082 private static final int BRACKET_OPEN = 2;
083 private static final int BRACKET_CLOSE = 3;
084 private static final int BRACE_OPEN = 4;
085 private static final int BRACE_CLOSE = 5;
086 private static final int PAREN_OPEN = 6;
087 private static final int PAREN_CLOSE = 7;
088 private static final int END = -1;
089
090 private static final char[] charMapping = { 0, 0, '[', ']', '{',
091 '}', '(', ')', 0 };
092
093 /** Set to true if one character has been read ahead. */
094 private boolean didPushChar;
095 /** The read ahead character. */
096 private int pushedChar;
097 /** Temporary place to hold identifiers. */
098 private StringBuffer unitBuffer;
099 /** Used to indicate blocks. */
100 private int[] unitStack;
101 /** Number of valid blocks. */
102 private int stackCount;
103 /** Holds the incoming CSS rules. */
104 private Reader reader;
105 /** Set to true when the first non @ rule is encountered. */
106 private boolean encounteredRuleSet;
107 /** Notified of state. */
108 private CSSParserCallback callback;
109 /** nextToken() inserts the string here. */
110 private char[] tokenBuffer;
111 /** Current number of chars in tokenBufferLength. */
112 private int tokenBufferLength;
113 /** Set to true if any whitespace is read. */
114 private boolean readWS;
115
116 // The delegate interface.
117 static interface CSSParserCallback {
118 /** Called when an @import is encountered. */
119 void handleImport(String importString);
120
121 // There is currently no way to distinguish between '"foo,"' and
122 // 'foo,'. But this generally isn't valid CSS. If it becomes
123 // a problem, handleSelector will have to be told if the string is
124 // quoted.
125 void handleSelector(String selector);
126
127 void startRule();
128
129 // Property names are mapped to lower case before being passed to
130 // the delegate.
131 void handleProperty(String property);
132
133 void handleValue(String value);
134
135 void endRule();
136 }
137
138 CSSParser() {
139 unitStack = new int[2];
140 tokenBuffer = new char[80];
141 unitBuffer = new StringBuffer();
142 }
143
144 void parse(Reader reader, CSSParserCallback callback, boolean inRule)
145 throws IOException {
146 this .callback = callback;
147 stackCount = tokenBufferLength = 0;
148 this .reader = reader;
149 encounteredRuleSet = false;
150 try {
151 if (inRule) {
152 parseDeclarationBlock();
153 } else {
154 while (getNextStatement())
155 ;
156 }
157 } finally {
158 callback = null;
159 reader = null;
160 }
161 }
162
163 /**
164 * Gets the next statement, returning false if the end is reached. A
165 * statement is either an @rule, or a ruleset.
166 */
167 private boolean getNextStatement() throws IOException {
168 unitBuffer.setLength(0);
169
170 int token = nextToken((char) 0);
171
172 switch (token) {
173 case IDENTIFIER:
174 if (tokenBufferLength > 0) {
175 if (tokenBuffer[0] == '@') {
176 parseAtRule();
177 } else {
178 encounteredRuleSet = true;
179 parseRuleSet();
180 }
181 }
182 return true;
183 case BRACKET_OPEN:
184 case BRACE_OPEN:
185 case PAREN_OPEN:
186 parseTillClosed(token);
187 return true;
188
189 case BRACKET_CLOSE:
190 case BRACE_CLOSE:
191 case PAREN_CLOSE:
192 // Shouldn't happen...
193 throw new RuntimeException(
194 "Unexpected top level block close");
195
196 case END:
197 return false;
198 }
199 return true;
200 }
201
202 /**
203 * Parses an @ rule, stopping at a matching brace pair, or ;.
204 */
205 private void parseAtRule() throws IOException {
206 // PENDING: make this more effecient.
207 boolean done = false;
208 boolean isImport = (tokenBufferLength == 7
209 && tokenBuffer[0] == '@' && tokenBuffer[1] == 'i'
210 && tokenBuffer[2] == 'm' && tokenBuffer[3] == 'p'
211 && tokenBuffer[4] == 'o' && tokenBuffer[5] == 'r' && tokenBuffer[6] == 't');
212
213 unitBuffer.setLength(0);
214 while (!done) {
215 int nextToken = nextToken(';');
216
217 switch (nextToken) {
218 case IDENTIFIER:
219 if (tokenBufferLength > 0
220 && tokenBuffer[tokenBufferLength - 1] == ';') {
221 --tokenBufferLength;
222 done = true;
223 }
224 if (tokenBufferLength > 0) {
225 if (unitBuffer.length() > 0 && readWS) {
226 unitBuffer.append(' ');
227 }
228 unitBuffer
229 .append(tokenBuffer, 0, tokenBufferLength);
230 }
231 break;
232
233 case BRACE_OPEN:
234 if (unitBuffer.length() > 0 && readWS) {
235 unitBuffer.append(' ');
236 }
237 unitBuffer.append(charMapping[nextToken]);
238 parseTillClosed(nextToken);
239 done = true;
240 // Skip a tailing ';', not really to spec.
241 {
242 int nextChar = readWS();
243 if (nextChar != -1 && nextChar != ';') {
244 pushChar(nextChar);
245 }
246 }
247 break;
248
249 case BRACKET_OPEN:
250 case PAREN_OPEN:
251 unitBuffer.append(charMapping[nextToken]);
252 parseTillClosed(nextToken);
253 break;
254
255 case BRACKET_CLOSE:
256 case BRACE_CLOSE:
257 case PAREN_CLOSE:
258 throw new RuntimeException("Unexpected close in @ rule");
259
260 case END:
261 done = true;
262 break;
263 }
264 }
265 if (isImport && !encounteredRuleSet) {
266 callback.handleImport(unitBuffer.toString());
267 }
268 }
269
270 /**
271 * Parses the next rule set, which is a selector followed by a
272 * declaration block.
273 */
274 private void parseRuleSet() throws IOException {
275 if (parseSelectors()) {
276 callback.startRule();
277 parseDeclarationBlock();
278 callback.endRule();
279 }
280 }
281
282 /**
283 * Parses a set of selectors, returning false if the end of the stream
284 * is reached.
285 */
286 private boolean parseSelectors() throws IOException {
287 // Parse the selectors
288 int nextToken;
289
290 if (tokenBufferLength > 0) {
291 callback.handleSelector(new String(tokenBuffer, 0,
292 tokenBufferLength));
293 }
294
295 unitBuffer.setLength(0);
296 for (;;) {
297 while ((nextToken = nextToken((char) 0)) == IDENTIFIER) {
298 if (tokenBufferLength > 0) {
299 callback.handleSelector(new String(tokenBuffer, 0,
300 tokenBufferLength));
301 }
302 }
303 switch (nextToken) {
304 case BRACE_OPEN:
305 return true;
306
307 case BRACKET_OPEN:
308 case PAREN_OPEN:
309 parseTillClosed(nextToken);
310 // Not too sure about this, how we handle this isn't very
311 // well spec'd.
312 unitBuffer.setLength(0);
313 break;
314
315 case BRACKET_CLOSE:
316 case BRACE_CLOSE:
317 case PAREN_CLOSE:
318 throw new RuntimeException(
319 "Unexpected block close in selector");
320
321 case END:
322 // Prematurely hit end.
323 return false;
324 }
325 }
326 }
327
328 /**
329 * Parses a declaration block. Which a number of declarations followed
330 * by a })].
331 */
332 private void parseDeclarationBlock() throws IOException {
333 for (;;) {
334 int token = parseDeclaration();
335 switch (token) {
336 case END:
337 case BRACE_CLOSE:
338 return;
339
340 case BRACKET_CLOSE:
341 case PAREN_CLOSE:
342 // Bail
343 throw new RuntimeException(
344 "Unexpected close in declaration block");
345 case IDENTIFIER:
346 break;
347 }
348 }
349 }
350
351 /**
352 * Parses a single declaration, which is an identifier a : and another
353 * identifier. This returns the last token seen.
354 */
355 // identifier+: identifier* ;|}
356 private int parseDeclaration() throws IOException {
357 int token;
358
359 if ((token = parseIdentifiers(':', false)) != IDENTIFIER) {
360 return token;
361 }
362 // Make the property name to lowercase
363 for (int counter = unitBuffer.length() - 1; counter >= 0; counter--) {
364 unitBuffer.setCharAt(counter, Character
365 .toLowerCase(unitBuffer.charAt(counter)));
366 }
367 callback.handleProperty(unitBuffer.toString());
368
369 token = parseIdentifiers(';', true);
370 callback.handleValue(unitBuffer.toString());
371 return token;
372 }
373
374 /**
375 * Parses identifiers until <code>extraChar</code> is encountered,
376 * returning the ending token, which will be IDENTIFIER if extraChar
377 * is found.
378 */
379 private int parseIdentifiers(char extraChar, boolean wantsBlocks)
380 throws IOException {
381 int nextToken;
382 int ubl;
383
384 unitBuffer.setLength(0);
385 for (;;) {
386 nextToken = nextToken(extraChar);
387
388 switch (nextToken) {
389 case IDENTIFIER:
390 if (tokenBufferLength > 0) {
391 if (tokenBuffer[tokenBufferLength - 1] == extraChar) {
392 if (--tokenBufferLength > 0) {
393 if (readWS && unitBuffer.length() > 0) {
394 unitBuffer.append(' ');
395 }
396 unitBuffer.append(tokenBuffer, 0,
397 tokenBufferLength);
398 }
399 return IDENTIFIER;
400 }
401 if (readWS && unitBuffer.length() > 0) {
402 unitBuffer.append(' ');
403 }
404 unitBuffer
405 .append(tokenBuffer, 0, tokenBufferLength);
406 }
407 break;
408
409 case BRACKET_OPEN:
410 case BRACE_OPEN:
411 case PAREN_OPEN:
412 ubl = unitBuffer.length();
413 if (wantsBlocks) {
414 unitBuffer.append(charMapping[nextToken]);
415 }
416 parseTillClosed(nextToken);
417 if (!wantsBlocks) {
418 unitBuffer.setLength(ubl);
419 }
420 break;
421
422 case BRACE_CLOSE:
423 // No need to throw for these two, we return token and
424 // caller can do whatever.
425 case BRACKET_CLOSE:
426 case PAREN_CLOSE:
427 case END:
428 // Hit the end
429 return nextToken;
430 }
431 }
432 }
433
434 /**
435 * Parses till a matching block close is encountered. This is only
436 * appropriate to be called at the top level (no nesting).
437 */
438 private void parseTillClosed(int openToken) throws IOException {
439 int nextToken;
440 boolean done = false;
441
442 startBlock(openToken);
443 while (!done) {
444 nextToken = nextToken((char) 0);
445 switch (nextToken) {
446 case IDENTIFIER:
447 if (unitBuffer.length() > 0 && readWS) {
448 unitBuffer.append(' ');
449 }
450 if (tokenBufferLength > 0) {
451 unitBuffer
452 .append(tokenBuffer, 0, tokenBufferLength);
453 }
454 break;
455
456 case BRACKET_OPEN:
457 case BRACE_OPEN:
458 case PAREN_OPEN:
459 if (unitBuffer.length() > 0 && readWS) {
460 unitBuffer.append(' ');
461 }
462 unitBuffer.append(charMapping[nextToken]);
463 startBlock(nextToken);
464 break;
465
466 case BRACKET_CLOSE:
467 case BRACE_CLOSE:
468 case PAREN_CLOSE:
469 if (unitBuffer.length() > 0 && readWS) {
470 unitBuffer.append(' ');
471 }
472 unitBuffer.append(charMapping[nextToken]);
473 endBlock(nextToken);
474 if (!inBlock()) {
475 done = true;
476 }
477 break;
478
479 case END:
480 // Prematurely hit end.
481 throw new RuntimeException("Unclosed block");
482 }
483 }
484 }
485
486 /**
487 * Fetches the next token.
488 */
489 private int nextToken(char idChar) throws IOException {
490 readWS = false;
491
492 int nextChar = readWS();
493
494 switch (nextChar) {
495 case '\'':
496 readTill('\'');
497 if (tokenBufferLength > 0) {
498 tokenBufferLength--;
499 }
500 return IDENTIFIER;
501 case '"':
502 readTill('"');
503 if (tokenBufferLength > 0) {
504 tokenBufferLength--;
505 }
506 return IDENTIFIER;
507 case '[':
508 return BRACKET_OPEN;
509 case ']':
510 return BRACKET_CLOSE;
511 case '{':
512 return BRACE_OPEN;
513 case '}':
514 return BRACE_CLOSE;
515 case '(':
516 return PAREN_OPEN;
517 case ')':
518 return PAREN_CLOSE;
519 case -1:
520 return END;
521 default:
522 pushChar(nextChar);
523 getIdentifier(idChar);
524 return IDENTIFIER;
525 }
526 }
527
528 /**
529 * Gets an identifier, returning true if the length of the string is greater than 0,
530 * stopping when <code>stopChar</code>, whitespace, or one of {}()[] is
531 * hit.
532 */
533 // NOTE: this could be combined with readTill, as they contain somewhat
534 // similiar functionality.
535 private boolean getIdentifier(char stopChar) throws IOException {
536 boolean lastWasEscape = false;
537 boolean done = false;
538 int escapeCount = 0;
539 int escapeChar = 0;
540 int nextChar;
541 int intStopChar = (int) stopChar;
542 // 1 for '\', 2 for valid escape char [0-9a-fA-F], 3 for
543 // stop character (white space, ()[]{}) 0 otherwise
544 short type;
545 int escapeOffset = 0;
546
547 tokenBufferLength = 0;
548 while (!done) {
549 nextChar = readChar();
550 switch (nextChar) {
551 case '\\':
552 type = 1;
553 break;
554
555 case '0':
556 case '1':
557 case '2':
558 case '3':
559 case '4':
560 case '5':
561 case '6':
562 case '7':
563 case '8':
564 case '9':
565 type = 2;
566 escapeOffset = nextChar - '0';
567 break;
568
569 case 'a':
570 case 'b':
571 case 'c':
572 case 'd':
573 case 'e':
574 case 'f':
575 type = 2;
576 escapeOffset = nextChar - 'a' + 10;
577 break;
578
579 case 'A':
580 case 'B':
581 case 'C':
582 case 'D':
583 case 'E':
584 case 'F':
585 type = 2;
586 escapeOffset = nextChar - 'A' + 10;
587 break;
588
589 case '\'':
590 case '"':
591 case '[':
592 case ']':
593 case '{':
594 case '}':
595 case '(':
596 case ')':
597 case ' ':
598 case '\n':
599 case '\t':
600 case '\r':
601 type = 3;
602 break;
603
604 case '/':
605 type = 4;
606 break;
607
608 case -1:
609 // Reached the end
610 done = true;
611 type = 0;
612 break;
613
614 default:
615 type = 0;
616 break;
617 }
618 if (lastWasEscape) {
619 if (type == 2) {
620 // Continue with escape.
621 escapeChar = escapeChar * 16 + escapeOffset;
622 if (++escapeCount == 4) {
623 lastWasEscape = false;
624 append((char) escapeChar);
625 }
626 } else {
627 // no longer escaped
628 lastWasEscape = false;
629 if (escapeCount > 0) {
630 append((char) escapeChar);
631 // Make this simpler, reprocess the character.
632 pushChar(nextChar);
633 } else if (!done) {
634 append((char) nextChar);
635 }
636 }
637 } else if (!done) {
638 if (type == 1) {
639 lastWasEscape = true;
640 escapeChar = escapeCount = 0;
641 } else if (type == 3) {
642 done = true;
643 pushChar(nextChar);
644 } else if (type == 4) {
645 // Potential comment
646 nextChar = readChar();
647 if (nextChar == '*') {
648 done = true;
649 readComment();
650 readWS = true;
651 } else {
652 append('/');
653 if (nextChar == -1) {
654 done = true;
655 } else {
656 pushChar(nextChar);
657 }
658 }
659 } else {
660 append((char) nextChar);
661 if (nextChar == intStopChar) {
662 done = true;
663 }
664 }
665 }
666 }
667 return (tokenBufferLength > 0);
668 }
669
670 /**
671 * Reads till a <code>stopChar</code> is encountered, escaping characters
672 * as necessary.
673 */
674 private void readTill(char stopChar) throws IOException {
675 boolean lastWasEscape = false;
676 int escapeCount = 0;
677 int escapeChar = 0;
678 int nextChar;
679 boolean done = false;
680 int intStopChar = (int) stopChar;
681 // 1 for '\', 2 for valid escape char [0-9a-fA-F], 0 otherwise
682 short type;
683 int escapeOffset = 0;
684
685 tokenBufferLength = 0;
686 while (!done) {
687 nextChar = readChar();
688 switch (nextChar) {
689 case '\\':
690 type = 1;
691 break;
692
693 case '0':
694 case '1':
695 case '2':
696 case '3':
697 case '4':
698 case '5':
699 case '6':
700 case '7':
701 case '8':
702 case '9':
703 type = 2;
704 escapeOffset = nextChar - '0';
705 break;
706
707 case 'a':
708 case 'b':
709 case 'c':
710 case 'd':
711 case 'e':
712 case 'f':
713 type = 2;
714 escapeOffset = nextChar - 'a' + 10;
715 break;
716
717 case 'A':
718 case 'B':
719 case 'C':
720 case 'D':
721 case 'E':
722 case 'F':
723 type = 2;
724 escapeOffset = nextChar - 'A' + 10;
725 break;
726
727 case -1:
728 // Prematurely reached the end!
729 throw new RuntimeException("Unclosed " + stopChar);
730
731 default:
732 type = 0;
733 break;
734 }
735 if (lastWasEscape) {
736 if (type == 2) {
737 // Continue with escape.
738 escapeChar = escapeChar * 16 + escapeOffset;
739 if (++escapeCount == 4) {
740 lastWasEscape = false;
741 append((char) escapeChar);
742 }
743 } else {
744 // no longer escaped
745 if (escapeCount > 0) {
746 append((char) escapeChar);
747 if (type == 1) {
748 lastWasEscape = true;
749 escapeChar = escapeCount = 0;
750 } else {
751 if (nextChar == intStopChar) {
752 done = true;
753 }
754 append((char) nextChar);
755 lastWasEscape = false;
756 }
757 } else {
758 append((char) nextChar);
759 lastWasEscape = false;
760 }
761 }
762 } else if (type == 1) {
763 lastWasEscape = true;
764 escapeChar = escapeCount = 0;
765 } else {
766 if (nextChar == intStopChar) {
767 done = true;
768 }
769 append((char) nextChar);
770 }
771 }
772 }
773
774 private void append(char character) {
775 if (tokenBufferLength == tokenBuffer.length) {
776 char[] newBuffer = new char[tokenBuffer.length * 2];
777 System.arraycopy(tokenBuffer, 0, newBuffer, 0,
778 tokenBuffer.length);
779 tokenBuffer = newBuffer;
780 }
781 tokenBuffer[tokenBufferLength++] = character;
782 }
783
784 /**
785 * Parses a comment block.
786 */
787 private void readComment() throws IOException {
788 int nextChar;
789
790 for (;;) {
791 nextChar = readChar();
792 switch (nextChar) {
793 case -1:
794 throw new RuntimeException("Unclosed comment");
795 case '*':
796 nextChar = readChar();
797 if (nextChar == '/') {
798 return;
799 } else if (nextChar == -1) {
800 throw new RuntimeException("Unclosed comment");
801 } else {
802 pushChar(nextChar);
803 }
804 break;
805 default:
806 break;
807 }
808 }
809 }
810
811 /**
812 * Called when a block start is encountered ({[.
813 */
814 private void startBlock(int startToken) {
815 if (stackCount == unitStack.length) {
816 int[] newUS = new int[stackCount * 2];
817
818 System.arraycopy(unitStack, 0, newUS, 0, stackCount);
819 unitStack = newUS;
820 }
821 unitStack[stackCount++] = startToken;
822 }
823
824 /**
825 * Called when an end block is encountered )]}
826 */
827 private void endBlock(int endToken) {
828 int startToken;
829
830 switch (endToken) {
831 case BRACKET_CLOSE:
832 startToken = BRACKET_OPEN;
833 break;
834 case BRACE_CLOSE:
835 startToken = BRACE_OPEN;
836 break;
837 case PAREN_CLOSE:
838 startToken = PAREN_OPEN;
839 break;
840 default:
841 // Will never happen.
842 startToken = -1;
843 break;
844 }
845 if (stackCount > 0 && unitStack[stackCount - 1] == startToken) {
846 stackCount--;
847 } else {
848 // Invalid state, should do something.
849 throw new RuntimeException("Unmatched block");
850 }
851 }
852
853 /**
854 * @return true if currently in a block.
855 */
856 private boolean inBlock() {
857 return (stackCount > 0);
858 }
859
860 /**
861 * Skips any white space, returning the character after the white space.
862 */
863 private int readWS() throws IOException {
864 int nextChar;
865 while ((nextChar = readChar()) != -1
866 && Character.isWhitespace((char) nextChar)) {
867 readWS = true;
868 }
869 return nextChar;
870 }
871
872 /**
873 * Reads a character from the stream.
874 */
875 private int readChar() throws IOException {
876 if (didPushChar) {
877 didPushChar = false;
878 return pushedChar;
879 }
880 return reader.read();
881 // Uncomment the following to do case insensitive parsing.
882 /*
883 if (retValue != -1) {
884 return (int)Character.toLowerCase((char)retValue);
885 }
886 return retValue;
887 */
888 }
889
890 /**
891 * Supports one character look ahead, this will throw if called twice
892 * in a row.
893 */
894 private void pushChar(int tempChar) {
895 if (didPushChar) {
896 // Should never happen.
897 throw new RuntimeException(
898 "Can not handle look ahead of more than one character");
899 }
900 didPushChar = true;
901 pushedChar = tempChar;
902 }
903 }
|