Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
   package com.thaiopensource.datatype.xsd.regex.jdk1_4;
   
   
   import java.util.Collections;
   import java.util.Iterator;
   import java.util.List;
  import java.util.Vector;
Translates XML Schema regexes into java.util.regex regexes.

See also:
java.util.regex.Pattern
XML Schema Part 2
  
  public class Translator {
    private final String regExp;
    private int pos = 0;
    private final int length;
    private char curChar;
    private boolean eos = false;
    private final StringBuffer result = new StringBuffer();
  
    static private final String categories = "LMNPZSC";
    static private final CharClass[] categoryCharClasses = new CharClass[.length()];
    static private final String subCategories = "LuLlLtLmLoMnMcMeNdNlNoPcPdPsPePiPfPoZsZlZpSmScSkSoCcCfCoCn";
    static private final CharClass[] subCategoryCharClasses = new CharClass[.length() / 2];
  
    static private final int NONBMP_MIN = 0x10000;
    static private final int NONBMP_MAX = 0x10FFFF;
    static private final char SURROGATE2_MIN = '\uDC00';
    static private final char SURROGATE2_MAX = '\uDFFF';
  
    static final Localizer localizer = new Localizer(Translator.class);
  
    static private final String[] blockNames = {
      "BasicLatin",
      "Latin-1Supplement",
      "LatinExtended-A",
      "LatinExtended-B",
      "IPAExtensions",
      "SpacingModifierLetters",
      "CombiningDiacriticalMarks",
      "Greek",
      "Cyrillic",
      "Armenian",
      "Hebrew",
      "Arabic",
      "Syriac",
      "Thaana",
      "Devanagari",
      "Bengali",
      "Gurmukhi",
      "Gujarati",
      "Oriya",
      "Tamil",
      "Telugu",
      "Kannada",
      "Malayalam",
      "Sinhala",
      "Thai",
      "Lao",
      "Tibetan",
      "Myanmar",
      "Georgian",
      "HangulJamo",
      "Ethiopic",
      "Cherokee",
      "UnifiedCanadianAboriginalSyllabics",
      "Ogham",
      "Runic",
      "Khmer",
      "Mongolian",
      "LatinExtendedAdditional",
      "GreekExtended",
      "GeneralPunctuation",
      "SuperscriptsandSubscripts",
      "CurrencySymbols",
      "CombiningMarksforSymbols",
      "LetterlikeSymbols",
      "NumberForms",
      "Arrows",
      "MathematicalOperators",
      "MiscellaneousTechnical",
      "ControlPictures",
      "OpticalCharacterRecognition",
      "EnclosedAlphanumerics",
      "BoxDrawing",
      "BlockElements",
      "GeometricShapes",
      "MiscellaneousSymbols",
      "Dingbats",
      "BraillePatterns",
      "CJKRadicalsSupplement",
      "KangxiRadicals",
      "IdeographicDescriptionCharacters",
     "CJKSymbolsandPunctuation",
     "Hiragana",
     "Katakana",
     "Bopomofo",
     "HangulCompatibilityJamo",
     "Kanbun",
     "BopomofoExtended",
     "EnclosedCJKLettersandMonths",
     "CJKCompatibility",
     "CJKUnifiedIdeographsExtensionA",
     "CJKUnifiedIdeographs",
     "YiSyllables",
     "YiRadicals",
     "HangulSyllables",
     // surrogates excluded because there are never any *characters* with codes in surrogate range
     // "PrivateUse", excluded because 3.1 adds non-BMP ranges
     "CJKCompatibilityIdeographs",
     "AlphabeticPresentationForms",
     "ArabicPresentationForms-A",
     "CombiningHalfMarks",
     "CJKCompatibilityForms",
     "SmallFormVariants",
     "ArabicPresentationForms-B",
     "Specials",
     "HalfwidthandFullwidthForms",
     "Specials"
   };


  
Names of blocks including ranges outside the BMP.
 
   static private final String[] specialBlockNames = {
     "OldItalic",
     "Gothic",
     "Deseret",
     "ByzantineMusicalSymbols",
     "MusicalSymbols",
     "MathematicalAlphanumericSymbols",
     "CJKUnifiedIdeographsExtensionB",
     "CJKCompatibilityIdeographsSupplement",
     "Tags",
     "PrivateUse",
     "HighSurrogates",
     "HighPrivateUseSurrogates",
     "LowSurrogates",
   };

  
CharClass for each block name in specialBlockNames.
 
   static private final CharClass[] specialBlockCharClasses = {
     new CharRange(0x10300, 0x1032F),
     new CharRange(0x10330, 0x1034F),
     new CharRange(0x10400, 0x1044F),
     new CharRange(0x1D000, 0x1D0FF),
     new CharRange(0x1D100, 0x1D1FF),
     new CharRange(0x1D400, 0x1D7FF),
     new CharRange(0x20000, 0x2A6D6),
     new CharRange(0x2F800, 0x2FA1F),
     new CharRange(0xE0000, 0xE007F),
     new Union(new CharClass[] {
       new CharRange(0xE000, 0xF8FF),
       new CharRange(0xF0000, 0xFFFFD),
       new CharRange(0x100000, 0x10FFFD)
     }),
     Empty.getInstance(),
     Empty.getInstance(),
     Empty.getInstance()
   };
 
   static private final CharClass DOT = new Complement(new Union(new CharClass[] { new SingleChar('\n'), new SingleChar('\r') }));
 
   static private final CharClass ESC_d = new Property("Nd");
 
   static private final CharClass ESC_D = new Complement();
 
   static private final CharClass ESC_W = new Union(new CharClass[] {new Property("P"), new Property("Z"), new Property("C")});
 
   static private final CharClass ESC_w = new Complement();
 
   static private final CharClass ESC_s = new Union(new CharClass[] {
     new SingleChar(' '),
     new SingleChar('\n'),
     new SingleChar('\r'),
     new SingleChar('\t')
   });
 
   static private final CharClass ESC_S = new Complement();
 
                                                        .,
                                                        .);
 
   static private final CharClass ESC_I = new Complement();
 
                                                        .,
                                                        .);
 
   static private final CharClass ESC_C = new Complement();
 
   static private final char EOS = '\0';
 
   private Translator(String regExp) {
     this. = regExp;
     this. = regExp.length();
     advance();
   }

  
