Skip to content

Commit

Permalink
Merge pull request #14 from nulib/12-post-qualified-components
Browse files Browse the repository at this point in the history
Add support for Level 2 Group Qualifiers
  • Loading branch information
mbklein authored Jan 27, 2025
2 parents f86d4c9 + 90ed8ac commit 08f9371
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 8 deletions.
9 changes: 6 additions & 3 deletions lib/edtf/parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,24 @@ defmodule EDTF.Parser do

# Signed year with optional qualifier
qualified_year =
optional(component_qualifier |> tag(:qualifier))
optional(component_qualifier |> tag(:pre_qualifier))
|> concat(optional(sign) |> tag(:sign))
|> concat(year |> tag(:value))
|> concat(optional(component_qualifier |> tag(:post_qualifier)))
|> post_traverse({Helpers, :bitmask, [0]})

# Month with optional qualifier
qualified_month =
optional(component_qualifier |> tag(:qualifier))
optional(component_qualifier |> tag(:pre_qualifier))
|> concat(month |> tag(:value))
|> concat(optional(component_qualifier |> tag(:post_qualifier)))
|> post_traverse({Helpers, :bitmask, [4]})

# Day with optional qualifier
qualified_day =
optional(component_qualifier |> tag(:qualifier))
optional(component_qualifier |> tag(:pre_qualifier))
|> concat(day |> tag(:value))
|> concat(optional(component_qualifier |> tag(:post_qualifier)))
|> post_traverse({Helpers, :bitmask, [6]})

# Basic [-]YYYY[-MM[-DD]] with optional qualifiers
Expand Down
27 changes: 22 additions & 5 deletions lib/edtf/parser/helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,42 @@ defmodule EDTF.Parser.Helpers do
being fully masked (15 for year, 48 for month, or 192 for day). Unspecified digits
(`X`) flip individual bits.
A pre-component qualifier results in only that component being masked. A post-
component qualifier results in that component plus all components to the left
being masked.
Example:
```elixir
iex> bitmask("-%02", [value: ~c"200X", sign: ~c"-"], %{}, nil, nil, 0)
{"-%02", [[value: -2000, attributes: [unspecified: 8]]], %{}}
iex> bitmask("", [value: ~c"02", qualifier: ~c"%"], %{}, nil, nil, 4)
iex> bitmask("", [value: ~c"02", pre_qualifier: ~c"%"], %{}, nil, nil, 4)
{"", [[value: 2, attributes: [approximate: 48, uncertain: 48]]], %{}}
iex> bitmask("", [value: ~c"02", post_qualifier: ~c"%"], %{}, nil, nil, 4)
{"", [[value: 2, attributes: [approximate: 63, uncertain: 63]]], %{}}
```
"""
def bitmask(rest, value, context, _line, _offset, shift) do
qualifier =
cond do
Keyword.get(value, :pre_qualifier) -> {:pre, Keyword.get(value, :pre_qualifier)}
Keyword.get(value, :post_qualifier) -> {:post, Keyword.get(value, :post_qualifier)}
true -> nil
end

{rest,
[
bitmask(
Keyword.get(value, :value),
Keyword.get(value, :sign, ~c""),
Keyword.get(value, :qualifier, ~c""),
qualifier,
shift
)
], context}
end

def bitmask(bitstring, sign, [], shift) do
def bitmask(bitstring, sign, nil, shift) do
{output, mask} =
bitstring
|> Enum.with_index()
Expand All @@ -71,8 +85,8 @@ defmodule EDTF.Parser.Helpers do
end
end

def bitmask(bitstring, sign, qualifier, shift) do
mask = ((1 <<< length(bitstring)) - 1) <<< shift
def bitmask(bitstring, sign, {qualifier_position, qualifier}, shift) do
mask = calculate_mask(bitstring, shift, qualifier_position)

attributes =
Map.get(@qualifier_attributes, qualifier)
Expand All @@ -84,6 +98,9 @@ defmodule EDTF.Parser.Helpers do
]
end

defp calculate_mask(bitstring, shift, :pre), do: ((1 <<< length(bitstring)) - 1) <<< shift
defp calculate_mask(bitstring, shift, :post), do: (1 <<< (length(bitstring) + shift)) - 1

@doc """
Apply a parsed sign to a parsed integer value.
Expand Down
10 changes: 10 additions & 0 deletions test/edtf/date_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,16 @@ defmodule EDTF.DateTest do
assert subject.attributes[:uncertain] == 192
refute subject.attributes[:unspecified]
end

@tag edtf: "1978-02?-23"
test "approximate month and year", %{subject: subject} do
assert subject.type == :date
assert subject.values == [1978, 1, 23]
assert subject.level == 2
refute subject.attributes[:approximate]
assert subject.attributes[:uncertain] == 63
refute subject.attributes[:unspecified]
end
end

describe "unspecified" do
Expand Down

0 comments on commit 08f9371

Please sign in to comment.