Packages
localize
0.45.0
1.0.0-rc.4
1.0.0-rc.3
1.0.0-rc.2
1.0.0-rc.1
1.0.0-rc.0
0.50.0
0.49.0
0.48.0
0.47.0
0.46.0
0.45.0
0.44.0
0.41.3
0.41.2
0.41.1
0.41.0
0.40.0
0.39.0
0.38.0
0.37.0
0.36.0
0.35.0
0.34.0
0.33.0
0.32.0
0.31.0
0.30.1
0.30.0
retired
0.29.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.0
0.23.0
0.22.0
0.21.0
0.20.0
0.19.0
0.18.0
0.16.0
0.15.0
0.14.0
0.13.0
0.12.0
0.11.0
0.10.0
0.9.0
0.8.0
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
0.1.0-alpha.1
Localization (parsing, formatting) of numbers, dates/time/calendar, units of measure, messages and lists. Includes localized collation.
Current section
Files
Jump to
Current section
Files
lib/localize/collation/sort_key.ex
defmodule Localize.Collation.SortKey do
# Constructs binary sort keys from processed collation elements.
#
# Sort keys are multi-level byte sequences that can be compared with
# binary comparison (`<`, `>`, `==`) to determine string ordering.
#
# Structure: [L1 weights] 0000 [L2 weights] 0000 [L3 weights] [0000 L4 weights]
#
@moduledoc false
import Bitwise
alias Localize.Collation.{Element, Options}
@level_separator <<0x00, 0x00>>
@doc """
Build a binary sort key from processed collation elements.
### Arguments
* `processed_elements` - a list of `{element, quaternary}` tuples as returned
by `Localize.Collation.Variable.process/3`.
* `options` - a `%Localize.Collation.Options{}` struct controlling which levels
to include.
* `original_string` - the original input string, used for the identical level
(default: `nil`).
### Returns
A binary sort key where levels are separated by `<<0x00, 0x00>>`.
### Examples
iex> elements = [{{0x23EC, 0x0020, 0x0008, false}, 0}]
iex> options = Localize.Collation.Options.new(strength: :primary)
iex> Localize.Collation.SortKey.build(elements, options)
<<0x23, 0xEC>>
"""
@spec build([{Element.t(), non_neg_integer()}], Options.t(), String.t() | nil) :: binary()
def build(processed_elements, %Options{} = options, original_string \\ nil) do
key = build_primary(processed_elements)
key =
if options.strength in [:secondary, :tertiary, :quaternary, :identical] do
key <> @level_separator <> build_secondary(processed_elements, options)
else
key
end
key =
if options.strength in [:tertiary, :quaternary, :identical] do
key =
if options.case_level do
key <> @level_separator <> build_case_level(processed_elements)
else
key
end
key <> @level_separator <> build_tertiary(processed_elements, options)
else
if options.case_level do
key <> @level_separator <> build_case_level(processed_elements)
else
key
end
end
key =
if options.strength in [:quaternary, :identical] and options.alternate == :shifted do
key <> @level_separator <> build_quaternary(processed_elements)
else
key
end
key =
if options.strength == :identical do
nfd =
if original_string, do: :unicode.characters_to_nfd_binary(original_string), else: <<>>
key <> @level_separator <> nfd
else
key
end
key
end
defp build_primary(elements) do
elements
|> Enum.reduce(<<>>, fn {elem, _q}, acc ->
p = Element.primary(elem)
if p > 0 do
acc <> <<p::16>>
else
acc
end
end)
end
defp build_secondary(elements, options) do
weights =
elements
|> Enum.reduce([], fn {elem, _q}, acc ->
s = Element.secondary(elem)
if s > 0 do
[s | acc]
else
acc
end
end)
|> Enum.reverse()
weights =
if options.backwards do
Enum.reverse(weights)
else
weights
end
weights
|> Enum.reduce(<<>>, fn w, acc -> acc <> <<w::16>> end)
end
defp build_tertiary(elements, options) do
elements
|> Enum.reduce(<<>>, fn {elem, _q}, acc ->
t = apply_case_first(Element.tertiary(elem), options.case_first)
if t > 0 do
acc <> <<t::16>>
else
acc
end
end)
end
defp build_case_level(elements) do
elements
|> Enum.reduce(<<>>, fn {elem, _q}, acc ->
if Element.primary(elem) > 0 do
acc <> <<case_bit(elem)::16>>
else
acc
end
end)
end
defp case_bit(elem) do
if (Element.tertiary(elem) &&& 0x08) != 0, do: 1, else: 0
end
defp build_quaternary(elements) do
elements
|> Enum.reduce(<<>>, fn {_elem, q}, acc ->
if q > 0 do
acc <> <<q::16>>
else
acc
end
end)
end
defp apply_case_first(tertiary, false), do: tertiary
defp apply_case_first(tertiary, :upper) do
case_bit = tertiary &&& 0x08
if case_bit != 0 do
tertiary &&& Bitwise.bnot(0x08)
else
tertiary ||| 0x08
end
end
defp apply_case_first(tertiary, :lower) do
tertiary
end
end