diff --git a/common/web/types/src/ldml-keyboard/pattern-parser.ts b/common/web/types/src/ldml-keyboard/pattern-parser.ts index b0d6fb07962..055926960a1 100644 --- a/common/web/types/src/ldml-keyboard/pattern-parser.ts +++ b/common/web/types/src/ldml-keyboard/pattern-parser.ts @@ -65,10 +65,18 @@ export class MarkerParser { /** Max count of markers */ public static readonly MAX_MARKER_COUNT = constants.marker_max_count; + /** 0000 … FFFF */ + private static hexQuad(n: number): string { + if (n < 0x000 || n > 0xFFFF) { + throw RangeError(`${n} not in [0x0000,0xFFFF]`); + } + return n.toString(16).padStart(4, '0'); + } + private static anyMarkerMatch() : string { - const start = (`0000` + (this.MIN_MARKER_INDEX).toString(16)).slice(-4); - const end = (`0000` + (this.MAX_MARKER_INDEX).toString(16)).slice(-4); - return `${this.SENTINEL}${this.MARKER_CODE}[\\u${start}-\\u${end}]`; + const start = MarkerParser.hexQuad(this.MIN_MARKER_INDEX); + const end = MarkerParser.hexQuad(this.MAX_MARKER_INDEX); + return `${this.SENTINEL}${this.MARKER_CODE}[\\u${start}-\\u${end}]`; // TODO-LDML: #9121 wrong escape format } /** Expression that matches any marker */ @@ -91,12 +99,20 @@ export class MarkerParser { return matchArray(str, this.REFERENCE); } + private static markerCodeToString(n: number, forMatch?: boolean): string { + if (!forMatch) { + return String.fromCharCode(n); + } else { + return `\\u${MarkerParser.hexQuad(n)}`; // TODO-LDML: #9121 wrong escape format + } + } + /** @returns string for marker #n */ - public static markerOutput(n: number): string { + public static markerOutput(n: number, forMatch?: boolean): string { if (n < MarkerParser.MIN_MARKER_INDEX || n > MarkerParser.ANY_MARKER_INDEX) { throw RangeError(`Internal Error: marker index out of range ${n}`); } - return this.SENTINEL + this.MARKER_CODE + String.fromCharCode(n); + return this.SENTINEL + this.MARKER_CODE + this.markerCodeToString(n, forMatch); } /** @returns all marker strings as sentinel values */ @@ -118,7 +134,7 @@ export class MarkerParser { } else if(order > MarkerParser.MAX_MARKER_INDEX) { throw RangeError(`Internal Error: marker \\m{${arg}} has out of range index ${order}`); } else { - return MarkerParser.markerOutput(order + 1); + return MarkerParser.markerOutput(order + 1, forMatch); } }); } diff --git a/core/src/ldml/ldml_transforms.cpp b/core/src/ldml/ldml_transforms.cpp index a69e0d328ad..6f1ddcb0a5f 100644 --- a/core/src/ldml/ldml_transforms.cpp +++ b/core/src/ldml/ldml_transforms.cpp @@ -518,8 +518,8 @@ transform_entry::init() { } // TODO-LDML: if we have mapFrom, may need to do other processing. std::u16string patstr = km::core::kmx::u32string_to_u16string(fFrom); - // normalize, including markers - normalize_nfd_markers(patstr); + // normalize, including markers, for regex + normalize_nfd_markers(patstr, regex_sentinel); UErrorCode status = U_ZERO_ERROR; /* const */ icu::UnicodeString patustr = icu::UnicodeString(patstr.data(), (int32_t)patstr.length()); // add '$' to match to end @@ -950,9 +950,9 @@ bool normalize_nfd(std::u16string &str) { return normalize(nfd, str, status); } -bool normalize_nfd_markers(std::u16string &str, marker_map &map) { +bool normalize_nfd_markers(std::u16string &str, marker_map &map, marker_encoding encoding) { std::u32string rstr = km::core::kmx::u16string_to_u32string(str); - if(!normalize_nfd_markers(rstr, map)) { + if(!normalize_nfd_markers(rstr, map, encoding)) { return false; } else { str = km::core::kmx::u32string_to_u16string(rstr); @@ -960,7 +960,7 @@ bool normalize_nfd_markers(std::u16string &str, marker_map &map) { } } -void add_back_markers(std::u32string &str, const std::u32string &src, const marker_map &map) { +static void add_back_markers(std::u32string &str, const std::u32string &src, const marker_map &map, marker_encoding encoding) { // need to reconstitute. marker_map map2(map); // make a copy of the map // clear the string @@ -970,7 +970,7 @@ void add_back_markers(std::u32string &str, const std::u32string &src, const mark const auto ch = MARKER_BEFORE_EOT; const auto m = map2.find(ch); if (m != map2.end()) { - prepend_marker(str, m->second); + prepend_marker(str, m->second, encoding); map2.erase(ch); // remove it } } @@ -981,7 +981,7 @@ void add_back_markers(std::u32string &str, const std::u32string &src, const mark const auto m = map2.find(ch); if (m != map2.end()) { - prepend_marker(str, m->second); + prepend_marker(str, m->second, encoding); map2.erase(ch); // remove it } } @@ -992,9 +992,9 @@ void add_back_markers(std::u32string &str, const std::u32string &src, const mark * - doesn't support >1 marker per char - may need a set instead of a map! * - ideally this should be used on a normalization safe subsequence */ -bool normalize_nfd_markers(std::u32string &str, marker_map &map) { +bool normalize_nfd_markers(std::u32string &str, marker_map &map, marker_encoding encoding) { /** original string, but no markers */ - std::u32string str_unmarked = remove_markers(str, map); + std::u32string str_unmarked = remove_markers(str, map, encoding); /** original string, no markers, NFD */ std::u32string str_unmarked_nfd = str_unmarked; if(!normalize_nfd(str_unmarked_nfd)) { @@ -1006,14 +1006,14 @@ bool normalize_nfd_markers(std::u32string &str, marker_map &map) { // Normalization produced no change when markers were removed. // So, we'll call this a no-op. } else { - add_back_markers(str, str_unmarked_nfd, map); + add_back_markers(str, str_unmarked_nfd, map, encoding); } return true; // all OK } -bool normalize_nfc_markers(std::u32string &str, marker_map &map) { +bool normalize_nfc_markers(std::u32string &str, marker_map &map, marker_encoding encoding) { /** original string, but no markers */ - std::u32string str_unmarked = remove_markers(str, map); + std::u32string str_unmarked = remove_markers(str, map, encoding); /** original string, no markers, NFC */ std::u32string str_unmarked_nfc = str_unmarked; if(!normalize_nfc(str_unmarked_nfc)) { @@ -1025,7 +1025,7 @@ bool normalize_nfc_markers(std::u32string &str, marker_map &map) { // Normalization produced no change when markers were removed. // So, we'll call this a no-op. } else { - add_back_markers(str, str_unmarked_nfc, map); + add_back_markers(str, str_unmarked_nfc, map, encoding); } return true; // all OK } @@ -1048,7 +1048,78 @@ bool normalize_nfc(std::u16string &str) { return normalize(nfc, str, status); } -std::u32string remove_markers(const std::u32string &str, marker_map *markers) { +void +prepend_marker(std::u32string &str, KMX_DWORD marker, marker_encoding encoding) { + if (encoding == plain_sentinel) { + km_core_usv markstr[] = {LDML_UC_SENTINEL, LDML_MARKER_CODE, marker}; + str.insert(0, markstr, 3); + } else { + assert(encoding == regex_sentinel); + if (marker == LDML_MARKER_ANY_INDEX) { + // recreate the regex from back to front + str.insert(0, 1, U']'); + prepend_hex_quad(str, LDML_MARKER_MAX_INDEX); + str.insert(0, 1, U'u'); + str.insert(0, 1, U'\\'); + str.insert(0, 1, U'-'); + prepend_hex_quad(str, LDML_MARKER_MIN_INDEX); + str.insert(0, 1, U'u'); + str.insert(0, 1, U'\\'); + str.insert(0, 1, U'['); + str.insert(0, 1, LDML_MARKER_CODE); + str.insert(0, 1, LDML_UC_SENTINEL); + } else { + // add hex part + prepend_hex_quad(str, marker); + // add static part + km_core_usv markstr[] = {LDML_UC_SENTINEL, LDML_MARKER_CODE, u'\\', u'u'}; + str.insert(0, markstr, 4); + } + } +} + +void +prepend_hex_quad(std::u32string &str, KMX_DWORD marker) { + for (auto i = 0; i < 4; i++) { + KMX_DWORD remainder = marker & 0xF; // get the last nibble + char32_t ch; + if (remainder < 0xA) { + ch = U'0' + remainder; + } else { + ch = U'A' + (remainder - 0xA); + } + str.insert(0, 1, ch); // prepend + marker >>= 4; + } +} + +inline int xdigitval(km_core_usv ch) { + if (ch >= U'0' && ch <= U'9') { + return (ch - U'0'); + } else if (ch >= U'a' && ch <= U'f') { + return (0xA + ch - U'a'); + } else if (ch >= U'A' && ch <= U'F') { + return (0xA + ch - U'A'); + } else { + return -1; + } +} + +KMX_DWORD parse_hex_quad(const km_core_usv hex_str[]) { + KMX_DWORD mark_no = 0; + for(auto i = 0; i < 4; i++) { + mark_no <<= 4; + auto c = hex_str[i]; + auto n = xdigitval(c); + if (n == -1) { + return 0; + } + mark_no |= n; + } + return mark_no; +} + +std::u32string remove_markers(const std::u32string &str, marker_map *markers, marker_encoding encoding) { std::u32string out; auto i = str.begin(); auto last = i; @@ -1074,14 +1145,108 @@ std::u32string remove_markers(const std::u32string &str, marker_map *markers) { break; // hit end } - // #3 marker number - const KMX_DWORD marker_no = *i; - assert(marker_no >= LDML_MARKER_MIN_INDEX && marker_no <= LDML_MARKER_MAX_INDEX); - i++; // if end, we'll break out of the loop + KMX_DWORD marker_no; + if (encoding == plain_sentinel) { + // #3 marker number + marker_no = *i; + i++; // if end, we'll break out of the loop + } else { + assert(encoding == regex_sentinel); + // is it an escape or a range? + if (*i == U'\\') { + if (++i == str.end()) { + break; + } + assert(*i == U'u'); + if (++i == str.end()) { + break; + } + km_core_usv markno[4]; + + markno[0] = *(i++); + if (i == str.end()) { + break; + } + markno[1] = *(i++); + if (i == str.end()) { + break; + } + markno[2] = *(i++); + if (i == str.end()) { + break; + } + markno[3] = *(i++); + marker_no = parse_hex_quad(markno); + assert (marker_no != 0); // illegal marker number + } else if (*i == U'[') { + if (++i == str.end()) { + break; + } + assert(*i == U'\\'); + if (++i == str.end()) { + break; + } + assert(*i == U'u'); + if (++i == str.end()) { + break; + } + assert(xdigitval(*i) != -1); + if (++i == str.end()) { + break; + } + assert(xdigitval(*i) != -1); + if (++i == str.end()) { + break; + } + assert(xdigitval(*i) != -1); + if (++i == str.end()) { + break; + } + assert(xdigitval(*i) != -1); + if (++i == str.end()) { + break; + } + assert(*i == U'-'); + if (++i == str.end()) { + break; + } + assert(*i == U'\\'); + if (++i == str.end()) { + break; + } + assert(*i == U'u'); + if (++i == str.end()) { + break; + } + assert(xdigitval(*i) != -1); + if (++i == str.end()) { + break; + } + assert(xdigitval(*i) != -1); + if (++i == str.end()) { + break; + } + assert(xdigitval(*i) != -1); + if (++i == str.end()) { + break; + } + assert(xdigitval(*i) != -1); + if (++i == str.end()) { + break; + } + assert(*i == U']'); + i++; + marker_no = LDML_MARKER_ANY_INDEX; + } else { + assert(*i == U'\\' || *i == U'['); // error. + marker_no = 0; // error, don't record + } + } + assert(marker_no >= LDML_MARKER_MIN_INDEX && marker_no <= LDML_MARKER_ANY_INDEX); last = i; // record the marker - if (markers != nullptr) { + if (marker_no >= LDML_MARKER_MIN_INDEX && markers != nullptr) { if (i == str.end()) { markers->emplace(MARKER_BEFORE_EOT, marker_no); } else { diff --git a/core/src/ldml/ldml_transforms.hpp b/core/src/ldml/ldml_transforms.hpp index 23e22dd0b61..70f8b7b1f73 100644 --- a/core/src/ldml/ldml_transforms.hpp +++ b/core/src/ldml/ldml_transforms.hpp @@ -303,6 +303,14 @@ class transforms { /** indicates that the marker was before the end of text. */ const char32_t MARKER_BEFORE_EOT = km::core::kmx::Uni_FFFE_NONCHARACTER; +/** specify the type of encoding for marker text */ +enum marker_encoding { + /** encoding as UC_SENTINEL + CODE_DEADKEY + */ + plain_sentinel, + /** encoding as a regex matching the marker */ + regex_sentinel, +}; + /** map from following-char to marker number. */ typedef std::map marker_map; @@ -314,57 +322,58 @@ bool normalize_nfd(std::u16string &str); * @param markers will be populated with marker chars * @return false on failure **/ -bool normalize_nfd_markers(std::u32string &str, marker_map &markers); -bool normalize_nfd_markers(std::u16string &str, marker_map &markers); -inline bool normalize_nfd_markers(std::u32string &str); -inline bool normalize_nfd_markers(std::u16string &str); +bool normalize_nfd_markers(std::u32string &str, marker_map &markers, marker_encoding encoding = plain_sentinel); +bool normalize_nfd_markers(std::u16string &str, marker_map &markers, marker_encoding encoding = plain_sentinel); +inline bool normalize_nfd_markers(std::u32string &str, marker_encoding encoding = plain_sentinel); +inline bool normalize_nfd_markers(std::u16string &str, marker_encoding encoding = plain_sentinel); /** Normalize a u32string inplace to NFC, retaining markers. * @param markers will be populated with marker chars * @return false on failure **/ -bool normalize_nfc_markers(std::u32string &str, marker_map &markers); -bool normalize_nfc_markers(std::u16string &str, marker_map &markers); -inline bool normalize_nfc_markers(std::u32string &str); -inline bool normalize_nfc_markers(std::u16string &str); +bool normalize_nfc_markers(std::u32string &str, marker_map &markers, marker_encoding encoding = plain_sentinel); +bool normalize_nfc_markers(std::u16string &str, marker_map &markers, marker_encoding encoding = plain_sentinel); +inline bool normalize_nfc_markers(std::u32string &str, marker_encoding encoding = plain_sentinel); +inline bool normalize_nfc_markers(std::u16string &str, marker_encoding encoding = plain_sentinel); /** Normalize a u32string inplace to NFC. @return false on failure */ bool normalize_nfc(std::u32string &str); /** Normalize a u16string inplace to NFC. @return false on failure */ bool normalize_nfc(std::u16string &str); /** Remove markers and optionally note their glue characters in the map */ -std::u32string remove_markers(const std::u32string &str, marker_map *markers = nullptr); +std::u32string remove_markers(const std::u32string &str, marker_map *markers = nullptr, marker_encoding encoding = plain_sentinel); /** same but with a reference */ -inline std::u32string remove_markers(const std::u32string &str, marker_map &markers) { - return remove_markers(str, &markers); +inline std::u32string remove_markers(const std::u32string &str, marker_map &markers, marker_encoding encoding = plain_sentinel) { + return remove_markers(str, &markers, encoding); } /** prepend the marker string in UC_SENTINEL format to the str */ -inline static void prepend_marker(std::u32string &str, KMX_DWORD marker); +void prepend_marker(std::u32string &str, KMX_DWORD marker, marker_encoding encoding = plain_sentinel); -void -prepend_marker(std::u32string &str, KMX_DWORD marker) { - km_core_usv triple[] = {LDML_UC_SENTINEL, LDML_MARKER_CODE, marker}; - str.insert(0, triple, 3); -} +/** format 'marker' as 0001...FFFF and put it at the beginning of the string */ +void prepend_hex_quad(std::u32string &str, KMX_DWORD marker); -bool normalize_nfd_markers(std::u16string &str) { +/** parse 0001...FFFF into a KMX_DWORD. Returns 0 on failure */ +KMX_DWORD parse_hex_quad(const km_core_usv hex_str[]); + +bool normalize_nfd_markers(std::u16string &str, marker_encoding encoding) { marker_map m; - return normalize_nfd_markers(str, m); + return normalize_nfd_markers(str, m, encoding); } -bool normalize_nfc_markers(std::u16string &str) { +bool normalize_nfc_markers(std::u16string &str, marker_encoding encoding) { marker_map m; - return normalize_nfc_markers(str, m); + return normalize_nfc_markers(str, m, encoding); } -bool normalize_nfd_markers(std::u32string &str) { + +bool normalize_nfd_markers(std::u32string &str, marker_encoding encoding) { marker_map m; - return normalize_nfd_markers(str, m); + return normalize_nfd_markers(str, m, encoding); } -bool normalize_nfc_markers(std::u32string &str) { +bool normalize_nfc_markers(std::u32string &str, marker_encoding encoding) { marker_map m; - return normalize_nfc_markers(str, m); + return normalize_nfc_markers(str, m, encoding); } diff --git a/core/tests/unit/ldml/keyboards/k_211_marker_escape-test.xml b/core/tests/unit/ldml/keyboards/k_211_marker_escape-test.xml new file mode 100644 index 00000000000..f8eb8ed921a --- /dev/null +++ b/core/tests/unit/ldml/keyboards/k_211_marker_escape-test.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/core/tests/unit/ldml/keyboards/k_211_marker_escape.xml b/core/tests/unit/ldml/keyboards/k_211_marker_escape.xml new file mode 100644 index 00000000000..8ddae398195 --- /dev/null +++ b/core/tests/unit/ldml/keyboards/k_211_marker_escape.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/tests/unit/ldml/keyboards/meson.build b/core/tests/unit/ldml/keyboards/meson.build index c703d542ab8..d5bc5fb584e 100644 --- a/core/tests/unit/ldml/keyboards/meson.build +++ b/core/tests/unit/ldml/keyboards/meson.build @@ -41,6 +41,7 @@ tests_with_testdata = [ 'k_200_reorder_nod_Lana', 'k_201_reorder_esk', 'k_210_marker', + 'k_211_marker_escape', ] tests = tests_without_testdata diff --git a/core/tests/unit/ldml/test_transforms.cpp b/core/tests/unit/ldml/test_transforms.cpp index 430261eb4f8..fae6eb33db4 100644 --- a/core/tests/unit/ldml/test_transforms.cpp +++ b/core/tests/unit/ldml/test_transforms.cpp @@ -658,6 +658,32 @@ int test_strutils() { assert_equal(map[0x0300], 0x3L); assert_equal(map[MARKER_BEFORE_EOT], 0x4L); } + { + std::cout << __FILE__ << ":" << __LINE__ << " - prepend hex quad" << std::endl; + { + std::u32string dst; + prepend_hex_quad(dst, 0x0001); + zassert_string_equal(dst, U"0001"); + } + { + std::u32string dst; + prepend_hex_quad(dst, 0xCAFE); + zassert_string_equal(dst, U"CAFE"); + } + { + std::u32string dst; + prepend_hex_quad(dst, 0xFFFF); + zassert_string_equal(dst, U"FFFF"); + } + } + { + std::cout << __FILE__ << ":" << __LINE__ << " - parse hex quad" << std::endl; + assert_equal(parse_hex_quad(U"0001"), 0x0001); + assert_equal(parse_hex_quad(U"CAFE"), 0xCAFE); + assert_equal(parse_hex_quad(U"D00d"), 0xD00D); + assert_equal(parse_hex_quad(U"FFFF"), 0xFFFF); + assert_equal(parse_hex_quad(U"zzzz"), 0); // err + } return EXIT_SUCCESS; } @@ -744,11 +770,11 @@ int test_normalize() { } { - // u"4è\U0000ffff\b\U00000001̠" + // u"4è\U0000ffff\u0008\U00000001̠" marker_map map; std::cout << __FILE__ << ":" << __LINE__ << " - complex test 4a" << std::endl; - const std::u32string src = U"4e\u0300\uFFFF\b\u0001\u0320"; - const std::u32string expect = U"4e\uFFFF\b\u0001\u0320\u0300"; + const std::u32string src = U"4e\u0300\uFFFF\u0008\u0001\u0320"; + const std::u32string expect = U"4e\uFFFF\u0008\u0001\u0320\u0300"; std::u32string dst = src; assert(normalize_nfd_markers(dst, map)); if (dst != expect) { @@ -778,6 +804,41 @@ int test_normalize() { assert_equal(map[MARKER_BEFORE_EOT], 0x1L); } + { + // from tests - regex edition + marker_map map; + std::cout << __FILE__ << ":" << __LINE__ << " - complex test 9c+regex" << std::endl; + const std::u32string src = U"9ce\u0300\uFFFF\u0008\\u0002\u0320\uFFFF\u0008\\u0001"; + const std::u32string expect = U"9ce\uFFFF\u0008\\u0002\u0320\u0300\uFFFF\u0008\\u0001"; + std::u32string dst = src; + assert(normalize_nfd_markers(dst, map, regex_sentinel)); // TODO-LDML: need regex flag + if (dst != expect) { + std::cout << "dst: " << Debug_UnicodeString(dst) << std::endl; + std::cout << "exp: " << Debug_UnicodeString(expect) << std::endl; + } + zassert_string_equal(dst, expect); + assert_equal(map.size(), 2); + assert_equal(map[0x0320], 0x2L); + assert_equal(map[MARKER_BEFORE_EOT], 0x1L); + } + { + // from tests - regex edition + marker_map map; + std::cout << __FILE__ << ":" << __LINE__ << " - complex test \\m{.}" << std::endl; + const std::u32string src = U"9ce\u0300\uFFFF\u0008[\\u0001-\\uD7FE]\u0320\uFFFF\u0008\\u0001"; + const std::u32string expect = U"9ce\uFFFF\u0008[\\u0001-\\uD7FE]\u0320\u0300\uFFFF\u0008\\u0001"; + std::u32string dst = src; + assert(normalize_nfd_markers(dst, map, regex_sentinel)); + if (dst != expect) { + std::cout << "dst: " << Debug_UnicodeString(dst) << std::endl; + std::cout << "exp: " << Debug_UnicodeString(expect) << std::endl; + } + zassert_string_equal(dst, expect); + assert_equal(map.size(), 2); + assert_equal(map[0x0320], LDML_MARKER_ANY_INDEX); + assert_equal(map[MARKER_BEFORE_EOT], 0x1L); + } + return EXIT_SUCCESS; } diff --git a/developer/src/kmc-ldml/test/fixtures/basic.txt b/developer/src/kmc-ldml/test/fixtures/basic.txt index a079ccf320d..d90e762685a 100644 --- a/developer/src/kmc-ldml/test/fixtures/basic.txt +++ b/developer/src/kmc-ldml/test/fixtures/basic.txt @@ -516,7 +516,7 @@ block(strs) # struct COMP_KMXPLUS_STRS { diff(strs,strKeys) sizeof(strKeys,2) diff(strs,strIndicator) sizeof(strIndicator,2) diff(strs,strSentinel0001) sizeof(strSentinel0001,2) - + diff(strs,strSentinel0001r) sizeof(strSentinel0001r,2) # String table -- block(x) is used to store the null u16char at end of each string # without interfering with sizeof() calculation above @@ -555,8 +555,8 @@ block(strs) # struct COMP_KMXPLUS_STRS { block(strKeys) 90 17 b6 17 block(x) 00 00 # 'ថា' # block(strIndicator) 3d d8 40 de block(x) 00 00 # '🙀' - block(strSentinel0001) FF FF 08 00 01 00 block(x) 00 00 # UC_SENTINEL CODE_DEADKEY U+0001 - + block(strSentinel0001) FF FF 08 00 01 00 block(x) 00 00 # UC_SENTINEL CODE_DEADKEY U+0001 + block(strSentinel0001r) FF FF 08 00 5C 00 75 00 30 00 30 00 30 00 31 00 block(x) 00 00 # UC_SENTINEL CODE_DEADKEY \u0001 (regex form) block(endstrs) # end of strs block @@ -598,12 +598,12 @@ block(tran) # struct COMP_KMXPLUS_TRAN { block(tranTransform1) index(strNull,strA,2) # KMXPLUS_STR from; 'a' - index(strNull,strSentinel0001,2) # KMXPLUS_STR to; \m{a} + index(strNull,strSentinel0001,2) # KMXPLUS_STR to; \m{a} (plain form) index(strNull,strNull,2) # mapFrom index(strNull,strNull,2) # mapTo block(tranTransform2) # Next group - index(strNull,strSentinel0001,2) # KMXPLUS_STR from; (\m{a}) + index(strNull,strSentinel0001r,2) # KMXPLUS_STR from; (\m{a}) (regex form) index(strNull,strNull,2) # KMXPLUS_STR to; (none) index(strNull,strNull,2) # mapFrom index(strNull,strNull,2) # mapTo