зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1719747 - Part 1: Unify ListFormat in SM. r=gregtatum,anba,platform-i18n-reviewers,tcampbell
Move implementations to mozilla::intl::ListFormat Differential Revision: https://phabricator.services.mozilla.com/D122334
This commit is contained in:
Родитель
945a8b6938
Коммит
39cadacc67
|
@ -0,0 +1,140 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "mozilla/intl/ListFormat.h"
|
||||
#include "mozilla/Span.h"
|
||||
#include "TestBuffer.h"
|
||||
|
||||
namespace mozilla::intl {
|
||||
|
||||
// Test ListFormat.format with default options.
|
||||
TEST(IntlListFormat, FormatDefault)
|
||||
{
|
||||
ListFormat::Options options;
|
||||
UniquePtr<ListFormat> lf = ListFormat::TryCreate("en-US", options).unwrap();
|
||||
ListFormat::StringList list;
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Alice")));
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Bob")));
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Charlie")));
|
||||
TestBuffer<char16_t> buf16;
|
||||
ASSERT_TRUE(lf->Format(list, buf16).isOk());
|
||||
ASSERT_EQ(buf16.get_string_view(), u"Alice, Bob, and Charlie");
|
||||
|
||||
UniquePtr<ListFormat> lfDe = ListFormat::TryCreate("de", options).unwrap();
|
||||
ASSERT_TRUE(lfDe->Format(list, buf16).isOk());
|
||||
ASSERT_EQ(buf16.get_string_view(), u"Alice, Bob und Charlie");
|
||||
}
|
||||
|
||||
// Test ListFormat.format with Type::Conjunction and other styles.
|
||||
TEST(IntlListFormat, FormatConjunction)
|
||||
{
|
||||
ListFormat::Options options{ListFormat::Type::Conjunction,
|
||||
ListFormat::Style::Narrow};
|
||||
UniquePtr<ListFormat> lf = ListFormat::TryCreate("en-US", options).unwrap();
|
||||
ListFormat::StringList list;
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Alice")));
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Bob")));
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Charlie")));
|
||||
TestBuffer<char16_t> buf16;
|
||||
ASSERT_TRUE(lf->Format(list, buf16).isOk());
|
||||
ASSERT_EQ(buf16.get_string_view(), u"Alice, Bob, Charlie");
|
||||
|
||||
ListFormat::Options optionsSh{ListFormat::Type::Conjunction,
|
||||
ListFormat::Style::Short};
|
||||
UniquePtr<ListFormat> lfSh =
|
||||
ListFormat::TryCreate("en-US", optionsSh).unwrap();
|
||||
ASSERT_TRUE(lfSh->Format(list, buf16).isOk());
|
||||
ASSERT_EQ(buf16.get_string_view(), u"Alice, Bob, & Charlie");
|
||||
}
|
||||
|
||||
// Test ListFormat.format with Type::Disjunction.
|
||||
TEST(IntlListFormat, FormatDisjunction)
|
||||
{
|
||||
// When Type is Disjunction, the results will be the same regardless of the
|
||||
// style for most locales, so simply test with Style::Long.
|
||||
ListFormat::Options options{ListFormat::Type::Disjunction,
|
||||
ListFormat::Style::Long};
|
||||
UniquePtr<ListFormat> lf = ListFormat::TryCreate("en-US", options).unwrap();
|
||||
ListFormat::StringList list;
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Alice")));
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Bob")));
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Charlie")));
|
||||
TestBuffer<char16_t> buf16;
|
||||
ASSERT_TRUE(lf->Format(list, buf16).isOk());
|
||||
ASSERT_EQ(buf16.get_string_view(), u"Alice, Bob, or Charlie");
|
||||
}
|
||||
|
||||
// Test ListFormat.format with Type::Unit.
|
||||
TEST(IntlListFormat, FormatUnit)
|
||||
{
|
||||
ListFormat::Options options{ListFormat::Type::Unit, ListFormat::Style::Long};
|
||||
// For locale "en", Style::Long and Style::Short have the same result, so just
|
||||
// test Style::Long here.
|
||||
UniquePtr<ListFormat> lf = ListFormat::TryCreate("en-US", options).unwrap();
|
||||
ListFormat::StringList list;
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Alice")));
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Bob")));
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Charlie")));
|
||||
TestBuffer<char16_t> buf16;
|
||||
ASSERT_TRUE(lf->Format(list, buf16).isOk());
|
||||
ASSERT_EQ(buf16.get_string_view(), u"Alice, Bob, Charlie");
|
||||
|
||||
ListFormat::Options optionsNa{ListFormat::Type::Unit,
|
||||
ListFormat::Style::Narrow};
|
||||
UniquePtr<ListFormat> lfNa =
|
||||
ListFormat::TryCreate("en-US", optionsNa).unwrap();
|
||||
ASSERT_TRUE(lfNa->Format(list, buf16).isOk());
|
||||
ASSERT_EQ(buf16.get_string_view(), u"Alice Bob Charlie");
|
||||
}
|
||||
|
||||
// Pass a long list (list.length() > DEFAULT_LIST_LENGTH) and check the result
|
||||
// is still correct. (result.length > INITIAL_CHAR_BUFFER_SIZE)
|
||||
TEST(IntlListFormat, FormatBufferLength)
|
||||
{
|
||||
ListFormat::Options options;
|
||||
UniquePtr<ListFormat> lf = ListFormat::TryCreate("en-US", options).unwrap();
|
||||
ListFormat::StringList list;
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Alice")));
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Bob")));
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Charlie")));
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"David")));
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Eve")));
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Frank")));
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Grace")));
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Heidi")));
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Ivan")));
|
||||
TestBuffer<char16_t> buf16;
|
||||
ASSERT_TRUE(lf->Format(list, buf16).isOk());
|
||||
ASSERT_EQ(buf16.get_string_view(),
|
||||
u"Alice, Bob, Charlie, David, Eve, Frank, Grace, Heidi, and Ivan");
|
||||
}
|
||||
|
||||
TEST(IntlListFormat, FormatToParts)
|
||||
{
|
||||
ListFormat::Options options;
|
||||
UniquePtr<ListFormat> lf = ListFormat::TryCreate("en-US", options).unwrap();
|
||||
ListFormat::StringList list;
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Alice")));
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Bob")));
|
||||
MOZ_RELEASE_ASSERT(list.append(MakeStringSpan(u"Charlie")));
|
||||
ListFormat::PartVector parts;
|
||||
ASSERT_TRUE(lf->FormatToParts(list, parts).isOk());
|
||||
|
||||
// 3 elements, and 2 literals.
|
||||
ASSERT_EQ(parts.length(), 5u);
|
||||
|
||||
ASSERT_EQ(parts[0], (ListFormat::Part{ListFormat::PartType::Element,
|
||||
MakeStringSpan(u"Alice")}));
|
||||
ASSERT_EQ(parts[1], (ListFormat::Part{ListFormat::PartType::Literal,
|
||||
MakeStringSpan(u", ")}));
|
||||
ASSERT_EQ(parts[2], (ListFormat::Part{ListFormat::PartType::Element,
|
||||
MakeStringSpan(u"Bob")}));
|
||||
ASSERT_EQ(parts[3], (ListFormat::Part{ListFormat::PartType::Literal,
|
||||
MakeStringSpan(u", and ")}));
|
||||
ASSERT_EQ(parts[4], (ListFormat::Part{ListFormat::PartType::Element,
|
||||
MakeStringSpan(u"Charlie")}));
|
||||
}
|
||||
|
||||
} // namespace mozilla::intl
|
|
@ -8,6 +8,7 @@ UNIFIED_SOURCES += [
|
|||
"TestCalendar.cpp",
|
||||
"TestCollator.cpp",
|
||||
"TestDateTimeFormat.cpp",
|
||||
"TestListFormat.cpp",
|
||||
"TestLocaleCanonicalizer.cpp",
|
||||
"TestNumberFormat.cpp",
|
||||
"TestPluralRules.cpp",
|
||||
|
|
|
@ -10,6 +10,7 @@ EXPORTS.mozilla.intl = [
|
|||
"src/DateTimePatternGenerator.h",
|
||||
"src/ICU4CGlue.h",
|
||||
"src/ICUError.h",
|
||||
"src/ListFormat.h",
|
||||
"src/LocaleCanonicalizer.h",
|
||||
"src/NumberFormat.h",
|
||||
"src/NumberFormatFields.h",
|
||||
|
@ -24,6 +25,7 @@ UNIFIED_SOURCES += [
|
|||
"src/DateTimeFormat.cpp",
|
||||
"src/DateTimePatternGenerator.cpp",
|
||||
"src/ICU4CGlue.cpp",
|
||||
"src/ListFormat.cpp",
|
||||
"src/LocaleCanonicalizer.cpp",
|
||||
"src/NumberFormat.cpp",
|
||||
"src/NumberFormatFields.cpp",
|
||||
|
|
|
@ -64,6 +64,8 @@ class ICUPointer {
|
|||
const T* GetConst() const { return const_cast<const T*>(mPointer); }
|
||||
T* GetMut() { return mPointer; }
|
||||
|
||||
explicit operator bool() const { return !!mPointer; }
|
||||
|
||||
private:
|
||||
T* mPointer;
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@ namespace mozilla::intl {
|
|||
enum class ICUError : uint8_t {
|
||||
OutOfMemory,
|
||||
InternalError,
|
||||
OverflowError,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "mozilla/intl/ListFormat.h"
|
||||
|
||||
namespace mozilla::intl {
|
||||
|
||||
/*static*/ Result<UniquePtr<ListFormat>, ICUError> ListFormat::TryCreate(
|
||||
mozilla::Span<const char> aLocale, const Options& aOptions) {
|
||||
UListFormatterType utype = ToUListFormatterType(aOptions.mType);
|
||||
UListFormatterWidth uwidth = ToUListFormatterWidth(aOptions.mStyle);
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
UListFormatter* fmt =
|
||||
ulistfmt_openForType(aLocale.data(), utype, uwidth, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
return Err(ICUError::InternalError);
|
||||
}
|
||||
|
||||
UFormattedList* fl = ulistfmt_openResult(&status);
|
||||
if (U_FAILURE(status)) {
|
||||
return Err(ICUError::InternalError);
|
||||
}
|
||||
|
||||
return UniquePtr<ListFormat>(new ListFormat(fmt, fl));
|
||||
}
|
||||
|
||||
ListFormat::~ListFormat() {
|
||||
if (mListFormatter) {
|
||||
ulistfmt_close(mListFormatter.GetMut());
|
||||
}
|
||||
|
||||
if (mFormattedList) {
|
||||
ulistfmt_closeResult(mFormattedList.GetMut());
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ UListFormatterType ListFormat::ToUListFormatterType(Type type) {
|
||||
switch (type) {
|
||||
case Type::Conjunction:
|
||||
return ULISTFMT_TYPE_AND;
|
||||
case Type::Disjunction:
|
||||
return ULISTFMT_TYPE_OR;
|
||||
case Type::Unit:
|
||||
return ULISTFMT_TYPE_UNITS;
|
||||
}
|
||||
MOZ_ASSERT_UNREACHABLE();
|
||||
return ULISTFMT_TYPE_AND;
|
||||
}
|
||||
|
||||
/* static */ UListFormatterWidth ListFormat::ToUListFormatterWidth(
|
||||
Style style) {
|
||||
switch (style) {
|
||||
case Style::Long:
|
||||
return ULISTFMT_WIDTH_WIDE;
|
||||
case Style::Short:
|
||||
return ULISTFMT_WIDTH_SHORT;
|
||||
case Style::Narrow:
|
||||
return ULISTFMT_WIDTH_NARROW;
|
||||
}
|
||||
MOZ_ASSERT_UNREACHABLE();
|
||||
return ULISTFMT_WIDTH_WIDE;
|
||||
}
|
||||
|
||||
ICUResult ListFormat::FormatToParts(const StringList& list, PartVector& parts) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
mozilla::Vector<const char16_t*, DEFAULT_LIST_LENGTH> u16strings;
|
||||
mozilla::Vector<int32_t, DEFAULT_LIST_LENGTH> u16stringLens;
|
||||
MOZ_TRY(ConvertStringListToVectors(list, u16strings, u16stringLens));
|
||||
|
||||
ulistfmt_formatStringsToResult(mListFormatter.GetConst(), u16strings.begin(),
|
||||
u16stringLens.begin(), int32_t(list.length()),
|
||||
mFormattedList.GetMut(), &status);
|
||||
if (U_FAILURE(status)) {
|
||||
return Err(ICUError::InternalError);
|
||||
}
|
||||
|
||||
const UFormattedValue* formattedValue =
|
||||
ulistfmt_resultAsValue(mFormattedList.GetConst(), &status);
|
||||
if (U_FAILURE(status)) {
|
||||
return Err(ICUError::InternalError);
|
||||
}
|
||||
|
||||
int32_t formattedCharsLen;
|
||||
const char16_t* formattedChars =
|
||||
ufmtval_getString(formattedValue, &formattedCharsLen, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
return Err(ICUError::InternalError);
|
||||
}
|
||||
|
||||
size_t formattedSize = AssertedCast<size_t>(formattedCharsLen);
|
||||
mozilla::Span<const char16_t> formattedSpan{formattedChars, formattedSize};
|
||||
size_t lastEndIndex = 0;
|
||||
|
||||
auto AppendPart = [&](PartType type, size_t beginIndex, size_t endIndex) {
|
||||
if (!parts.emplaceBack(type, formattedSpan.FromTo(beginIndex, endIndex))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
lastEndIndex = endIndex;
|
||||
return true;
|
||||
};
|
||||
|
||||
UConstrainedFieldPosition* fpos = ucfpos_open(&status);
|
||||
if (U_FAILURE(status)) {
|
||||
return Err(ICUError::InternalError);
|
||||
}
|
||||
ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos);
|
||||
|
||||
// We're only interested in ULISTFMT_ELEMENT_FIELD fields.
|
||||
ucfpos_constrainField(fpos, UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD,
|
||||
&status);
|
||||
if (U_FAILURE(status)) {
|
||||
return Err(ICUError::InternalError);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
bool hasMore = ufmtval_nextPosition(formattedValue, fpos, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
return Err(ICUError::InternalError);
|
||||
}
|
||||
if (!hasMore) {
|
||||
break;
|
||||
}
|
||||
|
||||
int32_t beginIndexInt, endIndexInt;
|
||||
ucfpos_getIndexes(fpos, &beginIndexInt, &endIndexInt, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
return Err(ICUError::InternalError);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(beginIndexInt <= endIndexInt,
|
||||
"field iterator returning invalid range");
|
||||
|
||||
size_t beginIndex = AssertedCast<size_t>(beginIndexInt);
|
||||
size_t endIndex = AssertedCast<size_t>(endIndexInt);
|
||||
|
||||
// Indices are guaranteed to be returned in order (from left to right).
|
||||
MOZ_ASSERT(lastEndIndex <= beginIndex,
|
||||
"field iteration didn't return fields in order start to "
|
||||
"finish as expected");
|
||||
|
||||
if (lastEndIndex < beginIndex) {
|
||||
if (!AppendPart(PartType::Literal, lastEndIndex, beginIndex)) {
|
||||
return Err(ICUError::InternalError);
|
||||
}
|
||||
}
|
||||
|
||||
if (!AppendPart(PartType::Element, beginIndex, endIndex)) {
|
||||
return Err(ICUError::InternalError);
|
||||
}
|
||||
}
|
||||
|
||||
// Append any final literal.
|
||||
if (lastEndIndex < formattedSize) {
|
||||
if (!AppendPart(PartType::Literal, lastEndIndex, formattedSize)) {
|
||||
return Err(ICUError::InternalError);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
} // namespace mozilla::intl
|
|
@ -0,0 +1,171 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef intl_components_ListFormat_h_
|
||||
#define intl_components_ListFormat_h_
|
||||
|
||||
#include "mozilla/CheckedInt.h"
|
||||
#include "mozilla/intl/ICU4CGlue.h"
|
||||
#include "mozilla/Result.h"
|
||||
#include "mozilla/ResultVariant.h"
|
||||
#include "mozilla/Vector.h"
|
||||
#include "unicode/ulistformatter.h"
|
||||
|
||||
struct UListFormatter;
|
||||
struct UFormattedList;
|
||||
|
||||
namespace mozilla::intl {
|
||||
|
||||
static constexpr size_t DEFAULT_LIST_LENGTH = 8;
|
||||
|
||||
/**
|
||||
* This component is a Mozilla-focused API for the list formatting provided by
|
||||
* ICU. It implements the API provided by the ECMA-402 Intl.ListFormat object.
|
||||
*
|
||||
* https://tc39.es/ecma402/#listformat-objects
|
||||
*/
|
||||
class ListFormat final {
|
||||
public:
|
||||
/**
|
||||
* The [[Type]] and [[Style]] properties of ListFormat instances.
|
||||
*
|
||||
* https://tc39.es/ecma402/#sec-properties-of-intl-listformat-instances
|
||||
*/
|
||||
// [[Type]]
|
||||
enum class Type { Conjunction, Disjunction, Unit };
|
||||
// [[Style]]
|
||||
enum class Style { Long, Short, Narrow };
|
||||
|
||||
/**
|
||||
* The 'options' object to create Intl.ListFormat instance.
|
||||
*
|
||||
* https://tc39.es/ecma402/#sec-Intl.ListFormat
|
||||
*/
|
||||
struct Options {
|
||||
// "conjunction" is the default fallback value.
|
||||
Type mType = Type::Conjunction;
|
||||
|
||||
// "long" is the default fallback value.
|
||||
Style mStyle = Style::Long;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a ListFormat object for the provided locale and options.
|
||||
*
|
||||
* https://tc39.es/ecma402/#sec-Intl.ListFormat
|
||||
*/
|
||||
static Result<UniquePtr<ListFormat>, ICUError> TryCreate(
|
||||
mozilla::Span<const char> aLocale, const Options& aOptions);
|
||||
|
||||
~ListFormat();
|
||||
|
||||
/**
|
||||
* The list of String values for FormatList and FormatListToParts.
|
||||
*
|
||||
* https://tc39.es/ecma402/#sec-formatlist
|
||||
* https://tc39.es/ecma402/#sec-formatlisttoparts
|
||||
*/
|
||||
using StringList =
|
||||
mozilla::Vector<mozilla::Span<const char16_t>, DEFAULT_LIST_LENGTH>;
|
||||
|
||||
/**
|
||||
* Format the list according and write the result in buffer.
|
||||
*
|
||||
* https://tc39.es/ecma402/#sec-Intl.ListFormat.prototype.format
|
||||
* https://tc39.es/ecma402/#sec-formatlist
|
||||
*/
|
||||
template <typename Buffer>
|
||||
ICUResult Format(const StringList& list, Buffer& buffer) const {
|
||||
static_assert(std::is_same_v<typename Buffer::CharType, char16_t>,
|
||||
"Currently only UTF-16 buffers are supported.");
|
||||
|
||||
mozilla::Vector<const char16_t*, DEFAULT_LIST_LENGTH> u16strings;
|
||||
mozilla::Vector<int32_t, DEFAULT_LIST_LENGTH> u16stringLens;
|
||||
MOZ_TRY(ConvertStringListToVectors(list, u16strings, u16stringLens));
|
||||
|
||||
int32_t u16stringCount = mozilla::AssertedCast<int32_t>(list.length());
|
||||
MOZ_TRY(FillBufferWithICUCall(
|
||||
buffer, [this, &u16strings, &u16stringLens, u16stringCount](
|
||||
char16_t* chars, int32_t size, UErrorCode* status) {
|
||||
return ulistfmt_format(mListFormatter.GetConst(), u16strings.begin(),
|
||||
u16stringLens.begin(), u16stringCount, chars,
|
||||
size, status);
|
||||
}));
|
||||
|
||||
return Ok{};
|
||||
}
|
||||
|
||||
/**
|
||||
* The corresponding list of parts according to the effective locale and the
|
||||
* formatting options of ListFormat.
|
||||
* Each part has a [[Type]] field, which must be "element" or "literal".
|
||||
*
|
||||
* https://tc39.es/ecma402/#sec-createpartsfromlist
|
||||
*/
|
||||
enum class PartType {
|
||||
Element,
|
||||
Literal,
|
||||
};
|
||||
using Part = std::pair<PartType, mozilla::Span<const char16_t>>;
|
||||
using PartVector = mozilla::Vector<Part, DEFAULT_LIST_LENGTH>;
|
||||
|
||||
/**
|
||||
* Format the list to a list of parts, and write the result into parts.
|
||||
* The PartVector contains mozilla::Span which point to memory which may be
|
||||
* overridden when the next format method is called.
|
||||
*
|
||||
* https://tc39.es/ecma402/#sec-Intl.ListFormat.prototype.formatToParts
|
||||
* https://tc39.es/ecma402/#sec-formatlisttoparts
|
||||
*/
|
||||
ICUResult FormatToParts(const StringList& list, PartVector& parts);
|
||||
|
||||
private:
|
||||
ListFormat() = delete;
|
||||
ListFormat(UListFormatter* fmt, UFormattedList* fl)
|
||||
: mListFormatter(fmt), mFormattedList(fl) {}
|
||||
ListFormat(const ListFormat&) = delete;
|
||||
ListFormat& operator=(const ListFormat&) = delete;
|
||||
|
||||
ICUPointer<UListFormatter> mListFormatter =
|
||||
ICUPointer<UListFormatter>(nullptr);
|
||||
ICUPointer<UFormattedList> mFormattedList =
|
||||
ICUPointer<UFormattedList>(nullptr);
|
||||
|
||||
// Convert StringList to an array of type 'const char16_t*' and an array of
|
||||
// int32 for ICU-API.
|
||||
ICUResult ConvertStringListToVectors(
|
||||
const StringList& list,
|
||||
mozilla::Vector<const char16_t*, DEFAULT_LIST_LENGTH>& u16strings,
|
||||
mozilla::Vector<int32_t, DEFAULT_LIST_LENGTH>& u16stringLens) const {
|
||||
// Keep a conservative running count of overall length.
|
||||
mozilla::CheckedInt<int32_t> stringLengthTotal(0);
|
||||
for (const auto& string : list) {
|
||||
if (!u16strings.append(string.data())) {
|
||||
return Err(ICUError::InternalError);
|
||||
}
|
||||
|
||||
int32_t len = mozilla::AssertedCast<int32_t>(string.size());
|
||||
if (!u16stringLens.append(len)) {
|
||||
return Err(ICUError::InternalError);
|
||||
}
|
||||
|
||||
stringLengthTotal += len;
|
||||
}
|
||||
|
||||
// Add space for N unrealistically large conjunctions.
|
||||
constexpr int32_t MaxConjunctionLen = 100;
|
||||
stringLengthTotal += CheckedInt<int32_t>(list.length()) * MaxConjunctionLen;
|
||||
// If the overestimate exceeds ICU length limits, don't try to format.
|
||||
if (!stringLengthTotal.isValid()) {
|
||||
return Err(ICUError::OverflowError);
|
||||
}
|
||||
|
||||
return Ok{};
|
||||
}
|
||||
|
||||
static UListFormatterType ToUListFormatterType(Type type);
|
||||
static UListFormatterWidth ToUListFormatterWidth(Style style);
|
||||
};
|
||||
|
||||
} // namespace mozilla::intl
|
||||
#endif // intl_components_ListFormat_h_
|
|
@ -102,6 +102,9 @@ void js::intl::ReportInternalError(JSContext* cx,
|
|||
case mozilla::intl::ICUError::InternalError:
|
||||
ReportInternalError(cx);
|
||||
return;
|
||||
case mozilla::intl::ICUError::OverflowError:
|
||||
ReportAllocationOverflow(cx);
|
||||
return;
|
||||
}
|
||||
MOZ_CRASH("Unexpected ICU error");
|
||||
}
|
||||
|
|
|
@ -7,21 +7,18 @@
|
|||
#include "builtin/intl/ListFormat.h"
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/CheckedInt.h"
|
||||
#include "mozilla/intl/ListFormat.h"
|
||||
#include "mozilla/PodOperations.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "builtin/Array.h"
|
||||
#include "builtin/intl/CommonFunctions.h"
|
||||
#include "builtin/intl/FormatBuffer.h"
|
||||
#include "builtin/intl/ScopedICUObject.h"
|
||||
#include "gc/FreeOp.h"
|
||||
#include "js/Utility.h"
|
||||
#include "js/Vector.h"
|
||||
#include "unicode/uformattedvalue.h"
|
||||
#include "unicode/ulistformatter.h"
|
||||
#include "unicode/utypes.h"
|
||||
#include "vm/JSContext.h"
|
||||
#include "vm/PlainObject.h" // js::PlainObject
|
||||
#include "vm/Runtime.h" // js::ReportAllocationOverflow
|
||||
|
@ -137,19 +134,20 @@ static bool ListFormat(JSContext* cx, unsigned argc, Value* vp) {
|
|||
void js::ListFormatObject::finalize(JSFreeOp* fop, JSObject* obj) {
|
||||
MOZ_ASSERT(fop->onMainThread());
|
||||
|
||||
if (UListFormatter* lf = obj->as<ListFormatObject>().getListFormatter()) {
|
||||
mozilla::intl::ListFormat* lf =
|
||||
obj->as<ListFormatObject>().getListFormatSlot();
|
||||
if (lf) {
|
||||
intl::RemoveICUCellMemory(fop, obj, ListFormatObject::EstimatedMemoryUse);
|
||||
|
||||
ulistfmt_close(lf);
|
||||
delete lf;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new UListFormatter with the locale and list formatting options
|
||||
* Returns a new ListFormat with the locale and list formatting options
|
||||
* of the given ListFormat.
|
||||
*/
|
||||
static UListFormatter* NewUListFormatter(JSContext* cx,
|
||||
Handle<ListFormatObject*> listFormat) {
|
||||
static mozilla::intl::ListFormat* NewListFormat(
|
||||
JSContext* cx, Handle<ListFormatObject*> listFormat) {
|
||||
RootedObject internals(cx, intl::GetInternalsObject(cx, listFormat));
|
||||
if (!internals) {
|
||||
return nullptr;
|
||||
|
@ -165,9 +163,9 @@ static UListFormatter* NewUListFormatter(JSContext* cx,
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
enum class ListFormatType { Conjunction, Disjunction, Unit };
|
||||
mozilla::intl::ListFormat::Options options;
|
||||
|
||||
ListFormatType type;
|
||||
using ListFormatType = mozilla::intl::ListFormat::Type;
|
||||
if (!GetProperty(cx, internals, internals, cx->names().type, &value)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -178,18 +176,16 @@ static UListFormatter* NewUListFormatter(JSContext* cx,
|
|||
}
|
||||
|
||||
if (StringEqualsLiteral(strType, "conjunction")) {
|
||||
type = ListFormatType::Conjunction;
|
||||
options.mType = ListFormatType::Conjunction;
|
||||
} else if (StringEqualsLiteral(strType, "disjunction")) {
|
||||
type = ListFormatType::Disjunction;
|
||||
options.mType = ListFormatType::Disjunction;
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsLiteral(strType, "unit"));
|
||||
type = ListFormatType::Unit;
|
||||
options.mType = ListFormatType::Unit;
|
||||
}
|
||||
}
|
||||
|
||||
enum class ListFormatStyle { Long, Short, Narrow };
|
||||
|
||||
ListFormatStyle style;
|
||||
using ListFormatStyle = mozilla::intl::ListFormat::Style;
|
||||
if (!GetProperty(cx, internals, internals, cx->names().style, &value)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -200,81 +196,43 @@ static UListFormatter* NewUListFormatter(JSContext* cx,
|
|||
}
|
||||
|
||||
if (StringEqualsLiteral(strStyle, "long")) {
|
||||
style = ListFormatStyle::Long;
|
||||
options.mStyle = ListFormatStyle::Long;
|
||||
} else if (StringEqualsLiteral(strStyle, "short")) {
|
||||
style = ListFormatStyle::Short;
|
||||
options.mStyle = ListFormatStyle::Short;
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsLiteral(strStyle, "narrow"));
|
||||
style = ListFormatStyle::Narrow;
|
||||
options.mStyle = ListFormatStyle::Narrow;
|
||||
}
|
||||
}
|
||||
|
||||
UListFormatterType utype;
|
||||
switch (type) {
|
||||
case ListFormatType::Conjunction:
|
||||
utype = ULISTFMT_TYPE_AND;
|
||||
break;
|
||||
case ListFormatType::Disjunction:
|
||||
utype = ULISTFMT_TYPE_OR;
|
||||
break;
|
||||
case ListFormatType::Unit:
|
||||
utype = ULISTFMT_TYPE_UNITS;
|
||||
break;
|
||||
auto result = mozilla::intl::ListFormat::TryCreate(
|
||||
mozilla::MakeStringSpan(IcuLocale(locale.get())), options);
|
||||
|
||||
if (result.isOk()) {
|
||||
return result.unwrap().release();
|
||||
}
|
||||
|
||||
UListFormatterWidth uwidth;
|
||||
switch (style) {
|
||||
case ListFormatStyle::Long:
|
||||
uwidth = ULISTFMT_WIDTH_WIDE;
|
||||
break;
|
||||
case ListFormatStyle::Short:
|
||||
uwidth = ULISTFMT_WIDTH_SHORT;
|
||||
break;
|
||||
case ListFormatStyle::Narrow:
|
||||
uwidth = ULISTFMT_WIDTH_NARROW;
|
||||
break;
|
||||
}
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
UListFormatter* lf =
|
||||
ulistfmt_openForType(IcuLocale(locale.get()), utype, uwidth, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
js::intl::ReportInternalError(cx, result.unwrapErr());
|
||||
return nullptr;
|
||||
}
|
||||
return lf;
|
||||
}
|
||||
|
||||
static constexpr size_t DEFAULT_LIST_LENGTH = 8;
|
||||
|
||||
using ListFormatStringVector = Vector<UniqueTwoByteChars, DEFAULT_LIST_LENGTH>;
|
||||
using ListFormatStringLengthVector = Vector<int32_t, DEFAULT_LIST_LENGTH>;
|
||||
|
||||
static_assert(sizeof(UniqueTwoByteChars) == sizeof(char16_t*),
|
||||
"UniqueTwoByteChars are stored efficiently and are held in "
|
||||
"continuous memory");
|
||||
|
||||
/**
|
||||
* FormatList ( listFormat, list )
|
||||
*/
|
||||
static bool FormatList(JSContext* cx, UListFormatter* lf,
|
||||
const ListFormatStringVector& strings,
|
||||
const ListFormatStringLengthVector& stringLengths,
|
||||
static bool FormatList(JSContext* cx, mozilla::intl::ListFormat* lf,
|
||||
const mozilla::intl::ListFormat::StringList& list,
|
||||
MutableHandleValue result) {
|
||||
MOZ_ASSERT(strings.length() == stringLengths.length());
|
||||
MOZ_ASSERT(strings.length() <= INT32_MAX);
|
||||
|
||||
JSString* str = intl::CallICU(cx, [lf, &strings, &stringLengths](
|
||||
UChar* chars, int32_t size,
|
||||
UErrorCode* status) {
|
||||
return ulistfmt_format(
|
||||
lf, reinterpret_cast<char16_t* const*>(strings.begin()),
|
||||
stringLengths.begin(), int32_t(strings.length()), chars, size, status);
|
||||
});
|
||||
if (!str) {
|
||||
intl::FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> formatBuffer(cx);
|
||||
auto formatResult = lf->Format(list, formatBuffer);
|
||||
if (formatResult.isErr()) {
|
||||
js::intl::ReportInternalError(cx, formatResult.unwrapErr());
|
||||
return false;
|
||||
}
|
||||
|
||||
JSString* str = formatBuffer.toString();
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
result.setString(str);
|
||||
return true;
|
||||
}
|
||||
|
@ -282,39 +240,13 @@ static bool FormatList(JSContext* cx, UListFormatter* lf,
|
|||
/**
|
||||
* FormatListToParts ( listFormat, list )
|
||||
*/
|
||||
static bool FormatListToParts(JSContext* cx, UListFormatter* lf,
|
||||
const ListFormatStringVector& strings,
|
||||
const ListFormatStringLengthVector& stringLengths,
|
||||
static bool FormatListToParts(JSContext* cx, mozilla::intl::ListFormat* lf,
|
||||
const mozilla::intl::ListFormat::StringList& list,
|
||||
MutableHandleValue result) {
|
||||
MOZ_ASSERT(strings.length() == stringLengths.length());
|
||||
MOZ_ASSERT(strings.length() <= INT32_MAX);
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
UFormattedList* formatted = ulistfmt_openResult(&status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
ScopedICUObject<UFormattedList, ulistfmt_closeResult> toClose(formatted);
|
||||
|
||||
ulistfmt_formatStringsToResult(
|
||||
lf, reinterpret_cast<char16_t* const*>(strings.begin()),
|
||||
stringLengths.begin(), int32_t(strings.length()), formatted, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
const UFormattedValue* formattedValue =
|
||||
ulistfmt_resultAsValue(formatted, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedString overallResult(cx,
|
||||
intl::FormattedValueToString(cx, formattedValue));
|
||||
if (!overallResult) {
|
||||
mozilla::intl::ListFormat::PartVector parts;
|
||||
auto formatResult = lf->FormatToParts(list, parts);
|
||||
if (formatResult.isErr()) {
|
||||
js::intl::ReportInternalError(cx, formatResult.unwrapErr());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -323,30 +255,31 @@ static bool FormatListToParts(JSContext* cx, UListFormatter* lf,
|
|||
return false;
|
||||
}
|
||||
|
||||
using FieldType = js::ImmutablePropertyNamePtr JSAtomState::*;
|
||||
|
||||
size_t lastEndIndex = 0;
|
||||
RootedObject singlePart(cx);
|
||||
RootedValue val(cx);
|
||||
|
||||
auto AppendPart = [&](FieldType type, size_t beginIndex, size_t endIndex) {
|
||||
for (const mozilla::intl::ListFormat::Part& part : parts) {
|
||||
singlePart = NewPlainObject(cx);
|
||||
if (!singlePart) {
|
||||
return false;
|
||||
}
|
||||
|
||||
val = StringValue(cx->names().*type);
|
||||
if (part.first == mozilla::intl::ListFormat::PartType::Element) {
|
||||
val = StringValue(cx->names().element);
|
||||
} else {
|
||||
val = StringValue(cx->names().literal);
|
||||
}
|
||||
|
||||
if (!DefineDataProperty(cx, singlePart, cx->names().type, val)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JSLinearString* partSubstr = NewDependentString(
|
||||
cx, overallResult, beginIndex, endIndex - beginIndex);
|
||||
if (!partSubstr) {
|
||||
JSString* partStr =
|
||||
NewStringCopyN<CanGC>(cx, part.second.data(), part.second.size());
|
||||
if (!partStr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
val = StringValue(partSubstr);
|
||||
val = StringValue(partStr);
|
||||
if (!DefineDataProperty(cx, singlePart, cx->names().value, val)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -354,73 +287,6 @@ static bool FormatListToParts(JSContext* cx, UListFormatter* lf,
|
|||
if (!NewbornArrayPush(cx, partsArray, ObjectValue(*singlePart))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
lastEndIndex = endIndex;
|
||||
return true;
|
||||
};
|
||||
|
||||
UConstrainedFieldPosition* fpos = ucfpos_open(&status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos);
|
||||
|
||||
// We're only interested in ULISTFMT_ELEMENT_FIELD fields.
|
||||
ucfpos_constrainField(fpos, UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD,
|
||||
&status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
bool hasMore = ufmtval_nextPosition(formattedValue, fpos, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
if (!hasMore) {
|
||||
break;
|
||||
}
|
||||
|
||||
int32_t beginIndexInt, endIndexInt;
|
||||
ucfpos_getIndexes(fpos, &beginIndexInt, &endIndexInt, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(beginIndexInt >= 0);
|
||||
MOZ_ASSERT(endIndexInt >= 0);
|
||||
MOZ_ASSERT(beginIndexInt <= endIndexInt,
|
||||
"field iterator returning invalid range");
|
||||
|
||||
size_t beginIndex = size_t(beginIndexInt);
|
||||
size_t endIndex = size_t(endIndexInt);
|
||||
|
||||
// Indices are guaranteed to be returned in order (from left to right).
|
||||
MOZ_ASSERT(lastEndIndex <= beginIndex,
|
||||
"field iteration didn't return fields in order start to "
|
||||
"finish as expected");
|
||||
|
||||
if (lastEndIndex < beginIndex) {
|
||||
if (!AppendPart(&JSAtomState::literal, lastEndIndex, beginIndex)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!AppendPart(&JSAtomState::element, beginIndex, endIndex)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Append any final literal.
|
||||
if (lastEndIndex < overallResult->length()) {
|
||||
if (!AppendPart(&JSAtomState::literal, lastEndIndex,
|
||||
overallResult->length())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
result.setObject(*partsArray);
|
||||
|
@ -436,30 +302,30 @@ bool js::intl_FormatList(JSContext* cx, unsigned argc, Value* vp) {
|
|||
|
||||
bool formatToParts = args[2].toBoolean();
|
||||
|
||||
// Obtain a cached UListFormatter object.
|
||||
UListFormatter* lf = listFormat->getListFormatter();
|
||||
// Obtain a cached mozilla::intl::ListFormat object.
|
||||
mozilla::intl::ListFormat* lf = listFormat->getListFormatSlot();
|
||||
if (!lf) {
|
||||
lf = NewUListFormatter(cx, listFormat);
|
||||
lf = NewListFormat(cx, listFormat);
|
||||
if (!lf) {
|
||||
return false;
|
||||
}
|
||||
listFormat->setListFormatter(lf);
|
||||
listFormat->setListFormatSlot(lf);
|
||||
|
||||
intl::AddICUCellMemory(listFormat, ListFormatObject::EstimatedMemoryUse);
|
||||
}
|
||||
|
||||
// Collect all strings and their lengths.
|
||||
ListFormatStringVector strings(cx);
|
||||
ListFormatStringLengthVector stringLengths(cx);
|
||||
//
|
||||
// 'strings' takes the ownership of those strings, and 'list' will be passed
|
||||
// to mozilla::intl::ListFormat as a Span.
|
||||
Vector<UniqueTwoByteChars, mozilla::intl::DEFAULT_LIST_LENGTH> strings(cx);
|
||||
mozilla::intl::ListFormat::StringList list;
|
||||
|
||||
// Keep a conservative running count of overall length.
|
||||
CheckedInt<int32_t> stringLengthTotal(0);
|
||||
|
||||
RootedArrayObject list(cx, &args[1].toObject().as<ArrayObject>());
|
||||
RootedArrayObject listObj(cx, &args[1].toObject().as<ArrayObject>());
|
||||
RootedValue value(cx);
|
||||
uint32_t listLen = list->length();
|
||||
uint32_t listLen = listObj->length();
|
||||
for (uint32_t i = 0; i < listLen; i++) {
|
||||
if (!GetElement(cx, list, list, i, &value)) {
|
||||
if (!GetElement(cx, listObj, listObj, i, &value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -469,10 +335,6 @@ bool js::intl_FormatList(JSContext* cx, unsigned argc, Value* vp) {
|
|||
}
|
||||
|
||||
size_t linearLength = linear->length();
|
||||
if (!stringLengths.append(linearLength)) {
|
||||
return false;
|
||||
}
|
||||
stringLengthTotal += linearLength;
|
||||
|
||||
UniqueTwoByteChars chars = cx->make_pod_array<char16_t>(linearLength);
|
||||
if (!chars) {
|
||||
|
@ -483,21 +345,14 @@ bool js::intl_FormatList(JSContext* cx, unsigned argc, Value* vp) {
|
|||
if (!strings.append(std::move(chars))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Add space for N unrealistically large conjunctions.
|
||||
constexpr int32_t MaxConjunctionLen = 100;
|
||||
stringLengthTotal += CheckedInt<int32_t>(listLen) * MaxConjunctionLen;
|
||||
|
||||
// If the overestimate exceeds ICU length limits, don't try to format.
|
||||
if (!stringLengthTotal.isValid()) {
|
||||
ReportAllocationOverflow(cx);
|
||||
if (!list.emplaceBack(strings[i].get(), linearLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use the UListFormatter to actually format the strings.
|
||||
if (formatToParts) {
|
||||
return FormatListToParts(cx, lf, strings, stringLengths, args.rval());
|
||||
}
|
||||
return FormatList(cx, lf, strings, stringLengths, args.rval());
|
||||
|
||||
if (formatToParts) {
|
||||
return FormatListToParts(cx, lf, list, args.rval());
|
||||
}
|
||||
return FormatList(cx, lf, list, args.rval());
|
||||
}
|
||||
|
|
|
@ -15,7 +15,10 @@
|
|||
#include "vm/NativeObject.h"
|
||||
|
||||
class JSFreeOp;
|
||||
struct UListFormatter;
|
||||
|
||||
namespace mozilla::intl {
|
||||
class ListFormat;
|
||||
} // namespace mozilla::intl
|
||||
|
||||
namespace js {
|
||||
|
||||
|
@ -25,7 +28,7 @@ class ListFormatObject : public NativeObject {
|
|||
static const JSClass& protoClass_;
|
||||
|
||||
static constexpr uint32_t INTERNALS_SLOT = 0;
|
||||
static constexpr uint32_t ULIST_FORMATTER_SLOT = 1;
|
||||
static constexpr uint32_t LIST_FORMAT_SLOT = 1;
|
||||
static constexpr uint32_t SLOT_COUNT = 2;
|
||||
|
||||
static_assert(INTERNALS_SLOT == INTL_INTERNALS_OBJECT_SLOT,
|
||||
|
@ -35,16 +38,16 @@ class ListFormatObject : public NativeObject {
|
|||
// Estimated memory use for UListFormatter (see IcuMemoryUsage).
|
||||
static constexpr size_t EstimatedMemoryUse = 24;
|
||||
|
||||
UListFormatter* getListFormatter() const {
|
||||
const auto& slot = getFixedSlot(ULIST_FORMATTER_SLOT);
|
||||
mozilla::intl::ListFormat* getListFormatSlot() const {
|
||||
const auto& slot = getFixedSlot(LIST_FORMAT_SLOT);
|
||||
if (slot.isUndefined()) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<UListFormatter*>(slot.toPrivate());
|
||||
return static_cast<mozilla::intl::ListFormat*>(slot.toPrivate());
|
||||
}
|
||||
|
||||
void setListFormatter(UListFormatter* formatter) {
|
||||
setFixedSlot(ULIST_FORMATTER_SLOT, PrivateValue(formatter));
|
||||
void setListFormatSlot(mozilla::intl::ListFormat* format) {
|
||||
setFixedSlot(LIST_FORMAT_SLOT, PrivateValue(format));
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
Загрузка…
Ссылка в новой задаче