1 14 package org.wings.style; 15 16 import java.io.IOException ; 17 import java.io.Reader ; 18 19 49 class CSSParser { 50 66 67 71 72 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 89 private boolean didPushChar; 90 93 private int pushedChar; 94 97 private StringBuffer unitBuffer; 98 101 private int[] unitStack; 102 105 private int stackCount; 106 109 private Reader reader; 110 113 private boolean encounteredRuleSet; 114 117 private CSSParserCallback callback; 118 121 private char[] tokenBuffer; 122 125 private int tokenBufferLength; 126 129 private boolean readWS; 130 131 132 static interface CSSParserCallback { 134 137 void handleImport(String importString); 138 139 void handleSelector(String selector); 144 145 void startRule(); 146 147 void handleProperty(String property); 150 151 void handleValue(String value); 152 153 void endRule(); 154 } 155 156 CSSParser() { 157 unitStack = new int[2]; 158 tokenBuffer = new char[80]; 159 unitBuffer = new StringBuffer (); 160 } 161 162 void parse(Reader reader, CSSParserCallback callback, 163 boolean inRule) throws IOException { 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 184 private boolean getNextStatement() throws IOException { 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 throw new RuntimeException ("Unexpected top level block close"); 211 212 case END: 213 return false; 214 } 215 return true; 216 } 217 218 221 private void parseAtRule() throws IOException { 222 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 { 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 ("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 290 private void parseRuleSet() throws IOException { 291 if (parseSelectors()) { 292 callback.startRule(); 293 parseDeclarationBlock(); 294 callback.endRule(); 295 } 296 } 297 298 302 private boolean parseSelectors() throws IOException { 303 int nextToken; 305 306 if (tokenBufferLength > 0) { 307 callback.handleSelector(new String (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 (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 unitBuffer.setLength(0); 329 break; 330 331 case BRACKET_CLOSE: 332 case BRACE_CLOSE: 333 case PAREN_CLOSE: 334 throw new RuntimeException ("Unexpected block close in selector"); 335 336 case END: 337 return false; 339 } 340 } 341 } 342 343 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 throw new RuntimeException ("Unexpected close in declaration block"); 359 case IDENTIFIER: 360 break; 361 } 362 } 363 } 364 365 369 private int parseDeclaration() throws IOException { 371 int token; 372 373 if ((token = parseIdentifiers(':', false)) != IDENTIFIER) { 374 return token; 375 } 376 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 393 private int parseIdentifiers(char extraChar, 394 boolean wantsBlocks) throws IOException { 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 case BRACKET_CLOSE: 439 case PAREN_CLOSE: 440 case END: 441 return nextToken; 443 } 444 } 445 } 446 447 451 private void parseTillClosed(int openToken) throws IOException { 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 throw new RuntimeException ("Unclosed block"); 494 } 495 } 496 } 497 498 501 private int nextToken(char idChar) throws IOException { 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 545 private boolean getIdentifier(char stopChar) throws IOException { 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 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 done = true; 623 type = 0; 624 break; 625 626 default: 627 type = 0; 628 break; 629 } 630 if (lastWasEscape) { 631 if (type == 2) { 632 escapeChar = escapeChar * 16 + escapeOffset; 634 if (++escapeCount == 4) { 635 lastWasEscape = false; 636 append((char) escapeChar); 637 } 638 } else { 639 lastWasEscape = false; 641 if (escapeCount > 0) { 642 append((char) escapeChar); 643 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 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 686 private void readTill(char stopChar) throws IOException { 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 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 throw new RuntimeException ("Unclosed " + stopChar); 742 743 default: 744 type = 0; 745 break; 746 } 747 if (lastWasEscape) { 748 if (type == 2) { 749 escapeChar = escapeChar * 16 + escapeOffset; 751 if (++escapeCount == 4) { 752 lastWasEscape = false; 753 append((char) escapeChar); 754 } 755 } else { 756 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 798 private void readComment() throws IOException { 799 int nextChar; 800 801 for (; ;) { 802 nextChar = readChar(); 803 switch (nextChar) { 804 case -1: 805 throw new RuntimeException ("Unclosed comment"); 806 case '*': 807 nextChar = readChar(); 808 if (nextChar == '/') { 809 return; 810 } else if (nextChar == -1) { 811 throw new RuntimeException ("Unclosed comment"); 812 } else { 813 pushChar(nextChar); 814 } 815 break; 816 default: 817 break; 818 } 819 } 820 } 821 822 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 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 startToken = -1; 854 break; 855 } 856 if (stackCount > 0 && unitStack[stackCount - 1] == startToken) { 857 stackCount--; 858 } else { 859 throw new RuntimeException ("Unmatched block"); 861 } 862 } 863 864 867 private boolean inBlock() { 868 return (stackCount > 0); 869 } 870 871 874 private int readWS() throws IOException { 875 int nextChar; 876 while ((nextChar = readChar()) != -1 && 877 Character.isWhitespace((char) nextChar)) { 878 readWS = true; 879 } 880 return nextChar; 881 } 882 883 886 private int readChar() throws IOException { 887 if (didPushChar) { 888 didPushChar = false; 889 return pushedChar; 890 } 891 return reader.read(); 892 899 } 900 901 905 private void pushChar(int tempChar) { 906 if (didPushChar) { 907 throw new RuntimeException ("Can not handle look ahead of more than one character"); 909 } 910 didPushChar = true; 911 pushedChar = tempChar; 912 } 913 } 914 915 916 | Popular Tags |