KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > wings > style > CSSParser


1 /*
2  * $Id: CSSParser.java,v 1.3 2004/12/01 07:54:27 hengels Exp $
3  * Copyright 2000,2005 wingS development team.
4  *
5  * This file is part of wingS (http://www.j-wings.org).
6  *
7  * wingS is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU Lesser General Public License
9  * as published by the Free Software Foundation; either version 2.1
10  * of the License, or (at your option) any later version.
11  *
12  * Please see COPYING for the complete licence.
13  */

14 package org.wings.style;
15
16 import java.io.IOException JavaDoc;
17 import java.io.Reader JavaDoc;
18
19 /**
20  * A CSS parser. This works by way of a delegate that implements the
21  * CSSParserCallback interface. The delegate is notified of the following
22  * events:
23  * <ul>
24  * <li>Import statement: <code>handleImport</code>
25  * <li>Selectors <code>handleSelector</code>. This is invoked for each
26  * string. For example if the Reader contained p, bar , a {}, the delegate
27  * would be notified 4 times, for 'p,' 'bar' ',' and 'a'.
28  * <li>When a rule starts, <code>startRule</code>
29  * <li>Properties in the rule via the <code>handleProperty</code>. This
30  * is invoked one per property/value key, eg font size: foo;, would
31  * cause the delegate to be notified once with a value of 'font size'.
32  * <li>Values in the rule via the <code>handleValue</code>, this is notified
33  * for the total value.
34  * <li>When a rule ends, <code>endRule</code>
35  * </ul>
36  * This will parse much more than CSS 1, and loosely implements the
37  * recommendation for <i>Forward-compatible parsing</i> in section
38  * 7.1 of the CSS spec found at:
39  * <a HREF=http://www.w3.org/TR/REC-CSS1>http://www.w3.org/TR/REC-CSS1</a>.
40  * If an error results in parsing, a RuntimeException will be thrown.
41  * <p/>
42  * This will preserve case. If the callback wishes to treat certain poritions
43  * case insensitively (such as selectors), it should use toLowerCase, or
44  * something similar.
45  *
46  * @author Scott Violet
47  * @version 1.5 03/20/00
48  */

