lutaml-lml 0.3.4 → 0.3.5

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.
@@ -9,7 +9,28 @@
9
9
  # capture binds the whole match (Parslet's `.repeat.as(:x)` shape);
10
10
  # `[ x as k ]` yields k: nil when absent (Parslet omits the key —
11
11
  # consumers treat nil and absent as equivalent).
12
- grammar Lml version "1.0.0" {
12
+ grammar Lml version "1.0.0" edition 2 {
13
+ # ---- Glue (parsanol-ruby#364) ----
14
+ # Edition 2 names every uncaptured rule reference; these are the
15
+ # token mechanics the tree must not see: separators that eat `;` and
16
+ # comments, verbatim-scan classes, quote delimiters, identifier
17
+ # runs and keyword tokens. Declaring the whole chain keeps the tree
18
+ # the authored captures only — byte-identical to edition 1 (the
19
+ # corpus differential proves it). Un-glue a rule when its node
20
+ # should surface.
21
+ glue whitespace line_comment block_comment
22
+ glue any_char quotes
23
+ glue word nonspace class_run class_name_chars type_run type_token quoted_name_run
24
+ glue definition_content
25
+ glue kw_abstract_sp kw_artifact_sp kw_association_sp kw_attribute_sp kw_caption_sp
26
+ glue kw_class_sp kw_collection_sp kw_condition_sp kw_data_type_sp kw_definition_sp
27
+ glue kw_derive_sp kw_element_sp kw_enum_sp kw_fontname_sp kw_format_sp kw_hide_sp
28
+ glue kw_import_sp kw_instance_sp kw_isa_sp kw_lml_sp kw_map_attribute_sp kw_map_content_sp
29
+ glue kw_map_element_sp kw_map_key_sp kw_map_sp kw_map_value_sp kw_mapping_sp kw_member_sp
30
+ glue kw_member_type_sp kw_namespace_sp kw_owner_sp kw_owner_type_sp kw_package_sp
31
+ glue kw_pattern_sp kw_prefix_sp kw_primitive_sp kw_require_sp kw_show_sp
32
+ glue kw_string_format_sp kw_title_sp kw_to_instance_sp kw_uri_sp kw_value_sp
33
+ glue kw_values_sp kw_version_sp kw_visibility_sp
13
34
  # ---- Trivia (parsanol-ruby#134 skip rule) ----
14
35
  # Grammar-native trivia replaces the pre-parse comment stripping:
15
36
  # `//` line and `/* */` block comments (RS 3001 par. Comment) plus
@@ -136,14 +157,17 @@ grammar Lml version "1.0.0" {
136
157
  visibility = kw_visibility_modifier as visibility_modifier
137
158
  attribute_stereotype = "<<" ( 1*( %x41-5A / %x61-7A / %x30-39 / "_" / "-" / "/" ) ) as stereotype ">>"
138
159
  atomic type_run = 1*( !( "//" / "/*" ) ( %x41-5A / %x61-7A / %x30-39 / "_" / "-" / ":" / "/" / "+" ) )
139
- atomic type_token = type_run *( %x20 type_run )
160
+ atomic type_token = type_run *( %x20 !( "as" 1*" " ":" ) type_run )
140
161
  atomic namechar = %x41-5A / %x61-7A / %x30-39 / "_" / "-" / "/" / "+"
141
162
  attribute_type = [ ":" ] [ attribute_stereotype ] [ quotes as type_literal ] ( type_token ) as type [ quotes ]
142
163
  attribute_name = ( 1*namechar ) as name
143
164
  atomic quoted_name_run = 1*( %x41-5A / %x61-7A / %x30-39 / "_" / "-" / "/" / "+" )
144
165
  atomic quoted_name_body = quoted_name_run *( %x20 quoted_name_run )
145
166
  attribute_definition_name = ( quotes ( quoted_name_body ) as name quotes ) / attribute_name
146
- attribute_definition = ( [ visibility ] as visibility attribute_definition_name [ attribute_type ] [ cardinality ] [ class_body ] ) as attributes
167
+ # Issue lutaml/lutaml#49: `as :element` / `as :attribute` picks the XML
168
+ # node kind of an attribute (lutaml-model map_element / map_attribute).
169
+ atomic xml_role_suffix = "as" 1*" " ":" ( ( "element" / "attribute" ) as xml_role )
170
+ attribute_definition = ( [ visibility ] as visibility attribute_definition_name [ attribute_type ] [ cardinality ] [ xml_role_suffix ] [ class_body ] ) as attributes
147
171
 
148
172
  keyword_type_argument = "type" ( 1*nonspace ) as type [ whitespace ]
149
173
  keyword_cardinality_argument = "cardinality" cardinality_body_definition as cardinality [ whitespace ]
@@ -154,7 +178,7 @@ grammar Lml version "1.0.0" {
154
178
  value_set_body = "{" [ whitespace ] ( *( ( ( data_value / variable ) as item ) [ "," ] [ whitespace ] ) ) as items "}"
155
179
  keyword_attribute_options = *( keyword_type_argument / keyword_cardinality_argument / keyword_values_argument / keyword_pattern_argument / ( keyword_any_argument ) as properties )
156
180
  keyword_attribute_body = "{" [ whitespace ] keyword_attribute_options [ whitespace ] "}"
157
- keyword_attribute_definition = ( kw_attribute_sp attribute_name keyword_attribute_body ) as attributes
181
+ keyword_attribute_definition = ( kw_attribute_sp attribute_name [ xml_role_suffix ] keyword_attribute_body ) as attributes
158
182
 
159
183
  # ---- Associations ----
160
184
  owner_cardinality = "[" cardinality_body_definition as owner_end_cardinality "]"
@@ -235,7 +259,10 @@ grammar Lml version "1.0.0" {
235
259
  string_format_body = "{" [ whitespace ] ( *( string_format_item ) ) as items [ whitespace ] "}"
236
260
  string_format_definition = ( [ whitespace ] kw_string_format_sp ( variable ) as format string_format_body ) as string_format
237
261
 
238
- class_inner_definitions = visibility_declaration / abstract_declaration / definition_body / definition_line / class_mapping_section / derive_definition / string_format_definition / ( operation_definition ) as operations / attribute_comma_definition / ( !( kw_attribute_sp ) attribute_definition ) / keyword_attribute_definition / comment_definition / comment_multiline_definition
262
+ # Issue lutaml/lutaml#49: a quoted `namespace "uri" [prefix "p"]` line
263
+ # inside a class declares the class's XML namespace.
264
+ class_namespace_definition = ( kw_namespace_sp ( quoted_string ) as uri [ kw_prefix_sp ( quoted_string / ( 1*word ) ) as prefix ] ) as class_namespace [ whitespace ]
265
+ class_inner_definitions = class_namespace_definition / visibility_declaration / abstract_declaration / definition_body / definition_line / class_mapping_section / derive_definition / string_format_definition / ( operation_definition ) as operations / attribute_comma_definition / ( !( kw_attribute_sp ) attribute_definition ) / keyword_attribute_definition / comment_definition / comment_multiline_definition
239
266
  class_inner_definition = class_inner_definitions [ whitespace ]
240
267
  class_body = "{" [ whitespace ] ( *( class_inner_definition ) ) as members "}"
241
268
  parent_class = "<" ( class_name_chars ) as parent_class
@@ -49,18 +49,40 @@ module Lutaml
49
49
  end
50
50
  end
51
51
 
52
+ # Default XML mapping: every attribute an element, except those
53
+ # declared `as :attribute`; an in-class `namespace "uri"` becomes
54
+ # the class's XML namespace (issue #49).
52
55
  def apply_xml_mapping(klass, root_name)
53
56
  attr_names = klass.attributes.keys
54
- return if attr_names.empty?
55
-
57
+ def_obj = @class_defs[root_name]
58
+ roles = xml_roles_of(def_obj)
59
+ ns_class = inline_namespace_class(def_obj)
56
60
  klass.xml do |mapping|
57
61
  mapping.element(root_name)
62
+ mapping.namespace(ns_class) if ns_class
58
63
  attr_names.each do |attr_name|
59
- mapping.map_element(attr_name.to_s, to: attr_name)
64
+ if roles[attr_name] == 'attribute'
65
+ mapping.map_attribute(attr_name.to_s, to: attr_name)
66
+ else
67
+ mapping.map_element(attr_name.to_s, to: attr_name)
68
+ end
60
69
  end
61
70
  end
62
71
  end
63
72
 
73
+ def xml_roles_of(def_obj)
74
+ Array(def_obj&.attributes).each_with_object({}) do |attr, roles|
75
+ roles[attr.name.to_sym] = attr.xml_role if attr.xml_role
76
+ end
77
+ end
78
+
79
+ def inline_namespace_class(def_obj)
80
+ decl = def_obj.xml_namespace if def_obj.is_a?(UmlClass)
81
+ return nil unless decl&.uri
82
+
83
+ build_namespace_class("#{decl.uri}|#{decl.prefix}", decl.uri, decl.prefix.to_s)
84
+ end
85
+
64
86
  # RS 3010 extension: key-value mapping blocks for the declared
65
87
  # formats (yaml/json/toml share lutaml-model's key_value model).
66
88
 
@@ -89,7 +111,7 @@ module Lutaml
89
111
  check_namespace_ambiguity(mapping)
90
112
  ns_class = namespace_class_for(mapping.namespace_ref) if mapping.namespace_ref
91
113
  rule_ns = mapping.rules.select { |r| r.kind == 'namespace' }
92
- .to_h { |r| [r.field, namespace_class_for(r.field)] }
114
+ .to_h { |r| [r.field, namespace_class_for(r.field)] }
93
115
  klass.xml do |m|
94
116
  m.element(mapping.element_name) if mapping.element_name
95
117
  m.namespace(ns_class) if ns_class
@@ -149,12 +171,16 @@ module Lutaml
149
171
  declared = @declared_namespaces.find { |ns| ns.name.to_s == ref }
150
172
  raise ArgumentError, "unknown namespace '#{ref}'" unless declared
151
173
 
152
- ns_class = Class.new(Lutaml::Xml::Namespace)
153
- uri = declared.uri
154
- prefix = declared.prefix.to_s
155
- ns_class.define_singleton_method(:uri) { uri }
156
- ns_class.define_singleton_method(:prefix_default) { prefix }
157
- @namespace_classes[ref] = ns_class
174
+ build_namespace_class(ref, declared.uri, declared.prefix.to_s)
175
+ end
176
+
177
+ def build_namespace_class(key, uri, prefix)
178
+ require 'lutaml/xml/namespace' unless defined?(Lutaml::Xml::Namespace)
179
+
180
+ @namespace_classes[key] ||= Class.new(Lutaml::Xml::Namespace).tap do |ns_class|
181
+ ns_class.define_singleton_method(:uri) { uri }
182
+ ns_class.define_singleton_method(:prefix_default) { prefix }
183
+ end
158
184
  end
159
185
  end
160
186
  end
@@ -59,6 +59,7 @@ module Lutaml
59
59
  @declared_namespaces = []
60
60
  @serialization_mappings = []
61
61
  @namespace_classes = {}
62
+ @class_defs = {}
62
63
  end
63
64
 
64
65
  def compile(input)
@@ -140,6 +141,7 @@ module Lutaml
140
141
 
141
142
  def compile_class(klass_def)
142
143
  name = klass_def.name.to_s
144
+ @class_defs[name] = klass_def
143
145
  compiled_klass = build_compiled_class(klass_def)
144
146
  apply_declared_mappings(compiled_klass, klass_def)
145
147
  declare_derived_fields(compiled_klass, klass_def)
@@ -293,11 +295,17 @@ module Lutaml
293
295
 
294
296
  def register(name, klass)
295
297
  @compiled[name] = klass
296
- return unless @namespace
298
+ return name_anonymous_class(klass, name) unless @namespace
297
299
 
298
300
  namespace_module.const_set(name, klass)
299
301
  end
300
302
 
303
+ # Without a target namespace the class stays anonymous, and
304
+ # lutaml-model's schema generators need a class name for $defs/$ref.
305
+ def name_anonymous_class(klass, name)
306
+ klass.define_singleton_method(:name) { name }
307
+ end
308
+
301
309
  def namespace_module
302
310
  return @namespace if @namespace.is_a?(Module)
303
311
 
@@ -24,6 +24,8 @@ module Lutaml
24
24
  attribute :default, :string
25
25
  attribute :value_set, :string, collection: true, default: -> { [] }
26
26
  attribute :pattern, :string
27
+ # `as :element` / `as :attribute`: the XML node kind (issue #49)
28
+ attribute :xml_role, :string
27
29
 
28
30
  # LML-specific attributes
29
31
  attribute :properties, "Lutaml::Lml::TopElementAttribute", collection: true, default: -> { [] }
@@ -60,6 +62,7 @@ module Lutaml
60
62
  map "default", to: :default
61
63
  map "value_set", to: :value_set
62
64
  map "pattern", to: :pattern
65
+ map "xml_role", to: :xml_role
63
66
  map "properties", to: :properties
64
67
  map "value", to: :value, with: { to: :value_to, from: :value_from }
65
68
  map "attributes", to: :attributes
@@ -32,6 +32,8 @@ module Lutaml
32
32
  attribute :serialization_mappings, "Lutaml::Lml::SerializationMapping", collection: true, default: -> { [] }
33
33
 
34
34
  # LML-specific
35
+ # In-class `namespace "uri"` declaration (issue #49)
36
+ attribute :xml_namespace, "Lutaml::Lml::Namespace"
35
37
  attribute :parent_class, :string
36
38
 
37
39
  yaml do
@@ -42,6 +44,7 @@ module Lutaml
42
44
  map "stereotype", to: :stereotype
43
45
  map "visibility", to: :visibility
44
46
  map "type", to: :type
47
+ map "xml_namespace", to: :xml_namespace
45
48
  map "attributes", to: :attributes
46
49
  map "operations", to: :operations
47
50
  map "constraints", to: :constraints
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Lutaml
4
4
  module Lml
5
- VERSION = "0.3.4"
5
+ VERSION = "0.3.5"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lutaml-lml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-10-21 00:00:00.000000000 Z
11
+ date: 2026-11-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lutaml-model
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.8.114
19
+ version: 0.8.156
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.8.114
26
+ version: 0.8.156
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: parsanol
29
29
  requirement: !ruby/object:Gem::Requirement