diff --git a/icu4c/source/i18n/formatted_string_builder.cpp b/icu4c/source/i18n/formatted_string_builder.cpp index 4ffc4c704a1..3024bff6add 100644 --- a/icu4c/source/i18n/formatted_string_builder.cpp +++ b/icu4c/source/i18n/formatted_string_builder.cpp @@ -144,10 +144,6 @@ FormattedStringBuilder &FormattedStringBuilder::clear() { return *this; } -int32_t FormattedStringBuilder::appendCodePoint(UChar32 codePoint, Field field, UErrorCode &status) { - return insertCodePoint(fLength, codePoint, field, status); -} - int32_t FormattedStringBuilder::insertCodePoint(int32_t index, UChar32 codePoint, Field field, UErrorCode &status) { int32_t count = U16_LENGTH(codePoint); @@ -166,10 +162,6 @@ FormattedStringBuilder::insertCodePoint(int32_t index, UChar32 codePoint, Field return count; } -int32_t FormattedStringBuilder::append(const UnicodeString &unistr, Field field, UErrorCode &status) { - return insert(fLength, unistr, field, status); -} - int32_t FormattedStringBuilder::insert(int32_t index, const UnicodeString &unistr, Field field, UErrorCode &status) { if (unistr.length() == 0) { diff --git a/icu4c/source/i18n/formatted_string_builder.h b/icu4c/source/i18n/formatted_string_builder.h index cc13b66353d..2949ae73e0f 100644 --- a/icu4c/source/i18n/formatted_string_builder.h +++ b/icu4c/source/i18n/formatted_string_builder.h @@ -85,29 +85,73 @@ class U_I18N_API FormattedStringBuilder : public UMemory { FormattedStringBuilder &clear(); - int32_t appendCodePoint(UChar32 codePoint, Field field, UErrorCode &status); + /** Appends a UTF-16 code unit. */ + inline int32_t appendChar16(char16_t codeUnit, Field field, UErrorCode& status) { + // appendCodePoint handles both code units and code points. + return insertCodePoint(fLength, codeUnit, field, status); + } + /** Inserts a UTF-16 code unit. Note: insert at index 0 is very efficient. */ + inline int32_t insertChar16(int32_t index, char16_t codeUnit, Field field, UErrorCode& status) { + // insertCodePoint handles both code units and code points. + return insertCodePoint(index, codeUnit, field, status); + } + + /** Appends a Unicode code point. */ + inline int32_t appendCodePoint(UChar32 codePoint, Field field, UErrorCode &status) { + return insertCodePoint(fLength, codePoint, field, status); + } + + /** Inserts a Unicode code point. Note: insert at index 0 is very efficient. */ int32_t insertCodePoint(int32_t index, UChar32 codePoint, Field field, UErrorCode &status); - int32_t append(const UnicodeString &unistr, Field field, UErrorCode &status); + /** Appends a string. */ + inline int32_t append(const UnicodeString &unistr, Field field, UErrorCode &status) { + return insert(fLength, unistr, field, status); + } + /** Inserts a string. Note: insert at index 0 is very efficient. */ int32_t insert(int32_t index, const UnicodeString &unistr, Field field, UErrorCode &status); + /** Inserts a substring. Note: insert at index 0 is very efficient. + * + * @param start Start index of the substring of unistr to be inserted. + * @param end End index of the substring of unistr to be inserted (exclusive). + */ int32_t insert(int32_t index, const UnicodeString &unistr, int32_t start, int32_t end, Field field, UErrorCode &status); + /** Deletes a substring and then inserts a string at that same position. + * Similar to JavaScript Array.prototype.splice(). + * + * @param startThis Start of the span to delete. + * @param endThis End of the span to delete (exclusive). + * @param unistr The string to insert at the deletion position. + * @param startOther Start index of the substring of unistr to be inserted. + * @param endOther End index of the substring of unistr to be inserted (exclusive). + */ int32_t splice(int32_t startThis, int32_t endThis, const UnicodeString &unistr, int32_t startOther, int32_t endOther, Field field, UErrorCode& status); + /** Appends a formatted string. */ int32_t append(const FormattedStringBuilder &other, UErrorCode &status); + /** Inserts a formatted string. Note: insert at index 0 is very efficient. */ int32_t insert(int32_t index, const FormattedStringBuilder &other, UErrorCode &status); + /** + * Ensures that the string buffer contains a NUL terminator. The NUL terminator does + * not count toward the string length. Any further changes to the string (insert or + * append) may invalidate the NUL terminator. + * + * You should call this method after the formatted string is completely built if you + * plan to return a pointer to the string from a C API. + */ void writeTerminator(UErrorCode& status); /** * Gets a "safe" UnicodeString that can be used even after the FormattedStringBuilder is destructed. - * */ + */ UnicodeString toUnicodeString() const; /** diff --git a/icu4c/source/i18n/measfmt.cpp b/icu4c/source/i18n/measfmt.cpp index 3c4d5d6f2ce..8f306c08f87 100644 --- a/icu4c/source/i18n/measfmt.cpp +++ b/icu4c/source/i18n/measfmt.cpp @@ -777,7 +777,7 @@ UnicodeString &MeasureFormat::formatNumeric( case u'm': case u's': if (protect) { - fsb.appendCodePoint(c, undefinedField, status); + fsb.appendChar16(c, undefinedField, status); } else { UnicodeString tmp; if ((i + 1 < patternLength) && pattern[i + 1] == c) { // doubled @@ -793,14 +793,14 @@ UnicodeString &MeasureFormat::formatNumeric( case u'\'': // '' is escaped apostrophe if ((i + 1 < patternLength) && pattern[i + 1] == c) { - fsb.appendCodePoint(c, undefinedField, status); + fsb.appendChar16(c, undefinedField, status); i++; } else { protect = !protect; } break; default: - fsb.appendCodePoint(c, undefinedField, status); + fsb.appendChar16(c, undefinedField, status); } } diff --git a/icu4c/source/test/intltest/formatted_string_builder_test.cpp b/icu4c/source/test/intltest/formatted_string_builder_test.cpp index b0c45525de4..4ce63daec56 100644 --- a/icu4c/source/test/intltest/formatted_string_builder_test.cpp +++ b/icu4c/source/test/intltest/formatted_string_builder_test.cpp @@ -175,6 +175,18 @@ void FormattedStringBuilderTest::testInsertAppendCodePoint() { sb5.insertCodePoint(2, cas, UNUM_FIELD_COUNT, status); assertSuccess("Inserting into sb5", status); assertEqualsImpl(sb4, sb5); + + UnicodeString sb6; + FormattedStringBuilder sb7; + sb6.append(cas); + if (U_IS_SUPPLEMENTARY(cas)) { + sb7.appendChar16(U16_TRAIL(cas), UNUM_FIELD_COUNT, status); + sb7.insertChar16(0, U16_LEAD(cas), UNUM_FIELD_COUNT, status); + } else { + sb7.insertChar16(0, cas, UNUM_FIELD_COUNT, status); + } + assertSuccess("Insert/append into sb7", status); + assertEqualsImpl(sb6, sb7); } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/FormattedStringBuilder.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/FormattedStringBuilder.java index ca3cb187bda..0eb54f5a695 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/FormattedStringBuilder.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/FormattedStringBuilder.java @@ -106,6 +106,18 @@ public class FormattedStringBuilder implements CharSequence { return this; } + public int appendChar16(char codeUnit, Field field) { + return insertChar16(length, codeUnit, field); + } + + public int insertChar16(int index, char codeUnit, Field field) { + int count = 1; + int position = prepareForInsert(index, count); + chars[position] = codeUnit; + fields[position] = field; + return count; + } + /** * Appends the specified codePoint to the end of the string. * diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/MeasureFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/MeasureFormat.java index d3f230678e4..4925c63d088 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/MeasureFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/MeasureFormat.java @@ -904,7 +904,7 @@ public class MeasureFormat extends UFormat { case 'm': case 's': if (protect) { - fsb.appendCodePoint(c, null); + fsb.appendChar16(c, null); } else { if ((i + 1 < pattern.length()) && pattern.charAt(i + 1) == c) { // doubled fsb.append(numberFormatter2.format(value), null); // TODO: Use proper Field @@ -917,14 +917,14 @@ public class MeasureFormat extends UFormat { case '\'': // '' is escaped apostrophe if ((i + 1 < pattern.length()) && pattern.charAt(i + 1) == c) { - fsb.appendCodePoint(c, null); + fsb.appendChar16(c, null); i++; } else { protect = !protect; } break; default: - fsb.appendCodePoint(c, null); + fsb.appendChar16(c, null); } } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/FormattedStringBuilderTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/FormattedStringBuilderTest.java index caf21627e5f..b297b1f71db 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/FormattedStringBuilderTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/FormattedStringBuilderTest.java @@ -37,8 +37,8 @@ public class FormattedStringBuilderTest { sb1.append(str); sb2.append(str, null); sb3.append(str, null); - assertCharSequenceEquals(sb1, sb2); - assertCharSequenceEquals(sb3, str); + assertCharSequenceEquals(str, sb1, sb2); + assertCharSequenceEquals(str, sb3, str); StringBuilder sb4 = new StringBuilder(); FormattedStringBuilder sb5 = new FormattedStringBuilder(); @@ -47,25 +47,25 @@ public class FormattedStringBuilderTest { sb4.append("xx"); sb5.append("😇xx", null); sb5.insert(2, str, null); - assertCharSequenceEquals(sb4, sb5); + assertCharSequenceEquals(str, sb4, sb5); int start = Math.min(1, str.length()); int end = Math.min(10, str.length()); sb4.insert(3, str, start, end); sb5.insert(3, str, start, end, null); - assertCharSequenceEquals(sb4, sb5); + assertCharSequenceEquals(str, sb4, sb5); sb4.append(str.toCharArray()); sb5.append(str.toCharArray(), null); - assertCharSequenceEquals(sb4, sb5); + assertCharSequenceEquals(str, sb4, sb5); sb4.insert(4, str.toCharArray()); sb5.insert(4, str.toCharArray(), null); - assertCharSequenceEquals(sb4, sb5); + assertCharSequenceEquals(str, sb4, sb5); sb4.append(sb4.toString()); sb5.append(new FormattedStringBuilder(sb5)); - assertCharSequenceEquals(sb4, sb5); + assertCharSequenceEquals(str, sb4, sb5); } } @@ -96,7 +96,7 @@ public class FormattedStringBuilderTest { sb2.clear(); sb2.append(input, null); sb2.splice(startThis, endThis, replacement, 0, replacement.length(), null); - assertCharSequenceEquals(sb1, sb2); + assertCharSequenceEquals(input, sb1, sb2); // Test replacement with partial string if (replacement.length() <= 2) { @@ -108,7 +108,7 @@ public class FormattedStringBuilderTest { sb2.clear(); sb2.append(input, null); sb2.splice(startThis, endThis, replacement, 1, 3, null); - assertCharSequenceEquals(sb1, sb2); + assertCharSequenceEquals(input, sb1, sb2); } } } @@ -124,7 +124,7 @@ public class FormattedStringBuilderTest { sb1.appendCodePoint(cas); sb2.appendCodePoint(cas, null); sb3.appendCodePoint(cas, null); - assertCharSequenceEquals(sb1, sb2); + assertCharSequenceEquals(Integer.toString(cas), sb1, sb2); assertEquals(Character.codePointAt(sb3, 0), cas); StringBuilder sb4 = new StringBuilder(); @@ -134,7 +134,18 @@ public class FormattedStringBuilderTest { sb4.append("xx"); sb5.append("😇xx", null); sb5.insertCodePoint(2, cas, null); - assertCharSequenceEquals(sb4, sb5); + assertCharSequenceEquals(Integer.toString(cas), sb4, sb5); + + StringBuilder sb6 = new StringBuilder(); + FormattedStringBuilder sb7 = new FormattedStringBuilder(); + sb6.appendCodePoint(cas); + if (Character.charCount(cas) == 2) { + sb7.appendChar16(Character.lowSurrogate(cas), null); + sb7.insertChar16(0, Character.highSurrogate(cas), null); + } else { + sb7.insertChar16(0, (char) cas, null); + } + assertCharSequenceEquals(Integer.toString(cas), sb6, sb7); } } @@ -144,7 +155,7 @@ public class FormattedStringBuilderTest { FormattedStringBuilder sb1 = new FormattedStringBuilder(); sb1.append(str, null); FormattedStringBuilder sb2 = new FormattedStringBuilder(sb1); - assertCharSequenceEquals(sb1, sb2); + assertCharSequenceEquals(str, sb1, sb2); assertTrue(sb1.contentEquals(sb2)); sb1.append("12345", null); @@ -251,21 +262,21 @@ public class FormattedStringBuilderTest { assertEquals("Code point count is 2", 2, nsb.codePointCount()); } - private static void assertCharSequenceEquals(CharSequence a, CharSequence b) { - assertEquals(a.toString(), b.toString()); + private static void assertCharSequenceEquals(String msg, CharSequence a, CharSequence b) { + assertEquals(msg, a.toString(), b.toString()); - assertEquals(a.length(), b.length()); + assertEquals(msg, a.length(), b.length()); for (int i = 0; i < a.length(); i++) { - assertEquals(a.charAt(i), b.charAt(i)); + assertEquals(msg, a.charAt(i), b.charAt(i)); } int start = Math.min(2, a.length()); int end = Math.min(12, a.length()); if (start != end) { - assertCharSequenceEquals(a.subSequence(start, end), b.subSequence(start, end)); + assertCharSequenceEquals(msg, a.subSequence(start, end), b.subSequence(start, end)); if (b instanceof FormattedStringBuilder) { FormattedStringBuilder bnsb = (FormattedStringBuilder) b; - assertCharSequenceEquals(a.subSequence(start, end), bnsb.subString(start, end)); + assertCharSequenceEquals(msg, a.subSequence(start, end), bnsb.subString(start, end)); } } }