2 **********************************************************************
3 * Copyright (c) 2002-2004, International Business Machines Corporation
4 * and others. All Rights Reserved.
5 **********************************************************************
6 * Date Name Description
7 * 01/14/2002 aliu Creation.
8 **********************************************************************
11 #include "unicode/utypes.h"
13 #if !UCONFIG_NO_TRANSLITERATION
19 #include "unicode/parsepos.h"
20 #include "unicode/translit.h"
21 #include "unicode/uchar.h"
22 #include "unicode/uniset.h"
23 #include "unicode/unistr.h"
24 #include "unicode/utrans.h"
30 static const UChar ID_DELIM
= 0x003B; // ;
31 static const UChar TARGET_SEP
= 0x002D; // -
32 static const UChar VARIANT_SEP
= 0x002F; // /
33 static const UChar OPEN_REV
= 0x0028; // (
34 static const UChar CLOSE_REV
= 0x0029; // )
36 static const UChar EMPTY
[] = {0}; // ""
37 static const UChar ANY
[] = {65,110,121,0}; // "Any"
38 static const UChar ANY_NULL
[] = {65,110,121,45,78,117,108,108,0}; // "Any-Null"
40 static const int32_t FORWARD
= UTRANS_FORWARD
;
41 static const int32_t REVERSE
= UTRANS_REVERSE
;
43 static Hashtable
* SPECIAL_INVERSES
= NULL
;
46 * The mutex controlling access to SPECIAL_INVERSES
50 TransliteratorIDParser::Specs::Specs(const UnicodeString
& s
, const UnicodeString
& t
,
51 const UnicodeString
& v
, UBool sawS
,
52 const UnicodeString
& f
) {
60 TransliteratorIDParser::SingleID::SingleID(const UnicodeString
& c
, const UnicodeString
& b
,
61 const UnicodeString
& f
) {
67 TransliteratorIDParser::SingleID::SingleID(const UnicodeString
& c
, const UnicodeString
& b
) {
72 Transliterator
* TransliteratorIDParser::SingleID::createInstance() {
74 if (basicID
.length() == 0) {
75 t
= createBasicInstance(ANY_NULL
, &canonID
);
77 t
= createBasicInstance(basicID
, &canonID
);
80 if (filter
.length() != 0) {
81 UErrorCode ec
= U_ZERO_ERROR
;
82 UnicodeSet
*set
= new UnicodeSet(filter
, ec
);
95 * Parse a single ID, that is, an ID of the general form
96 * "[f1] s1-t1/v1 ([f2] s2-t3/v2)", with the parenthesized element
97 * optional, the filters optional, and the variants optional.
98 * @param id the id to be parsed
99 * @param pos INPUT-OUTPUT parameter. On input, the position of
100 * the first character to parse. On output, the position after
101 * the last character parsed.
102 * @param dir the direction. If the direction is REVERSE then the
103 * SingleID is constructed for the reverse direction.
104 * @return a SingleID object or NULL
106 TransliteratorIDParser::SingleID
*
107 TransliteratorIDParser::parseSingleID(const UnicodeString
& id
, int32_t& pos
,
108 int32_t dir
, UErrorCode
& status
) {
112 // The ID will be of the form A, A(), A(B), or (B), where
113 // A and B are filter IDs.
114 Specs
* specsA
= NULL
;
115 Specs
* specsB
= NULL
;
116 UBool sawParen
= FALSE
;
118 // On the first pass, look for (B) or (). If this fails, then
119 // on the second pass, look for A, A(B), or A().
120 for (int32_t pass
=1; pass
<=2; ++pass
) {
122 specsA
= parseFilterID(id
, pos
, TRUE
);
123 if (specsA
== NULL
) {
128 if (ICU_Utility::parseChar(id
, pos
, OPEN_REV
)) {
130 if (!ICU_Utility::parseChar(id
, pos
, CLOSE_REV
)) {
131 specsB
= parseFilterID(id
, pos
, TRUE
);
132 // Must close with a ')'
133 if (specsB
== NULL
|| !ICU_Utility::parseChar(id
, pos
, CLOSE_REV
)) {
143 // Assemble return results
146 if (dir
== FORWARD
) {
147 SingleID
* b
= specsToID(specsB
, FORWARD
);
148 single
= specsToID(specsA
, FORWARD
);
149 single
->canonID
.append(OPEN_REV
)
150 .append(b
->canonID
).append(CLOSE_REV
);
151 if (specsA
!= NULL
) {
152 single
->filter
= specsA
->filter
;
156 SingleID
* a
= specsToID(specsA
, FORWARD
);
157 single
= specsToID(specsB
, FORWARD
);
158 single
->canonID
.append(OPEN_REV
)
159 .append(a
->canonID
).append(CLOSE_REV
);
160 if (specsB
!= NULL
) {
161 single
->filter
= specsB
->filter
;
166 // assert(specsA != NULL);
167 if (dir
== FORWARD
) {
168 single
= specsToID(specsA
, FORWARD
);
170 single
= specsToSpecialInverse(*specsA
, status
);
171 if (single
== NULL
) {
172 single
= specsToID(specsA
, REVERSE
);
175 single
->filter
= specsA
->filter
;
185 * Parse a filter ID, that is, an ID of the general form
186 * "[f1] s1-t1/v1", with the filters optional, and the variants optional.
187 * @param id the id to be parsed
188 * @param pos INPUT-OUTPUT parameter. On input, the position of
189 * the first character to parse. On output, the position after
190 * the last character parsed.
191 * @return a SingleID object or null if the parse fails
193 TransliteratorIDParser::SingleID
*
194 TransliteratorIDParser::parseFilterID(const UnicodeString
& id
, int32_t& pos
) {
198 Specs
* specs
= parseFilterID(id
, pos
, TRUE
);
204 // Assemble return results
205 SingleID
* single
= specsToID(specs
, FORWARD
);
206 single
->filter
= specs
->filter
;
212 * Parse a global filter of the form "[f]" or "([f])", depending
214 * @param id the pattern the parse
215 * @param pos INPUT-OUTPUT parameter. On input, the position of
216 * the first character to parse. On output, the position after
217 * the last character parsed.
218 * @param dir the direction.
219 * @param withParens INPUT-OUTPUT parameter. On entry, if
220 * withParens is 0, then parens are disallowed. If it is 1,
221 * then parens are requires. If it is -1, then parens are
222 * optional, and the return result will be set to 0 or 1.
223 * @param canonID OUTPUT parameter. The pattern for the filter
224 * added to the canonID, either at the end, if dir is FORWARD, or
225 * at the start, if dir is REVERSE. The pattern will be enclosed
226 * in parentheses if appropriate, and will be suffixed with an
227 * ID_DELIM character. May be NULL.
228 * @return a UnicodeSet object or NULL. A non-NULL results
229 * indicates a successful parse, regardless of whether the filter
230 * applies to the given direction. The caller should discard it
231 * if withParens != (dir == REVERSE).
233 UnicodeSet
* TransliteratorIDParser::parseGlobalFilter(const UnicodeString
& id
, int32_t& pos
,
236 UnicodeString
* canonID
) {
237 UnicodeSet
* filter
= NULL
;
240 if (withParens
== -1) {
241 withParens
= ICU_Utility::parseChar(id
, pos
, OPEN_REV
) ? 1 : 0;
242 } else if (withParens
== 1) {
243 if (!ICU_Utility::parseChar(id
, pos
, OPEN_REV
)) {
249 ICU_Utility::skipWhitespace(id
, pos
, TRUE
);
251 if (UnicodeSet::resemblesPattern(id
, pos
)) {
252 ParsePosition
ppos(pos
);
253 UErrorCode ec
= U_ZERO_ERROR
;
254 filter
= new UnicodeSet(id
, ppos
, USET_IGNORE_SPACE
, NULL
, ec
);
266 UnicodeString pattern
;
267 id
.extractBetween(pos
, ppos
.getIndex(), pattern
);
268 pos
= ppos
.getIndex();
270 if (withParens
== 1 && !ICU_Utility::parseChar(id
, pos
, CLOSE_REV
)) {
275 // In the forward direction, append the pattern to the
276 // canonID. In the reverse, insert it at zero, and invert
277 // the presence of parens ("A" <-> "(A)").
278 if (canonID
!= NULL
) {
279 if (dir
== FORWARD
) {
280 if (withParens
== 1) {
281 pattern
.insert(0, OPEN_REV
);
282 pattern
.append(CLOSE_REV
);
284 canonID
->append(pattern
).append(ID_DELIM
);
286 if (withParens
== 0) {
287 pattern
.insert(0, OPEN_REV
);
288 pattern
.append(CLOSE_REV
);
290 canonID
->insert(0, pattern
);
291 canonID
->insert(pattern
.length(), ID_DELIM
);
300 static void U_CALLCONV
_deleteSingleID(void* obj
) {
301 delete (TransliteratorIDParser::SingleID
*) obj
;
304 static void U_CALLCONV
_deleteTransliteratorTrIDPars(void* obj
) {
305 delete (Transliterator
*) obj
;
310 * Parse a compound ID, consisting of an optional forward global
311 * filter, a separator, one or more single IDs delimited by
312 * separators, an an optional reverse global filter. The
313 * separator is a semicolon. The global filters are UnicodeSet
314 * patterns. The reverse global filter must be enclosed in
316 * @param id the pattern the parse
317 * @param dir the direction.
318 * @param canonID OUTPUT parameter that receives the canonical ID,
319 * consisting of canonical IDs for all elements, as returned by
320 * parseSingleID(), separated by semicolons. Previous contents
322 * @param list OUTPUT parameter that receives a list of SingleID
323 * objects representing the parsed IDs. Previous contents are
325 * @param globalFilter OUTPUT parameter that receives a pointer to
326 * a newly created global filter for this ID in this direction, or
327 * NULL if there is none.
328 * @return TRUE if the parse succeeds, that is, if the entire
329 * id is consumed without syntax error.
331 UBool
TransliteratorIDParser::parseCompoundID(const UnicodeString
& id
, int32_t dir
,
332 UnicodeString
& canonID
,
334 UnicodeSet
*& globalFilter
) {
335 UErrorCode ec
= U_ZERO_ERROR
;
338 int32_t withParens
= 1;
339 list
.removeAllElements();
344 // Parse leading global filter, if any
345 withParens
= 0; // parens disallowed
346 filter
= parseGlobalFilter(id
, pos
, dir
, withParens
, &canonID
);
347 if (filter
!= NULL
) {
348 if (!ICU_Utility::parseChar(id
, pos
, ID_DELIM
)) {
349 // Not a global filter; backup and resume
353 if (dir
== FORWARD
) {
354 globalFilter
= filter
;
361 UBool sawDelimiter
= TRUE
;
363 SingleID
* single
= parseSingleID(id
, pos
, dir
, ec
);
364 if (single
== NULL
) {
367 if (dir
== FORWARD
) {
368 list
.addElement(single
, ec
);
370 list
.insertElementAt(single
, 0, ec
);
375 if (!ICU_Utility::parseChar(id
, pos
, ID_DELIM
)) {
376 sawDelimiter
= FALSE
;
381 if (list
.size() == 0) {
385 // Construct canonical ID
386 for (i
=0; i
<list
.size(); ++i
) {
387 SingleID
* single
= (SingleID
*) list
.elementAt(i
);
388 canonID
.append(single
->canonID
);
389 if (i
!= (list
.size()-1)) {
390 canonID
.append(ID_DELIM
);
394 // Parse trailing global filter, if any, and only if we saw
395 // a trailing delimiter after the IDs.
397 withParens
= 1; // parens required
398 filter
= parseGlobalFilter(id
, pos
, dir
, withParens
, &canonID
);
399 if (filter
!= NULL
) {
400 // Don't require trailing ';', but parse it if present
401 ICU_Utility::parseChar(id
, pos
, ID_DELIM
);
403 if (dir
== REVERSE
) {
404 globalFilter
= filter
;
412 // Trailing unparsed text is a syntax error
413 ICU_Utility::skipWhitespace(id
, pos
, TRUE
);
414 if (pos
!= id
.length()) {
421 UObjectDeleter
*save
= list
.setDeleter(_deleteSingleID
);
422 list
.removeAllElements();
423 list
.setDeleter(save
);
430 * Convert the elements of the 'list' vector, which are SingleID
431 * objects, into actual Transliterator objects. In the course of
432 * this, some (or all) entries may be removed. If all entries
433 * are removed, the NULL transliterator will be added.
435 * Delete entries with empty basicIDs; these are generated by
436 * elements like "(A)" in the forward direction, or "A()" in
437 * the reverse. THIS MAY RESULT IN AN EMPTY VECTOR. Convert
438 * SingleID entries to actual transliterators.
440 * Also, optionally, insert the given transliterator at the given
441 * position. This effectively happens before anything else.
443 * @param list vector of SingleID objects. On exit, vector
444 * of one or more Transliterators.
445 * @param insert Transliterator to insert, or NULL if none.
447 * @param insertIndex index from 0..list.size()-1, at which
448 * to place 'insert', or -1 if none.
449 * @return new value of insertIndex. The index will shift if
450 * there are empty items, like "(Lower)", with indices less than
453 int32_t TransliteratorIDParser::instantiateList(UVector
& list
,
454 Transliterator
* insert
,
461 tlist
.setDeleter(_deleteTransliteratorTrIDPars
);
465 for (i
=0; i
<=list
.size(); ++i
) { // [sic]: i<=list.size()
466 if (insertIndex
== i
) {
467 insertIndex
= tlist
.size();
468 tlist
.addElement(insert
, ec
);
475 // We run the loop too long by one, so we can
476 // do an insert after the last element
477 if (i
==list
.size()) {
481 SingleID
* single
= (SingleID
*) list
.elementAt(i
);
482 if (single
->basicID
.length() != 0) {
483 t
= single
->createInstance();
488 tlist
.addElement(t
, ec
);
496 // An empty list is equivalent to a NULL transliterator.
497 if (tlist
.size() == 0) {
498 t
= createBasicInstance(ANY_NULL
, NULL
);
500 // Should never happen
501 ec
= U_INTERNAL_TRANSLITERATOR_ERROR
;
503 tlist
.addElement(t
, ec
);
511 UObjectDeleter
*save
= list
.setDeleter(_deleteSingleID
);
512 list
.removeAllElements();
515 list
.setDeleter(_deleteTransliteratorTrIDPars
);
517 while (tlist
.size() > 0) {
518 t
= (Transliterator
*) tlist
.orphanElementAt(0);
519 list
.addElement(t
, ec
);
522 list
.removeAllElements();
528 delete insert
; // Clean up in case of failure
529 list
.setDeleter(save
);
534 * Parse an ID into pieces. Take IDs of the form T, T/V, S-T,
535 * S-T/V, or S/V-T. If the source is missing, return a source of
537 * @param id the id string, in any of several forms
538 * @return an array of 4 strings: source, target, variant, and
539 * isSourcePresent. If the source is not present, ANY will be
540 * given as the source, and isSourcePresent will be NULL. Otherwise
541 * isSourcePresent will be non-NULL. The target may be empty if the
542 * id is not well-formed. The variant may be empty.
544 void TransliteratorIDParser::IDtoSTV(const UnicodeString
& id
,
545 UnicodeString
& source
,
546 UnicodeString
& target
,
547 UnicodeString
& variant
,
548 UBool
& isSourcePresent
) {
553 int32_t sep
= id
.indexOf(TARGET_SEP
);
554 int32_t var
= id
.indexOf(VARIANT_SEP
);
558 isSourcePresent
= FALSE
;
561 // Form: T/V or T (or /V)
562 id
.extractBetween(0, var
, target
);
563 id
.extractBetween(var
, id
.length(), variant
);
564 } else if (sep
< var
) {
565 // Form: S-T/V or S-T (or -T/V or -T)
567 id
.extractBetween(0, sep
, source
);
568 isSourcePresent
= TRUE
;
570 id
.extractBetween(++sep
, var
, target
);
571 id
.extractBetween(var
, id
.length(), variant
);
573 // Form: (S/V-T or /V-T)
575 id
.extractBetween(0, var
, source
);
576 isSourcePresent
= TRUE
;
578 id
.extractBetween(var
, sep
++, variant
);
579 id
.extractBetween(sep
, id
.length(), target
);
582 if (variant
.length() > 0) {
583 variant
.remove(0, 1);
588 * Given source, target, and variant strings, concatenate them into a
589 * full ID. If the source is empty, then "Any" will be used for the
590 * source, so the ID will always be of the form s-t/v or s-t.
592 void TransliteratorIDParser::STVtoID(const UnicodeString
& source
,
593 const UnicodeString
& target
,
594 const UnicodeString
& variant
,
597 if (id
.length() == 0) {
600 id
.append(TARGET_SEP
).append(target
);
601 if (variant
.length() != 0) {
602 id
.append(VARIANT_SEP
).append(variant
);
607 * Register two targets as being inverses of one another. For
608 * example, calling registerSpecialInverse("NFC", "NFD", TRUE) causes
609 * Transliterator to form the following inverse relationships:
614 * Any-NFD => Any-NFC</pre>
616 * (Without the special inverse registration, the inverse of NFC
617 * would be NFC-Any.) Note that NFD is shorthand for Any-NFD, but
618 * that the presence or absence of "Any-" is preserved.
620 * <p>The relationship is symmetrical; registering (a, b) is
621 * equivalent to registering (b, a).
623 * <p>The relevant IDs must still be registered separately as
624 * factories or classes.
626 * <p>Only the targets are specified. Special inverses always
627 * have the form Any-Target1 <=> Any-Target2. The target should
628 * have canonical casing (the casing desired to be produced when
629 * an inverse is formed) and should contain no whitespace or other
630 * extraneous characters.
632 * @param target the target against which to register the inverse
633 * @param inverseTarget the inverse of target, that is
634 * Any-target.getInverse() => Any-inverseTarget
635 * @param bidirectional if TRUE, register the reverse relation
636 * as well, that is, Any-inverseTarget.getInverse() => Any-target
638 void TransliteratorIDParser::registerSpecialInverse(const UnicodeString
& target
,
639 const UnicodeString
& inverseTarget
,
641 UErrorCode
&status
) {
643 if (U_FAILURE(status
)) {
647 // If target == inverseTarget then force bidirectional => FALSE
648 if (bidirectional
&& 0==target
.caseCompare(inverseTarget
, U_FOLD_CASE_DEFAULT
)) {
649 bidirectional
= FALSE
;
655 SPECIAL_INVERSES
->put(target
, new UnicodeString(inverseTarget
), status
);
657 SPECIAL_INVERSES
->put(inverseTarget
, new UnicodeString(target
), status
);
661 //----------------------------------------------------------------
662 // Private implementation
663 //----------------------------------------------------------------
666 * Parse an ID into component pieces. Take IDs of the form T,
667 * T/V, S-T, S-T/V, or S/V-T. If the source is missing, return a
669 * @param id the id string, in any of several forms
670 * @param pos INPUT-OUTPUT parameter. On input, pos is the
671 * offset of the first character to parse in id. On output,
672 * pos is the offset after the last parsed character. If the
673 * parse failed, pos will be unchanged.
674 * @param allowFilter2 if TRUE, a UnicodeSet pattern is allowed
675 * at any location between specs or delimiters, and is returned
676 * as the fifth string in the array.
677 * @return a Specs object, or NULL if the parse failed. If
678 * neither source nor target was seen in the parsed id, then the
679 * parse fails. If allowFilter is TRUE, then the parsed filter
680 * pattern is returned in the Specs object, otherwise the returned
681 * filter reference is NULL. If the parse fails for any reason
684 TransliteratorIDParser::Specs
*
685 TransliteratorIDParser::parseFilterID(const UnicodeString
& id
, int32_t& pos
,
688 UnicodeString source
;
689 UnicodeString target
;
690 UnicodeString variant
;
691 UnicodeString filter
;
693 int32_t specCount
= 0;
696 // This loop parses one of the following things with each
697 // pass: a filter, a delimiter character (either '-' or '/'),
698 // or a spec (source, target, or variant).
700 ICU_Utility::skipWhitespace(id
, pos
, TRUE
);
701 if (pos
== id
.length()) {
706 if (allowFilter
&& filter
.length() == 0 &&
707 UnicodeSet::resemblesPattern(id
, pos
)) {
709 ParsePosition
ppos(pos
);
710 UErrorCode ec
= U_ZERO_ERROR
;
711 UnicodeSet
set(id
, ppos
, USET_IGNORE_SPACE
, NULL
, ec
);
716 id
.extractBetween(pos
, ppos
.getIndex(), filter
);
717 pos
= ppos
.getIndex();
721 if (delimiter
== 0) {
722 UChar c
= id
.charAt(pos
);
723 if ((c
== TARGET_SEP
&& target
.length() == 0) ||
724 (c
== VARIANT_SEP
&& variant
.length() == 0)) {
731 // We are about to try to parse a spec with no delimiter
732 // when we can no longer do so (we can only do so at the
734 if (delimiter
== 0 && specCount
> 0) {
738 UnicodeString spec
= ICU_Utility::parseUnicodeIdentifier(id
, pos
);
739 if (spec
.length() == 0) {
740 // Note that if there was a trailing delimiter, we
741 // consume it. So Foo-, Foo/, Foo-Bar/, and Foo/Bar-
761 // A spec with no prior character is either source or target,
762 // depending on whether an explicit "-target" was seen.
763 if (first
.length() != 0) {
764 if (target
.length() == 0) {
771 // Must have either source or target
772 if (source
.length() == 0 && target
.length() == 0) {
777 // Empty source or target defaults to ANY
778 UBool sawSource
= TRUE
;
779 if (source
.length() == 0) {
783 if (target
.length() == 0) {
787 return new Specs(source
, target
, variant
, sawSource
, filter
);
791 * Givens a Spec object, convert it to a SingleID object. The
792 * Spec object is a more unprocessed parse result. The SingleID
793 * object contains information about canonical and basic IDs.
794 * @return a SingleID; never returns NULL. Returned object always
795 * has 'filter' field of NULL.
797 TransliteratorIDParser::SingleID
*
798 TransliteratorIDParser::specsToID(const Specs
* specs
, int32_t dir
) {
799 UnicodeString canonID
;
800 UnicodeString basicID
;
801 UnicodeString basicPrefix
;
804 if (dir
== FORWARD
) {
805 if (specs
->sawSource
) {
806 buf
.append(specs
->source
).append(TARGET_SEP
);
808 basicPrefix
= specs
->source
;
809 basicPrefix
.append(TARGET_SEP
);
811 buf
.append(specs
->target
);
813 buf
.append(specs
->target
).append(TARGET_SEP
).append(specs
->source
);
815 if (specs
->variant
.length() != 0) {
816 buf
.append(VARIANT_SEP
).append(specs
->variant
);
818 basicID
= basicPrefix
;
820 if (specs
->filter
.length() != 0) {
821 buf
.insert(0, specs
->filter
);
825 return new SingleID(canonID
, basicID
);
829 * Given a Specs object, return a SingleID representing the
830 * special inverse of that ID. If there is no special inverse
832 * @return a SingleID or NULL. Returned object always has
833 * 'filter' field of NULL.
835 TransliteratorIDParser::SingleID
*
836 TransliteratorIDParser::specsToSpecialInverse(const Specs
& specs
, UErrorCode
&status
) {
837 if (0!=specs
.source
.caseCompare(ANY
, U_FOLD_CASE_DEFAULT
)) {
842 UnicodeString
* inverseTarget
;
846 inverseTarget
= (UnicodeString
*) SPECIAL_INVERSES
->get(specs
.target
);
849 if (inverseTarget
!= NULL
) {
850 // If the original ID contained "Any-" then make the
851 // special inverse "Any-Foo"; otherwise make it "Foo".
852 // So "Any-NFC" => "Any-NFD" but "NFC" => "NFD".
854 if (specs
.filter
.length() != 0) {
855 buf
.append(specs
.filter
);
857 if (specs
.sawSource
) {
858 buf
.append(ANY
).append(TARGET_SEP
);
860 buf
.append(*inverseTarget
);
862 UnicodeString
basicID(ANY
);
863 basicID
.append(TARGET_SEP
).append(*inverseTarget
);
865 if (specs
.variant
.length() != 0) {
866 buf
.append(VARIANT_SEP
).append(specs
.variant
);
867 basicID
.append(VARIANT_SEP
).append(specs
.variant
);
869 return new SingleID(buf
, basicID
);
875 * Glue method to get around access problems in C++. This would
876 * ideally be inline but we want to avoid a circular header
879 Transliterator
* TransliteratorIDParser::createBasicInstance(const UnicodeString
& id
, const UnicodeString
* canonID
) {
880 return Transliterator::createBasicInstance(id
, canonID
);
884 * Initialize static memory.
886 void TransliteratorIDParser::init(UErrorCode
&status
) {
887 if (SPECIAL_INVERSES
!= NULL
) {
891 Hashtable
* special_inverses
= new Hashtable(TRUE
, status
);
892 special_inverses
->setValueDeleter(uhash_deleteUnicodeString
);
896 if (SPECIAL_INVERSES
== NULL
) {
897 SPECIAL_INVERSES
= special_inverses
;
898 special_inverses
= NULL
;
901 delete special_inverses
;
903 ucln_i18n_registerCleanup(UCLN_I18N_TRANSLITERATOR
, transliterator_cleanup
);
907 * Free static memory.
909 void TransliteratorIDParser::cleanup() {
910 if (SPECIAL_INVERSES
) {
911 delete SPECIAL_INVERSES
;
912 SPECIAL_INVERSES
= NULL
;
919 #endif /* #if !UCONFIG_NO_TRANSLITERATION */