ICU-20941 Support formatting joule-per-furlong (builtin-per-builtin).
See #1409
This commit is contained in:
parent
7c8f857da8
commit
710fa5aaf9
@ -389,8 +389,12 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe,
|
||||
fMixedUnitLongNameHandler.getAlias(), status);
|
||||
chain = fMixedUnitLongNameHandler.getAlias();
|
||||
} else {
|
||||
MeasureUnit unit = macros.unit;
|
||||
if (!utils::unitIsBaseUnit(macros.perUnit)) {
|
||||
unit = unit.product(macros.perUnit.reciprocal(status), status);
|
||||
}
|
||||
fLongNameHandler.adoptInsteadAndCheckErrorCode(new LongNameHandler(), status);
|
||||
LongNameHandler::forMeasureUnit(macros.locale, macros.unit, macros.perUnit, unitWidth,
|
||||
LongNameHandler::forMeasureUnit(macros.locale, unit, unitWidth,
|
||||
resolvePluralRules(macros.rules, macros.locale, status),
|
||||
chain, fLongNameHandler.getAlias(), status);
|
||||
chain = fLongNameHandler.getAlias();
|
||||
|
@ -218,38 +218,38 @@ UnicodeString getPerUnitFormat(const Locale& locale, const UNumberUnitWidth &wid
|
||||
} // namespace
|
||||
|
||||
void LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef,
|
||||
const MeasureUnit &perUnit, const UNumberUnitWidth &width,
|
||||
const PluralRules *rules, const MicroPropsGenerator *parent,
|
||||
LongNameHandler *fillIn, UErrorCode &status) {
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, LongNameHandler *fillIn,
|
||||
UErrorCode &status) {
|
||||
// Not valid for mixed units that aren't built-in units, and there should
|
||||
// not be any built-in mixed units!
|
||||
U_ASSERT(uprv_strcmp(unitRef.getType(), "") != 0 ||
|
||||
unitRef.getComplexity(status) != UMEASURE_UNIT_MIXED);
|
||||
U_ASSERT(fillIn != nullptr);
|
||||
|
||||
MeasureUnit unit = unitRef;
|
||||
if (uprv_strcmp(perUnit.getType(), "none") != 0) {
|
||||
// Compound unit: first try to simplify (e.g., meters per second is its own unit).
|
||||
MeasureUnit simplified = unit.product(perUnit.reciprocal(status), status);
|
||||
if (uprv_strcmp(simplified.getType(), "") != 0) {
|
||||
unit = simplified;
|
||||
} else {
|
||||
// No simplified form is available.
|
||||
forCompoundUnit(loc, unit, perUnit, width, rules, parent, fillIn, status);
|
||||
return;
|
||||
if (uprv_strcmp(unitRef.getType(), "") == 0) {
|
||||
// Not a built-in unit. Split it up, since we can already format
|
||||
// "builtin-per-builtin".
|
||||
// TODO(ICU-20941): support more generic case than builtin-per-builtin.
|
||||
MeasureUnitImpl fullUnit = MeasureUnitImpl::forMeasureUnitMaybeCopy(unitRef, status);
|
||||
MeasureUnitImpl unit;
|
||||
MeasureUnitImpl perUnit;
|
||||
for (int32_t i = 0; i < fullUnit.units.length(); i++) {
|
||||
SingleUnitImpl *subUnit = fullUnit.units[i];
|
||||
if (subUnit->dimensionality > 0) {
|
||||
unit.append(*subUnit, status);
|
||||
} else {
|
||||
subUnit->dimensionality *= -1;
|
||||
perUnit.append(*subUnit, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (uprv_strcmp(unit.getType(), "") == 0) {
|
||||
// TODO(ICU-20941): Unsanctioned unit. Not yet fully supported. Set an
|
||||
// error code. Once we support not-built-in units here, unitRef may be
|
||||
// anything, but if not built-in, perUnit has to be "none".
|
||||
status = U_UNSUPPORTED_ERROR;
|
||||
forCompoundUnit(loc, std::move(unit).build(status), std::move(perUnit).build(status), width,
|
||||
rules, parent, fillIn, status);
|
||||
return;
|
||||
}
|
||||
|
||||
UnicodeString simpleFormats[ARRAY_LENGTH];
|
||||
getMeasureData(loc, unit, width, simpleFormats, status);
|
||||
getMeasureData(loc, unitRef, width, simpleFormats, status);
|
||||
if (U_FAILURE(status)) {
|
||||
return;
|
||||
}
|
||||
@ -574,7 +574,7 @@ LongNameMultiplexer::forMeasureUnits(const Locale &loc, const MaybeStackVector<M
|
||||
result->fHandlers[i] = mlnh;
|
||||
} else {
|
||||
LongNameHandler *lnh = result->fLongNameHandlers.createAndCheckErrorCode(status);
|
||||
LongNameHandler::forMeasureUnit(loc, unit, MeasureUnit(), width, rules, NULL, lnh, status);
|
||||
LongNameHandler::forMeasureUnit(loc, unit, width, rules, NULL, lnh, status);
|
||||
result->fHandlers[i] = lnh;
|
||||
}
|
||||
if (U_FAILURE(status)) {
|
||||
|
@ -38,9 +38,6 @@ class LongNameHandler : public MicroPropsGenerator, public ModifierStore, public
|
||||
/**
|
||||
* Construct a localized LongNameHandler for the specified MeasureUnit.
|
||||
*
|
||||
* Compound units can be constructed via `unit` and `perUnit`. Both of these
|
||||
* must then be built-in units.
|
||||
*
|
||||
* Mixed units are not supported, use MixedUnitLongNameHandler::forMeasureUnit.
|
||||
*
|
||||
* This function uses a fillIn intead of returning a pointer, because we
|
||||
@ -48,15 +45,13 @@ class LongNameHandler : public MicroPropsGenerator, public ModifierStore, public
|
||||
* didn't create itself).
|
||||
*
|
||||
* @param loc The desired locale.
|
||||
* @param unit The measure unit to construct a LongNameHandler for. If
|
||||
* `perUnit` is also defined, `unit` must not be a mixed unit.
|
||||
* @param perUnit If `unit` is a mixed unit, `perUnit` must be "none".
|
||||
* @param unitRef The measure unit to construct a LongNameHandler for.
|
||||
* @param width Specifies the desired unit rendering.
|
||||
* @param rules Does not take ownership.
|
||||
* @param parent Does not take ownership.
|
||||
* @param fillIn Required.
|
||||
*/
|
||||
static void forMeasureUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
|
||||
static void forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef,
|
||||
const UNumberUnitWidth &width, const PluralRules *rules,
|
||||
const MicroPropsGenerator *parent, LongNameHandler *fillIn,
|
||||
UErrorCode &status);
|
||||
|
@ -1040,37 +1040,12 @@ void blueprint_helpers::parseIdentifierUnitOption(const StringSegment& segment,
|
||||
SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status);
|
||||
|
||||
ErrorCode internalStatus;
|
||||
auto fullUnit = MeasureUnitImpl::forIdentifier(buffer.toStringPiece(), internalStatus);
|
||||
macros.unit = MeasureUnit::forIdentifier(buffer.toStringPiece(), internalStatus);
|
||||
if (internalStatus.isFailure()) {
|
||||
// throw new SkeletonSyntaxException("Invalid core unit identifier", segment, e);
|
||||
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
// Mixed units can only be represented by full MeasureUnit instances, so we
|
||||
// don't split the denominator into macros.perUnit.
|
||||
if (fullUnit.complexity == UMEASURE_UNIT_MIXED) {
|
||||
macros.unit = std::move(fullUnit).build(status);
|
||||
return;
|
||||
}
|
||||
|
||||
// When we have a built-in unit (e.g. meter-per-second), we don't split it up
|
||||
MeasureUnit testBuiltin = fullUnit.copy(status).build(status);
|
||||
if (uprv_strcmp(testBuiltin.getType(), "") != 0) {
|
||||
macros.unit = std::move(testBuiltin);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(ICU-20941): Clean this up.
|
||||
for (int32_t i = 0; i < fullUnit.units.length(); i++) {
|
||||
SingleUnitImpl* subUnit = fullUnit.units[i];
|
||||
if (subUnit->dimensionality > 0) {
|
||||
macros.unit = macros.unit.product(subUnit->build(status), status);
|
||||
} else {
|
||||
subUnit->dimensionality *= -1;
|
||||
macros.perUnit = macros.perUnit.product(subUnit->build(status), status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void blueprint_helpers::parseUnitUsageOption(const StringSegment &segment, MacroProps ¯os,
|
||||
|
@ -867,22 +867,21 @@ void NumberFormatterApiTest::unitCompoundMeasure() {
|
||||
u"0.008765 J/fur",
|
||||
u"0 J/fur");
|
||||
|
||||
// TODO(ICU-20941): Support constructions such as this one.
|
||||
// assertFormatDescending(
|
||||
// u"Joules Per Furlong Short with unit identifier via API",
|
||||
// u"measure-unit/energy-joule per-measure-unit/length-furlong",
|
||||
// u"unit/joule-per-furlong",
|
||||
// NumberFormatter::with().unit(MeasureUnit::forIdentifier("joule-per-furlong", status)),
|
||||
// Locale::getEnglish(),
|
||||
// u"87,650 J/fur",
|
||||
// u"8,765 J/fur",
|
||||
// u"876.5 J/fur",
|
||||
// u"87.65 J/fur",
|
||||
// u"8.765 J/fur",
|
||||
// u"0.8765 J/fur",
|
||||
// u"0.08765 J/fur",
|
||||
// u"0.008765 J/fur",
|
||||
// u"0 J/fur");
|
||||
assertFormatDescending(
|
||||
u"Joules Per Furlong Short with unit identifier via API",
|
||||
u"measure-unit/energy-joule per-measure-unit/length-furlong",
|
||||
u"unit/joule-per-furlong",
|
||||
NumberFormatter::with().unit(MeasureUnit::forIdentifier("joule-per-furlong", status)),
|
||||
Locale::getEnglish(),
|
||||
u"87,650 J/fur",
|
||||
u"8,765 J/fur",
|
||||
u"876.5 J/fur",
|
||||
u"87.65 J/fur",
|
||||
u"8.765 J/fur",
|
||||
u"0.8765 J/fur",
|
||||
u"0.08765 J/fur",
|
||||
u"0.008765 J/fur",
|
||||
u"0 J/fur");
|
||||
|
||||
assertFormatDescending(
|
||||
u"Pounds per Square Inch: composed",
|
||||
|
@ -13,6 +13,8 @@ import com.ibm.icu.impl.SimpleFormatterImpl;
|
||||
import com.ibm.icu.impl.StandardPlural;
|
||||
import com.ibm.icu.impl.UResource;
|
||||
import com.ibm.icu.impl.number.Modifier.Signum;
|
||||
import com.ibm.icu.impl.units.MeasureUnitImpl;
|
||||
import com.ibm.icu.impl.units.SingleUnitImpl;
|
||||
import com.ibm.icu.number.NumberFormatter.UnitWidth;
|
||||
import com.ibm.icu.text.NumberFormat;
|
||||
import com.ibm.icu.text.PluralRules;
|
||||
@ -195,15 +197,10 @@ public class LongNameHandler
|
||||
/**
|
||||
* Construct a localized LongNameHandler for the specified MeasureUnit.
|
||||
* <p>
|
||||
* Compound units can be constructed via `unit` and `perUnit`. Both of these
|
||||
* must then be built-in units.
|
||||
* <p>
|
||||
* Mixed units are not supported, use MixedUnitLongNameHandler.forMeasureUnit.
|
||||
*
|
||||
* @param locale The desired locale.
|
||||
* @param unit The measure unit to construct a LongNameHandler for. If
|
||||
* `perUnit` is also defined, `unit` must not be a mixed unit.
|
||||
* @param perUnit If `unit` is a mixed unit, `perUnit` must be null.
|
||||
* @param unit The measure unit to construct a LongNameHandler for.
|
||||
* @param width Specifies the desired unit rendering.
|
||||
* @param rules Plural rules.
|
||||
* @param parent Plural rules.
|
||||
@ -211,25 +208,34 @@ public class LongNameHandler
|
||||
public static LongNameHandler forMeasureUnit(
|
||||
ULocale locale,
|
||||
MeasureUnit unit,
|
||||
MeasureUnit perUnit,
|
||||
UnitWidth width,
|
||||
PluralRules rules,
|
||||
MicroPropsGenerator parent) {
|
||||
if (perUnit != null) {
|
||||
// Compound unit: first try to simplify (e.g., meters per second is its own unit).
|
||||
MeasureUnit simplified = unit.product(perUnit.reciprocal());
|
||||
if (simplified.getType() != null) {
|
||||
unit = simplified;
|
||||
} else {
|
||||
// No simplified form is available.
|
||||
return forCompoundUnit(locale, unit, perUnit, width, rules, parent);
|
||||
}
|
||||
}
|
||||
|
||||
if (unit.getType() == null) {
|
||||
// TODO(ICU-20941): Unsanctioned unit. Not yet fully supported.
|
||||
throw new UnsupportedOperationException("Unsanctioned unit, not yet supported: " +
|
||||
unit.getIdentifier());
|
||||
// Not a built-in unit. Split it up, since we can already format
|
||||
// "builtin-per-builtin".
|
||||
// TODO(ICU-20941): support more generic case than builtin-per-builtin.
|
||||
MeasureUnitImpl fullUnit = unit.getCopyOfMeasureUnitImpl();
|
||||
unit = null;
|
||||
MeasureUnit perUnit = null;
|
||||
for (SingleUnitImpl subUnit : fullUnit.getSingleUnits()) {
|
||||
if (subUnit.getDimensionality() > 0) {
|
||||
if (unit == null) {
|
||||
unit = subUnit.build();
|
||||
} else {
|
||||
unit = unit.product(subUnit.build());
|
||||
}
|
||||
} else {
|
||||
// It's okay to mutate fullUnit, we made a temporary copy:
|
||||
subUnit.setDimensionality(subUnit.getDimensionality() * -1);
|
||||
if (perUnit == null) {
|
||||
perUnit = subUnit.build();
|
||||
} else {
|
||||
perUnit = perUnit.product(subUnit.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
return forCompoundUnit(locale, unit, perUnit, width, rules, parent);
|
||||
}
|
||||
|
||||
String[] simpleFormats = new String[ARRAY_LENGTH];
|
||||
@ -336,6 +342,7 @@ public class LongNameHandler
|
||||
* Does not call parent.processQuantity, so cannot get a MicroProps instance
|
||||
* that way. Instead, the instance is passed in as a parameter.
|
||||
*/
|
||||
@Override
|
||||
public MicroProps processQuantityWithMicros(DecimalQuantity quantity, MicroProps micros) {
|
||||
StandardPlural pluralForm = RoundingUtils.getPluralSafe(micros.rounder, rules, quantity);
|
||||
micros.modOuter = modifiers.get(pluralForm);
|
||||
|
@ -8,7 +8,6 @@ import java.util.List;
|
||||
import com.ibm.icu.number.NumberFormatter;
|
||||
import com.ibm.icu.text.PluralRules;
|
||||
import com.ibm.icu.util.MeasureUnit;
|
||||
import com.ibm.icu.util.NoUnit;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
/**
|
||||
@ -64,8 +63,7 @@ public class LongNameMultiplexer implements MicroPropsGenerator {
|
||||
.forMeasureUnit(locale, unit, width, rules, null);
|
||||
result.fHandlers.add(mlnh);
|
||||
} else {
|
||||
LongNameHandler lnh = LongNameHandler
|
||||
.forMeasureUnit(locale, unit, NoUnit.BASE, width, rules, null);
|
||||
LongNameHandler lnh = LongNameHandler.forMeasureUnit(locale, unit, width, rules, null);
|
||||
result.fHandlers.add(lnh);
|
||||
}
|
||||
}
|
||||
|
@ -400,12 +400,11 @@ class NumberFormatterImpl {
|
||||
pluralRules,
|
||||
chain);
|
||||
} else {
|
||||
chain = LongNameHandler.forMeasureUnit(macros.loc,
|
||||
macros.unit,
|
||||
macros.perUnit,
|
||||
unitWidth,
|
||||
pluralRules,
|
||||
chain);
|
||||
MeasureUnit unit = macros.unit;
|
||||
if (macros.perUnit != null) {
|
||||
unit = unit.product(macros.perUnit.reciprocal());
|
||||
}
|
||||
chain = LongNameHandler.forMeasureUnit(macros.loc, unit, unitWidth, pluralRules, chain);
|
||||
}
|
||||
} else if (isCurrency && unitWidth == UnitWidth.FULL_NAME) {
|
||||
if (rules == null) {
|
||||
|
@ -12,8 +12,6 @@ import com.ibm.icu.impl.SoftCache;
|
||||
import com.ibm.icu.impl.StringSegment;
|
||||
import com.ibm.icu.impl.number.MacroProps;
|
||||
import com.ibm.icu.impl.number.RoundingUtils;
|
||||
import com.ibm.icu.impl.units.MeasureUnitImpl;
|
||||
import com.ibm.icu.impl.units.SingleUnitImpl;
|
||||
import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
|
||||
import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
|
||||
import com.ibm.icu.number.NumberFormatter.SignDisplay;
|
||||
@ -1063,45 +1061,11 @@ class NumberSkeletonImpl {
|
||||
* specified via a "unit/" concise skeleton.
|
||||
*/
|
||||
private static void parseIdentifierUnitOption(StringSegment segment, MacroProps macros) {
|
||||
MeasureUnitImpl fullUnit;
|
||||
try {
|
||||
fullUnit = MeasureUnitImpl.forIdentifier(segment.asString());
|
||||
macros.unit = MeasureUnit.forIdentifier(segment.asString());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new SkeletonSyntaxException("Invalid unit stem", segment);
|
||||
}
|
||||
|
||||
// Mixed units can only be represented by full MeasureUnit instances, so we
|
||||
// don't split the denominator into macros.perUnit.
|
||||
if (fullUnit.getComplexity() == MeasureUnit.Complexity.MIXED) {
|
||||
macros.unit = fullUnit.build();
|
||||
return;
|
||||
}
|
||||
|
||||
// When we have a built-in unit (e.g. meter-per-second), we don't split it up
|
||||
MeasureUnit testBuiltin = fullUnit.build();
|
||||
if (testBuiltin.getType() != null) {
|
||||
macros.unit = testBuiltin;
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(ICU-20941): Clean this up.
|
||||
for (SingleUnitImpl subUnit : fullUnit.getSingleUnits()) {
|
||||
if (subUnit.getDimensionality() > 0) {
|
||||
if (macros.unit == null) {
|
||||
macros.unit = subUnit.build();
|
||||
} else {
|
||||
macros.unit = macros.unit.product(subUnit.build());
|
||||
}
|
||||
} else {
|
||||
// It's okay to mutate fullUnit, we're throwing it away after this:
|
||||
subUnit.setDimensionality(subUnit.getDimensionality() * -1);
|
||||
if (macros.perUnit == null) {
|
||||
macros.perUnit = subUnit.build();
|
||||
} else {
|
||||
macros.perUnit = macros.perUnit.product(subUnit.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseUnitUsageOption(StringSegment segment, MacroProps macros) {
|
||||
|
@ -542,7 +542,7 @@ public class MeasureUnit implements Serializable {
|
||||
return implCopy.build();
|
||||
}
|
||||
|
||||
final MeasureUnitImpl otherImplRef = other.getMayBeReferenceOfMeasureUnitImpl();
|
||||
final MeasureUnitImpl otherImplRef = other.getMaybeReferenceOfMeasureUnitImpl();
|
||||
if (implCopy.getComplexity() == Complexity.MIXED || otherImplRef.getComplexity() == Complexity.MIXED) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@ -571,7 +571,8 @@ public class MeasureUnit implements Serializable {
|
||||
* @provisional This API might change or be removed in a future release.
|
||||
*/
|
||||
public List<MeasureUnit> splitToSingleUnits() {
|
||||
final ArrayList<SingleUnitImpl> singleUnits = getMayBeReferenceOfMeasureUnitImpl().getSingleUnits();
|
||||
final ArrayList<SingleUnitImpl> singleUnits =
|
||||
getMaybeReferenceOfMeasureUnitImpl().getSingleUnits();
|
||||
List<MeasureUnit> result = new ArrayList<>(singleUnits.size());
|
||||
for (SingleUnitImpl singleUnit : singleUnits) {
|
||||
result.add(singleUnit.build());
|
||||
@ -1992,8 +1993,11 @@ public class MeasureUnit implements Serializable {
|
||||
/**
|
||||
*
|
||||
* @return this object in a MeasureUnitImpl form.
|
||||
* @internal
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
private MeasureUnitImpl getCopyOfMeasureUnitImpl() {
|
||||
@Deprecated
|
||||
public MeasureUnitImpl getCopyOfMeasureUnitImpl() {
|
||||
return this.measureUnitImpl == null ?
|
||||
MeasureUnitImpl.forIdentifier(getIdentifier()) :
|
||||
this.measureUnitImpl.copy();
|
||||
@ -2003,7 +2007,7 @@ public class MeasureUnit implements Serializable {
|
||||
*
|
||||
* @return this object in a MeasureUnitImpl form.
|
||||
*/
|
||||
private MeasureUnitImpl getMayBeReferenceOfMeasureUnitImpl(){
|
||||
private MeasureUnitImpl getMaybeReferenceOfMeasureUnitImpl() {
|
||||
return this.measureUnitImpl == null ?
|
||||
MeasureUnitImpl.forIdentifier(getIdentifier()) :
|
||||
this.measureUnitImpl;
|
||||
|
@ -824,22 +824,21 @@ public class NumberFormatterApiTest extends TestFmwk {
|
||||
"0.008765 J/fur",
|
||||
"0 J/fur");
|
||||
|
||||
// // TODO(ICU-20941): Support constructions such as this one.
|
||||
// assertFormatDescending(
|
||||
// "Joules Per Furlong Short with unit identifier via API",
|
||||
// "measure-unit/energy-joule per-measure-unit/length-furlong",
|
||||
// "unit/joule-per-furlong",
|
||||
// NumberFormatter.with().unit(MeasureUnit.forIdentifier("joule-per-furlong")),
|
||||
// ULocale.ENGLISH,
|
||||
// "87,650 J/fur",
|
||||
// "8,765 J/fur",
|
||||
// "876.5 J/fur",
|
||||
// "87.65 J/fur",
|
||||
// "8.765 J/fur",
|
||||
// "0.8765 J/fur",
|
||||
// "0.08765 J/fur",
|
||||
// "0.008765 J/fur",
|
||||
// "0 J/fur");
|
||||
assertFormatDescending(
|
||||
"Joules Per Furlong Short with unit identifier via API",
|
||||
"measure-unit/energy-joule per-measure-unit/length-furlong",
|
||||
"unit/joule-per-furlong",
|
||||
NumberFormatter.with().unit(MeasureUnit.forIdentifier("joule-per-furlong")),
|
||||
ULocale.ENGLISH,
|
||||
"87,650 J/fur",
|
||||
"8,765 J/fur",
|
||||
"876.5 J/fur",
|
||||
"87.65 J/fur",
|
||||
"8.765 J/fur",
|
||||
"0.8765 J/fur",
|
||||
"0.08765 J/fur",
|
||||
"0.008765 J/fur",
|
||||
"0 J/fur");
|
||||
|
||||
assertFormatDescending(
|
||||
"Pounds per Square Inch: composed",
|
||||
|
Loading…
Reference in New Issue
Block a user