Skip to content

Commit

Permalink
Add "number-format" expression for string representation of numbers..
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisLoer committed Dec 20, 2018
1 parent 2fe94c3 commit b43478c
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 0 deletions.
7 changes: 7 additions & 0 deletions docs/components/expression-metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,13 @@ const types = {
'...',
'input_n: string, options_n: { "font-scale": number, "text-font": array<string> }'
]
}],
'number-format': [{
type: 'string',
parameters: [
'input: number',
'options: { "locale": string, "currency": string, "min-fraction-digits": number, "max-fraction-digits": number }'
]
}]
};

Expand Down
2 changes: 2 additions & 0 deletions src/style-spec/expression/definitions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
GreaterThanOrEqual
} from './comparison';
import CollatorExpression from './collator';
import NumberFormat from './number_format';
import FormatExpression from './format';
import Length from './length';

Expand Down Expand Up @@ -66,6 +67,7 @@ const expressions: ExpressionRegistry = {
'literal': Literal,
'match': Match,
'number': Assertion,
'number-format': NumberFormat,
'object': Assertion,
'step': Step,
'string': Assertion,
Expand Down
142 changes: 142 additions & 0 deletions src/style-spec/expression/definitions/number_format.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// @flow

import { StringType, NumberType } from '../types';

import type { Expression } from '../expression';
import type EvaluationContext from '../evaluation_context';
import type ParsingContext from '../parsing_context';
import type { Type } from '../types';

declare var Intl: {
NumberFormat: Class<Intl$NumberFormat>
};

declare class Intl$NumberFormat {
constructor (
locales?: string | string[],
options?: NumberFormatOptions
): Intl$NumberFormat;

static (
locales?: string | string[],
options?: NumberFormatOptions
): Intl$NumberFormat;

format(a: number): string;

resolvedOptions(): any;
}

type NumberFormatOptions = {
style?: 'decimal' | 'currency' | 'percent';
currency?: null | string;
minimumFractionDigits?: null | string;
maximumFractionDigits?: null | string;
};

export default class NumberFormat implements Expression {
type: Type;
number: Expression;
locale: Expression | null; // BCP 47 language tag
currency: Expression | null; // ISO 4217 currency code, required if style=currency
minFractionDigits: Expression | null; // Default 0
maxFractionDigits: Expression | null; // Default 3

constructor(number: Expression,
locale: Expression | null,
currency: Expression | null,
minFractionDigits: Expression | null,
maxFractionDigits: Expression | null) {
this.type = StringType;
this.number = number;
this.locale = locale;
this.currency = currency;
this.minFractionDigits = minFractionDigits;
this.maxFractionDigits = maxFractionDigits;
}

static parse(args: Array<mixed>, context: ParsingContext): ?Expression {
if (args.length !== 3)
return context.error(`Expected two arguments.`);

const number = context.parse(args[1], 1, NumberType);
if (!number) return null;

const options = (args[2]: any);
if (typeof options !== "object" || Array.isArray(options))
return context.error(`NumberFormat options argument must be an object.`);

let locale = null;
if (options['locale']) {
locale = context.parse(options['locale'], 1, StringType);
if (!locale) return null;
}

let currency = null;
if (options['currency']) {
currency = context.parse(options['currency'], 1, StringType);
if (!currency) return null;
}

let minFractionDigits = null;
if (options['min-fraction-digits']) {
minFractionDigits = context.parse(options['min-fraction-digits'], 1, NumberType);
if (!minFractionDigits) return null;
}

let maxFractionDigits = null;
if (options['max-fraction-digits']) {
maxFractionDigits = context.parse(options['max-fraction-digits'], 1, NumberType);
if (!maxFractionDigits) return null;
}

return new NumberFormat(number, locale, currency, minFractionDigits, maxFractionDigits);
}

evaluate(ctx: EvaluationContext) {
return new Intl.NumberFormat(this.locale ? this.locale.evaluate(ctx) : [],
{
style: this.currency ? "currency" : "decimal",
currency: this.currency ? this.currency.evaluate(ctx) : undefined,
minimumFractionDigits: this.minFractionDigits ? this.minFractionDigits.evaluate(ctx) : undefined,
maximumFractionDigits: this.maxFractionDigits ? this.maxFractionDigits.evaluate(ctx) : undefined,
}).format(this.number.evaluate(ctx));
}

eachChild(fn: (Expression) => void) {
fn(this.number);
if (this.locale) {
fn(this.locale);
}
if (this.currency) {
fn(this.currency);
}
if (this.minFractionDigits) {
fn(this.minFractionDigits);
}
if (this.maxFractionDigits) {
fn(this.maxFractionDigits);
}
}

possibleOutputs() {
return [undefined];
}

serialize() {
const options = {};
if (this.locale) {
options['locale'] = this.locale.serialize();
}
if (this.currency) {
options['currency'] = this.currency.serialize();
}
if (this.minFractionDigits) {
options['min-fraction-digits'] = this.minFractionDigits.serialize();
}
if (this.maxFractionDigits) {
options['max-fraction-digits'] = this.maxFractionDigits.serialize();
}
return ["number-format", this.number.serialize(), options];
}
}
9 changes: 9 additions & 0 deletions src/style-spec/reference/v8.json
Original file line number Diff line number Diff line change
Expand Up @@ -2593,6 +2593,15 @@
}
}
},
"number-format": {
"doc": "Converts the input number into a string representation using the providing formatting rules. If set, the `locale` argument specifies the locale to use, as a BCP 47 language tag. If set, the `currency` argument specifies an ISO 4217 code to use for currency-style formatting. If set, the `min-fraction-digits` and `max-fraction-digits` arguments specify the minimum and maximum number of fractional digits to include.",
"group": "Types",
"sdk-support": {
"basic functionality": {
"js": "0.54.0"
}
}
},
"to-string": {
"doc": "Converts the input value to a string. If the input is `null`, the result is `\"\"`. If the input is a boolean, the result is `\"true\"` or `\"false\"`. If the input is a number, it is converted to a string as specified by the [\"NumberToString\" algorithm](https://tc39.github.io/ecma262/#sec-tostring-applied-to-the-number-type) of the ECMAScript Language Specification. If the input is a color, it is converted to a string of the form `\"rgba(r,g,b,a)\"`, where `r`, `g`, and `b` are numerals ranging from 0 to 255, and `a` ranges from 0 to 1. Otherwise, the input is converted to a string in the format specified by the [`JSON.stringify`](https://tc39.github.io/ecma262/#sec-json.stringify) function of the ECMAScript Language Specification.",
"group": "Types",
Expand Down
31 changes: 31 additions & 0 deletions test/integration/expression-tests/number-format/currency/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"expression": [
"number-format",
123456.789,
{
"locale": ["get", "locale"],
"currency": ["get", "currency"]
}
],
"inputs": [
[{}, {"properties": {"locale": "ja-JP", "currency": "JPY"}}],
[{}, {"properties": {"locale": "de-DE", "currency": "EUR"}}]
],
"expected": {
"compiled": {
"result": "success",
"isFeatureConstant": false,
"isZoomConstant": true,
"type": "string"
},
"outputs": ["JP¥ 123,457", "€ 123,456.79"],
"serialized": [
"number-format",
123456.789,
{
"locale": ["string", ["get", "locale"]],
"currency": ["string", ["get", "currency"]]
}
]
}
}
21 changes: 21 additions & 0 deletions test/integration/expression-tests/number-format/default/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

{
"expression": [
"number-format",
123456.789,
{}
],
"inputs": [
[{}, {}]
],
"expected": {
"compiled": {
"result": "success",
"isFeatureConstant": true,
"isZoomConstant": true,
"type": "string"
},
"outputs": ["123,456.789"],
"serialized": "123,456.789"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"expression": [
"number-format",
987654321.23456789,
{
"locale": ["get", "locale"],
"min-fraction-digits": ["get", "min"],
"max-fraction-digits": ["get", "max"]
}
],
"inputs": [
[{}, {"properties": {"locale": "en-US", "min": 15, "max": 20}}],
[{}, {"properties": {"locale": "en-US", "min": 2, "max": 4}}]
],
"expected": {
"compiled": {
"result": "success",
"isFeatureConstant": false,
"isZoomConstant": true,
"type": "string"
},
"outputs": ["987,654,321.234568000000000", "987,654,321.2346"],
"serialized": [
"number-format",
987654321.2345679,
{
"locale": ["string", ["get", "locale"]],
"min-fraction-digits": ["number", ["get", "min"]],
"max-fraction-digits": ["number", ["get", "max"]]
}
]
}
}

0 comments on commit b43478c

Please sign in to comment.