Translates a regular expression in the syntax of XML Schemas Part 2 into a regular expression in the syntax of java.util.regex.Pattern. The translation assumes that the string to be matched against the regex uses surrogate pairs correctly. If the string comes from XML content, a conforming XML parser will automatically check this; if the string comes from elsewhere, it may be necessary to check surrogate usage before matching.

Parameters:
regexp a String containing a regular expression in the syntax of XML Schemas Part 2
Returns:
a String containing a regular expression in the syntax of java.util.regex.Pattern
Throws:
com.thaiopensource.datatype.xsd.regex.RegexSyntaxException if regexp is not a regular expression in the syntax of XML Schemas Part 2
See also:
java.util.regex.Pattern
XML Schema Part 2
 
   static public String translate(String regexpthrows RegexSyntaxException {
     Translator tr = new Translator(regexp);
     tr.translateTop();
     return tr.result.toString();
   }
 
   private void advance() {
     if ( < )
        = .charAt(++);
     else {
       ++;
        = ;
        = true;
     }
   }
 
   private void translateTop() throws RegexSyntaxException {
     translateRegExp();
     if (!)
       throw makeException("expected_eos");
   }
 
   private void translateRegExp() throws RegexSyntaxException {
     translateBranch();
     while ( == '|') {
       copyCurChar();
       translateBranch();
     }
   }
 
   private void translateBranch() throws RegexSyntaxException {
     while (translateAtom())
       translateQuantifier();
   }
 
   private void translateQuantifier() throws RegexSyntaxException {
     switch () {
     case '*':
     case '?':
     case '+':
       copyCurChar();
       return;
     case '{':
       copyCurChar();
       translateQuantity();
       expect('}');
       copyCurChar();
     }
   }
 
   private void translateQuantity() throws RegexSyntaxException {
     String lower = parseQuantExact();
     int lowerValue = -1;
     try {
       lowerValue = Integer.parseInt(lower);
       .append(lower);
     }
     catch (NumberFormatException e) {
       // JDK 1.4 cannot handle ranges bigger than this
     }
     if ( == ',') {
       copyCurChar();
       if ( != '}') {
         String upper = parseQuantExact();
         try {
           int upperValue = Integer.parseInt(upper);
           .append(upper);
           if (lowerValue < 0 || upperValue < lowerValue)
             throw makeException("invalid_quantity_range");
         }
         catch (NumberFormatException e) {
           .append(.);
           if (lowerValue < 0 && new BigDecimal(lower).compareTo(new BigDecimal(upper)) > 0)
             throw makeException("invalid_quantity_range");
         }
       }
     }
   }
 
   private String parseQuantExact() throws RegexSyntaxException {
     StringBuffer buf = new StringBuffer();
     do {
       if ("0123456789".indexOf() < 0)
         throw makeException("expected_digit");
       buf.append();
       advance();
     } while ( != ',' &&  != '}');
     return buf.toString();
   }
 
   private void copyCurChar() {
     advance();
   }
 
   static final int NONE = -1;
   static final int SOME = 0;
   static final int ALL = 1;
 
   static final String SURROGATES1_CLASS = "[\uD800-\uDBFF]";
   static final String SURROGATES2_CLASS = "[\uDC00-\uDFFF]";
   static final String NOT_ALLOWED_CLASS = "[\u0000&&[^\u0000]]";
 
   static final class Range implements Comparable {
     private final int min;
     private final int max;
 
     Range(int minint max) {
       this. = min;
       this. = max;
     }
 
     int getMin() {
       return ;
     }
 
     int getMax() {
       return ;
     }
 
     public int compareTo(Object o) {
       Range other = (Range)o;
       if (this. < other.min)
         return -1;
       if (this. > other.min)
         return 1;
       if (this. > other.max)
         return -1;
       if (this. < other.max)
         return 1;
       return 0;
     }
   }
 
   static abstract class CharClass  {
 
     private final int containsBmp;
     // if it contains ALL and containsBmp != NONE, then the generated class for containsBmp must
     // contain all the high surrogates
     private final int containsNonBmp;
 
     protected CharClass(int containsBmpint containsNonBmp) {
       this. = containsBmp;
       this. = containsNonBmp;
     }
 
     int getContainsBmp() {
       return ;
     }
 
     int getContainsNonBmp() {
       return ;
     }
 
     final void output(StringBuffer buf) {
       switch () {
       case :
         if ( == )
           buf.append();
         else
           outputBmp(buf);
         break;
       case :
         buf.append('(');
         if ( == ) {
           buf.append();
           buf.append();
         }
         else {
           outputBmp(buf);
           buf.append();
           buf.append('?');
         }
         buf.append(')');
         break;
       case :
         buf.append('(');
         boolean needSep = false;
         if ( != ) {
           needSep = true;
           outputBmp(buf);
         }
         List ranges = new Vector();
         addNonBmpRanges(ranges);
         sortRangeList(ranges);
         String hi = highSurrogateRanges(ranges);
         if (hi.length() > 0) {
           if (needSep)
             buf.append('|');
           else
             needSep = true;
           buf.append('[');
           for (int i = 0, len = hi.length(); i < leni += 2) {
             char min = hi.charAt(i);
             char max = hi.charAt(i + 1);
             if (min == max)
               buf.append(min);
             else {
               buf.append(min);
               buf.append('-');
               buf.append(max);
             }
           }
           buf.append(']');
           buf.append();
         }
         String lo = lowSurrogateRanges(ranges);
         for (int i = 0, len = lo.length(); i < leni += 3) {
           if (needSep)
             buf.append('|');
           else
             needSep = true;
           buf.append(lo.charAt(i));
           char min = lo.charAt(i + 1);
           char max = lo.charAt(i + 2);
           if (min == max && (i + 3 >= len || lo.charAt(i + 3) != lo.charAt(i)))
             buf.append(min);
           else {
             buf.append('[');
             for (;;) {
               if (min == max)
                 buf.append(min);
               else {
                 buf.append(min);
                 buf.append('-');
                 buf.append(max);
               }
               if (i + 3 >= len || lo.charAt(i + 3) != lo.charAt(i))
                 break;
               i += 3;
               min = lo.charAt(i + 1);
               max = lo.charAt(i + 2);
             }
             buf.append(']');
           }
         }
         if (!needSep)
           buf.append();
         buf.append(')');
         break;
       }
     }
 
     static String highSurrogateRanges(List ranges) {
       StringBuffer highRanges = new StringBuffer();
       for (int i = 0, len = ranges.size(); i < leni++) {
         Range r = (Range)ranges.get(i);
         char min1 = Utf16.surrogate1(r.getMin());
         char min2 = Utf16.surrogate2(r.getMin());
         char max1 = Utf16.surrogate1(r.getMax());
         char max2 = Utf16.surrogate2(r.getMax());
         if (min2 != )
           min1++;
         if (max2 != )
           max1--;
         if (max1 >= min1) {
           highRanges.append(min1);
           highRanges.append(max1);
         }
       }
       return highRanges.toString();
     }
 
     static String lowSurrogateRanges(List ranges) {
       StringBuffer lowRanges = new StringBuffer();
       for (int i = 0, len = ranges.size(); i < leni++) {
         Range r = (Range)ranges.get(i);
         char min1 = Utf16.surrogate1(r.getMin());
         char min2 = Utf16.surrogate2(r.getMin());
         char max1 = Utf16.surrogate1(r.getMax());
         char max2 = Utf16.surrogate2(r.getMax());
         if (min1 == max1) {
           if (min2 !=  || max2 != ) {
             lowRanges.append(min1);
             lowRanges.append(min2);
             lowRanges.append(max2);
           }
         }
         else {
           if (min2 != ) {
             lowRanges.append(min1);
             lowRanges.append(min2);
             lowRanges.append();
           }
           if (max2 != ) {
             lowRanges.append(max1);
             lowRanges.append();
             lowRanges.append(max2);
           }
         }
       }
       return lowRanges.toString();
     }
 
     abstract void outputBmp(StringBuffer buf);
     abstract void outputComplementBmp(StringBuffer buf);
 
     int singleChar() {
       return -1;
     }
 
     void addNonBmpRanges(List ranges) {
     }
 
 
     static void sortRangeList(List ranges) {
       Collections.sort(ranges);
       int toIndex = 0;
       int fromIndex = 0;
       int len = ranges.size();
       while (fromIndex < len) {
         Range r = (Range)ranges.get(fromIndex);
         int min = r.getMin();
         int max = r.getMax();
         while (++fromIndex < len) {
           Range r2 = (Range)ranges.get(fromIndex);
           if (r2.getMin() > max + 1)
             break;
           if (r2.getMax() > max)
             max = r2.getMax();
         }
         if (max != r.getMax())
           r = new Range(minmax);
         ranges.set(toIndex++, r);
       }
       while (len > toIndex)
         ranges.remove(--len);
     }
 
   }
 
   static abstract class SimpleCharClass extends CharClass {
     SimpleCharClass(int containsBmpint containsNonBmp) {
       super(containsBmpcontainsNonBmp);
     }
 
     void outputBmp(StringBuffer buf) {
       buf.append('[');
       inClassOutputBmp(buf);
       buf.append(']');
     }
 
     // must not call if containsBmp == ALL
     void outputComplementBmp(StringBuffer buf) {
       if (getContainsBmp() == )
         buf.append("[\u0000-\uFFFF]");
       else {
         buf.append("[^");
         inClassOutputBmp(buf);
         buf.append(']');
       }
     }
     abstract void inClassOutputBmp(StringBuffer buf);
   }
 
   static class SingleChar extends SimpleCharClass {
     private final char c;
     SingleChar(char c) {
       super();
       this. = c;
     }
 
     int singleChar() {
       return ;
     }
 
     void outputBmp(StringBuffer buf) {
       inClassOutputBmp(buf);
     }
 
     void inClassOutputBmp(StringBuffer buf) {
       if (isJavaMetaChar())
         buf.append('\\');
       buf.append();
     }
 
   }
 
   static class WideSingleChar extends SimpleCharClass {
     private final int c;
 
     WideSingleChar(int c) {
       super();
       this. = c;
     }
 
     void inClassOutputBmp(StringBuffer buf) {
       throw new RuntimeException("BMP output botch");
     }
 
     int singleChar() {
       return ;
     }
 
     void addNonBmpRanges(List ranges) {
       ranges.add(new Range());
     }
   }
 
   static class Empty extends SimpleCharClass {
     static private final Empty instance = new Empty();
     private Empty() {
       super();
     }
 
     static Empty getInstance() {
       return ;
     }
 
     void inClassOutputBmp(StringBuffer buf) {
       throw new RuntimeException("BMP output botch");
     }
 
   }
 
   static class CharRange extends SimpleCharClass {
     private final int lower;
     private final int upper;
 
     CharRange(int lowerint upper) {
       super(lower <  ?  : ,
             // don't use ALL here, because that requires that the BMP class contains high surrogates
             upper >=  ?  : );
       this. = lower;
       this. = upper;
     }
 
     void inClassOutputBmp(StringBuffer buf) {
       if ( >= )
         throw new RuntimeException("BMP output botch");
       if (isJavaMetaChar((char)))
         buf.append('\\');
       buf.append((char));
       buf.append('-');
       if ( < ) {
         if (isJavaMetaChar((char)))
           buf.append('\\');
         buf.append((char));
       }
       else
         buf.append('\uFFFF');
     }
 
     void addNonBmpRanges(List ranges) {
       if ( >= )
         ranges.add(new Range( <  ?  : ));
     }
   }
 
   static class Property extends SimpleCharClass {
     private final String name;
 
     Property(String name) {
       super();
       this. = name;
     }
 
     void outputBmp(StringBuffer buf) {
       inClassOutputBmp(buf);
     }
 
     void inClassOutputBmp(StringBuffer buf) {
       buf.append("\\p{");
       buf.append();
       buf.append('}');
     }
 
     void outputComplementBmp(StringBuffer buf) {
       buf.append("\\P{");
       buf.append();
       buf.append('}');
     }
   }
 
   static class Subtraction extends CharClass {
     private final CharClass cc1;
     private final CharClass cc2;
     Subtraction(CharClass cc1CharClass cc2) {
       // min corresponds to intersection
       // complement corresponds to negation
       super(Math.min(cc1.getContainsBmp(), -cc2.getContainsBmp()),
             Math.min(cc1.getContainsNonBmp(), -cc2.getContainsNonBmp()));
       this. = cc1;
       this. = cc2;
     }
 
     void outputBmp(StringBuffer buf) {
       buf.append('[');
       .outputBmp(buf);
       buf.append("&&");
       .outputComplementBmp(buf);
       buf.append(']');
     }
 
     void outputComplementBmp(StringBuffer buf) {
       buf.append('[');
       .outputComplementBmp(buf);
       .outputBmp(buf);
       buf.append(']');
     }
 
     void addNonBmpRanges(List ranges) {
       List posList = new Vector();
       .addNonBmpRanges(posList);
       List negList = new Vector();
       .addNonBmpRanges(negList);
       sortRangeList(posList);
       sortRangeList(negList);
       Iterator negIter = negList.iterator();
       Range negRange;
       if (negIter.hasNext())
         negRange = (Range)negIter.next();
       else
         negRange = null;
       for (int i = 0, len = posList.size(); i < leni++) {
         Range posRange = (Range)posList.get(i);
         while (negRange != null && negRange.getMax() < posRange.getMin()) {
           if (negIter.hasNext())
             negRange = (Range)negIter.next();
           else
             negRange = null;
         }
         // if negRange != null, negRange.max >= posRange.min
         int min = posRange.getMin();
         while (negRange != null && negRange.getMin() <= posRange.getMax()) {
           if (min < negRange.getMin()) {
             ranges.add(new Range(minnegRange.getMin() - 1));
           }
           min = negRange.getMax() + 1;
           if (min > posRange.getMax())
             break;
           if (negIter.hasNext())
             negRange = (Range)negIter.next();
           else
             negRange = null;
         }
         if (min <= posRange.getMax())
           ranges.add(new Range(minposRange.getMax()));
       }
     }
   }
 
   static class Union extends CharClass {
     private final List members;
 
     Union(CharClass[] v) {
       this(toList(v));
     }
 
     static private List toList(CharClass[] v) {
       List members = new Vector();
       for (int i = 0; i < v.lengthi++)
         members.add(v[i]);
       return members;
     }
 
     Union(List members) {
       super(computeContainsBmp(members), computeContainsNonBmp(members));
       this. = members;
     }
 
     void outputBmp(StringBuffer buf) {
       buf.append('[');
       for (int i = 0, len = .size(); i < leni++) {
         CharClass cc = (CharClass).get(i);
         if (cc.getContainsBmp() != ) {
           if (cc instanceof SimpleCharClass)
             ((SimpleCharClass)cc).inClassOutputBmp(buf);
           else
             cc.outputBmp(buf);
         }
       }
       buf.append(']');
     }
 
     void outputComplementBmp(StringBuffer buf) {
       boolean first = true;
       int len = .size();
       for (int i = 0; i < leni++) {
         CharClass cc = (CharClass).get(i);
         if (cc.getContainsBmp() !=  && cc instanceof SimpleCharClass) {
           if (first) {
             buf.append("[^");
             first = false;
           }
           ((SimpleCharClass)cc).inClassOutputBmp(buf);
         }
       }
       for (int i = 0; i < leni++) {
         CharClass cc = (CharClass).get(i);
         if (cc.getContainsBmp() !=  && !(cc instanceof SimpleCharClass)) {
           if (first) {
             buf.append('[');
             first = false;
           }
           else
             buf.append("&&");
           // can't have any members that are ALL, because that would make this ALL, which violates
           // the precondition for outputComplementBmp
           cc.outputComplementBmp(buf);
         }
       }
       if (first == true)
         // all members are NONE, so this is NONE, so complement is everything
         buf.append("[\u0000-\uFFFF]");
       else
         buf.append(']');
     }
 
     void addNonBmpRanges(List ranges) {
       for (int i = 0, len = .size(); i < leni++)
         ((CharClass).get(i)).addNonBmpRanges(ranges);
     }
 
     private static int computeContainsBmp(List members) {
       int ret = ;
       for (int i = 0, len = members.size(); i < leni++)
         ret = Math.max(ret, ((CharClass)members.get(i)).getContainsBmp());
       return ret;
     }
 
     private static int computeContainsNonBmp(List members) {
       int ret = ;
       for (int i = 0, len = members.size(); i < leni++)
         ret = Math.max(ret, ((CharClass)members.get(i)).getContainsNonBmp());
       return ret;
     }
   }
 
   static class Complement extends CharClass {
     private final CharClass cc;
     Complement(CharClass cc) {
       super(-cc.getContainsBmp(), -cc.getContainsNonBmp());
       this. = cc;
     }
 
     void outputBmp(StringBuffer buf) {
       .outputComplementBmp(buf);
     }
 
     void outputComplementBmp(StringBuffer buf) {
       .outputBmp(buf);
     }
 
     void addNonBmpRanges(List ranges) {
       List tem = new Vector();
       .addNonBmpRanges(tem);
       sortRangeList(tem);
       int c = ;
       for (int i = 0, len = tem.size(); i < leni++) {
         Range r = (Range)tem.get(i);
         if (r.getMin() > c)
           ranges.add(new Range(cr.getMin() - 1));
         c = r.getMax() + 1;
       }
       if (c !=  + 1)
         ranges.add(new Range(c));
     }
   }
 
   private boolean translateAtom() throws RegexSyntaxException {
     switch () {
     case :
       if (!)
         break;
       // fall through
     case '?':
     case '*':
     case '+':
     case ')':
     case '{':
     case '}':
     case '|':
     case ']':
       return false;
     case '(':
       copyCurChar();
       translateRegExp();
       expect(')');
       copyCurChar();
       return true;
     case '\\':
       advance();
       parseEsc().output();
       return true;
     case '[':
       advance();
       return true;
     case '.':
       .output();
       advance();
       return true;
     case '$':
     case '^':
       .append('\\');
       break;
     }
     copyCurChar();
     return true;
   }
 
 
   static private CharClass makeCharClass(String categoriesString includesString excludeRanges) {
     List includeList = new Vector();
     for (int i = 0, len = categories.length(); i < leni += 2)
       includeList.add(new Property(categories.substring(ii + 2)));
     for (int i = 0, len = includes.length(); i < leni++) {
       int j = i + 1;
       for (; j < len && includes.charAt(j) - includes.charAt(i) == j - ij++)
         ;
       --j;
       if (i == j - 1)
         --j;
       if (i == j)
         includeList.add(new SingleChar(includes.charAt(i)));
       else
         includeList.add(new CharRange(includes.charAt(i), includes.charAt(j)));
       i = j;
     }
     List excludeList = new Vector();
     for (int i = 0, len = excludeRanges.length(); i < leni += 2) {
       char min = excludeRanges.charAt(i);
       char max = excludeRanges.charAt(i + 1);
       if (min == max)
         excludeList.add(new SingleChar(min));
       else if (min == max - 1) {
         excludeList.add(new SingleChar(min));
         excludeList.add(new SingleChar(max));
       }
       else
         excludeList.add(new CharRange(minmax));
     }
     return new Subtraction(new Union(includeList), new Union(excludeList));
   }
 
   private CharClass parseEsc() throws RegexSyntaxException {
     switch () {
     case 'n':
       advance();
       return new SingleChar('\n');
     case 'r':
       advance();
       return new SingleChar('\r');
     case 't':
       advance();
       return new SingleChar('\t');
     case '\\':
     case '|':
     case '.':
     case '-':
     case '^':
     case '?':
     case '*':
     case '+':
     case '(':
     case ')':
     case '{':
     case '}':
     case '[':
     case ']':
       break;
     case 's':
       advance();
       return ;
     case 'S':
       advance();
       return ;
     case 'i':
       advance();
       return ;
     case 'I':
       advance();
       return ;
    case 'c':
      advance();
      return ;
    case 'C':
      advance();
      return ;
    case 'd':
      advance();
      return ;
    case 'D':
      advance();
      return ;
    case 'w':
      advance();
      return ;
    case 'W':
      advance();
      return ;
    case 'p':
      advance();
      return parseProp();
    case 'P':
      advance();
      return new Complement(parseProp());
    default:
      throw makeException("bad_escape");
    }
    CharClass tem = new SingleChar();
    advance();
    return tem;
  }
  private CharClass parseProp() throws RegexSyntaxException {
    expect('{');
    int start = ;
    for (;;) {
      advance();
      if ( == '}')
        break;
      if (!isAsciiAlnum() &&  != '-')
        expect('}');
    }
    String propertyName = .substring(start - 1);
    advance();
    switch (propertyName.length()) {
    case 0:
      throw makeException("empty_property_name");
    case 2:
      int sci = .indexOf(propertyName);