• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

evolvedbinary / elemental / 982

29 Apr 2025 08:34PM UTC coverage: 56.409% (+0.007%) from 56.402%
982

push

circleci

adamretter
[feature] Improve README.md badges

28451 of 55847 branches covered (50.94%)

Branch coverage included in aggregate %.

77468 of 131924 relevant lines covered (58.72%)

0.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.62
/exist-core/src/main/java/org/exist/xquery/functions/integer/WordPicture.java
1
/*
2
 * Elemental
3
 * Copyright (C) 2024, Evolved Binary Ltd
4
 *
5
 * admin@evolvedbinary.com
6
 * https://www.evolvedbinary.com | https://www.elemental.xyz
7
 *
8
 * Use of this software is governed by the Business Source License 1.1
9
 * included in the LICENSE file and at www.mariadb.com/bsl11.
10
 *
11
 * Change Date: 2028-04-27
12
 *
13
 * On the date above, in accordance with the Business Source License, use
14
 * of this software will be governed by the Apache License, Version 2.0.
15
 *
16
 * Additional Use Grant: Production use of the Licensed Work for a permitted
17
 * purpose. A Permitted Purpose is any purpose other than a Competing Use.
18
 * A Competing Use means making the Software available to others in a commercial
19
 * product or service that: substitutes for the Software; substitutes for any
20
 * other product or service we offer using the Software that exists as of the
21
 * date we make the Software available; or offers the same or substantially
22
 * similar functionality as the Software.
23
 */
24
package org.exist.xquery.functions.integer;
25

26
import com.ibm.icu.text.MessageFormat;
27
import com.ibm.icu.text.RuleBasedNumberFormat;
28
import org.exist.xquery.XPathException;
29

30
import java.math.BigInteger;
31
import java.util.*;
32

33
import static org.exist.util.StringUtil.capitalize;
34

35
/**
36
 * Format numbers according to rules 6/7/8 (formatting as words)
37
 * <a href="https://www.w3.org/TR/xpath-functions-31/#formatting-integers">format-integer</a>
38
 * <p>
39
 * Uses <a href="https://unicode-org.github.io/icu/userguide/icu4j/">ICU4J</a> to format the numbers
40
 * <p>
41
 * Ask ICU4J which "spellouts" are available for a language, guided by the format modifier
42
 * Use the closest spellout available.
43
 */
