Packages
ex_cldr
0.0.6
2.47.5
2.47.4
2.47.3
2.47.2
2.47.1
2.47.0
2.46.0
2.45.2
2.45.1
2.45.0
2.44.1
2.44.0
2.43.2
2.43.1
2.43.0
2.42.0
2.41.1
2.41.0
2.40.4
2.40.3
2.40.2
2.40.1
2.40.0
2.39.2
2.39.1
2.39.0
2.38.1
2.38.0
2.37.5
2.37.4
2.37.3
retired
2.37.2
2.37.1
2.37.0
2.36.0
2.35.1
2.35.0
retired
2.34.2
2.34.1
2.34.0
2.33.2
2.33.1
2.33.1-rc.0
2.33.0
2.32.1
2.32.0
2.31.0
2.30.0
2.29.0
2.28.0
2.27.1
2.27.0
retired
2.26.3
2.26.2
2.26.1
2.26.0
2.25.0
2.24.2
2.24.1
2.24.0
2.24.0-rc.6
2.24.0-rc.5
2.24.0-rc.4
2.24.0-rc.3
2.24.0-rc.2
2.24.0-rc.1
2.24.0-rc.0
2.23.2
2.23.1
2.23.0
2.22.1
2.22.0
2.21.0
2.20.0
2.20.0-rc.3
2.20.0-rc.2
2.20.0-rc.1
2.20.0-rc.0
2.19.1
2.19.0
2.18.2
2.18.1
2.18.0
2.18.0-rc.0
2.17.2
2.17.1
2.17.0
2.16.2
2.16.1
2.16.1-rc.0
2.16.0
2.15.0
2.14.1
2.14.0
2.14.0-rc.0
2.13.0
2.12.1
2.12.0
2.11.1
2.11.0
2.10.2
2.10.1
2.10.0
retired
2.9.0
2.8.1
2.8.0
2.7.2
2.7.1
2.7.0
2.6.2
2.6.1
2.6.0
2.5.0
2.4.3
2.4.2
2.4.1
2.4.0
2.3.2
2.3.1
2.3.0
2.2.7
2.2.6
2.2.5
2.2.4
2.2.3
2.2.2
2.2.1
2.2.0
2.1.0
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
2.0.0-rc.3
retired
2.0.0-rc.2
retired
2.0.0-rc.1
retired
2.0.0-rc.0
retired
1.8.2
1.8.1
1.8.0
1.7.1
1.7.0
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.2
1.5.1
1.5.0
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
retired
1.4.0-rc.3
retired
1.4.0-rc.2
retired
1.4.0-rc.1
retired
1.4.0-rc.0
retired
1.3.2
1.3.1
1.3.0
1.2.0
1.1.0
1.0.0
1.0.0-rc.3
retired
1.0.0-rc.2
retired
1.0.0-rc.1
retired
1.0.0-rc.0
retired
0.13.1
0.13.0
0.12.2
retired
0.12.1
retired
0.12.0
retired
0.11.0
retired
0.10.0
retired
0.9.0
retired
0.8.3
retired
0.8.2
retired
0.8.1
retired
0.8.0
retired
0.7.0
retired
0.6.2
retired
0.6.1
retired
0.6.0
retired
0.5.2
retired
0.5.1
retired
0.5.0
retired
0.4.2
retired
0.4.1
retired
0.4.0
retired
0.3.0
retired
0.2.1
retired
0.2.0
retired
0.1.3
retired
0.1.2
retired
0.1.1
retired
0.1.0
retired
0.0.20
retired
0.0.19
retired
0.0.18
retired
0.0.17
retired
0.0.16
retired
0.0.15
retired
0.0.14
retired
0.0.13
retired
0.0.12
retired
0.0.11
retired
0.0.10
retired
0.0.8
retired
0.0.7
retired
0.0.6
retired
0.0.5
retired
0.0.4
retired
0.0.3
retired
Common Locale Data Repository (CLDR) functions for Elixir to localize and format numbers, dates, lists, messages, languages, territories and units with support for over 700 locales for internationalized (i18n) and localized (L10N) applications.
Retired package: Deprecated
Current section
Files
Jump to
Current section
Files
lib/cldr/number/rbnf.ex
defmodule Cldr.Rbnf do
@moduledoc """
Rules Base Number Formatting
During compilation we want to look up the configured locales
and generate the functions needed for only those locales.
For any other recognized locale we need a way to either fallback
to a known locale, or error exit (configurable)
"""
import Xml
alias Cldr.Rbnf.Rule
@default_radix 10
@data_dir "./downloads/common"
@rbnf_dir Path.join(@data_dir, "rbnf")
@spec rbnf_dir :: String.t
def rbnf_dir do
@rbnf_dir
end
if File.exists?(@rbnf_dir) do
@rbnf_locales Enum.map(File.ls!(@rbnf_dir), &Path.basename(&1, ".xml"))
|> Enum.map(&String.replace(&1, "_", "-"))
else
@rbnf_locales []
end
@doc """
Returns a list of the locales for which there is an rbnf rule set
Relies on the presence of downloaded CLDR data. This can be achieved
by runnuing `mix cldr.download`. This function is usefully primarily
to a Cldr library developer.
"""
@spec rbnf_locales :: [String.t] | []
def rbnf_locales do
@rbnf_locales
end
@doc """
Returns the list of locales that is the intersection of
`Cldr.known_locales/0` and `Cldr.Rbnf.rbnf_locales/0`
This list is therefore the set of known locales for which
there are rbnf rules defined.
"""
def known_locales do
MapSet.intersection(MapSet.new(Cldr.known_locales), MapSet.new(rbnf_locales()))
|> MapSet.to_list
end
@doc """
Returns the rbnf rules for a `locale` or `{:error, :rbnf_file_not_found}`
* `locale` is any locale returned by `Rbnf.known_locales/0`.
Note that `for_locale/1` does not raise if the locale does not exist
like the majority of `Cldr`. This is by design since the set of locales
that have rbnf rules is substantially less than the set of locales
supported by `Cldr`.
"""
@spec for_locale(Locale.t) :: %{} | {:error, :rbnf_file_not_found}
def for_locale(locale) do
if File.exists?(locale_path(locale)) do
xml = locale
|> locale_path
|> Xml.parse
xml
|> rule_groups
|> rule_sets_from_groups(xml)
|> rules_from_rule_sets(xml)
else
{:error, :rbnf_file_not_found}
end
end
@doc """
Returns a map that merges all rules by the primary dimension of
RuleGroup, within which rbnf rules are keyed by locale.
This function is primarily intended to support compile-time generation
of functions to process rbnf rules.
"""
@spec for_all_locales :: %{}
def for_all_locales do
Enum.map(known_locales(), fn locale ->
Enum.map(for_locale(locale), fn {group, sets} ->
locale = String.replace(locale, "_", "-")
{group, %{locale => sets}}
end)
|> Enum.into(%{})
end)
|> Cldr.Map.merge_map_list
end
# Returns all the rules in rbnf without any tagging for rulegroup or set.
# This is helpful for testing only.
@doc false
def all_rules do
rbnf_locales()
|> Enum.map(&for_locale/1)
|> Enum.flat_map(&Map.values/1) # Get sets from groups
|> Enum.flat_map(&Map.values/1) # Get rules from set
|> Enum.flat_map(&(&1.rules)) # Get rule definitions from rules
end
defp rule_sets_from_groups(groups, xml) do
Enum.reduce groups, %{}, fn group, acc ->
Map.put(acc, group, rule_sets(xml, group))
end
end
defp rules_from_rule_sets(rulesets, xml) do
Enum.map(rulesets, fn {group, sets} ->
{group, rules_from_one_group(group, sets, xml)}
end)
|> Enum.into(%{})
end
defp rules_from_one_group(group, sets, xml) do
Enum.reduce sets, %{}, fn [set, access], acc ->
Map.put acc, set, %{access: access, rules: rules(xml, group, set)}
end
end
# Rbnf is directly from XML and hence has "_" as a separator
# in a locale whereas we use "-" elsewhere
@spec locale_path(binary) :: String.t
defp locale_path(locale) when is_binary(locale) do
locale = String.replace(locale, "-", "_")
Path.join(rbnf_dir(), "/#{locale}.xml")
end
@spec rule_groups(Xml.xml_node, String.t) :: [String.t]
defp rule_groups(xml, path \\ "//rulesetGrouping") do
Enum.map(all(xml, path), fn(xml_node) -> attr(xml_node, "type") end)
end
@spec rule_sets(Xml.xml_node, binary) :: list([type: String.t, access: String.t])
defp rule_sets(xml, rulegroup) do
path = "//rulesetGrouping[@type='#{rulegroup}']/ruleset"
Enum.map(all(xml, path), fn(xml_node) ->
[attr(xml_node, "type"), attr(xml_node, "access") || "public"]
end)
end
@spec rules(Xml.xml_node, String.t, String.t) :: [%Rule{}]
defp rules(xml, rulegroup, ruleset) do
path = "//rulesetGrouping[@type='#{rulegroup}']/ruleset[@type='#{ruleset}']/rbnfrule"
xml
|> all(path)
|> Enum.map(fn(xml_node) ->
%Rule{base_value: to_integer(attr(xml_node, "value")),
radix: to_integer(attr(xml_node, "radix")) || @default_radix,
definition: remove_trailing_semicolon(text(xml_node))}
end)
|> set_range
|> set_divisor
end
defp to_integer(nil) do
nil
end
defp to_integer(value) do
with {int, ""} <- Integer.parse(value) do
int
else
_ -> value
end
end
defp remove_trailing_semicolon(text) do
String.replace_suffix(text, ";", "")
end
# If the current rule is numeric and the next rule is numeric then
# the next rules value determines the upper bound of the validity
# of the current rule.
#
# ie. "0": "one;"
# "10": "ten;"
#
# Means that rule "0" is applied for values up to but not including "10"
defp set_range([rule | [next_rule | rest]]) do
[%Rule{rule | range: range_from_next_rule(rule.base_value, next_rule.base_value)}] ++ set_range([next_rule] ++ rest)
end
defp set_range([rule | []]) do
[%Rule{rule | :range => :undefined}]
end
defp range_from_next_rule(rule, next_rule) when is_number(rule) and is_number(next_rule) do
next_rule
end
defp range_from_next_rule(_rule, _next_rule) do
:undefined
end
defp set_divisor([rule]) do
[%Rule{rule | divisor: divisor(rule.base_value, rule.radix)}]
end
defp set_divisor([rule | rest]) do
[%Rule{rule | divisor: divisor(rule.base_value, rule.radix)} | set_divisor(rest)]
end
# Thanks to twitter-cldr:
# https://github.com/twitter/twitter-cldr-rb/blob/master/lib/twitter_cldr/formatters/numbers/rbnf/rule.rb
defp divisor(base_value, radix) when is_integer(base_value) and is_integer(radix) do
exponent = if base_value > 0 do
Float.ceil(:math.log(base_value) / :math.log(radix)) |> trunc
else
1
end
divisor = if exponent > 0 do
:math.pow(radix, exponent) |> trunc
else
1
end
if divisor > base_value do
:math.pow(radix, exponent - 1) |> trunc
else
divisor
end
end
defp divisor(_base_value, _radix) do
nil
end
end