/
lang_string.ex
137 lines (106 loc) · 4.28 KB
/
lang_string.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
defmodule RDF.LangString do
@moduledoc """
`RDF.Literal.Datatype` for `rdf:langString`s.
"""
defstruct [:value, :language]
use RDF.Literal.Datatype,
name: "langString",
id: RDF.Utils.Bootstrapping.rdf_iri("langString")
import RDF.Utils.Guards
alias RDF.Literal.Datatype
alias RDF.Literal
@type t :: %__MODULE__{
value: String.t(),
language: String.t()
}
@doc """
Creates a new `RDF.Literal` with this datatype and the given `value` and `language`.
"""
@impl RDF.Literal.Datatype
@spec new(any, String.t() | atom | keyword) :: Literal.t()
def new(value, language_or_opts \\ [])
def new(value, language) when is_binary(language), do: new(value, language: language)
def new(value, language) when is_ordinary_atom(language), do: new(value, language: language)
def new(value, opts) do
%Literal{
literal: %__MODULE__{
value: to_string(value),
language: Keyword.get(opts, :language) |> normalize_language()
}
}
end
defp normalize_language(nil), do: nil
defp normalize_language(""), do: nil
defp normalize_language(language) when is_ordinary_atom(language),
do: language |> to_string() |> normalize_language()
defp normalize_language(language), do: String.downcase(language)
@impl RDF.Literal.Datatype
@spec new!(any, String.t() | atom | keyword) :: Literal.t()
def new!(value, language_or_opts \\ []) do
literal = new(value, language_or_opts)
if valid?(literal) do
literal
else
raise ArgumentError,
"#{inspect(value)} with language #{inspect(literal.literal.language)} is not a valid #{inspect(__MODULE__)}"
end
end
@impl Datatype
def language(%Literal{literal: literal}), do: language(literal)
def language(%__MODULE__{} = literal), do: literal.language
@impl Datatype
def value(%Literal{literal: literal}), do: value(literal)
def value(%__MODULE__{} = literal), do: literal.value
@impl Datatype
def lexical(%Literal{literal: literal}), do: lexical(literal)
def lexical(%__MODULE__{} = literal), do: literal.value
@impl Datatype
def canonical(%Literal{literal: %__MODULE__{}} = literal), do: literal
def canonical(%__MODULE__{} = literal), do: literal(literal)
@impl Datatype
def canonical?(%Literal{literal: literal}), do: canonical?(literal)
def canonical?(%__MODULE__{}), do: true
@impl Datatype
def valid?(%Literal{literal: %__MODULE__{} = literal}), do: valid?(literal)
def valid?(%__MODULE__{language: language}) when is_binary(language), do: language != ""
def valid?(_), do: false
@impl Datatype
def do_cast(_), do: nil
@impl Datatype
def update(literal, fun, opts \\ [])
def update(%Literal{literal: literal}, fun, opts), do: update(literal, fun, opts)
def update(%__MODULE__{} = literal, fun, _opts) do
literal
|> value()
|> fun.()
|> new(language: literal.language)
end
@doc """
Checks if a language tagged string literal or language tag matches a language range.
The check is performed per the basic filtering scheme defined in
[RFC4647](http://www.ietf.org/rfc/rfc4647.txt) section 3.3.1.
A language range is a basic language range per _Matching of Language Tags_ in
RFC4647 section 2.1.
A language range of `"*"` matches any non-empty language-tag string.
see <https://www.w3.org/TR/sparql11-query/#func-langMatches>
"""
@spec match_language?(Literal.t() | t() | String.t(), String.t()) :: boolean
def match_language?(language_tag, language_range)
def match_language?(%Literal{literal: literal}, language_range),
do: match_language?(literal, language_range)
def match_language?(%__MODULE__{language: nil}, _), do: false
def match_language?(%__MODULE__{language: language_tag}, language_range),
do: match_language?(language_tag, language_range)
def match_language?("", "*"), do: false
def match_language?(str, "*") when is_binary(str), do: true
def match_language?(language_tag, language_range)
when is_binary(language_tag) and is_binary(language_range) do
language_tag = String.downcase(language_tag)
language_range = String.downcase(language_range)
case String.split(language_tag, language_range, parts: 2) do
[_, rest] -> rest == "" or String.starts_with?(rest, "-")
_ -> false
end
end
def match_language?(_, _), do: false
end