# `Localize.Number.Parser`
[🔗](https://github.com/elixir-localize/localize/blob/v1.5.0/lib/localize/number/parser.ex#L1)

Functions for parsing numbers and currencies from strings
in a locale-aware manner.

The parser handles locale-specific digit transliteration,
grouping separators, and decimal separators to convert
localized number strings back into Elixir numeric values.

Parsing reads with a locale's own data or not at all. When the
locale is not installed and `:allow_runtime_locale_download` is
`false`, formatting falls back to a parent locale's data or to
English, but every function here returns
`{:error, %Localize.LocaleNotFoundInCacheError{}}`, since with
English data `en-AU` "$5" would be US dollars. Install the locale
with `mix localize.download_locales` or allow it to be downloaded.

# `per`

```elixir
@type per() :: :percent | :permille
```

# `find_and_replace`

```elixir
@spec find_and_replace(map(), String.t(), float() | nil) ::
  {:ok, list()} | {:error, Exception.t()}
```

Finds and replaces substrings from a map at the beginning
and/or end of a string.

### Arguments

* `string_map` is a map of `%{search_string => replacement}`.

* `string` is the string to search.

* `fuzzy` is an optional float for fuzzy matching. Of equally
  close strings, the alphabetically first is taken.

### Returns

* `{:ok, list}` with replacements applied.

* `{:error, exception}` if no match found.

### Examples

    iex> Localize.Number.Parser.find_and_replace(%{"usd" => :USD}, "USD 100")
    {:ok, [:USD, " 100"]}

    iex> Localize.Number.Parser.find_and_replace(%{"%" => :percent}, "11%")
    {:ok, ["11", :percent]}

# `parse`

```elixir
@spec parse(String.t(), Keyword.t()) ::
  {:ok, integer() | float() | Decimal.t()} | {:error, Exception.t()}
```

Parses a string in a locale-aware manner and returns a number.

### Arguments

* `string` is any string.
  An underscore between two digits is Elixir's digit separator and
  is ignored, as in `"1_000_000.34"`; anywhere else it makes the text
  no number.

* `options` is a keyword list of options.

### Options

* `:number` is one of `:integer`, `:float`, `:decimal`, or
  `nil`. The default is `nil` (auto-detect).

* `:locale` is a locale identifier. The default is `:en`.

* `:number_system` is a number system name or type.

* `:lenient` governs how strictly grouping separators must be
  positioned. `true`, the default, requires each group to be at
  least two digits, which is what keeps `"3 4 5"` three numbers
  rather than one. `false` requires each
  group to be exactly the locale's grouping size, so `fr` takes
  `"1 234 567"` and refuses `"1 23"`.

* `:grouping` set to `:sizes` checks each group against the sizes the
  locale writes rather than as `:lenient` says: the last group of the
  locale's grouping size, the others of that size or its secondary one,
  so `de` refuses `"1.23"`, which a lenient reading takes as 123, and
  `en-IN` takes both `"12,34,567"` and `"1,234,567"`. Suited to amounts of
  money, where a misread group is a wrong amount. The default is `nil`.

* `:currency` is a currency code or a `t:Localize.Currency.t/0`. When set,
  the string is read as an amount of that currency, with the separators
  `Localize.Number.to_string/2` formats it with. Two locales separate a
  currency amount differently from a plain number, `de-AT`, which formats
  `€ 1.234,56` but writes `1 234,56` plain, and `fr-CH`, which writes a
  franc amount `1'234.56` but a plain number `1'234,56`. There the string
  is read with both, and refused should both readings be valid and give
  different numbers. `de-AT` groups the euro with a period, so `"1.234"`
  is 1234 euros. The default is `nil`, a plain number.

* `:root_separators` set to `true` also reads the string with the root
  locale's separators, the locale-neutral `1,234.56`, for an amount of
  something CLDR has no data for, such as a digital token, which is
  commonly written that way. A string both read, as different numbers, is
  refused: `de` `"1.234"` is 1234 or 1.234. The default is `false`.

* `:separators` names the set of decimal and grouping separators to read
  with, for a locale that has more than one. `en-ZA` writes `1 234,56` with
  its `:standard` set and `1,234.56` with its `:us` set. A locale without
  the named set reads with its standard one. The default is `:standard`.

### Returns

* `{:ok, number}` on success, or

* `{:error, %Localize.LocaleNotFoundInCacheError{}}` when the locale's
  data is not installed (see the module documentation), or

* `{:error, exception}` if parsing fails.

### Examples

    iex> Localize.Number.Parser.parse("-1_000_000.34")
    {:ok, -1000000.34}

# `parse_currency_amount`

```elixir
@spec parse_currency_amount(String.t(), Keyword.t()) ::
  {:ok, {Localize.Currency.currency_code() | term(), Decimal.t()}}
  | {:error, Exception.t()}
```

Parses a string that holds one currency amount into its currency and
its amount.

The currency, a code, symbol or name, may come before or after the
amount, with or without a space between them. A sign may stand
before the currency, between the currency and the amount, or after
the amount, or the parentheses of an accounting format may hold it,
but not both. The amount is read with the separators the locale uses
for that currency, so `fr-CH` "1’234.56 CHF" reads as it is written,
and in `de-AT` and `fr-CH`, which write plain numbers otherwise, with
those too. Its groups must be the sizes the locale writes
(`grouping: :sizes` of `parse/2`), and a period that is none of its
separators is read only where it can be nothing but a decimal point.
Where the text could be two amounts it is
refused rather than one guessed: `de` "1.23 EUR" is not €123, `fr`
"1.234 €" is not €1.234, "($-5)" is neither -5 nor 5 dollars,
"-$.50" is refused since Swiss "Fr. –.15" writes a dash for the zero,
and `fr` "$2.00" is refused since several dollars share "$" there,
where `resolve_currency/2` takes the Canadian one. A narrow symbol of
another currency than the locale's own is read only when it is a
currency sign that names that currency alone, in the locale and in
CLDR's root data, so `en-AU` "€5" is the euro and `de-CH` "€ 15.40"
too, though `de-CH` writes the euro "EUR", but Swiss "15 Rp." (15
Rappen) is not the rupiah, whose narrow symbol `de-CH` gives as "Rp",
and `bg` "₩5" is refused, since root gives "₩" to both wons.
A historic currency is read only by its code or its name, since its
symbols are reused: `ru` "6 300 Р" is not the ruble of 1991–1998. One
to three digits, a decimal mark and three more are refused in a
currency of fewer than three decimal places, since they may be a
thousands group: `de` "1,599 €" and `en` "$59.568". The currency text,
but not the number, is matched after NFKC normalization, so `ja`
"¥1.00" is the yen, which `ja` writes "￥", while "$5²" is refused.
Bidi controls are ignored. Nothing else may surround the amount.

ex_money's `Money.parse/2` is built on this function, and its
[parsing guide](https://hexdocs.pm/ex_money/parsing.html) describes
what is read and what is refused, with examples.

### Arguments

* `string` is the string to parse.

* `options` is a keyword list of options.

### Options

* `:locale` is a locale identifier. The default is
  `Localize.get_locale/0`.

* `:number_system` and `:separators` are as for `parse/2`.

* `:only`, `:except` and `:fuzzy` are as for `resolve_currency/2`,
  except that a fuzzy match is refused where another currency's
  string is as close: `fr` "100 dollars" is neither the Fiji nor the
  Rhodesian dollar.

* `:default_currency` is the currency of an amount that names none:
  a currency code, or a name or symbol, resolved as currency text
  in the string would be. Without it, such an amount is an error.

* `:caller_currency` is a function of one argument, called with the
  currency text, for currencies of the caller's own such as digital
  tokens. It returns `{:ok, currency}` when the text names one, or
  anything else. Text that names no currency of the locale is read as
  the caller's currency; text that names one of the locale's and a
  different one of the caller's is ambiguous and refused, as `₮` is the
  tugrik and a digital token's symbol, except where it names the
  locale's own currency: `mn` "₮5" is the tugrik. Pass it only when
  the caller has such currencies, since it is called for every amount.
  Their amounts are read with the locale's separators and the root
  locale's (`root_separators: true` of `parse/2`), and refused where
  the two differ. A caller's currency text may have digits in it, as a
  digital token's identifier "Q9PB7XDNF" does. It is then the first
  word of the amount or the last, which a space separates from the
  number, and it is read only where the amount has no other reading:
  `fr` "USD1 234" is also 1 234 US dollars, so it is neither.

### Returns

* `{:ok, {currency, amount}}` where `currency` is a currency code, or
  what `:caller_currency` returned, and `amount` is a `t:Decimal.t/0`,
  or

* `{:error, %Localize.UnknownCurrencyError{currency: text}}` when the
  currency text names no currency, or

* `{:error, %Localize.ParseError{reason: :missing_currency}}` when the
  string names no currency and there is no `:default_currency`, or

* `{:error, %Localize.InvalidAmountError{currency: currency, amount: text}}`
  when the string names a currency but has no number, or one that
  cannot be read with the locale's separators for it, or

* `{:error, %Localize.ParseError{reason: :unexpected_input}}` when the
  string is not one currency amount, or

* `{:error, %Localize.LocaleNotFoundInCacheError{}}` when the locale's
  data is not installed (see the module documentation), or

* `{:error, exception}` for an invalid locale or option.

### Examples

    iex> Localize.Number.Parser.parse_currency_amount("USD 100", locale: :en)
    {:ok, {:USD, Decimal.new("100")}}

    iex> Localize.Number.Parser.parse_currency_amount("(1 234,56 €)", locale: :fr)
    {:ok, {:EUR, Decimal.new("-1234.56")}}

    iex> Localize.Number.Parser.parse_currency_amount("kr-127,54", locale: :da)
    {:ok, {:DKK, Decimal.new("-127.54")}}

    iex> token = fn "Q9PB7XDNF" -> {:ok, "Q9PB7XDNF"}; _other -> :error end
    iex> Localize.Number.Parser.parse_currency_amount("Q9PB7XDNF 5", locale: :en, caller_currency: token)
    {:ok, {"Q9PB7XDNF", Decimal.new("5")}}

# `resolve`

```elixir
@spec resolve(list(), (String.t(), Keyword.t() -&gt; term()), Keyword.t()) ::
  list() | {:error, Exception.t()}
```

Maps a list applying a resolver function to each binary element.

### Arguments

* `list` is a list of terms.

* `resolver` is a function that takes a string and options.

* `options` is a keyword list passed to the resolver.

### Options

* The options are passed through unchanged to `resolver` for each
  binary element. See the resolver function (for example
  `resolve_currency/2` or `resolve_per/2`) for the options it
  supports.

### Returns

* The list with binary elements resolved.

### Examples

    iex> resolver = &Localize.Number.Parser.resolve_currency/2
    iex> Localize.Number.Parser.resolve(["100", "USD"], resolver, locale: :en)
    ["100", :USD]

# `resolve_currencies`

```elixir
@spec resolve_currencies([String.t() | number()], Keyword.t()) ::
  [atom() | String.t() | number()] | {:error, Exception.t()}
```

Resolves currencies from strings within a list.

### Arguments

* `list` is a list of strings and numbers.

* `options` is a keyword list of options.

### Options

* `:locale` is a locale identifier. The default is `:en`.

* `:only` is a filter for currencies to include.

* `:except` is a filter for currencies to exclude.

* `:fuzzy` is a float for fuzzy matching via `String.jaro_distance/2`.
  Of equally close currency strings, the alphabetically first is taken.

### Returns

* A list with currency strings replaced by currency code atoms.

### Examples

    iex> Localize.Number.Parser.scan("100 US dollars")
    ...> |> Localize.Number.Parser.resolve_currencies()
    [100, :USD]

# `resolve_currency`

```elixir
@spec resolve_currency(String.t(), Keyword.t()) ::
  [atom() | String.t()] | {:error, Exception.t()}
```

Resolves a currency from the beginning and/or end of a string.

A string several of the locale's currencies share names the currency
of the territory whose CLDR locale is nearest the locale, when that
locale is a good fit and no other claimant's is as near: "$" is the
Canadian dollar in `fr`, as in `fr-CA`. Otherwise it stays unknown.

### Arguments

* `string` is a string potentially containing a currency name or symbol.

* `options` is a keyword list of options.

### Options

* `:locale` is a locale identifier. The default is `:en`.

* `:only` is a filter for currencies to include.

* `:except` is a filter for currencies to exclude.

* `:fuzzy` is a float for fuzzy matching. Of equally close
  currency strings, the alphabetically first is taken.

### Returns

* A list with the currency code and remainder, or
  `{:error, exception}`.

### Examples

    iex> Localize.Number.Parser.resolve_currency("US dollars")
    [:USD]

    iex> Localize.Number.Parser.resolve_currency("$100")
    [:USD, "100"]

# `resolve_per`

```elixir
@spec resolve_per(String.t(), Keyword.t()) ::
  [per() | String.t()] | {:error, Exception.t()}
```

Resolves percent or permille from a string.

### Arguments

* `string` is a string potentially containing percent
  or permille symbols.

* `options` is a keyword list of options.

### Options

* `:locale` is a locale identifier atom, string, or a
  `t:Localize.LanguageTag.t/0`. The default is
  `Localize.get_locale()`.

* `:number_system` is a number system name or type atom.
  The default is the number system of `:locale`.

### Returns

* A list with the symbol replaced by `:percent` or `:permille`.

* `{:error, exception}` if no symbol found.

### Examples

    iex> Localize.Number.Parser.resolve_per("11%")
    ["11", :percent]

# `resolve_pers`

```elixir
@spec resolve_pers([String.t() | number()], Keyword.t()) ::
  [per() | String.t() | number()] | {:error, Exception.t()}
```

Resolves percent and permille symbols from strings within a list.

### Arguments

* `list` is a list of strings and numbers.

* `options` is a keyword list of options.

### Options

* `:locale` is a locale identifier atom, string, or a
  `t:Localize.LanguageTag.t/0`. The default is
  `Localize.get_locale()`.

* `:number_system` is a number system name or type atom.
  The default is the number system of `:locale`.

### Returns

* A list with percent/permille strings replaced by `:percent`
  or `:permille` atoms.

### Examples

    iex> Localize.Number.Parser.scan("100%")
    ...> |> Localize.Number.Parser.resolve_pers()
    [100, :percent]

# `scan`

```elixir
@spec scan(String.t(), Keyword.t()) ::
  [String.t() | integer() | float() | Decimal.t()] | {:error, Exception.t()}
```

Scans a string in a locale-aware manner and returns a list
of strings and numbers.

In a locale that groups with a space, a space between digits is
read as a grouping separator, which is what lets `"1 234,5"` be
found as one number under `fr`. The consequence is that adjacent
numbers separated by a space can be read as a single number when
each run is two digits or more — a list of room numbers or the
parts of a phone number among them. Pass `lenient: false` to
require exact grouping sizes, which reads those as separate
numbers again:

    iex> Localize.Number.Parser.scan("chambres 12 14 16", locale: "fr")
    ["chambres ", 121416]

    iex> Localize.Number.Parser.scan("chambres 12 14 16", locale: "fr", lenient: false)
    ["chambres ", 12, " ", 14, " ", 16]

A number in the text has the shape `Localize.Number.Scanner` reads:
a sign before the digits and touching them, the locale's group and
decimal separators, an exponent, and leniently a zero fraction
written as a dash, `"5.–"`. Each such run is read by `parse/2` with
these options and is a number where that succeeds; otherwise it is
text, as written. An underscore between two digits is Elixir's
digit separator, so `"1_000"` is 1000, and anywhere else it makes
the run text, so `"5_"` stays `"5_"`.

### Arguments

* `string` is any string.

* `options` is a keyword list of options.

### Options

* `:number` is one of `:integer`, `:float`, `:decimal`, or
  `nil`. The default is `nil` (auto-detect).

* `:locale` is a locale identifier. The default is `:en`.

* `:number_system` is a number system name or type.

* `:lenient` governs how strictly grouping separators must be
  positioned. `true`, the default, requires each group to be at
  least two digits, which is what keeps `"3 4 5"` three numbers
  rather than one. `false` requires each
  group to be exactly the locale's grouping size, so `fr` takes
  `"1 234 567"` and refuses `"1 23"`.

* `:grouping` set to `:sizes` checks each group against the sizes the
  locale writes rather than as `:lenient` says: the last group of the
  locale's grouping size, the others of that size or its secondary one,
  so `de` refuses `"1.23"`, which a lenient reading takes as 123, and
  `en-IN` takes both `"12,34,567"` and `"1,234,567"`. Suited to amounts of
  money, where a misread group is a wrong amount. The default is `nil`.

* `:currency` is a currency code or a `t:Localize.Currency.t/0`. When set,
  the string is read as an amount of that currency, with the separators
  `Localize.Number.to_string/2` formats it with. Two locales separate a
  currency amount differently from a plain number, `de-AT`, which formats
  `€ 1.234,56` but writes `1 234,56` plain, and `fr-CH`, which writes a
  franc amount `1'234.56` but a plain number `1'234,56`. There the string
  is read with both, and refused should both readings be valid and give
  different numbers. `de-AT` groups the euro with a period, so `"1.234"`
  is 1234 euros. The default is `nil`, a plain number.

* `:root_separators` set to `true` also reads the string with the root
  locale's separators, the locale-neutral `1,234.56`, for an amount of
  something CLDR has no data for, such as a digital token, which is
  commonly written that way. A string both read, as different numbers, is
  refused: `de` `"1.234"` is 1234 or 1.234. The default is `false`.

* `:separators` names the set of decimal and grouping separators to read
  with, for a locale that has more than one. `en-ZA` writes `1 234,56` with
  its `:standard` set and `1,234.56` with its `:us` set. A locale without
  the named set reads with its standard one. The default is `:standard`.

### Returns

* A list of strings and numbers.

### Examples

    iex> Localize.Number.Parser.scan("The prize is 23")
    ["The prize is ", 23]

    iex> Localize.Number.Parser.scan("1kg")
    [1, "kg"]

---

*Consult [api-reference.md](api-reference.md) for complete listing*
