Skip to content

Commit

Permalink
Allow qualifiers on decades and centuries
Browse files Browse the repository at this point in the history
  • Loading branch information
mbklein committed Oct 10, 2024
1 parent 77dfde1 commit fed06f3
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 16 deletions.
11 changes: 6 additions & 5 deletions lib/edtf/date.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ defmodule EDTF.Date do

def assemble({_, nil}), do: nil

def assemble({:century, value}),
do: %__MODULE__{type: :century, values: [Keyword.get(value, :value)]}

def assemble({:decade, value}),
do: %__MODULE__{type: :decade, values: [Keyword.get(value, :value)]}
def assemble({type, value}) when type == :decade or type == :century,
do: %__MODULE__{
type: type,
values: [Keyword.get(value, :value)],
attributes: Keyword.get(value, :attributes)
}

def assemble({:year, value}) do
attributes = Keyword.get(value, :attributes, [])
Expand Down
6 changes: 5 additions & 1 deletion lib/edtf/parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ defmodule EDTF.Parser do
# Basic combinators
qualifier = ascii_char([??, ?~, ?%])
component_qualifier = lookahead_not(qualifier |> concat(eos())) |> concat(qualifier)
digit = ascii_char([?0..?9])
digit_or_x = ascii_char([?0..?9, ?X])
sign = ascii_char([?+, ?-])
year = times(digit_or_x, 4)
Expand Down Expand Up @@ -83,11 +84,14 @@ defmodule EDTF.Parser do
signed_integer = fn digits ->
optional(sign |> tag(:sign))
|> concat(
times(ascii_char([?0..?9]), digits)
times(digit, digits)
|> post_traverse({Helpers, :to_integer, []})
|> unwrap_and_tag(:value)
|> wrap()
)
|> concat(optional(qualifier) |> tag(:qualifier))
|> post_traverse({Helpers, :apply_sign, []})
|> post_traverse({Helpers, :apply_qualifier, []})
end

edtf_century = signed_integer.(2)
Expand Down
44 changes: 34 additions & 10 deletions lib/edtf/parser/helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ defmodule EDTF.Parser.Helpers do

import Bitwise

@qualifier_attributes %{
~c"~" => [:approximate],
~c"?" => [:uncertain],
~c"%" => [:approximate, :uncertain]
}

@doc """
Calculate the appropriate qualifier bitmasks for a given YYYY, MM, or DD. Bits
are calculated from the left and shifted left to account for the specific
Expand Down Expand Up @@ -69,11 +75,8 @@ defmodule EDTF.Parser.Helpers do
mask = ((1 <<< length(bitstring)) - 1) <<< shift

attributes =
case qualifier do
~c"~" -> [approximate: mask]
~c"?" -> [uncertain: mask]
~c"%" -> [approximate: mask, uncertain: mask]
end
Map.get(@qualifier_attributes, qualifier)
|> Enum.map(&{&1, mask})

[
value: IO.iodata_to_binary([sign | bitstring]) |> String.to_integer(),
Expand All @@ -94,6 +97,8 @@ defmodule EDTF.Parser.Helpers do
```
"""
def apply_sign(rest, value, context, _line, _offset) do
value = List.flatten(value)

result =
case Keyword.get(value, :sign) do
~c"-" -> 0 - Keyword.get(value, :value)
Expand All @@ -103,6 +108,28 @@ defmodule EDTF.Parser.Helpers do
{rest, value |> Keyword.delete(:sign) |> Keyword.put(:value, result), context}
end

@doc """
Apply a parsed qualifier to a single value.
Example:
```elixir
iex> apply_qualifier("", [value: 2000, qualifier: ~c"%"], %{}, nil, nil)
{"", [attributes: [approximate: true, uncertain: true], value: 2000], %{}}
iex> apply_qualifier("", [value: 2000], %{}, nil, nil)
{"", [attributes: [], value: 2000], %{}}
```
"""
def apply_qualifier(rest, value, context, _line, _offset) do
qualifier = Keyword.get(value, :qualifier)

attributes =
Map.get(@qualifier_attributes, qualifier, [])
|> Enum.map(&{&1, true})

{rest, Keyword.delete(value, :qualifier) |> Keyword.put(:attributes, attributes), context}
end

@doc """
Convert a parsed numeric bitstring to an integer
Expand Down Expand Up @@ -164,11 +191,8 @@ defmodule EDTF.Parser.Helpers do
defp reduce(qualifier, values) do
if Enum.all?(values, fn v -> Keyword.get(v, :attributes, []) |> length() == 0 end) do
attributes =
case qualifier do
~c"?" -> [uncertain: true]
~c"~" -> [approximate: true]
~c"%" -> [approximate: true, uncertain: true]
end
Map.get(@qualifier_attributes, qualifier, [])
|> Enum.map(&{&1, true})

values = Enum.reduce(values, [], fn value, acc -> [Keyword.get(value, :value) | acc] end)

Expand Down
40 changes: 40 additions & 0 deletions test/edtf/date_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,46 @@ defmodule EDTF.DateTest do
refute subject.attributes[:uncertain]
assert subject.attributes[:unspecified] == 165
end

@tag edtf: "201?"
test "decade", %{subject: subject} do
assert subject.type == :decade
assert subject.values == [201]
assert subject.level == 1
refute subject.attributes[:approximate]
assert subject.attributes[:uncertain]
refute subject.attributes[:unspecified]
end

@tag edtf: "-201~"
test "negative decade", %{subject: subject} do
assert subject.type == :decade
assert subject.values == [-201]
assert subject.level == 1
assert subject.attributes[:approximate]
refute subject.attributes[:uncertain]
refute subject.attributes[:unspecified]
end

@tag edtf: "20%"
test "century", %{subject: subject} do
assert subject.type == :century
assert subject.values == [20]
assert subject.level == 1
assert subject.attributes[:approximate]
assert subject.attributes[:uncertain]
refute subject.attributes[:unspecified]
end

@tag edtf: "-20?"
test "negative century", %{subject: subject} do
assert subject.type == :century
assert subject.values == [-20]
assert subject.level == 1
refute subject.attributes[:approximate]
assert subject.attributes[:uncertain]
refute subject.attributes[:unspecified]
end
end

describe "significant digits" do
Expand Down

0 comments on commit fed06f3

Please sign in to comment.