49 class CSSParser {
50     // Parsing something like the following:
51
// (@rule | ruleset | block)*
52
//
53
// @rule (block | identifier)*; (block with {} ends @rule)
54
// block matching [] () {} (that is, [()] is a block, [(){}{[]}]
55
// is a block, ()[] is two blocks)
56
// identifier "*" | '*' | anything but a [](){} and whitespace
57
//
58
// ruleset selector decblock
59
// selector (identifier | (block, except block '{}') )*
60
// declblock declaration* block*
61
// declaration (identifier* stopping when identifier ends with :)
62
// (identifier* stopping when identifier ends with ;)
63
//
64
// comments /* */ can appear any where, and are stripped.
65

66
67     // identifier - letters, digits, dashes and escaped characters
68
// block starts with { ends with matching }, () [] and {} always occur
69
// in matching pairs, '' and "" also occur in pairs, except " may be
70

71
72     // Indicates the type of token being parsed.
73
private static final int IDENTIFIER = 1;
74     private static final int BRACKET_OPEN = 2;
75     private static final int BRACKET_CLOSE = 3;
76     private static final int BRACE_OPEN = 4;
77     private static final int BRACE_CLOSE = 5;
78     private static final int PAREN_OPEN = 6;
79     private static final int PAREN_CLOSE = 7;
80     private static final int END = -1;
81
82     private static final char[] charMapping = {0, 0, '[', ']', '{', '}', '(',
83                                                ')', 0};
84
85
86     /**
87      * Set to true if one character has been read ahead.
88      */

89     private boolean didPushChar;
90     /**
91      * The read ahead character.
92      */

93     private int pushedChar;
94     /**
95      * Temporary place to hold identifiers.
96      */

97     private StringBuffer JavaDoc unitBuffer;
98     /**
99      * Used to indicate blocks.
100      */

101     private int[] unitStack;
102     /**
103      * Number of valid blocks.
104      */

105     private int stackCount;
106     /**
107      * Holds the incoming CSS rules.
108      */

109     private Reader JavaDoc reader;
110     /**
111      * Set to true when the first non @ rule is encountered.
112      */

113     private boolean encounteredRuleSet;
114     /**
115      * Notified of state.
116      */

117     private CSSParserCallback callback;
118     /**
119      * nextToken() inserts the string here.
120      */

121     private char[] tokenBuffer;
122     /**
123      * Current number of chars in tokenBufferLength.
124      */

125     private int tokenBufferLength;
126     /**
127      * Set to true if any whitespace is read.
128      */

129     private boolean readWS;
130
131
132     // The delegate interface.
133
static interface CSSParserCallback {
134         /**
135          * Called when an @import is encountered.
136          */

137         void handleImport(String JavaDoc importString);
138
139         // There is currently no way to distinguish between '"foo,"' and
140
// 'foo,'. But this generally isn't valid CSS. If it becomes
141
// a problem, handleSelector will have to be told if the string is
142
// quoted.
143
void handleSelector(String JavaDoc selector);
144
145         void startRule();
146
147         // Property names are mapped to lower case before being passed to
148
// the delegate.
149
void handleProperty(String JavaDoc property);
150
151         void handleValue(String JavaDoc value);
152
153         void endRule();
154     }
155
156     CSSParser() {
157         unitStack = new int[2];
158         tokenBuffer = new char[80];
159         unitBuffer = new StringBuffer JavaDoc();
160     }
161
162     void parse(Reader JavaDoc reader, CSSParserCallback callback,
163                boolean inRule) throws IOException JavaDoc {
164         this.callback = callback;
165         stackCount = tokenBufferLength = 0;
166         this.reader = reader;
167         encounteredRuleSet = false;
168         try {
169             if (inRule) {
170                 parseDeclarationBlock();
171             } else {
172                 while (getNextStatement()) ;
173             }
174         } finally {
175             callback = null;
176             reader = null;
177         }
178     }
179
180     /**
181      * Gets the next statement, returning false if the end is reached. A
182      * statement is either an @rule, or a ruleset.
183      */

184     private boolean getNextStatement() throws IOException JavaDoc {
185         unitBuffer.setLength(0);
186
187         int token = nextToken((char) 0);
188
189         switch (token) {
190             case IDENTIFIER:
191                 if (tokenBufferLength > 0) {
192                     if (tokenBuffer[0] == '@') {
193                         parseAtRule();
194                     } else {
195                         encounteredRuleSet = true;
196                         parseRuleSet();
197                     }
198                 }
199                 return true;
200             case BRACKET_OPEN:
201             case BRACE_OPEN:
202             case PAREN_OPEN:
203                 parseTillClosed(token);
204                 return true;
205
206             case BRACKET_CLOSE:
207             case BRACE_CLOSE:
208             case PAREN_CLOSE:
209                 // Shouldn't happen...
210
throw new RuntimeException JavaDoc("Unexpected top level block close");
211
212             case END:
213                 return false;
214         }
215         return true;
216     }
217
218     /**
219      * Parses an @ rule, stopping at a matching brace pair, or ;.
220      */

221     private void parseAtRule() throws IOException JavaDoc {
222         // PENDING: make this more effecient.
223
boolean done = false;
224         boolean isImport = (tokenBufferLength == 7 &&
225                 tokenBuffer[0] == '@' && tokenBuffer[1] == 'i' &&
226                 tokenBuffer[2] == 'm' && tokenBuffer[3] == 'p' &&
227                 tokenBuffer[4] == 'o' && tokenBuffer[5] == 'r' &&
228                 tokenBuffer[6] == 't');
229
230         unitBuffer.setLength(0);
231         while (!done) {
232             int nextToken = nextToken(';');
233
234             switch (nextToken) {
235                 case IDENTIFIER:
236                     if (tokenBufferLength > 0 &&
237                             tokenBuffer[tokenBufferLength - 1] == ';') {
238                         --tokenBufferLength;
239                         done = true;
240                     }
241                     if (tokenBufferLength > 0) {
242                         if (unitBuffer.length() > 0 && readWS) {
243                             unitBuffer.append(' ');
244                         }
245                         unitBuffer.append(tokenBuffer, 0, tokenBufferLength);
246                     }
247                     break;
248
249                 case BRACE_OPEN:
250                     if (unitBuffer.length() > 0 && readWS) {
251                         unitBuffer.append(' ');
252                     }
253                     unitBuffer.append(charMapping[nextToken]);
254                     parseTillClosed(nextToken);
255                     done = true;
256                     // Skip a tailing ';', not really to spec.
257
{
258                         int nextChar = readWS();
259                         if (nextChar != -1 && nextChar != ';') {
260                             pushChar(nextChar);
261                         }
262                     }
263                     break;
264
265                 case BRACKET_OPEN:
266                 case PAREN_OPEN:
267                     unitBuffer.append(charMapping[nextToken]);
268                     parseTillClosed(nextToken);
269                     break;
270
271                 case BRACKET_CLOSE:
272                 case BRACE_CLOSE:
273                 case PAREN_CLOSE:
274                     throw new RuntimeException JavaDoc("Unexpected close in @ rule");
275
276                 case END:
277                     done = true;
278                     break;
279             }
280         }
281         if (isImport && !encounteredRuleSet) {
282             callback.handleImport(unitBuffer.toString());
283         }
284     }
285
286     /**
287      * Parses the next rule set, which is a selector followed by a
288      * declaration block.
289      */

290     private void parseRuleSet() throws IOException JavaDoc {
291         if (parseSelectors()) {
292             callback.startRule();
293             parseDeclarationBlock();
294             callback.endRule();
295         }
296     }
297
298     /**
299      * Parses a set of selectors, returning false if the end of the stream
300      * is reached.
301      */

302     private boolean parseSelectors() throws IOException JavaDoc {
303         // Parse the selectors
304
int nextToken;
305
306         if (tokenBufferLength > 0) {
307             callback.handleSelector(new String JavaDoc(tokenBuffer, 0,
308                     tokenBufferLength));
309         }
310
311         unitBuffer.setLength(0);
312         for (; ;) {
313             while ((nextToken = nextToken((char) 0)) == IDENTIFIER) {
314                 if (tokenBufferLength > 0) {
315                     callback.handleSelector(new String JavaDoc(tokenBuffer, 0,
316                             tokenBufferLength));
317                 }
318             }
319             switch (nextToken) {
320                 case BRACE_OPEN:
321                     return true;
322
323                 case BRACKET_OPEN:
324                 case PAREN_OPEN:
325                     parseTillClosed(nextToken);
326                     // Not too sure about this, how we handle this isn't very
327
// well spec'd.
328
unitBuffer.setLength(0);
329                     break;
330
331                 case BRACKET_CLOSE:
332                 case BRACE_CLOSE:
333                 case PAREN_CLOSE:
334                     throw new RuntimeException JavaDoc("Unexpected block close in selector");
335
336                 case END:
337                     // Prematurely hit end.
338
return false;
339             }
340         }
341     }
342
343     /**
344      * Parses a declaration block. Which a number of declarations followed
345      * by a })].
346      */

347     private void parseDeclarationBlock() throws IOException JavaDoc {
348         for (; ;) {
349             int token = parseDeclaration();
350             switch (token) {
351                 case END:
352                 case BRACE_CLOSE:
353                     return;
354
355                 case BRACKET_CLOSE:
356                 case PAREN_CLOSE:
357                     // Bail
358
throw new RuntimeException JavaDoc("Unexpected close in declaration block");
359                 case IDENTIFIER:
360                     break;
361             }
362         }
363     }
364
365     /**
366      * Parses a single declaration, which is an identifier a : and another
367      * identifier. This returns the last token seen.
368      */

369     // identifier+: identifier* ;|}
370
private int parseDeclaration() throws IOException JavaDoc {
371         int token;
372
373         if ((token = parseIdentifiers(':', false)) != IDENTIFIER) {
374             return token;
375         }
376         // Make the property name to lowercase
377
for (int counter = unitBuffer.length() - 1; counter >= 0; counter--) {
378             unitBuffer.setCharAt(counter, Character.toLowerCase
379                     (unitBuffer.charAt(counter)));
380         }
381         callback.handleProperty(unitBuffer.toString());
382
383         token = parseIdentifiers(';', true);
384         callback.handleValue(unitBuffer.toString());
385         return token;
386     }
387
388     /**
389      * Parses identifiers until <code>extraChar</code> is encountered,
390      * returning the ending token, which will be IDENTIFIER if extraChar
391      * is found.
392      */

393     private int parseIdentifiers(char extraChar,
394                                  boolean wantsBlocks) throws IOException JavaDoc {
395         int nextToken;
396         int ubl;
397
398         unitBuffer.setLength(0);
399         for (; ;) {
400             nextToken = nextToken(extraChar);
401
402             switch (nextToken) {
403                 case IDENTIFIER:
404                     if (tokenBufferLength > 0) {
405                         if (tokenBuffer[tokenBufferLength - 1] == extraChar) {
406                             if (--tokenBufferLength > 0) {
407                                 if (readWS && unitBuffer.length() > 0) {
408                                     unitBuffer.append(' ');
409                                 }
410                                 unitBuffer.append(tokenBuffer, 0,
411                                         tokenBufferLength);
412                             }
413                             return IDENTIFIER;
414                         }
415                         if (readWS && unitBuffer.length() > 0) {
416                             unitBuffer.append(' ');
417                         }
418                         unitBuffer.append(tokenBuffer, 0, tokenBufferLength);
419                     }
420                     break;
421
422                 case BRACKET_OPEN:
423                 case BRACE_OPEN:
424                 case PAREN_OPEN:
425                     ubl = unitBuffer.length();
426                     if (wantsBlocks) {
427                         unitBuffer.append(charMapping[nextToken]);
428                     }
429                     parseTillClosed(nextToken);
430                     if (!wantsBlocks) {
431                         unitBuffer.setLength(ubl);
432                     }
433                     break;
434
435                 case BRACE_CLOSE:
436                     // No need to throw for these two, we return token and
437
// caller can do whatever.
438
case BRACKET_CLOSE:
439                 case PAREN_CLOSE:
440                 case END:
441                     // Hit the end
442
return nextToken;
443             }
444         }
445     }
446
447     /**
448      * Parses till a matching block close is encountered. This is only
449      * appropriate to be called at the top level (no nesting).
450      */

451     private void parseTillClosed(int openToken) throws IOException JavaDoc {
452         int nextToken;
453         boolean done = false;
454
455         startBlock(openToken);
456         while (!done) {
457             nextToken = nextToken((char) 0);
458             switch (nextToken) {
459                 case IDENTIFIER:
460                     if (unitBuffer.length() > 0 && readWS) {
461                         unitBuffer.append(' ');
462                     }
463                     if (tokenBufferLength > 0) {
464                         unitBuffer.append(tokenBuffer, 0, tokenBufferLength);
465                     }
466                     break;
467
468                 case BRACKET_OPEN:
469                 case BRACE_OPEN:
470                 case PAREN_OPEN:
471                     if (unitBuffer.length() > 0 && readWS) {
472                         unitBuffer.append(' ');
473                     }
474                     unitBuffer.append(charMapping[nextToken]);
475                     startBlock(nextToken);
476                     break;
477
478                 case BRACKET_CLOSE:
479                 case BRACE_CLOSE:
480                 case PAREN_CLOSE:
481                     if (unitBuffer.length() > 0 && readWS) {
482                         unitBuffer.append(' ');
483                     }
484                     unitBuffer.append(charMapping[nextToken]);
485                     endBlock(nextToken);
486                     if (!inBlock()) {
487                         done = true;
488                     }
489                     break;
490
491                 case END:
492                     // Prematurely hit end.
493
throw new RuntimeException JavaDoc("Unclosed block");
494             }
495         }
496     }
497
498     /**
499      * Fetches the next token.
500      */

501     private int nextToken(char idChar) throws IOException JavaDoc {
502         readWS = false;
503
504         int nextChar = readWS();
505
506         switch (nextChar) {
507             case '\'':
508                 readTill('\'');
509                 if (tokenBufferLength > 0) {
510                     tokenBufferLength--;
511                 }
512                 return IDENTIFIER;
513             case '"':
514                 readTill('"');
515                 if (tokenBufferLength > 0) {
516                     tokenBufferLength--;
517                 }
518                 return IDENTIFIER;
519             case '[':
520                 return BRACKET_OPEN;
521             case ']':
522                 return BRACKET_CLOSE;
523             case '{':
524                 return BRACE_OPEN;
525             case '}':
526                 return BRACE_CLOSE;
527             case '(':
528                 return PAREN_OPEN;
529             case ')':
530                 return PAREN_CLOSE;
531             case -1:
532                 return END;
533             default:
534                 pushChar(nextChar);
535                 getIdentifier(idChar);
536                 return IDENTIFIER;
537         }
538     }
539
540     /**
541      * Gets an identifier, returning true if the length of the string is greater than 0,
542      * stopping when <code>stopChar</code>, whitespace, or one of {}()[] is
543      * hit.
544      */

545     // NOTE: this could be combined with readTill, as they contain somewhat
546
// similiar functionality.
547
private boolean getIdentifier(char stopChar) throws IOException JavaDoc {
548         boolean lastWasEscape = false;
549         boolean done = false;
550         int escapeCount = 0;
551         int escapeChar = 0;
552         int nextChar;
553         int intStopChar = (int) stopChar;
554         // 1 for '\', 2 for valid escape char [0-9a-fA-F], 3 for
555
// stop character (white space, ()[]{}) 0 otherwise
556
short type;
557         int escapeOffset = 0;
558
559         tokenBufferLength = 0;
560         while (!done) {
561             nextChar = readChar();
562             switch (nextChar) {
563                 case '\\':
564                     type = 1;
565                     break;
566
567                 case '0':
568                 case '1':
569                 case '2':
570                 case '3':
571                 case '4':
572                 case '5':
573                 case '6':
574                 case '7':
575                 case '8':
576                 case '9':
577                     type = 2;
578                     escapeOffset = nextChar - '0';
579                     break;
580
581                 case 'a':
582                 case 'b':
583                 case 'c':
584                 case 'd':
585                 case 'e':
586                 case 'f':
587                     type = 2;
588                     escapeOffset = nextChar - 'a' + 10;
589                     break;
590
591                 case 'A':
592                 case 'B':
593                 case 'C':
594                 case 'D':
595                 case 'E':
596                 case 'F':
597                     type = 2;
598                     escapeOffset = nextChar - 'A' + 10;
599                     break;
600
601                 case '\'':
602                 case '"':
603                 case '[':
604                 case ']':
605                 case '{':
606                 case '}':
607                 case '(':
608                 case ')':
609                 case ' ':
610                 case '\n':
611                 case '\t':
612                 case '\r':
613                     type = 3;
614                     break;
615
616                 case '/':
617                     type = 4;
618                     break;
619
620                 case -1:
621                     // Reached the end
622
done = true;
623                     type = 0;
624                     break;
625
626                 default:
627                     type = 0;
628                     break;
629             }
630             if (lastWasEscape) {
631                 if (type == 2) {
632                     // Continue with escape.
633
escapeChar = escapeChar * 16 + escapeOffset;
634                     if (++escapeCount == 4) {
635                         lastWasEscape = false;
636                         append((char) escapeChar);
637                     }
638                 } else {
639                     // no longer escaped
640
lastWasEscape = false;
641                     if (escapeCount > 0) {
642                         append((char) escapeChar);
643                         // Make this simpler, reprocess the character.
644
pushChar(nextChar);
645                     } else if (!done) {
646                         append((char) nextChar);
647                     }
648                 }
649             } else if (!done) {
650                 if (type == 1) {
651                     lastWasEscape = true;
652                     escapeChar = escapeCount = 0;
653                 } else if (type == 3) {
654                     done = true;
655                     pushChar(nextChar);
656                 } else if (type == 4) {
657                     // Potential comment
658
nextChar = readChar();
659                     if (nextChar == '*') {
660                         done = true;
661                         readComment();
662                         readWS = true;
663                     } else {
664                         append('/');
665                         if (nextChar == -1) {
666                             done = true;
667                         } else {
668                             pushChar(nextChar);
669                         }
670                     }
671                 } else {
672                     append((char) nextChar);
673                     if (nextChar == intStopChar) {
674                         done = true;
675                     }
676                 }
677             }
678         }
679         return (tokenBufferLength > 0);
680     }
681
682     /**
683      * Reads till a <code>stopChar</code> is encountered, escaping characters
684      * as necessary.
685      */

686     private void readTill(char stopChar) throws IOException JavaDoc {
687         boolean lastWasEscape = false;
688         int escapeCount = 0;
689         int escapeChar = 0;
690         int nextChar;
691         boolean done = false;
692         int intStopChar = (int) stopChar;
693         // 1 for '\', 2 for valid escape char [0-9a-fA-F], 0 otherwise
694
short type;
695         int escapeOffset = 0;
696
697         tokenBufferLength = 0;
698         while (!done) {
699             nextChar = readChar();
700             switch (nextChar) {
701                 case '\\':
702                     type = 1;
703                     break;
704
705                 case '0':
706                 case '1':
707                 case '2':
708                 case '3':
709                 case '4':
710                 case '5':
711                 case '6':
712                 case '7':
713                 case '8':
714                 case '9':
715                     type = 2;
716                     escapeOffset = nextChar - '0';
717                     break;
718
719                 case 'a':
720                 case 'b':
721                 case 'c':
722                 case 'd':
723                 case 'e':
724                 case 'f':
725                     type = 2;
726                     escapeOffset = nextChar - 'a' + 10;
727                     break;
728
729                 case 'A':
730                 case 'B':
731                 case 'C':
732                 case 'D':
733                 case 'E':
734                 case 'F':
735                     type = 2;
736                     escapeOffset = nextChar - 'A' + 10;
737                     break;
738
739                 case -1:
740                     // Prematurely reached the end!
741
throw new RuntimeException JavaDoc("Unclosed " + stopChar);
742
743                 default:
744                     type = 0;
745                     break;
746             }
747             if (lastWasEscape) {
748                 if (type == 2) {
749                     // Continue with escape.
750
escapeChar = escapeChar * 16 + escapeOffset;
751                     if (++escapeCount == 4) {
752                         lastWasEscape = false;
753                         append((char) escapeChar);
754                     }
755                 } else {
756                     // no longer escaped
757
if (escapeCount > 0) {
758                         append((char) escapeChar);
759                         if (type == 1) {
760                             lastWasEscape = true;
761                             escapeChar = escapeCount = 0;
762                         } else {
763                             if (nextChar == intStopChar) {
764                                 done = true;
765                             }
766                             append((char) nextChar);
767                             lastWasEscape = false;
768                         }
769                     } else {
770                         append((char) nextChar);
771                         lastWasEscape = false;
772                     }
773                 }
774             } else if (type == 1) {
775                 lastWasEscape = true;
776                 escapeChar = escapeCount = 0;
777             } else {
778                 if (nextChar == intStopChar) {
779                     done = true;
780                 }
781                 append((char) nextChar);
782             }
783         }
784     }
785
786     private void append(char character) {
787         if (tokenBufferLength == tokenBuffer.length) {
788             char[] newBuffer = new char[tokenBuffer.length * 2];
789             System.arraycopy(tokenBuffer, 0, newBuffer, 0, tokenBuffer.length);
790             tokenBuffer = newBuffer;
791         }
792         tokenBuffer[tokenBufferLength++] = character;
793     }
794
795     /**
796      * Parses a comment block.
797      */

798     private void readComment() throws IOException JavaDoc {
799         int nextChar;
800
801         for (; ;) {
802             nextChar = readChar();
803             switch (nextChar) {
804                 case -1:
805                     throw new RuntimeException JavaDoc("Unclosed comment");
806                 case '*':
807                     nextChar = readChar();
808                     if (nextChar == '/') {
809                         return;
810                     } else if (nextChar == -1) {
811                         throw new RuntimeException JavaDoc("Unclosed comment");
812                     } else {
813                         pushChar(nextChar);
814                     }
815                     break;
816                 default:
817                     break;
818             }
819         }
820     }
821
822     /**
823      * Called when a block start is encountered ({[.
824      */

825     private void startBlock(int startToken) {
826         if (stackCount == unitStack.length) {
827             int[] newUS = new int[stackCount * 2];
828
829             System.arraycopy(unitStack, 0, newUS, 0, stackCount);
830             unitStack = newUS;
831         }
832         unitStack[stackCount++] = startToken;
833     }
834
835     /**
836      * Called when an end block is encountered )]}
837      */

838     private void endBlock(int endToken) {
839         int startToken;
840
841         switch (endToken) {
842             case BRACKET_CLOSE:
843                 startToken = BRACKET_OPEN;
844                 break;
845             case BRACE_CLOSE:
846                 startToken = BRACE_OPEN;
847                 break;
848             case PAREN_CLOSE:
849                 startToken = PAREN_OPEN;
850                 break;
851             default:
852                 // Will never happen.
853
startToken = -1;
854                 break;
855         }
856         if (stackCount > 0 && unitStack[stackCount - 1] == startToken) {
857             stackCount--;
858         } else {
859             // Invalid state, should do something.
860
throw new RuntimeException JavaDoc("Unmatched block");
861         }
862     }
863
864     /**
865      * @return true if currently in a block.
866      */

867     private boolean inBlock() {
868         return (stackCount > 0);
869     }
870
871     /**
872      * Skips any white space, returning the character after the white space.
873      */

874     private int readWS() throws IOException JavaDoc {
875         int nextChar;
876         while ((nextChar = readChar()) != -1 &&
877                 Character.isWhitespace((char) nextChar)) {
878             readWS = true;
879         }
880         return nextChar;
881     }
882
883     /**
884      * Reads a character from the stream.
885      */

886     private int readChar() throws IOException JavaDoc {
887         if (didPushChar) {
888             didPushChar = false;
889             return pushedChar;
890         }
891         return reader.read();
892         // Uncomment the following to do case insensitive parsing.
893
/*
894         if (retValue != -1) {
895             return (int)Character.toLowerCase((char)retValue);
896         }
897         return retValue;
898         */

899     }
900
901     /**
902      * Supports one character look ahead, this will throw if called twice
903      * in a row.
904      */

905     private void pushChar(int tempChar) {
906         if (didPushChar) {
907             // Should never happen.
908
throw new RuntimeException JavaDoc("Can not handle look ahead of more than one character");
909         }
910         didPushChar = true;
911         pushedChar = tempChar;
912     }
913 }
914
915
916
Popular Tags