lutaml-path 0.1.0 → 0.1.1

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.
data/README.adoc CHANGED
@@ -7,18 +7,621 @@ image:https://img.shields.io/gem/v/lutaml-path.svg["Gem Version", link="https://
7
7
 
8
8
  toc::[]
9
9
 
10
- == What is LutaML Path?
10
+ == Introduction
11
11
 
12
- LutaML Path provides a parser for path expressions that reference elements
13
- within UML models. It implements a path notation similar to the Object
14
- Constraint Language (OCL) to locate UML model elements across package
15
- hierarchies.
12
+ "`LutaML Path`" is a query language for LutaML Models. It allows for referencing
13
+ and locating elements within information models defined using the LutaML Model
14
+ system.
16
15
 
17
- This gem is specifically designed to work with OMG UML models and supports
18
- referencing any UML element including packages, classes, interfaces, properties,
19
- and operations.
16
+ There are two types of LutaML Path syntaxes that share a common syntax:
20
17
 
21
- == How to install
18
+ * Model definition query (e.g. LutaML Model and UML definitions) (see <<lutaml-path-models>>)
19
+ * Instance data query (information model instances) (see <<lutaml-path-instance>>)
20
+
21
+
22
+ == Common syntax
23
+
24
+ === General
25
+
26
+ The LutaML path syntax is designed to be concise and expressive. It supports
27
+ both absolute and relative paths, wildcards, and pattern matching.
28
+
29
+ The path syntax is inspired by the Object Constraint Language (OCL) and extends
30
+ beyond it to provide a mechanism for uniquely identifying model elements within
31
+ the LutaML Model hierarchy.
32
+
33
+ LutaML Path fully supports Unicode characters in package and element names.
34
+
35
+
36
+ === Expression
37
+
38
+ ==== General
39
+
40
+ An expression can be a single element, a hierarchy, or a pattern.
41
+
42
+ The path syntax supports the following features:
43
+
44
+ * Wildcard matching
45
+ * Pattern matching
46
+ * Path segments with a regular expression-like syntax
47
+
48
+ In LutaML Models, models are typically referred using TitleCase and attributes
49
+ in snake_case.
50
+
51
+
52
+
53
+ ==== Wildcard matching
54
+
55
+ Wildcard characters are used to match multiple elements.
56
+
57
+ The path syntax supports the following wildcards:
58
+
59
+ Wildcard match character `*`::
60
+ matches any sequence of characters. When used as double asterisks `**`, it
61
+ matches any nested depth of the hierarchy.
62
+ +
63
+ [example]
64
+ ====
65
+ [source]
66
+ ----
67
+ # Wildcard matching
68
+ ## Matches any class starting with "Base"
69
+ Base*
70
+
71
+ ## Matches any class ending with "Model"
72
+ *Model
73
+
74
+ ## Matches any class starting with "Base" in the "Core" package
75
+ Core::Base*
76
+
77
+ ## Matches any class ending with "Model" in the "Core" package
78
+ Core::*Model
79
+
80
+ ## Matches any class in the "Core" package
81
+ Core::*
82
+ ----
83
+ ====
84
+
85
+ Any match character `?`::
86
+ matches any single character
87
+ +
88
+ [example]
89
+ ====
90
+ [source]
91
+ ----
92
+ # Single character matching
93
+ ## Matches any class starting with "Bas" and one more character, such as "Base"
94
+ Bas?
95
+
96
+ ## Matches any class ending with "Model" and one more character, such as "BaseModel"
97
+ *Model?
98
+ ----
99
+ ====
100
+
101
+
102
+ ==== Pattern matching
103
+
104
+ The path syntax supports several kinds of patterns. These patterns are used to
105
+ match elements based on specific criteria. The patterns are similar to regular
106
+ expressions but are more concise and easier to read.
107
+
108
+ The path syntax is case-sensitive and follows the source model's character
109
+ cases.
110
+
111
+ Set match expression `[` character expression `]`::
112
+ The delimiters of `[` and `]` are used to define a set of characters. The set
113
+ can contain any characters, including Unicode characters.
114
+
115
+ Character match expression `[abc]`:::
116
+ matches any character in the set.
117
+
118
+ Negative match expression `[!...]`:::
119
+ matches any character not in the set.
120
+
121
+ Range match expression `[a-z]`:::
122
+ The `-` character is used to define a range of characters. It
123
+ matches any character in the range according to Unicode code points.
124
+
125
+ Alternatives match expression `{expression1,expression2}`::
126
+ matches any of the comma-separated patterns
127
+
128
+ [example]
129
+ ====
130
+ [source]
131
+ ----
132
+ # Pattern matching
133
+ ## Matches any class ending with "ase", such as "Base", "Case" but not "Vase"
134
+ [BC]ase
135
+
136
+ ## Matches any class starting with "Base" or "Case"
137
+ {Base,Case}
138
+
139
+ ## Matches any class starting with "Base" or "Case" and ending with "Radius"
140
+ {Base,Case}*Radius
141
+
142
+ # Range matching
143
+ ## Matches any class of "Vase" and "vase"
144
+ [Vv]ase
145
+
146
+ # Negative matching
147
+ ## Matches any class ending with "ase" but not "Vase" and "Case"
148
+ [!CV]ase
149
+
150
+ # Unicode matching
151
+ ## Matches any class starting with "建物"
152
+ 建物*
153
+
154
+ ## Matches exactly "ドア" or "窓"
155
+ {ドア,窓}
156
+ ----
157
+ ====
158
+
159
+
160
+
161
+ === Path
162
+
163
+ ==== General
164
+
165
+ A path is a sequence of path segments separated by hierarchy separators.
166
+
167
+ A path segment is a single element or a pattern that matches a single element.
168
+
169
+ ----
170
+ ╔════════════════╗
171
+ ║ Path ║
172
+ ║ (segments) ║
173
+ ╚════╦══════╦════╝
174
+ │ │
175
+ ▼ ▼
176
+ ╔════════════════╗
177
+ ║ Path Segment ║
178
+ ╚═══════╦════════╝
179
+ │
180
+ ▼
181
+ ╔════════════════╗
182
+ ║ Expression ║
183
+ ╚════════════════╝
184
+ ----
185
+
186
+
187
+ ==== Hierarchy separators
188
+
189
+ The hierarchy separators are used to separate path segments within a path:
190
+
191
+ `::`:: for model definitions
192
+ `.`:: for attributes and instance data.
193
+
194
+ The separators can be escaped with a backslash inside an expression.
195
+ Single colons (`:`) are not used as separators and behave as part of the
196
+ segment.
197
+
198
+ [example]
199
+ ====
200
+ [source]
201
+ ----
202
+ # Referencing the "Rectangle::Shape" object
203
+ ::Rectangle\::Shape
204
+
205
+ # Accessing the "width.length" attribute of the "Rectangle" object
206
+ ::Rectangle.width\.length
207
+ ----
208
+ ====
209
+
210
+ The leading hierarchy separator, indicating absolute paths, cannot be escaped.
211
+
212
+ [example]
213
+ ====
214
+ [source]
215
+ ----
216
+ \::Rectangle::Shape # This is invalid
217
+
218
+ \.width.length # This is invalid
219
+ ----
220
+ ====
221
+
222
+
223
+
224
+ ==== Absolute and relative paths
225
+
226
+ The path syntax supports both absolute and relative paths:
227
+
228
+ * Absolute paths start with `::` and begin at the model root
229
+ * Relative paths start without `::` and are resolved from the current context
230
+
231
+ The target element type may be a class, property, operation, or any other model
232
+ element.
233
+
234
+ The separator can be escaped with a backslash: `\::`, if the package name
235
+ contains a double colon.
236
+
237
+ [example]
238
+ ====
239
+ [source]
240
+ ----
241
+ # Absolute path
242
+ ## Locates a model called "Rectangle" in the "Shapes" package at root
243
+ ::Shapes::Rectangle
244
+
245
+ ## Locates an attribute called "width" in the "Rectangle" class at root
246
+ ::Shapes::Rectangle.width
247
+
248
+ ## Locates a model called "図形" in the "Geometry" package at root
249
+ ::Geometry::図形
250
+
251
+ ## Locates an attribute called "高" in the "図形" class
252
+ ::Geometry::図形.高
253
+
254
+ # Relative path
255
+ ## Locates a model called "Rectangle" in the current package
256
+ Shapes::Rectangle
257
+
258
+ ## Locates an attribute called "width" in the current class
259
+ Shapes::Rectangle.width
260
+
261
+ ## Locates a model called "図形" under the "Geometry" model in the current model
262
+ Geometry::図形
263
+
264
+ ## Locates an attribute called "高" in the "図形" model
265
+ Geometry::図形.高
266
+ ----
267
+ ====
268
+
269
+
270
+
271
+ [[lutaml-path-models]]
272
+ == LutaML path for model definitions
273
+
274
+ === Overview
275
+
276
+ The LutaML path for model definition query syntax ("LutaML model path") is used
277
+ to reference elements within model definitions. These paths are used to locate
278
+ classes, properties, operations, and other model elements within the model
279
+ hierarchy.
280
+
281
+ While the LutaML path syntax is designed to work with LutaML Models, it can also
282
+ be used with UML models.
283
+
284
+ It implements a path notation similar to the Object Constraint Language
285
+ (OCL) to locate UML model elements across package hierarchies.
286
+
287
+ // This gem is specifically designed to work with OMG UML models and supports
288
+ // referencing any UML element including packages, classes, interfaces, properties,
289
+ // and operations.
290
+
291
+ The UML element path specification extends the OCL 2.4 specification to provide
292
+ a mechanism for uniquely identifying model elements (classes, interfaces,
293
+ enumerations, etc.) within the UML package hierarchy. It provides both relative
294
+ and absolute path references.
295
+
296
+
297
+ === Hierarchical paths
298
+
299
+ An element path can be specified in these forms:
300
+
301
+ * Single element: `ElementName`
302
+ * Relative path: `Package1::Package2::ElementName`
303
+ * Absolute path: `::Package1::Package2::ElementName`
304
+
305
+ The absolute path variant starts with `::` to indicate the path begins at the model root.
306
+
307
+ [source]
308
+ ----
309
+ # Japanese package and class names
310
+ 建物::窓::ガラス
311
+ ::建築モデル::建物::窓
312
+
313
+ # Mixed language names
314
+ building::窓::Window
315
+ geometry::図形::円
316
+
317
+ # Patterns with Unicode
318
+ 建物::部品*
319
+ *部::Base*
320
+ ----
321
+
322
+ === Path segment patterns
323
+
324
+ Path segment wildcards can be used to match package hierarchy.
325
+
326
+ * Single segment: `Package1::*::Element` matches Element in any subpackage of Package1
327
+ * Multiple segments: `Package1::**::Element` matches Element in Package1 or any nested depth
328
+
329
+ [source]
330
+ ----
331
+ # Wildcard matching
332
+ Package1::*::Element
333
+ Package1::**::Element
334
+ ----
335
+
336
+
337
+ === Resolution rules
338
+
339
+ * Single element name or pattern matches in any package
340
+ * Relative paths are resolved from current context
341
+ * Absolute paths are resolved from model root
342
+ * Path segments must match patterns exactly
343
+ * Empty segments are invalid
344
+ * Multiple matches are allowed with wildcards/patterns
345
+ * Without wildcards/patterns, first match is used for multiple matches
346
+
347
+
348
+ [[lutaml-path-instance]]
349
+ == LutaML path for instance data
350
+
351
+ === Overview
352
+
353
+ The LutaML path for instance data query syntax ("LutaML instance path") is used
354
+ to navigate and query data within model instances. These paths are used to
355
+ access attributes, filter data, and navigate complex structures within model
356
+ instances.
357
+
358
+ Model instances are instances of data that conform to a model definition.
359
+
360
+ The LutaML model data syntax uses dot notation and filters to navigate and query
361
+ data within model instances.
362
+
363
+ Model instances can be queried using path expressions to access attributes,
364
+ filter data, and navigate complex structures.
365
+
366
+ Model are typically referred using TitleCase and attributes in snake_case.
367
+
368
+ An instance path expression when resolved provides two types of return values:
369
+
370
+ * A single element (e.g. attribute value)
371
+ * A collection of elements (e.g. filtered data)
372
+
373
+
374
+ === Hierarchical paths
375
+
376
+ An instance path can be specified in these forms:
377
+
378
+ * Collection of model instances: `{model_path}`
379
+ * Attribute: `{class_path}.{attribute}`
380
+
381
+ [example]
382
+ ====
383
+ [source]
384
+ ----
385
+ # Simple attribute access
386
+ obj.title
387
+ obj.edition.number
388
+ ----
389
+ ====
390
+
391
+ === Wildcard matching
392
+
393
+ Wildcard symbols can be used to match characters in the path.
394
+
395
+ These include:
396
+
397
+ * path segments
398
+ * attribute names
399
+ * attribute values in conditions
400
+
401
+ [example]
402
+ ====
403
+ [source]
404
+ ----
405
+ # Wildcard matching path segments
406
+ obj.*.docidentifier[type!='ISBN']
407
+
408
+ # Wildcard matching attribute names
409
+ obj.contributor.*[type='author']
410
+
411
+ # Wildcard in path segments
412
+ ## Matches all contributors with the name 'ISO'
413
+ ## e.g. matches obj.contributor.publisher.organization.name('ISO')
414
+ obj.contributor.**[name='ISO']
415
+
416
+ # Wildcard usage in conditions
417
+ ## Matches all authors from standards organizations
418
+ obj.contributor[role.type='author' && organization.type='stand*']
419
+ ----
420
+ ====
421
+
422
+
423
+
424
+ === Filtering
425
+
426
+ ==== General
427
+
428
+ Filters can be applied to model instances to query data based on specific
429
+ criteria. Filters are enclosed in square brackets `[]` and can contain
430
+ conditions.
431
+
432
+ A filter "condition" is a comparison expression that evaluates to a boolean value.
433
+
434
+ Syntax:
435
+
436
+ [source]
437
+ ----
438
+ attribute[condition]
439
+ ----
440
+
441
+ The result of a filter is a collection of elements that match the condition.
442
+
443
+
444
+ ==== Conditions
445
+
446
+ Conditions are used to filter data based on specific criteria.
447
+
448
+ A filter "condition" is a comparison expression that evaluates to a boolean value.
449
+
450
+ Conditions can include:
451
+
452
+ * Value comparison: `property='value'`
453
+ * Multiple conditions: `condition1 && condition2`
454
+ * List membership: `property in ('value1', 'value2')`
455
+ * Logical operators: `condition1 && (condition2 || condition3)`
456
+ * Negation: `!condition`
457
+ * Existence: `exists`
458
+
459
+ Logical operators that can conjoin conditions:
460
+
461
+ * Comparison: `=`, `!=`, `>`, `<`, `>=`, `<=`
462
+ * Logical: `&&`, `||`, `!`
463
+ * List membership: `in`
464
+
465
+ Operator precedence, from tightest to loosest: `!`, then the comparison,
466
+ membership and existence predicates (`=`, `!=`, `>`, `<`, `>=`, `<=`, `in`,
467
+ `exists`), then `&&`, then `||`. Use parentheses to override it.
468
+
469
+ So `a in ('x','y') && b = 'z'` groups as `(a in ('x','y')) && (b = 'z')`, and
470
+ `a = 'x' || b = 'y' && c = 'z'` groups as `(a = 'x') || ((b = 'y') && (c = 'z'))`.
471
+
472
+ `and` is accepted as an alias for `&&`.
473
+
474
+ Comparison values are single-quoted strings (`'author'`) or bare numbers
475
+ (`30`). Use `\'` for a literal quote inside a value.
476
+
477
+ Within a segment, `[` opens a filter only when all three hold:
478
+
479
+ . it is not the segment's first character;
480
+ . the bracket group ends the segment; and
481
+ . its body contains a condition operator.
482
+
483
+ Otherwise `[` is an ordinary wildcard character set. So `Class[A-Z]` remains a
484
+ pattern because its body has no operator, and `[exists]` remains a pattern
485
+ because its `[` is the segment's first character, while `contributor[exists]`
486
+ is a filter.
487
+
488
+
489
+ [example]
490
+ ====
491
+ [source]
492
+ ----
493
+ # Condition for attribute value
494
+ obj.contributor[role.type='publisher']
495
+ # => Returns all contributors with role type 'publisher'
496
+
497
+ # Multiple conditions
498
+ obj.contributor[role.type='author' && organization.type='standards']
499
+ # => Returns all authors from standards organizations
500
+
501
+ # List membership
502
+ obj.docidentifier[type in ('ISBN','ISSN','DOI')]
503
+ # => Returns all document identifiers of type ISBN, ISSN, or DOI
504
+
505
+ # Existence
506
+ obj.contributor[exists]
507
+ # => Returns all contributors
508
+
509
+ # Negation
510
+ obj.docidentifier[type!='ISBN']
511
+ # => Returns all document identifiers not of type ISBN
512
+
513
+ # Complex conditions
514
+ obj.contributor[role.type='author' && organization.type='standards']
515
+ # => Returns all authors from standards organizations
516
+ ----
517
+ ====
518
+
519
+ ==== Applying filters
520
+
521
+ Filters are applied to model instances to query data based on specific criteria.
522
+
523
+ Filters are enclosed in square brackets `[]` and can contain conditions.
524
+
525
+ [example]
526
+ ====
527
+ [source]
528
+ ----
529
+ # Filter by date type
530
+ ## Steps:
531
+ ## 1. The first portion of the path navigates to the date element
532
+ ## 2. The filter condition is applied to the type attribute
533
+ obj.date[type='updated']
534
+
535
+ # Filter by document identifier type
536
+ ## Steps:
537
+ ## 1. The first portion of the path navigates to the docidentifier element
538
+ ## 2. The filter condition is applied to the type attribute
539
+ obj.docidentifier[type!='ISBN']
540
+ ----
541
+ ====
542
+
543
+ Filters can be chained to navigate complex structures and query data based on
544
+ specific criteria.
545
+
546
+ [example]
547
+ ====
548
+ [source]
549
+ ----
550
+ # Nested navigation with filtering
551
+ ## Filter by role type
552
+ ## Steps:
553
+ ## 1. The first portion of the path navigates to the contributor element
554
+ ## 2. The filter condition is applied to the role type attribute
555
+ ## 3. The last portion of the path navigates to the organization name attribute
556
+ obj.contributor[role.type='publisher'].organization.name
557
+
558
+ # Filter by multiple conditions
559
+ ## Filter by role type and organization type
560
+ ## Steps:
561
+ ## 1. The first portion of the path navigates to the contributor element
562
+ ## 2. The filter conditions are applied to the role type and organization type attributes
563
+ ## 3. The last portion of the path navigates to the organization name attribute
564
+ obj.contributor[role.type='author' && organization.type='standards'].organization.name
565
+
566
+ # Hierarchical filtering (nested conditions)
567
+ ## Filter by role type and organization type
568
+ ## Steps:
569
+ ## 1. The first portion of the path navigates to the contributor element
570
+ ## 2. The filter conditions are applied to the role type and organization type attributes
571
+ ## 3. The path navigates to the organization name attribute
572
+ ## 4. The filter condition is applied to the organization name attribute
573
+ obj.contributor[role.type='author' && organization.type='standards'].organization[organization.name in ('ISO','IEC')].name
574
+ ----
575
+ ====
576
+
577
+ ==== Path expressions
578
+
579
+ Path expressions are used to navigate through model attributes and filter data
580
+ based on specific criteria.
581
+
582
+ The path expressions are evaluated against the current model context.
583
+
584
+ [example]
585
+ ====
586
+ [source]
587
+ ----
588
+ # Navigate through model attributes
589
+ contributor.role.type
590
+ organization.name
591
+
592
+ # Filter by attribute values
593
+ contributor[role.type = 'publisher'].organization.name
594
+ date[type = 'updated']
595
+ docidentifier[type != 'ISBN']
596
+
597
+ # Multiple conditions
598
+ contributor[role.type = 'author' and organization.type = 'standards']
599
+ ----
600
+ ====
601
+
602
+ === Resolution rules
603
+
604
+ * Single element name or pattern matches in any package
605
+ * Relative paths are resolved from current context
606
+ * Absolute paths are resolved from model root
607
+ * Path segments must match patterns exactly
608
+ * Empty segments are invalid
609
+ * Multiple matches are allowed with wildcards/patterns
610
+ * Without wildcards/patterns, first match is used for multiple matches
611
+
612
+
613
+
614
+ == Ruby API
615
+
616
+ === Introduction
617
+
618
+ The LutaML Path gem provides a simple API for parsing and matching paths.
619
+
620
+ WARNING: Instance data paths are parsed but not resolved. `Lutaml::Path.parse`
621
+ returns an `InstancePath` carrying the parsed structure; evaluating one against
622
+ model instance data is not yet supported.
623
+
624
+ === How to install
22
625
 
