]> granicus.if.org Git - icu/commitdiff
ICU-13634 The property mapper appears to be basically functional; data passes from...
authorShane Carr <shane@unicode.org>
Thu, 15 Mar 2018 10:08:26 +0000 (10:08 +0000)
committerShane Carr <shane@unicode.org>
Thu, 15 Mar 2018 10:08:26 +0000 (10:08 +0000)
X-SVN-Rev: 41108

icu4c/source/i18n/Makefile.in
icu4c/source/i18n/decimfmt.cpp
icu4c/source/i18n/number_formatimpl.cpp
icu4c/source/i18n/number_mapper.cpp
icu4c/source/i18n/number_mapper.h
icu4c/source/i18n/number_multiplier.cpp [new file with mode: 0644]
icu4c/source/i18n/number_multiplier.h [new file with mode: 0644]
icu4c/source/i18n/number_utils.h

index a1d186b43501db18f0608c136a419da609862453..326ce2170f5d489f47bcb403c2bb8f527d78fdf0 100644 (file)
@@ -108,7 +108,7 @@ double-conversion-cached-powers.o double-conversion-diy-fp.o double-conversion-f
 numparse_stringsegment.o numparse_unisets.o numparse_parsednumber.o \
 numparse_impl.o numparse_symbols.o numparse_decimal.o numparse_scientific.o \
 numparse_currency.o numparse_affixes.o numparse_compositions.o \
-number_mapper.o
+number_mapper.o number_multiplier.o
 
 
 ## Header files to install
