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