23
626
  [source,ruby]
24
627
  ----
@@ -32,7 +635,9 @@ Or add this line to your application's Gemfile:
32
635
  gem 'lutaml-path'
33
636
  ----
34
637
 
35
- == Basic usage
638
+ === Basic usage
639
+
640
+ The LutaML Path gem provides a simple API for parsing and matching paths.
36
641
 
37
642
  The path syntax follows UML namespace conventions using `::` as a separator:
38
643
 
@@ -40,29 +645,34 @@ The path syntax follows UML namespace conventions using `::` as a separator:
40
645
  ----
41
646
  require 'lutaml/path'
42
647
 
43
- # Simple element reference
648
+ # Model definition path
649
+ ## Simple element reference
44
650
  path = Lutaml::Path.parse("Package::Class")
45
651
 
46
- # Absolute path (starts from root namespace)
652
+ ## Absolute path (starts from root namespace)
47
653
  path = Lutaml::Path.parse("::Root::Package::Class")
48
654
 
49
- # Path with wildcards
655
+ ## Path with wildcards
50
656
  path = Lutaml::Path.parse("Package::*::BaseClass*")
51
657
  ----
52
658
 
53
- == Working with patterns
54
-
55
- The parser supports several kinds of patterns:
659
+ [source,ruby]
660
+ ----
661
+ # Model instance data path
662
+ ## Parse model data path
663
+ path = Lutaml::Path.parse("obj.contributor.organization.name")
56
664
 
