-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
240 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@open-pioneer/runtime": minor | ||
--- | ||
|
||
Introduce `NumberParserService` for parsing strings to numbers in the application's current locale. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
--- | ||
"@open-pioneer/core": minor | ||
--- | ||
|
||
Introduce the `NumberParser` class for parsing strings in a specified locale. | ||
|
||
```js | ||
import { NumberParser } from "@open-pioneer/core"; | ||
|
||
const parser = new NumberParser("de-DE"); | ||
const number = parser.parse("1.234,56"); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer) | ||
// SPDX-License-Identifier: Apache-2.0 | ||
import { it, expect } from "vitest"; | ||
import { NumberParser } from "./NumberParser"; | ||
|
||
it("correctly formats number in en-US", function () { | ||
const parser = new NumberParser("en-US"); | ||
const parsedNumber = parser.parse("12,345.6"); | ||
expect(parsedNumber).toBe(12345.6); | ||
}); | ||
|
||
it("correctly formats number in de-DE", function () { | ||
const parser = new NumberParser("de-DE"); | ||
const parsedNumber = parser.parse("12.345,6"); | ||
expect(parsedNumber).toBe(12345.6); | ||
}); | ||
|
||
it("correctly formats number in fr-FR", function () { | ||
const parser = new NumberParser("fr-FR"); | ||
const parsedNumber = parser.parse("12 345,6"); | ||
expect(parsedNumber).toBe(12345.6); | ||
}); | ||
|
||
it("returns NaN for invalid number", function () { | ||
const parser = new NumberParser("en-US"); | ||
const parsedNumber = parser.parse("abc"); | ||
expect(parsedNumber).toBeNaN(); | ||
}); | ||
|
||
it("returns NaN for empty string", function () { | ||
const parser = new NumberParser("en-US"); | ||
const parsedNumber = parser.parse(""); | ||
expect(parsedNumber).toBeNaN(); | ||
}); | ||
|
||
it("returns NaN for whitespace in languages that do not use whitespace as a separator", function () { | ||
const parser = new NumberParser("en-US"); | ||
const parsedNumber = parser.parse("12 345.6"); | ||
expect(parsedNumber).toBeNaN(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer) | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
/** | ||
* This class allows to parse numbers from strings according to the given locale. | ||
* Currently, in JavaScript there is no built-in way to parse numbers according to the current locale. | ||
* Only arabic numerals are supported. | ||
*/ | ||
export class NumberParser { | ||
private readonly decimalRegexPattern: RegExp; | ||
private readonly groupingRegexPattern: RegExp; | ||
|
||
constructor(locales: Intl.LocalesArgument) { | ||
const numberFormatOptions: Intl.NumberFormatOptions = { | ||
useGrouping: true | ||
}; | ||
const formattedParts = new Intl.NumberFormat(locales, numberFormatOptions).formatToParts( | ||
12345.6 | ||
); | ||
|
||
const decimalSeparator = formattedParts.find((part) => part.type === "decimal")?.value; | ||
if (!decimalSeparator) { | ||
throw new Error("Could not determine decimal separator."); | ||
} | ||
|
||
const groupingSeparator = formattedParts.find((part) => part.type === "group")?.value; | ||
if (!groupingSeparator) { | ||
throw new Error("Could not determine grouping separator."); | ||
} | ||
|
||
this.decimalRegexPattern = new RegExp(`${this.escapeRegExp(decimalSeparator)}`, "g"); | ||
if (groupingSeparator.trim() === "") { | ||
// if a language uses a whitespace as grouping separator, remove all whitespaces (as numbers may not contain whitespaces) | ||
this.groupingRegexPattern = /\s/g; | ||
} else { | ||
this.groupingRegexPattern = new RegExp(`${this.escapeRegExp(groupingSeparator)}`, "g"); | ||
} | ||
} | ||
|
||
/** | ||
* Parses a number from a string considering the locale. | ||
*/ | ||
parse(numberString: string): number { | ||
numberString = numberString.trim(); | ||
|
||
const parsedNumberString = numberString | ||
.replace(this.groupingRegexPattern, "") | ||
.replace(this.decimalRegexPattern, "."); | ||
return !parsedNumberString ? NaN : +parsedNumberString; | ||
} | ||
|
||
// see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions#escaping | ||
private escapeRegExp(string: string) { | ||
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
57 changes: 57 additions & 0 deletions
57
src/packages/runtime/builtin-services/NumberParserServiceImpl.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer) | ||
// SPDX-License-Identifier: Apache-2.0 | ||
import { expect, it } from "vitest"; | ||
import { ServiceOptions } from "../Service"; | ||
import { createCustomElement } from "../CustomElement"; | ||
import { renderComponentShadowDOM } from "@open-pioneer/test-utils/web-components"; | ||
import { NumberParserServiceImpl } from "./NumberParserServiceImpl"; | ||
|
||
it("can use the NumberParserService to parse a string to a number", async function () { | ||
const parsedNumber = await parseNumberWithLocale("123.45", "en-US"); | ||
expect(parsedNumber).toBe(123.45); | ||
}); | ||
|
||
it("can use the NumberParserService to parse a string to a number if local is 'de'", async function () { | ||
const parsedNumber = await parseNumberWithLocale("123.123,2", "de"); | ||
expect(parsedNumber).toBe(123123.2); | ||
}); | ||
|
||
async function parseNumberWithLocale(numberString: string, locale: string) { | ||
let parsedNumber: number | undefined; | ||
|
||
class TestService { | ||
constructor(options: ServiceOptions<{ numberParser: NumberParserServiceImpl }>) { | ||
const numberParser = options.references.numberParser; | ||
parsedNumber = numberParser.parseNumber(numberString); | ||
} | ||
} | ||
|
||
const elem = createCustomElement({ | ||
appMetadata: { | ||
packages: { | ||
test: { | ||
name: "test", | ||
services: { | ||
testService: { | ||
name: "testService", | ||
clazz: TestService, | ||
references: { | ||
numberParser: { | ||
name: "runtime.NumberParserService" | ||
} | ||
}, | ||
provides: [{ name: "runtime.AutoStart" }] | ||
} | ||
} | ||
} | ||
}, | ||
locales: [locale] | ||
}, | ||
config: { | ||
locale: locale | ||
} | ||
}); | ||
|
||
await renderComponentShadowDOM(elem); | ||
return parsedNumber; | ||
} |
27 changes: 27 additions & 0 deletions
27
src/packages/runtime/builtin-services/NumberParserServiceImpl.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// SPDX-FileCopyrightText: 2023 Open Pioneer project (https://github.com/open-pioneer) | ||
// SPDX-License-Identifier: Apache-2.0 | ||
import { NumberParserService } from "../api"; | ||
import { ServiceOptions } from "../Service"; | ||
import { createLogger, NumberParser } from "@open-pioneer/core"; | ||
|
||
const LOG = createLogger("runtime:NumberParserService"); | ||
|
||
export class NumberParserServiceImpl implements NumberParserService { | ||
private numberParser: NumberParser; | ||
|
||
constructor(serviceOptions: ServiceOptions, locale: string) { | ||
try { | ||
this.numberParser = new NumberParser(locale); | ||
} catch (e) { | ||
LOG.warn( | ||
"Failed to create NumberParserImpl with locale. Retrying with default 'en-US' locale.", | ||
e | ||
); | ||
this.numberParser = new NumberParser("en-US"); | ||
} | ||
} | ||
|
||
parseNumber(numberString: string): number { | ||
return this.numberParser.parse(numberString); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters