lutaml-model 0.8.37 → 0.8.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,8 +2,8 @@ module Lutaml
2
2
  module Model
3
3
  class Transformer
4
4
  class << self
5
- def call(value, rule, attribute, format: nil)
6
- new(rule, attribute, format).call(value)
5
+ def call(value, rule, attribute, format: nil, context: nil)
6
+ new(rule, attribute, format, context).call(value)
7
7
  end
8
8
 
9
9
  private
@@ -15,7 +15,7 @@ module Lutaml
15
15
  transform.is_a?(::Hash) ? transform[direction] : transform
16
16
  end
17
17
 
18
- def apply_static(value, sources, direction, format)
18
+ def apply_static(value, sources, direction, format, context = nil)
19
19
  methods = sources.filter_map do |obj|
20
20
  get_transform_static(obj, direction)
21
21
  end
@@ -34,16 +34,23 @@ module Lutaml
34
34
  tc.public_send(apply_direction, v, format)
35
35
  end
36
36
 
37
- methods.reduce(result) { |tv, m| m.call(tv) }
37
+ methods.reduce(result) do |tv, m|
38
+ if m.arity == 2
39
+ m.call(tv, context)
40
+ else
41
+ m.call(tv)
42
+ end
43
+ end
38
44
  end
39
45
  end
40
46
 
41
- attr_reader :rule, :attribute, :format
47
+ attr_reader :rule, :attribute, :format, :context
42
48
 
43
- def initialize(rule, attribute, format = nil)
49
+ def initialize(rule, attribute, format = nil, context = nil)
44
50
  @rule = rule
45
51
  @attribute = attribute
46
52
  @format = format
53
+ @context = context
47
54
  end
48
55
 
49
56
  def call(value)
@@ -61,7 +68,19 @@ module Lutaml
61
68
  end
62
69
 
63
70
  methods.reduce(result) do |transformed_value, method|
64
- method.call(transformed_value)
71
+ invoke_transform(method, transformed_value)
72
+ end
73
+ end
74
+
75
+ # lutaml-model#550: `with:` custom methods opt into the options
76
+ # passed to `from_*` / `to_*` by declaring a second parameter
77
+ # (exact arity 2); one-parameter methods keep the historical
78
+ # single-argument call.
79
+ def invoke_transform(method, value)
80
+ if method.arity == 2
81
+ method.call(value, context)
82
+ else
83
+ method.call(value)
65
84
  end
66
85
  end
67
86
 
@@ -87,8 +106,8 @@ module Lutaml
87
106
 
88
107
  class ImportTransformer < Transformer
89
108
  class << self
90
- def call(value, rule, attribute, format: nil)
91
- apply_static(value, [rule, attribute], :import, format)
109
+ def call(value, rule, attribute, format: nil, context: nil)
110
+ apply_static(value, [rule, attribute], :import, format, context)
92
111
  end
93
112
  end
94
113
 
@@ -103,8 +122,8 @@ module Lutaml
103
122
 
104
123
  class ExportTransformer < Transformer
105
124
  class << self
106
- def call(value, rule, attribute, format: nil)
107
- apply_static(value, [attribute, rule], :export, format)
125
+ def call(value, rule, attribute, format: nil, context: nil)
126
+ apply_static(value, [attribute, rule], :export, format, context)
108
127
  end
109
128
  end
110
129
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Model
5
- VERSION = "0.8.37"
5
+ VERSION = "0.8.38"
6
6
  end
7
7
  end
@@ -21,6 +21,7 @@ end
21
21
 
22
22
  module Lutaml
23
23
  module Xml
24
+ autoload :HtmlEntities, "#{__dir__}/html_entities"
24
25
  autoload :PlanCompiler, "#{__dir__}/plan_compiler"
25
26
  autoload :PlanHydrator, "#{__dir__}/plan_hydrator"
26
27
  autoload :PlanOrder, "#{__dir__}/plan_order"