57
- * `*` - matches any sequence of characters
58
- * `?` - matches any single character
59
- * `[abc]` - matches any character in the set
60
- * `{pattern1,pattern2}` - matches any of the comma-separated patterns
665
+ ## Parse model data path with filter
666
+ path = Lutaml::Path.parse("obj.contributor[role.type='publisher']")
667
+ ----
61
668
 
62
- Examples:
669
+ === Working with patterns
63
670
 
671
+ [example]
672
+ ====
64
673
  [source,ruby]
65
674
  ----
675
+ # Model location matching
66
676
  # Match any class starting with "Base"
67
677
  path = Lutaml::Path.parse("Base*")
68
678
 
@@ -72,8 +682,17 @@ path = Lutaml::Path.parse("Package::[A-Z]*::Interface")
72
682
  # Match multiple alternatives
73
683
  path = Lutaml::Path.parse("model::{Abstract,Base}Class")
74
684
  ----
685
+ ====
686
+
687
+ // TODO: enable
688
+ // [source,ruby]
689
+ // ----
690
+ // # Model data matching
691
+ // path = Lutaml::Path.parse("contributor[role.type='publisher']")
692
+ // path.match?(data) # Matches against model instance data
693
+ // ----
75
694
 
76
- == How to match paths
695
+ === How to match paths
77
696
 
