Source Code Cross Referenced for CSSParser.java in  » Swing-Library » wings3 » org » wings » style » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Swing Library » wings3 » org.wings.style 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


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