@@ -0,0 +1,290 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lutaml-model#154: HTML 4.01 named entity table (plus apos) for opt-in
4
+ # decoding of HTML entities in XML text (MathML and friends rely on
5
+ # them although XML itself defines none beyond the predefined five).
6
+
7
+ module Lutaml
8
+ module Xml
9
+ module HtmlEntities
10
+ # The table is the data: a gem dependency or data file for a static
11
+ # W3C list would cost more than the frozen literal itself.
12
+ # The table is the data: a gem dependency or data file for a static
13
+ # W3C list would cost more than the frozen literal itself.
14
+ NAMED_TO_CODEPOINT = { # rubocop:disable Metrics/CollectionLiteralLength
15
+ "AElig" => 198,
16
+ "Aacute" => 193,
17
+ "Acirc" => 194,
18
+ "Agrave" => 192,
19
+ "Alpha" => 913,
20
+ "Aring" => 197,
21
+ "Atilde" => 195,
22
+ "Auml" => 196,
23
+ "Beta" => 914,
24
+ "Ccedil" => 199,
25
+ "Chi" => 935,
26
+ "Dagger" => 8225,
27
+ "Delta" => 916,
28
+ "ETH" => 208,
29
+ "Eacute" => 201,
30
+ "Ecirc" => 202,
31
+ "Egrave" => 200,
32
+ "Epsilon" => 917,
33
+ "Eta" => 919,
34
+ "Euml" => 203,
35
+ "Gamma" => 915,
36
+ "Iacute" => 205,
37
+ "Icirc" => 206,
38
+ "Igrave" => 204,
39
+ "Iota" => 921,
40
+ "Iuml" => 207,
41
+ "Kappa" => 922,
42
+ "Lambda" => 923,
43
+ "Mu" => 924,
44
+ "Ntilde" => 209,
45
+ "Nu" => 925,
46
+ "OElig" => 338,
47
+ "Oacute" => 211,
48
+ "Ocirc" => 212,
49
+ "Ograve" => 210,
50
+ "Omega" => 937,
51
+ "Omicron" => 927,
52
+ "Oslash" => 216,
53
+ "Otilde" => 213,
54
+ "Ouml" => 214,
55
+ "Phi" => 934,
56
+ "Pi" => 928,
57
+ "Prime" => 8243,
58
+ "Psi" => 936,
59
+ "Rho" => 929,
60
+ "Scaron" => 352,
61
+ "Sigma" => 931,
62
+ "THORN" => 222,
63
+ "Tau" => 932,
64
+ "Theta" => 920,
65
+ "Uacute" => 218,
66
+ "Ucirc" => 219,
67
+ "Ugrave" => 217,
68
+ "Upsilon" => 933,
69
+ "Uuml" => 220,
70
+ "Xi" => 926,
71
+ "Yacute" => 221,
72
+ "Yuml" => 376,
73
+ "Zeta" => 918,
74
+ "aacute" => 225,
75
+ "acirc" => 226,
76
+ "acute" => 180,
77
+ "aelig" => 230,
78
+ "agrave" => 224,
79
+ "alefsym" => 8501,
80
+ "alpha" => 945,
81
+ "amp" => 38,
82
+ "and" => 8743,
83
+ "ang" => 8736,
84
+ "apos" => 39,
85
+ "aring" => 229,
86
+ "asymp" => 8776,
87
+ "atilde" => 227,
88
+ "auml" => 228,
89
+ "bdquo" => 8222,
90
+ "beta" => 946,
91
+ "brvbar" => 166,
92
+ "bull" => 8226,
93
+ "cap" => 8745,
94
+ "ccedil" => 231,
95
+ "cedil" => 184,
96
+ "cent" => 162,
97
+ "chi" => 967,
98
+ "circ" => 710,
99
+ "clubs" => 9827,
100
+ "cong" => 8773,
101
+ "copy" => 169,
102
+ "crarr" => 8629,
103
+ "cup" => 8746,
104
+ "curren" => 164,
105
+ "dArr" => 8659,
106
+ "dagger" => 8224,
107
+ "darr" => 8595,
108
+ "deg" => 176,
109
+ "delta" => 948,
110
+ "diams" => 9830,
111
+ "divide" => 247,
112
+ "eacute" => 233,
113
+ "ecirc" => 234,
114
+ "egrave" => 232,
115
+ "empty" => 8709,
116
+ "emsp" => 8195,
117
+ "ensp" => 8194,
118
+ "epsilon" => 949,
119
+ "equiv" => 8801,
120
+ "eta" => 951,
121
+ "eth" => 240,
122
+ "euml" => 235,
123
+ "euro" => 8364,
124
+ "exist" => 8707,
125
+ "fnof" => 402,
126
+ "forall" => 8704,
127
+ "frac12" => 189,
128
+ "frac14" => 188,
129
+ "frac34" => 190,
130
+ "frasl" => 8260,
131
+ "gamma" => 947,
132
+ "ge" => 8805,
133
+ "gt" => 62,
134
+ "hArr" => 8660,
135
+ "harr" => 8596,
136
+ "hearts" => 9829,
137
+ "hellip" => 8230,
138
+ "iacute" => 237,
139
+ "icirc" => 238,
140
+ "iexcl" => 161,
141
+ "igrave" => 236,
142
+ "image" => 8465,
143
+ "infin" => 8734,
144
+ "int" => 8747,
145
+ "iota" => 953,
146
+ "iquest" => 191,
147
+ "isin" => 8712,
148
+ "iuml" => 239,
149
+ "kappa" => 954,
150
+ "lArr" => 8656,
151
+ "lambda" => 955,
152
+ "lang" => 9001,
153
+ "laquo" => 171,
154
+ "larr" => 8592,
155
+ "lceil" => 8968,
156
+ "ldquo" => 8220,
157
+ "le" => 8804,
158
+ "lfloor" => 8970,
159
+ "lowast" => 8727,
160
+ "loz" => 9674,
161
+ "lrm" => 8206,
162
+ "lsaquo" => 8249,
163
+ "lsquo" => 8216,
164
+ "lt" => 60,
165
+ "macr" => 175,
166
+ "mdash" => 8212,
167
+ "micro" => 181,
168
+ "middot" => 183,
169
+ "minus" => 8722,
170
+ "mu" => 956,
171
+ "nabla" => 8711,
172
+ "nbsp" => 160,
173
+ "ndash" => 8211,
174
+ "ne" => 8800,
175
+ "ni" => 8715,
176
+ "not" => 172,
177
+ "notin" => 8713,
178
+ "nsub" => 8836,
179
+ "ntilde" => 241,
180
+ "nu" => 957,
181
+ "oacute" => 243,
182
+ "ocirc" => 244,
183
+ "oelig" => 339,
184
+ "ograve" => 242,
185
+ "oline" => 8254,
186
+ "omega" => 969,
187
+ "omicron" => 959,
188
+ "oplus" => 8853,
189
+ "or" => 8744,
190
+ "ordf" => 170,
191
+ "ordm" => 186,
192
+ "oslash" => 248,
193
+ "otilde" => 245,
194
+ "otimes" => 8855,
195
+ "ouml" => 246,
196
+ "para" => 182,
197
+ "part" => 8706,
198
+ "permil" => 8240,
199
+ "perp" => 8869,
200
+ "phi" => 966,
201
+ "pi" => 960,
202
+ "piv" => 982,
203
+ "plusmn" => 177,
204
+ "pound" => 163,
205
+ "prime" => 8242,
206
+ "prod" => 8719,
207
+ "prop" => 8733,
208
+ "psi" => 968,
209
+ "quot" => 34,
210
+ "rArr" => 8658,
211
+ "radic" => 8730,
212
+ "rang" => 9002,
213
+ "raquo" => 187,
214
+ "rarr" => 8594,
215
+ "rceil" => 8969,
216
+ "rdquo" => 8221,
217
+ "real" => 8476,
218
+ "reg" => 174,
219
+ "rfloor" => 8971,
220
+ "rho" => 961,
221
+ "rlm" => 8207,
222
+ "rsaquo" => 8250,
223
+ "rsquo" => 8217,
224
+ "sbquo" => 8218,
225
+ "scaron" => 353,
226
+ "sdot" => 8901,
227
+ "sect" => 167,
228
+ "shy" => 173,
229
+ "sigma" => 963,
230
+ "sigmaf" => 962,
231
+ "sim" => 8764,
232
+ "spades" => 9824,
233
+ "sub" => 8834,
234
+ "sube" => 8838,
235
+ "sum" => 8721,
236
+ "sup" => 8835,
237
+ "sup1" => 185,
238
+ "sup2" => 178,
239
+ "sup3" => 179,
240
+ "supe" => 8839,
241
+ "szlig" => 223,
242
+ "tau" => 964,
243
+ "there4" => 8756,
244
+ "theta" => 952,
245
+ "thetasym" => 977,
246
+ "thinsp" => 8201,
247
+ "thorn" => 254,
248
+ "tilde" => 732,
249
+ "times" => 215,
250
+ "trade" => 8482,
251
+ "uArr" => 8657,
252
+ "uacute" => 250,
253
+ "uarr" => 8593,
254
+ "ucirc" => 251,
255
+ "ugrave" => 249,
256
+ "uml" => 168,
257
+ "upsih" => 978,
258
+ "upsilon" => 965,
259
+ "uuml" => 252,
260
+ "weierp" => 8472,
261
+ "xi" => 958,
262
+ "yacute" => 253,
263
+ "yen" => 165,
264
+ "yuml" => 255,
265
+ "zeta" => 950,
266
+ "zwj" => 8205,
267
+ "zwnj" => 8204,
268
+ }.freeze
269
+
270
+ NAME_PATTERN = /&(#x?[0-9A-Fa-f]+|[A-Za-z][A-Za-z0-9]*);/
271
+
272
+ module_function
273
+
274
+ def decode(value)
275
+ value.gsub(NAME_PATTERN) do |match|
276
+ body = match[1..-2]
277
+ case body[0]
278
+ when "x", "X"
279
+ body[1..].to_i(16).chr(Encoding::UTF_8)
280
+ when "#"
281
+ body[1..].to_i.chr(Encoding::UTF_8)
282
+ else
283
+ codepoint = NAMED_TO_CODEPOINT[body]
284
+ codepoint ? codepoint.chr(Encoding::UTF_8) : match
285
+ end
286
+ end
287
+ end
288
+ end
289
+ end
290
+ end
@@ -121,9 +121,16 @@ module Lutaml
121
121
  @cached_attributes.clear
122
122
  @cached_mappings.clear
123
123
  @cached_attribute_name_counts.clear
124
+ @rule_records_version = (@rule_records_version || 0) + 1
124
125
  @finalized = true
125
126
  end
126
127
 
128
+ # Bumped at finalize; ModelTransform's compiled rule records are
129
+ # keyed on it so late mapping changes rebuild instead of going stale.
130
+ def rule_records_version
131
+ @rule_records_version ||= 0
132
+ end
133
+
127
134
  def finalized?
128
135
  @finalized
129
136
  end
@@ -1334,6 +1341,18 @@ module Lutaml
1334
1341
  !@namespace_class.nil?
1335
1342
  end
1336
1343
 
1344
+ # lutaml-model#154: opt-in decoding of HTML named/numeric entities
1345
+ # (e.g. &copy;, &#8212;) in mapped text and attribute values. XML
1346
+ # documents are otherwise kept byte-faithful: HTML entities are not
1347
+ # defined in XML without a DTD.
1348
+ def html_entities(enabled: true)
1349
+ @decode_html_entities = enabled
1350
+ end
1351
+
1352
+ def decode_html_entities?
1353
+ !!@decode_html_entities
1354
+ end
1355
+
1337
1356
  # Whether namespace was explicitly set via DSL
1338
1357
  def namespace_set?
1339
1358
  !!@namespace_set