78
697
  The parsed path can be used to match against actual element paths:
79
698
 
@@ -103,18 +722,6 @@ relative.match?(["model", "Class"]) # => true
103
722
  relative.match?(["root", "model", "Class"]) # => true
104
723
  ----
105
724
 
106
- == Path syntax reference
107
-
108
- The path expression grammar follows these rules:
109
-
110
- * Path segments are separated by `::`
111
- * The separator can be escaped with a backslash: `\::`
112
- * An absolute path starts with `::`
113
- * Each segment can contain:
114
- ** Regular characters (including Unicode)
115
- ** Wildcards (`*`, `?`)
116
- ** Character classes (`[abc]`)
117
- ** Alternatives (`{pattern1,pattern2}`)
118
725
 
119
726
  == Matching paths with escaped colons
120
727
 
@@ -129,7 +736,7 @@ path.match?(["model", "std::string"]) # => true
129
736
  path.match?(["model", "std", "string"]) # => false
130
737
  ----
131
738
 
132
- == Examples of UML element references
739
+ === Examples of UML element references
133
740
 
134
741
  [source,ruby]
135
742
  ----
@@ -150,4 +757,12 @@ path.match?(["model", "std", "string"]) # => false
150
757
  ----
151
758
 
152
759
  These paths can be used to locate elements across UML model hierarchies, making
153
- it easier to reference and work with model elements programmatically.
760
+ it easier to reference and work with model elements programmatically.
761
+
762
+ == License
763
+
764
+ Copyright Ribose.
765
+
766
+ The `lutaml-path` gem is available as open source under the terms of the MIT
767
+ License.
768
+