index 7772f330d67c4f94c80f145ddeb29f07fd9681ac..a60126904908dd08a7a8f667cebb7a63d3c08f99 100644 (file)
@@ -64,6 +64,7 @@ DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols*
 DecimalFormat::DecimalFormat(const DecimalFormatSymbols* symbolsToAdopt, UErrorCode& status) {
     fProperties.adoptInsteadAndCheckErrorCode(new DecimalFormatProperties(), status);
     fExportedProperties.adoptInsteadAndCheckErrorCode(new DecimalFormatProperties(), status);
+    fWarehouse.adoptInsteadAndCheckErrorCode(new DecimalFormatWarehouse(), status);
     if (symbolsToAdopt == nullptr) {
         fSymbols.adoptInsteadAndCheckErrorCode(new DecimalFormatSymbols(status), status);
     } else {
@@ -313,8 +314,10 @@ DecimalFormat::DecimalFormat(const UnicodeString& pattern, const DecimalFormatSy
 DecimalFormat::DecimalFormat(const DecimalFormat& source) {
     fProperties.adoptInstead(new DecimalFormatProperties());
     fExportedProperties.adoptInstead(new DecimalFormatProperties());
+    fWarehouse.adoptInstead(new DecimalFormatWarehouse());
     fSymbols.adoptInstead(new DecimalFormatSymbols(*source.fSymbols));
-    if (fProperties == nullptr || fExportedProperties == nullptr || fSymbols == nullptr) {
+    if (fProperties == nullptr || fExportedProperties == nullptr || fWarehouse == nullptr ||
+        fSymbols == nullptr) {
         return;
     }
     refreshFormatterNoError();
@@ -862,7 +865,7 @@ void DecimalFormat::formatToDecimalQuantity(const Formattable& number, DecimalQu
     status = U_UNSUPPORTED_ERROR;
 }
 
-number::LocalizedNumberFormatter DecimalFormat::toNumberFormatter() const {
+const number::LocalizedNumberFormatter& DecimalFormat::toNumberFormatter() const {
     return *fFormatter;
 }
 
@@ -897,14 +900,16 @@ void DecimalFormat::refreshFormatter(UErrorCode& status) {
     fFormatter.adoptInsteadAndCheckErrorCode(
             new LocalizedNumberFormatter(
                     NumberPropertyMapper::create(
-                            *fProperties, *fSymbols, *fExportedProperties, status).locale(
+                            *fProperties, *fSymbols, *fWarehouse, *fExportedProperties, status).locale(
                             locale)), status);
-    fParser.adoptInsteadAndCheckErrorCode(
-            NumberParserImpl::createParserFromProperties(
-                    *fProperties, *fSymbols, false, false, status), status);
-    fParserWithCurrency.adoptInsteadAndCheckErrorCode(
-            NumberParserImpl::createParserFromProperties(
-                    *fProperties, *fSymbols, true, false, status), status);
+
+    // fParser.adoptInsteadAndCheckErrorCode(
+    //         NumberParserImpl::createParserFromProperties(
+    //                 *fProperties, *fSymbols, false, false, status), status);
+
+    // fParserWithCurrency.adoptInsteadAndCheckErrorCode(
+    //         NumberParserImpl::createParserFromProperties(
+    //                 *fProperties, *fSymbols, true, false, status), status);
 }
 
 void DecimalFormat::refreshFormatterNoError() {
index 795c3d13482da5a6127b97c761ff8b212ee345c1..04344b368fd61241a612a053a3d4b648fffda38c 100644 (file)
@@ -276,6 +276,12 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe,
     /// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR ///
     /////////////////////////////////////////////////////////////////////////////////////
 
+    // Multiplier (compatibility mode value).
+    if (macros.multiplier.isValid()) {
+        fMicros.helpers.multiplier.setAndChain(macros.multiplier, chain);
+        chain = &fMicros.helpers.multiplier;
+    }
+
     // Rounding strategy
     if (!macros.rounder.isBogus()) {
         fMicros.rounding = macros.rounder;
@@ -342,7 +348,9 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe,
     // Middle modifier (patterns, positive/negative, currency symbols, percent)
     auto patternModifier = new MutablePatternModifier(false);
     fPatternModifier.adoptInstead(patternModifier);
-    patternModifier->setPatternInfo(fPatternInfo.getAlias());
+    patternModifier->setPatternInfo(
+            macros.affixProvider != nullptr ? macros.affixProvider
+                                            : static_cast<const AffixPatternProvider*>(fPatternInfo.getAlias()));
     patternModifier->setPatternAttributes(fMicros.sign, isPermille);
     if (patternModifier->needsPlurals()) {
         patternModifier->setSymbols(
index 949f88aadd24ff1707e344929325ab36586cbbcd..0f5567c442c5727a377acc8434ca19418a1d539e 100644 (file)
@@ -11,6 +11,8 @@
 
 #include "number_mapper.h"
 #include "number_patternstring.h"
+#include "unicode/errorcode.h"
+#include "number_utils.h"
 
 using namespace icu;
 using namespace icu::number;
@@ -61,10 +63,10 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert
     AffixPatternProvider* affixProvider;
     if (properties.currencyPluralInfo.fPtr.isNull()) {
         warehouse.currencyPluralInfoAPP.setToBogus();
-        warehouse.propertiesAPP.setTo(properties);
+        warehouse.propertiesAPP.setTo(properties, status);
         affixProvider = &warehouse.propertiesAPP;
     } else {
-        warehouse.currencyPluralInfoAPP.setTo(*properties.currencyPluralInfo.fPtr);
+        warehouse.currencyPluralInfoAPP.setTo(*properties.currencyPluralInfo.fPtr, status);
         warehouse.propertiesAPP.setToBogus();
         affixProvider = &warehouse.currencyPluralInfoAPP;
     }
@@ -229,8 +231,8 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert
                 macros.rounder = Rounder::constructSignificant(1, maxFrac_ + 1).withMode(roundingMode);
             } else {
                 // All other scientific patterns, which mean round to minInt+maxFrac
-                macros.rounder = Rounder::constructSignificant(minInt_ + minFrac_, minInt_ + maxFrac_)
-                        .withMode(roundingMode);
+                macros.rounder = Rounder::constructSignificant(
+                        minInt_ + minFrac_, minInt_ + maxFrac_).withMode(roundingMode);
             }
         }
     }
@@ -254,9 +256,9 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert
     /////////////////
 
     if (properties.magnitudeMultiplier != 0) {
-        macros.multiplier = MultiplierImpl::magnitude(properties.magnitudeMultiplier);
+        macros.multiplier = Multiplier::magnitude(properties.magnitudeMultiplier);
     } else if (properties.multiplier != 1) {
-        macros.multiplier = MultiplierImpl::integer(properties.multiplier);
+        macros.multiplier = Multiplier::integer(properties.multiplier);
     }
 
     //////////////////////
@@ -303,8 +305,127 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert
 }
 
 
-void PropertiesAffixPatternProvider::setTo(const DecimalFormatProperties& properties) {
-    // TODO
+void PropertiesAffixPatternProvider::setTo(const DecimalFormatProperties& properties, UErrorCode&) {
+    // There are two ways to set affixes in DecimalFormat: via the pattern string (applyPattern), and via the
+    // explicit setters (setPositivePrefix and friends).  The way to resolve the settings is as follows:
+    //
+    // 1) If the explicit setting is present for the field, use it.
+    // 2) Otherwise, follows UTS 35 rules based on the pattern string.
+    //
+    // Importantly, the explicit setters affect only the one field they override.  If you set the positive
+    // prefix, that should not affect the negative prefix.  Since it is impossible for the user of this class
+    // to know whether the origin for a string was the override or the pattern, we have to say that we always
+    // have a negative subpattern and perform all resolution logic here.
+
+    // Convenience: Extract the properties into local variables.
+    // Variables are named with three chars: [p/n][p/s][o/p]
+    // [p/n] => p for positive, n for negative
+    // [p/s] => p for prefix, s for suffix
+    // [o/p] => o for escaped custom override string, p for pattern string
+    UnicodeString ppo = AffixUtils::escape(UnicodeStringCharSequence(properties.positivePrefix));
+    UnicodeString pso = AffixUtils::escape(UnicodeStringCharSequence(properties.positiveSuffix));
+    UnicodeString npo = AffixUtils::escape(UnicodeStringCharSequence(properties.negativePrefix));
+    UnicodeString nso = AffixUtils::escape(UnicodeStringCharSequence(properties.negativeSuffix));
+    const UnicodeString& ppp = properties.positivePrefixPattern;
+    const UnicodeString& psp = properties.positiveSuffixPattern;
+    const UnicodeString& npp = properties.negativePrefixPattern;
+    const UnicodeString& nsp = properties.negativeSuffixPattern;
+
+    if (!properties.positivePrefix.isBogus()) {
+        posPrefix = ppo;
+    } else if (!ppp.isBogus()) {
+        posPrefix = ppp;
+    } else {
+        // UTS 35: Default positive prefix is empty string.
+        posPrefix = u"";
+    }
+
+    if (!properties.positiveSuffix.isBogus()) {
+        posSuffix = pso;
+    } else if (!psp.isBogus()) {
+        posSuffix = psp;
+    } else {
+        // UTS 35: Default positive suffix is empty string.
+        posSuffix = u"";
+    }
+
+    if (!properties.negativePrefix.isBogus()) {
+        negPrefix = npo;
+    } else if (!npp.isBogus()) {
+        negPrefix = npp;
+    } else {
+        // UTS 35: Default negative prefix is "-" with positive prefix.
+        // Important: We prepend the "-" to the pattern, not the override!
+        negPrefix = ppp.isBogus() ? u"-" : u"-" + ppp;
+    }
+
+    if (!properties.negativeSuffix.isBogus()) {
+        negSuffix = nso;
+    } else if (!nsp.isBogus()) {
+        negSuffix = nsp;
+    } else {
+        // UTS 35: Default negative prefix is the positive prefix.
+        negSuffix = psp.isBogus() ? u"" : psp;
+    }
+}
+
+char16_t PropertiesAffixPatternProvider::charAt(int flags, int i) const {
+    return getStringInternal(flags).charAt(i);
+}
+
+int PropertiesAffixPatternProvider::length(int flags) const {
+    return getStringInternal(flags).length();
+}
+
+UnicodeString PropertiesAffixPatternProvider::getString(int32_t flags) const {
+    return getStringInternal(flags);
+}
+
+const UnicodeString& PropertiesAffixPatternProvider::getStringInternal(int32_t flags) const {
+    bool prefix = (flags & AFFIX_PREFIX) != 0;
+    bool negative = (flags & AFFIX_NEGATIVE_SUBPATTERN) != 0;
+    if (prefix && negative) {
+        return negPrefix;
+    } else if (prefix) {
+        return posPrefix;
+    } else if (negative) {
+        return negSuffix;
+    } else {
+        return posSuffix;
+    }
+}
+
+bool PropertiesAffixPatternProvider::positiveHasPlusSign() const {
+    // TODO: Change the internal APIs to propagate out the error?
+    ErrorCode localStatus;
+    return AffixUtils::containsType(UnicodeStringCharSequence(posPrefix), TYPE_PLUS_SIGN, localStatus) ||
+           AffixUtils::containsType(UnicodeStringCharSequence(posSuffix), TYPE_PLUS_SIGN, localStatus);
+}
+
+bool PropertiesAffixPatternProvider::hasNegativeSubpattern() const {
+    // See comments in the constructor for more information on why this is always true.
+    return true;
+}
+
+bool PropertiesAffixPatternProvider::negativeHasMinusSign() const {
+    ErrorCode localStatus;
+    return AffixUtils::containsType(UnicodeStringCharSequence(negPrefix), TYPE_MINUS_SIGN, localStatus) ||
+           AffixUtils::containsType(UnicodeStringCharSequence(negSuffix), TYPE_MINUS_SIGN, localStatus);
+}
+
+bool PropertiesAffixPatternProvider::hasCurrencySign() const {
+    ErrorCode localStatus;
+    return AffixUtils::hasCurrencySymbols(UnicodeStringCharSequence(posPrefix), localStatus) ||
+           AffixUtils::hasCurrencySymbols(UnicodeStringCharSequence(posSuffix), localStatus) ||
+           AffixUtils::hasCurrencySymbols(UnicodeStringCharSequence(negPrefix), localStatus) ||
+           AffixUtils::hasCurrencySymbols(UnicodeStringCharSequence(negSuffix), localStatus);
+}
+
+bool PropertiesAffixPatternProvider::containsSymbolType(AffixPatternType type, UErrorCode& status) const {
+    return AffixUtils::containsType(UnicodeStringCharSequence(posPrefix), type, status) ||
+           AffixUtils::containsType(UnicodeStringCharSequence(posSuffix), type, status) ||
+           AffixUtils::containsType(UnicodeStringCharSequence(negPrefix), type, status) ||
+           AffixUtils::containsType(UnicodeStringCharSequence(negSuffix), type, status);
 }
 
 bool PropertiesAffixPatternProvider::hasBody() const {
@@ -312,8 +433,48 @@ bool PropertiesAffixPatternProvider::hasBody() const {
 }
 
 
-void CurrencyPluralInfoAffixProvider::setTo(const CurrencyPluralInfo& cpi) {
-    // TODO
+void CurrencyPluralInfoAffixProvider::setTo(const CurrencyPluralInfo& cpi, UErrorCode& status) {
+    for (int32_t plural = 0; plural < StandardPlural::COUNT; plural++) {
+        const char* keyword = StandardPlural::getKeyword(static_cast<StandardPlural::Form>(plural));
+        UnicodeString patternString;
+        patternString = cpi.getCurrencyPluralPattern(keyword, patternString);
+        PatternParser::parseToPatternInfo(patternString, affixesByPlural[plural], status);
+    }
+}
+
+char16_t CurrencyPluralInfoAffixProvider::charAt(int32_t flags, int32_t i) const {
+    int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
+    return affixesByPlural[pluralOrdinal].charAt(flags, i);
+}
+
+int32_t CurrencyPluralInfoAffixProvider::length(int32_t flags) const {
+    int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
+    return affixesByPlural[pluralOrdinal].length(flags);
+}
+
+UnicodeString CurrencyPluralInfoAffixProvider::getString(int32_t flags) const {
+    int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
+    return affixesByPlural[pluralOrdinal].getString(flags);
+}
+
+bool CurrencyPluralInfoAffixProvider::positiveHasPlusSign() const {
+    return affixesByPlural[StandardPlural::OTHER].positiveHasPlusSign();
+}
+
+bool CurrencyPluralInfoAffixProvider::hasNegativeSubpattern() const {
+    return affixesByPlural[StandardPlural::OTHER].hasNegativeSubpattern();
+}
+
+bool CurrencyPluralInfoAffixProvider::negativeHasMinusSign() const {
+    return affixesByPlural[StandardPlural::OTHER].negativeHasMinusSign();
+}
+
+bool CurrencyPluralInfoAffixProvider::hasCurrencySign() const {
+    return affixesByPlural[StandardPlural::OTHER].hasCurrencySign();
+}
+
+bool CurrencyPluralInfoAffixProvider::containsSymbolType(AffixPatternType type, UErrorCode& status) const {
+    return affixesByPlural[StandardPlural::OTHER].containsSymbolType(type, status);
 }
 
 bool CurrencyPluralInfoAffixProvider::hasBody() const {
index 842b183fd535164440fe035be7ba8ab9040e2f19..d9af7954995604076e857dcfe973d38f49f8ed17 100644 (file)
@@ -18,19 +18,23 @@ namespace impl {
 
 class PropertiesAffixPatternProvider : public AffixPatternProvider, public UMemory {
   public:
-    bool isBogus() const;
+    bool isBogus() const {
+        return fBogus;
+    }
 
-    void setTo(const DecimalFormatProperties& properties);
+    void setToBogus() {
+        fBogus = true;
+    }
 
-    void setToBogus();
+    void setTo(const DecimalFormatProperties& properties, UErrorCode& status);
 
     // AffixPatternProvider Methods:
 
-    char16_t charAt(int flags, int i) const U_OVERRIDE;
+    char16_t charAt(int32_t flags, int32_t i) const U_OVERRIDE;
 
-    int length(int flags) const U_OVERRIDE;
+    int32_t length(int32_t flags) const U_OVERRIDE;
 
-    UnicodeString getString(int flags) const U_OVERRIDE;
+    UnicodeString getString(int32_t flags) const U_OVERRIDE;
 
     bool hasCurrencySign() const U_OVERRIDE;
 
@@ -42,7 +46,7 @@ class PropertiesAffixPatternProvider : public AffixPatternProvider, public UMemo
 
     bool containsSymbolType(AffixPatternType, UErrorCode&) const U_OVERRIDE;
 
-    virtual bool hasBody() const U_OVERRIDE;
+    bool hasBody() const U_OVERRIDE;
 
   private:
     UnicodeString posPrefix;
@@ -50,25 +54,31 @@ class PropertiesAffixPatternProvider : public AffixPatternProvider, public UMemo
     UnicodeString negPrefix;
     UnicodeString negSuffix;
 
+    const UnicodeString& getStringInternal(int32_t flags) const;
+
     bool fBogus{true};
 };
 
 
 class CurrencyPluralInfoAffixProvider : public AffixPatternProvider, public UMemory {
   public:
-    bool isBogus() const;
+    bool isBogus() const {
+        return fBogus;
+    }
 
-    void setTo(const CurrencyPluralInfo& cpi);
+    void setToBogus() {
+        fBogus = true;
+    }
 
-    void setToBogus();
+    void setTo(const CurrencyPluralInfo& cpi, UErrorCode& status);
 
     // AffixPatternProvider Methods:
 
-    char16_t charAt(int flags, int i) const U_OVERRIDE;
+    char16_t charAt(int32_t flags, int32_t i) const U_OVERRIDE;
 
-    int length(int flags) const U_OVERRIDE;
+    int32_t length(int32_t flags) const U_OVERRIDE;
 
-    UnicodeString getString(int flags) const U_OVERRIDE;
+    UnicodeString getString(int32_t flags) const U_OVERRIDE;
 
     bool hasCurrencySign() const U_OVERRIDE;
 
@@ -80,7 +90,7 @@ class CurrencyPluralInfoAffixProvider : public AffixPatternProvider, public UMem
 
     bool containsSymbolType(AffixPatternType, UErrorCode&) const U_OVERRIDE;
 
-    virtual bool hasBody() const U_OVERRIDE;
+    bool hasBody() const U_OVERRIDE;
 
   private:
     ParsedPatternInfo affixesByPlural[StandardPlural::COUNT];
diff --git a/icu4c/source/i18n/number_multiplier.cpp b/icu4c/source/i18n/number_multiplier.cpp
new file mode 100644 (file)
index 0000000..ca445ba
--- /dev/null
@@ -0,0 +1,47 @@
+// © 2018 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+#include "unicode/utypes.h"
+
+#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
+
+// Allow implicit conversion from char16_t* to UnicodeString for this file:
+// Helpful in toString methods and elsewhere.
+#define UNISTR_FROM_STRING_EXPLICIT
+
+#include "number_types.h"
+#include "number_multiplier.h"
+
+using namespace icu;
+using namespace icu::number;
+using namespace icu::number::impl;
+
+
+Multiplier::Multiplier(int32_t magnitudeMultiplier, int32_t multiplier)
+        : magnitudeMultiplier(magnitudeMultiplier), multiplier(multiplier) {}
+
+Multiplier Multiplier::magnitude(int32_t magnitudeMultiplier) {
+    return {magnitudeMultiplier, 1};
+}
+
+Multiplier Multiplier::integer(int32_t multiplier) {
+    return {0, multiplier};
+}
+
+
+void MultiplierChain::setAndChain(const Multiplier& multiplier, const MicroPropsGenerator* parent) {
+    this->multiplier = multiplier;
+    this->parent = parent;
+}
+
+void
+MultiplierChain::processQuantity(DecimalQuantity& quantity, MicroProps& micros, UErrorCode& status) const {
+    parent->processQuantity(quantity, micros, status);
+    quantity.adjustMagnitude(multiplier.magnitudeMultiplier);
+    if (multiplier.multiplier != 1) {
+        quantity.multiplyBy(multiplier.multiplier);
+    }
+}
+
+
+#endif /* #if !UCONFIG_NO_FORMATTING */
diff --git a/icu4c/source/i18n/number_multiplier.h b/icu4c/source/i18n/number_multiplier.h
new file mode 100644 (file)
index 0000000..93d103d
--- /dev/null
@@ -0,0 +1,34 @@
+// © 2018 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+#include "unicode/utypes.h"
+
+#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT
+#ifndef __SOURCE_NUMBER_MULTIPLIER_H__
+#define __SOURCE_NUMBER_MULTIPLIER_H__
+
+#include "numparse_types.h"
+
+U_NAMESPACE_BEGIN namespace number {
+namespace impl {
+
+
+class MultiplierChain : public MicroPropsGenerator, public UMemory {
+  public:
+    void setAndChain(const Multiplier& other, const MicroPropsGenerator* parent);
+
+    void processQuantity(DecimalQuantity& quantity, MicroProps& micros,
+                         UErrorCode& status) const U_OVERRIDE;
+
+  private:
+    Multiplier multiplier;
+    const MicroPropsGenerator *parent;
+};
+
+
+} // namespace impl
+} // namespace number
+U_NAMESPACE_END
+
+#endif //__SOURCE_NUMBER_MULTIPLIER_H__
+#endif /* #if !UCONFIG_NO_FORMATTING */
index a889c69eb74caa84fea805b1589d9e7622217105..1e5494dc8137dd9b5546ac0145455d21a7e75a5e 100644 (file)
@@ -13,6 +13,7 @@
 #include "number_scientific.h"
 #include "number_patternstring.h"
 #include "number_modifiers.h"
+#include "number_multiplier.h"
 
 U_NAMESPACE_BEGIN namespace number {
 namespace impl {
@@ -73,6 +74,7 @@ struct MicroProps : public MicroPropsGenerator {
         ScientificModifier scientificModifier;
         EmptyModifier emptyWeakModifier{false};
         EmptyModifier emptyStrongModifier{true};
+        MultiplierChain multiplier;
     } helpers;