44
public class WordPicture extends IntegerPicture {
45

46
    static final String SPELLOUT_PREFIX_CARDINAL = "%spellout-cardinal";
47
    static final String SPELLOUT_PREFIX_ORDINAL = "%spellout-ordinal";
48
    static final List<String> SPELLOUT_EXTENSIONS = Arrays.asList("-feminine", "-masculine", "-neuter", "-native", "-common");
1✔
49

50
    /**
51
     * Common variations for formatters used by other implementations of format-integer.
52
     * <p>
53
     * Formatting German text, a user might ask for format modifier : "o(-er)"
54
     * This means they want ordinal text, using a formatter with -er endings.
55
     * There is a formatter
56
     */
57
    static final Map<String, Map<String, String>> shorthands = new HashMap<>();
1✔
58

59
    static {
60
        final Map<String, String> de = new HashMap<>();
1✔
61
        de.put("er", "r");
1✔
62
        de.put("es", "s");
1✔
63
        de.put("en", "n");
1✔
64
        WordPicture.shorthands.put("de", de);
1✔
65
        final Map<String, String> it = new HashMap<>();
1✔
66
        it.put("o", "masculine");
1✔
67
        it.put("a", "feminine");
1✔
68
        WordPicture.shorthands.put("it", it);
1✔
69
    }
1✔
70

71
    final CaseAndCaps capitalization;
72
    final FormatModifier formatModifier;
73

74
    WordPicture(final CaseAndCaps capitalization, final FormatModifier formatModifier) {
1✔
75
        this.capitalization = capitalization;
1✔
76
        this.formatModifier = formatModifier;
1✔
77
    }
1✔
78

79
    /**
80
     * Search for all the spellouts satisfying a locale and a prefix
81
     *
82
     * @param locale     search for spellouts in this locale
83
     * @param rulePrefix search for spellouts starting with this prefix
84
     * @return the set of spellouts found
85
     */
86
    static Set<String> getPrefixedSpelloutRules(final Locale locale, final String rulePrefix) {
87
        final RuleBasedNumberFormat ruleBasedNumberFormat = new RuleBasedNumberFormat(locale, RuleBasedNumberFormat.SPELLOUT);
1✔
88

89
        final Set<String> spelloutRuleSet = new HashSet<>();
1✔
90
        for (final String ruleSetName : ruleBasedNumberFormat.getRuleSetNames()) {
1✔
91
            if (ruleSetName.startsWith(rulePrefix)) {
1✔
92
                spelloutRuleSet.add(ruleSetName);
1✔
93
            }
94
        }
95

96
        return spelloutRuleSet;
1✔
97
    }
98

99
    /**
100
     * Pick the best match spellout for a language and format modifier
101
     *
102
     * @param locale         to pick a spellout for
103
     * @param formatModifier what sort of spellout ? ordinal or cardinal ? May contain name or suffix of the spellout required
104
     * @return our best guess at an appropriate spellout
105
     */
106
    static String getBestSpellout(final Locale locale, final FormatModifier formatModifier) {
107

108
        String spelloutPrefix = null;
1✔
109
        if (formatModifier.numbering == FormatModifier.Numbering.CARDINAL) spelloutPrefix = WordPicture.SPELLOUT_PREFIX_CARDINAL;
1✔
110
        if (formatModifier.numbering == FormatModifier.Numbering.ORDINAL) spelloutPrefix = WordPicture.SPELLOUT_PREFIX_ORDINAL;
1✔
111

112
        String spellout = WordPicture.getSpellout(locale, formatModifier, spelloutPrefix);
1✔
113
        if (spellout == null && formatModifier.numbering == FormatModifier.Numbering.ORDINAL) {
1!
114
            // Back off to cardinal if we can't get an ordinal spellout
115
            spellout = WordPicture.getSpellout(locale, formatModifier, WordPicture.SPELLOUT_PREFIX_CARDINAL);
1✔
116
        }
117
        return spellout;
1✔
118
    }
119

120
    static String mapVariationShorthand(final String language, final String variation) {
121
        if (variation == null) {
1✔
122
            return null;
1✔
123
        }
124
        final String trimmedVariation = variation.replace("-", "");
1✔
125
        final Map<String, String> languageMap = WordPicture.shorthands.get(language);
1✔
126
        if (languageMap == null) {
1✔
127
            //nothing to map
128
            return variation;
1✔
129
        }
130
        final String mappedVariation = languageMap.get(trimmedVariation);
1✔
131
        if (mappedVariation == null) {
1✔
132
            //not a known shorthand
133
            return variation;
1✔
134
        }
135
        if (variation.startsWith("-")) {
1!
136
            return "-" + mappedVariation;
1✔
137
        } else {
138
            return mappedVariation;
×
139
        }
140
    }
141

142
    /**
143
     * Pick the best match spellout for a language
144
     *
145
     * @param locale           to pick a spellout for
146
     * @param formatModifier   ordinal or cardinal ? Any hints at the spellout required ?
147
     * @param prefixAndDefault there is always a spellout with no suffix which can be used to fall back
148
     * @return our best guess at an appropriate spellout
149
     */
150
    static String getSpellout(final Locale locale, final FormatModifier formatModifier, final String prefixAndDefault) {
151

152
        // All the possibilities
153
        final Set<String> spelloutRuleSet = WordPicture.getPrefixedSpelloutRules(locale, prefixAndDefault);
1✔
154

155
        // Special case a prefix we know about into the standard ICU4J ones
156
        final String variation = WordPicture.mapVariationShorthand(locale.getLanguage(), formatModifier.variation);
1✔
157

158
        // Match the variation with the possibilities
159
        if (variation != null) {
1✔
160
            final String variantSpelloutRule;
161
            if (variation.startsWith("-")) {
1✔
162
                variantSpelloutRule = prefixAndDefault + variation;
1✔
163
            } else {
1✔
164
                variantSpelloutRule = prefixAndDefault + "-" + variation;
1✔
165
            }
166
            if (spelloutRuleSet.contains(variantSpelloutRule)) {
1✔
167
                return variantSpelloutRule;
1✔
168
            } else if (spelloutRuleSet.contains(variation)) {
1✔
169
                return variation;
1✔
170
            }
171
        }
172

173
        // None of the variations matched
174
        if (spelloutRuleSet.contains(prefixAndDefault)) {
1✔
175
            return prefixAndDefault;
1✔
176
        }
177

178
        // The default was not available - try standard variations in order
179
        for (final String extension : WordPicture.SPELLOUT_EXTENSIONS) {
1✔
180
            if (spelloutRuleSet.contains(prefixAndDefault + extension)) {
1✔
181
                return prefixAndDefault + extension;
1✔
182
            }
183
        }
184
        return null;
1✔
185
    }
186

187
    enum CaseAndCaps {
1✔
188
        UPPER, LOWER, CAPITALIZED;
1✔
189

190
        /**
191
         * Format an integer and then capitalize it correctly
192
         *
193
         * @param value          to format
194
         * @param locale         to format in
195
         * @param formatModifier guidance on how to format
196
         * @return the formatted and converted integer as a string
197
         */
198
        String formatAndConvert(final int value, final Locale locale, final FormatModifier formatModifier) {
199

200
            final String spelloutRule = WordPicture.getBestSpellout(locale, formatModifier);
1✔
201

202
            final MessageFormat ruleBasedMessageFormatFormat = new MessageFormat("{0,spellout," + spelloutRule + "}", locale);
1✔
203
            final String formatted = ruleBasedMessageFormatFormat.format(new Object[]{value});
1✔
204

205
            String result = null;
1✔
206
            switch (this) {
1!
207
                case UPPER:
208
                    result = formatted.toUpperCase(locale);
1✔
209
                    break;
1✔
210
                case LOWER:
211
                    result = formatted;
1✔
212
                    break;
1✔
213
                case CAPITALIZED:
214
                    result = capitalize(formatted);
1✔
215
                    break;
216
            }
217
            return result;
1✔
218
        }
219
    }
220

221
    @Override
222
    public String formatInteger(final BigInteger bigInteger, final Locale locale) throws XPathException {
223
        //spec says out of range should be formatted by "1"
224
        if (bigInteger.compareTo(BigInteger.valueOf(Integer.MIN_VALUE)) < 0 || bigInteger.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) {
1!
225
            return IntegerPicture.defaultPictureWithModifier(new FormatModifier("")).formatInteger(bigInteger, locale);
×
226
        }
227

228
        final BigInteger absInteger = bigInteger.abs();
1✔
229
        String prefix = "";
1✔
230
        if (absInteger.compareTo(bigInteger) != 0) {
1!
231
            prefix = "-";
×
232
        }
233
        return prefix + capitalization.formatAndConvert(absInteger.intValue(), locale, formatModifier);
1✔
234
    }
235
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc