From 90ed8acb0ae41cd5f7ccd798b4264858ae95004f Mon Sep 17 00:00:00 2001 From: "Michael B. Klein" Date: Mon, 27 Jan 2025 16:17:41 +0000 Subject: [PATCH] Add support for Level 2 Group Qualifiers --- lib/edtf/parser.ex | 9 ++++++--- lib/edtf/parser/helpers.ex | 27 ++++++++++++++++++++++----- test/edtf/date_test.exs | 10 ++++++++++ 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/lib/edtf/parser.ex b/lib/edtf/parser.ex index 156d3fb..380e15b 100644 --- a/lib/edtf/parser.ex +++ b/lib/edtf/parser.ex @@ -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 diff --git a/lib/edtf/parser/helpers.ex b/lib/edtf/parser/helpers.ex index 80ccf0f..5fcedc8 100644 --- a/lib/edtf/parser/helpers.ex +++ b/lib/edtf/parser/helpers.ex @@ -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() @@ -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) @@ -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. diff --git a/test/edtf/date_test.exs b/test/edtf/date_test.exs index 81482f6..933f840 100644 --- a/test/edtf/date_test.exs +++ b/test/edtf/date_test.exs @@ -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