Home | History | Annotate | Download | only in text
      1 /* GENERATED SOURCE. DO NOT MODIFY. */
      2 //  2016 and later: Unicode, Inc. and others.
      3 // License & terms of use: http://www.unicode.org/copyright.html#License
      4 /*
      5  * Copyright (C) 1996-2011, International Business Machines Corporation and
      6  * others. All Rights Reserved.
      7  *
      8  */
      9 package android.icu.text;
     10 
     11 import android.icu.impl.UCaseProps;
     12 import android.icu.lang.UCharacter;
     13 import android.icu.util.ULocale;
     14 
     15 /**
     16  * A transliterator that converts all letters (as defined by
     17  * <code>UCharacter.isLetter()</code>) to lower case, except for those
     18  * letters preceded by non-letters.  The latter are converted to title
     19  * case using <code>UCharacter.toTitleCase()</code>.
     20  * @author Alan Liu
     21  */
     22 class TitlecaseTransliterator extends Transliterator {
     23 
     24     static final String _ID = "Any-Title";
     25     // TODO: Add variants for tr/az, lt, default = default locale: ICU ticket #12720
     26 
     27     /**
     28      * System registration hook.
     29      */
     30     static void register() {
     31         Transliterator.registerFactory(_ID, new Transliterator.Factory() {
     32             @Override
     33             public Transliterator getInstance(String ID) {
     34                 return new TitlecaseTransliterator(ULocale.US);
     35             }
     36         });
     37 
     38         registerSpecialInverse("Title", "Lower", false);
     39     }
     40 
     41     private final ULocale locale;
     42 
     43     private final UCaseProps csp;
     44     private ReplaceableContextIterator iter;
     45     private StringBuilder result;
     46     private int caseLocale;
     47 
     48    /**
     49      * Constructs a transliterator.
     50      */
     51     public TitlecaseTransliterator(ULocale loc) {
     52         super(_ID, null);
     53         locale = loc;
     54         // Need to look back 2 characters in the case of "can't"
     55         setMaximumContextLength(2);
     56         csp=UCaseProps.INSTANCE;
     57         iter=new ReplaceableContextIterator();
     58         result = new StringBuilder();
     59         caseLocale = UCaseProps.getCaseLocale(locale);
     60     }
     61 
     62     /**
     63      * Implements {@link Transliterator#handleTransliterate}.
     64      */
     65     @Override
     66     protected synchronized void handleTransliterate(Replaceable text,
     67                                        Position offsets, boolean isIncremental) {
     68         // TODO reimplement, see ustrcase.c
     69         // using a real word break iterator
     70         //   instead of just looking for a transition between cased and uncased characters
     71         // call CaseMapTransliterator::handleTransliterate() for lowercasing? (set fMap)
     72         // needs to take isIncremental into account because case mappings are context-sensitive
     73         //   also detect when lowercasing function did not finish because of context
     74 
     75         if (offsets.start >= offsets.limit) {
     76             return;
     77         }
     78 
     79         // case type: >0 cased (UCaseProps.LOWER etc.)  ==0 uncased  <0 case-ignorable
     80         int type;
     81 
     82         // Our mode; we are either converting letter toTitle or
     83         // toLower.
     84         boolean doTitle = true;
     85 
     86         // Determine if there is a preceding context of cased case-ignorable*,
     87         // in which case we want to start in toLower mode.  If the
     88         // prior context is anything else (including empty) then start
     89         // in toTitle mode.
     90         int c, start;
     91         for (start = offsets.start - 1; start >= offsets.contextStart; start -= UTF16.getCharCount(c)) {
     92             c = text.char32At(start);
     93             type=csp.getTypeOrIgnorable(c);
     94             if(type>0) { // cased
     95                 doTitle=false;
     96                 break;
     97             } else if(type==0) { // uncased but not ignorable
     98                 break;
     99             }
    100             // else (type<0) case-ignorable: continue
    101         }
    102 
    103         // Convert things after a cased character toLower; things
    104         // after a uncased, non-case-ignorable character toTitle.  Case-ignorable
    105         // characters are copied directly and do not change the mode.
    106 
    107         iter.setText(text);
    108         iter.setIndex(offsets.start);
    109         iter.setLimit(offsets.limit);
    110         iter.setContextLimits(offsets.contextStart, offsets.contextLimit);
    111 
    112         result.setLength(0);
    113 
    114         // Walk through original string
    115         // If there is a case change, modify corresponding position in replaceable
    116         int delta;
    117 
    118         while((c=iter.nextCaseMapCP())>=0) {
    119             type=csp.getTypeOrIgnorable(c);
    120             if(type>=0) { // not case-ignorable
    121                 if(doTitle) {
    122                     c=csp.toFullTitle(c, iter, result, caseLocale);
    123                 } else {
    124                     c=csp.toFullLower(c, iter, result, caseLocale);
    125                 }
    126                 doTitle = type==0; // doTitle=isUncased
    127 
    128                 if(iter.didReachLimit() && isIncremental) {
    129                     // the case mapping function tried to look beyond the context limit
    130                     // wait for more input
    131                     offsets.start=iter.getCaseMapCPStart();
    132                     return;
    133                 }
    134 
    135                 /* decode the result */
    136                 if(c<0) {
    137                     /* c mapped to itself, no change */
    138                     continue;
    139                 } else if(c<=UCaseProps.MAX_STRING_LENGTH) {
    140                     /* replace by the mapping string */
    141                     delta=iter.replace(result.toString());
    142                     result.setLength(0);
    143                 } else {
    144                     /* replace by single-code point mapping */
    145                     delta=iter.replace(UTF16.valueOf(c));
    146                 }
    147 
    148                 if(delta!=0) {
    149                     offsets.limit += delta;
    150                     offsets.contextLimit += delta;
    151                 }
    152             }
    153         }
    154         offsets.start = offsets.limit;
    155     }
    156 
    157     // NOTE: normally this would be static, but because the results vary by locale....
    158     SourceTargetUtility sourceTargetUtility = null;
    159 
    160     /* (non-Javadoc)
    161      * @see android.icu.text.Transliterator#addSourceTargetSet(android.icu.text.UnicodeSet, android.icu.text.UnicodeSet, android.icu.text.UnicodeSet)
    162      */
    163     @Override
    164     public void addSourceTargetSet(UnicodeSet inputFilter, UnicodeSet sourceSet, UnicodeSet targetSet) {
    165         synchronized (this) {
    166             if (sourceTargetUtility == null) {
    167                 sourceTargetUtility = new SourceTargetUtility(new Transform<String,String>() {
    168                     @Override
    169                     public String transform(String source) {
    170                         return UCharacter.toTitleCase(locale, source, null);
    171                     }
    172                 });
    173             }
    174         }
    175         sourceTargetUtility.addSourceTargetSet(this, inputFilter, sourceSet, targetSet);
    176     }
    177 }
    178