diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 43c7998..0864c49 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -1,21 +1,13 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
-# on 2025-03-07 11:40:20 UTC using RuboCop version 1.73.2.
+# on 2025-11-14 15:24:37 UTC using RuboCop version 1.81.7.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
-# Offense count: 1
-# This cop supports safe autocorrection (--autocorrect).
-# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include.
-# Include: **/*.gemfile, **/Gemfile, **/gems.rb
-Bundler/OrderedGems:
- Exclude:
- - 'Gemfile'
-
-# Offense count: 2
+# Offense count: 10
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
# AllowedMethods: refine
Metrics/BlockLength:
- Max: 30
+ Max: 108
diff --git a/Gemfile b/Gemfile
index 3b9e3f5..d9b2006 100644
--- a/Gemfile
+++ b/Gemfile
@@ -6,7 +6,6 @@ source "https://rubygems.org"
gemspec
gem "base64"
-gem "canon"
gem "equivalent-xml"
gem "liquid"
gem "lutaml-model", github: "lutaml/lutaml-model", branch: "main"
@@ -15,4 +14,3 @@ gem "oga"
gem "rake"
gem "rspec"
gem "rubocop"
-gem "xml-c14n"
diff --git a/README.adoc b/README.adoc
index 4607d5c..b36fab1 100644
--- a/README.adoc
+++ b/README.adoc
@@ -125,9 +125,8 @@ This gem exposes convenient methods for use in Liquid templates via `to_liquid`
- `used_by` — Returns complex_types that use/reference this element.
- `attributes` — Returns attribute elements.
-- `min_occurrences` — Returns the element's minimum occurrences as Integer (default 1).
-- `max_occurrences` — Returns `*` for unbounded, otherwise Integer (default 1).
- `child_elements` — Returns all nested child elements (resolved across groups/choices/sequences).
+- `referenced_name` — Returns the name of the element or its referenced element by `ref`.
- `referenced_type` — Returns the name of the element's `type` if present, or the type name indicated by `ref` if defined.
- `referenced_object` — Resolves and returns the element or its referenced element by `ref`.
- `referenced_complex_type` — Resolves the complex type matching `referenced_type`.
@@ -165,6 +164,7 @@ This gem exposes convenient methods for use in Liquid templates via `to_liquid`
- `used_by` — Returns complex types that use this attribute group.
- `attribute_elements` — Returns a flattened list of attributes, resolving nested groups.
+- `referenced_object` — Resolves and returns the attribute group or its referenced group by `ref`.
[source,liquid]
----
@@ -184,8 +184,10 @@ This gem exposes convenient methods for use in Liquid templates via `to_liquid`
- `used_by` — Returns elements and group elements that reference this type.
- `attribute_elements` — Returns all attributes (including those from groups and simple content extensions).
+- `direct_child_elements` — Returns direct child elements, excluding attributes, attribute groups, annotations, and any attributes.
- `child_elements` — Returns all child elements (across sequences/choices/groups).
-- `find_elements_used(name)` — Boolean indicating if an element name is used within this type.
+- `find_elements_used(element_name)` — Boolean indicating if an element name is used within this type.
+- `find_used_by(object)` — Boolean indicating if this complex type is used by the given object.
[source,liquid]
----
@@ -205,7 +207,7 @@ This gem exposes convenient methods for use in Liquid templates via `to_liquid`
=== Group (`Lutaml::Xsd::LiquidMethods::Group`) and Sequence (`...::Sequence`) and Choice (`...::Choice`)
- `child_elements` — Returns all nested `Element` instances contained within.
-- `find_elements_used(name)` — Boolean indicating if an element name is used within this container.
+- `find_elements_used(element_name)` — Boolean indicating if an element name is used within this container.
- All three include the same helper surface and resolve nested structures consistently.
[source,liquid]
@@ -221,6 +223,7 @@ This gem exposes convenient methods for use in Liquid templates via `to_liquid`
=== SimpleContent (`Lutaml::Xsd::LiquidMethods::SimpleContent`)
- `attribute_elements` — Returns attributes coming from the `extension` and nested attribute groups.
+- `base_type` — Returns the base type from `base`, `extension.base`, or `restriction.base`.
[source,liquid]
----
@@ -241,9 +244,66 @@ This gem exposes convenient methods for use in Liquid templates via `to_liquid`
{% endfor %}
----
-=== ResolvedElementOrder (`Lutaml::Xsd::LiquidMethods::ResolvedElementOrder`)
+=== Schema Class Methods (`Lutaml::Xsd::Schema`)
-- `resolved_element_order` — A helper method that returns all elements in the order they originally appeared in the XSD.
+The `Schema` class exposes several helper methods in Liquid templates for easier access and navigation:
+
+- `elements_sorted_by_name` — Returns all `element` objects in the schema sorted by their `name` attribute.
+- `complex_types_sorted_by_name` — Returns all `complex_type` objects in the schema sorted by their `name` attribute.
+- `attribute_groups_sorted_by_name` — Returns all `attribute_group` objects in the schema sorted by their `name` attribute.
+
+These methods provide a predictable, alphabetical traversal of major parts of the XSD. Example usage:
+
+[source,liquid]
+----
+{% for el in schema.elements_sorted_by_name %}
+ - {{ el.name }}
+{% endfor %}
+
+{% for ct in schema.complex_types_sorted_by_name %}
+ - {{ ct.name }}
+{% endfor %}
+
+{% for ag in schema.attribute_groups_sorted_by_name %}
+ - {{ ag.name }}
+{% endfor %}
+----
+
+=== Base Class Methods (`Lutaml::Xsd::Base`)
+
+The following methods are available on all XSD objects via the base class:
+
+Type checking methods:
+- `any?` — Returns `true` if the object is an `Any` instance.
+- `all?` — Returns `true` if the object is an `All` instance.
+- `choice?` — Returns `true` if the object is a `Choice` instance.
+- `element?` — Returns `true` if the object is an `Element` instance.
+- `sequence?` — Returns `true` if the object is a `Sequence` instance.
+- `attribute?` — Returns `true` if the object is an `Attribute` instance.
+- `annotation?` — Returns `true` if the object is an `Annotation` instance.
+- `simple_content?` — Returns `true` if the object is a `SimpleContent` instance.
+- `attribute_group?` — Returns `true` if the object is an `AttributeGroup` instance.
+
+Other methods:
+- `min_occurrences` — Returns the minimum occurrences as Integer (default 1). Only available on objects that respond to `min_occurs`.
+- `max_occurrences` — Returns `*` for unbounded, otherwise Integer (default 1). Only available on objects that respond to `max_occurs`.
+- `to_xml` — Returns the XML representation of the object.
+- `to_formatted_xml` — Returns a formatted XML representation of the object.
+- `resolved_element_order` — Returns all elements in the order they originally appeared in the XSD.
+
+[source,liquid]
+----
+{% for item in schema.element %}
+ {% if item.element? %}
+ Element: {{ item.name }}
+ {% if item.min_occurrences %}
+ Min: {{ item.min_occurrences }}, Max: {{ item.max_occurrences }}
+ {% endif %}
+ {% endif %}
+{% endfor %}
+----
+
+NOTE: The `resolved_element_order` method is available on all objects via the base class (`Lutaml::Xsd::Base`) and returns all elements in the order they originally appeared in the XSD. This method is used internally by many of the liquid methods above.
== Development
diff --git a/lib/lutaml/xsd/base.rb b/lib/lutaml/xsd/base.rb
index e20c09a..e8c5c13 100644
--- a/lib/lutaml/xsd/base.rb
+++ b/lib/lutaml/xsd/base.rb
@@ -15,16 +15,76 @@ def to_formatted_xml(except: [])
end
def resolved_element_order
- element_order.each_with_object(element_order.dup) do |element, array|
+ element_order&.each_with_object(element_order.dup) do |element, array|
next delete_deletables(array, element) if deletable?(element)
update_element_array(array, element)
end
end
+ def sequence?
+ is_a?(Sequence)
+ end
+
+ def any?
+ is_a?(Any)
+ end
+
+ def all?
+ is_a?(All)
+ end
+
+ def choice?
+ is_a?(Choice)
+ end
+
+ def annotation?
+ is_a?(Annotation)
+ end
+
+ def attribute?
+ is_a?(Attribute)
+ end
+
+ def attribute_group?
+ is_a?(AttributeGroup)
+ end
+
+ def simple_content?
+ is_a?(SimpleContent)
+ end
+
+ def element?
+ is_a?(Element)
+ end
+
+ def min_occurrences
+ return unless respond_to?(:min_occurs)
+
+ @min_occurs&.to_i || 1
+ end
+
+ def max_occurrences
+ return unless respond_to?(:max_occurs)
+ return "*" if @max_occurs == "unbounded"
+
+ @max_occurs&.to_i || 1
+ end
+
liquid do
map "to_xml", to: :to_xml
+ map "any?", to: :any?
+ map "all?", to: :all?
+ map "choice?", to: :choice?
+ map "element?", to: :element?
+ map "sequence?", to: :sequence?
+ map "attribute?", to: :attribute?
+ map "annotation?", to: :annotation?
+ map "min_occurrences", to: :min_occurrences
+ map "max_occurrences", to: :max_occurrences
map "to_formatted_xml", to: :to_formatted_xml
+ map "simple_content?", to: :simple_content?
+ map "attribute_group?", to: :attribute_group?
map "resolved_element_order", to: :resolved_element_order
end
diff --git a/lib/lutaml/xsd/complex_type.rb b/lib/lutaml/xsd/complex_type.rb
index 9c63442..3658264 100644
--- a/lib/lutaml/xsd/complex_type.rb
+++ b/lib/lutaml/xsd/complex_type.rb
@@ -48,6 +48,7 @@ class ComplexType < Base
map "used_by", to: :used_by
map "child_elements", to: :child_elements
map "attribute_elements", to: :attribute_elements
+ map "direct_child_elements", to: :direct_child_elements
end
Lutaml::Xsd.register_model(self, :complex_type)
diff --git a/lib/lutaml/xsd/liquid_methods/attribute_group.rb b/lib/lutaml/xsd/liquid_methods/attribute_group.rb
index 9eec988..a18b473 100644
--- a/lib/lutaml/xsd/liquid_methods/attribute_group.rb
+++ b/lib/lutaml/xsd/liquid_methods/attribute_group.rb
@@ -20,7 +20,7 @@ def attribute_elements(array = [])
end
def find_used_by(object)
- object.resolved_element_order.any? do |child|
+ object&.resolved_element_order&.any? do |child|
if child.is_a?(Xsd::AttributeGroup)
child.ref == name
else
@@ -30,7 +30,7 @@ def find_used_by(object)
end
def referenced_object
- return self unless name
+ return self if name
@__root.attribute_group.find { |group| group.name == ref }
end
diff --git a/lib/lutaml/xsd/liquid_methods/complex_type.rb b/lib/lutaml/xsd/liquid_methods/complex_type.rb
index e4c82dd..3d4f3c5 100644
--- a/lib/lutaml/xsd/liquid_methods/complex_type.rb
+++ b/lib/lutaml/xsd/liquid_methods/complex_type.rb
@@ -4,6 +4,13 @@ module Lutaml
module Xsd
module LiquidMethods
module ComplexType
+ DIRECT_CHILD_ELEMENTS_EXCEPTION = %w[
+ AttributeGroup
+ AnyAttribute
+ Annotation
+ Attribute
+ ].freeze
+
def used_by
root_complex_types = @__root.complex_type.reject { |ct| ct == self }
raw_elements = @__root.group.map(&:child_elements).flatten
@@ -14,11 +21,20 @@ def used_by
def attribute_elements(array = [])
array.concat(attribute)
- attribute_group.flat_map { |group| group.attribute_elements(array) }
+ attribute_group.each { |group| group.attribute_elements(array) }
simple_content&.attribute_elements(array)
array
end
+ def direct_child_elements(array = [], except: DIRECT_CHILD_ELEMENTS_EXCEPTION)
+ resolved_element_order.each do |child|
+ next if except.any? { |klass| child.class.name.include?("::#{klass}") }
+
+ array << child
+ end
+ array
+ end
+
def child_elements(array = [])
resolved_element_order.each do |child|
if child.is_a?(Xsd::Element)
diff --git a/lib/lutaml/xsd/liquid_methods/element.rb b/lib/lutaml/xsd/liquid_methods/element.rb
index 1b7a9fe..9f009b8 100644
--- a/lib/lutaml/xsd/liquid_methods/element.rb
+++ b/lib/lutaml/xsd/liquid_methods/element.rb
@@ -9,17 +9,7 @@ def used_by
end
def attributes
- referenced_complex_type.attribute_elements
- end
-
- def min_occurrences
- @min_occurs&.to_i || 1
- end
-
- def max_occurrences
- return "*" if @max_occurs == "unbounded"
-
- @max_occurs&.to_i || 1
+ referenced_complex_type&.attribute_elements
end
def child_elements(array = [])
diff --git a/lib/lutaml/xsd/liquid_methods/group.rb b/lib/lutaml/xsd/liquid_methods/group.rb
index c796782..185262f 100644
--- a/lib/lutaml/xsd/liquid_methods/group.rb
+++ b/lib/lutaml/xsd/liquid_methods/group.rb
@@ -5,7 +5,7 @@ module Xsd
module LiquidMethods
module Group
def child_elements(array = [])
- resolved_element_order.each do |child|
+ referenced_object.resolved_element_order.each do |child|
if child.is_a?(Xsd::Element)
array << child
elsif child.respond_to?(:child_elements)
@@ -24,6 +24,12 @@ def find_elements_used(element_name)
end
end
end
+
+ def referenced_object
+ return self if name
+
+ @__root.group.find { |group| group.name == ref }
+ end
end
end
end
diff --git a/lib/lutaml/xsd/liquid_methods/schema.rb b/lib/lutaml/xsd/liquid_methods/schema.rb
new file mode 100644
index 0000000..abd25df
--- /dev/null
+++ b/lib/lutaml/xsd/liquid_methods/schema.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Lutaml
+ module Xsd
+ module LiquidMethods
+ module Schema
+ def elements_sorted_by_name
+ element.sort_by(&:name)
+ end
+
+ def complex_types_sorted_by_name
+ complex_type.sort_by(&:name)
+ end
+
+ def attribute_groups_sorted_by_name
+ attribute_group.sort_by(&:name)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/lutaml/xsd/liquid_methods/simple_content.rb b/lib/lutaml/xsd/liquid_methods/simple_content.rb
index 449481e..d1dc860 100644
--- a/lib/lutaml/xsd/liquid_methods/simple_content.rb
+++ b/lib/lutaml/xsd/liquid_methods/simple_content.rb
@@ -7,6 +7,12 @@ module SimpleContent
def attribute_elements(array = [])
extension.attribute_elements(array)
end
+
+ def base_type
+ base ||
+ extension&.base ||
+ restriction&.base
+ end
end
end
end
diff --git a/lib/lutaml/xsd/schema.rb b/lib/lutaml/xsd/schema.rb
index 6182ed9..980be44 100644
--- a/lib/lutaml/xsd/schema.rb
+++ b/lib/lutaml/xsd/schema.rb
@@ -1,9 +1,13 @@
# frozen_string_literal: true
+require_relative "liquid_methods/schema"
+
module Lutaml
module Xsd
# rubocop:disable Metrics/ClassLength
class Schema < Base
+ include LiquidMethods::Schema
+
attribute :id, :string
attribute :lang, :string
attribute :xmlns, :string
@@ -56,6 +60,12 @@ class Schema < Base
map_attribute :lang, to: :lang
end
+ liquid do
+ map "elements_sorted_by_name", to: :elements_sorted_by_name
+ map "complex_types_sorted_by_name", to: :complex_types_sorted_by_name
+ map "attribute_groups_sorted_by_name", to: :attribute_groups_sorted_by_name
+ end
+
def import_from_schema(model, value)
value.each do |schema|
setup_import_and_include(
diff --git a/lib/lutaml/xsd/simple_content.rb b/lib/lutaml/xsd/simple_content.rb
index b10d720..67eb2e2 100644
--- a/lib/lutaml/xsd/simple_content.rb
+++ b/lib/lutaml/xsd/simple_content.rb
@@ -25,6 +25,7 @@ class SimpleContent < Base
end
liquid do
+ map "base_type", to: :base_type
map "attribute_elements", to: :attribute_elements
end
diff --git a/lutaml-xsd.gemspec b/lutaml-xsd.gemspec
index 768f319..33c2dc6 100644
--- a/lutaml-xsd.gemspec
+++ b/lutaml-xsd.gemspec
@@ -34,5 +34,6 @@ Gem::Specification.new do |spec|
# TODO: Remove "activesupport" once lutaml-model > 0.7.7 is available.
spec.add_dependency "activesupport"
+ spec.add_dependency "canon", "0.1.3"
spec.add_dependency "lutaml-model", "~> 0.7"
end
diff --git a/spec/lutaml/xsd/liquid_methods/attribute_group_spec.rb b/spec/lutaml/xsd/liquid_methods/attribute_group_spec.rb
new file mode 100644
index 0000000..a8ae967
--- /dev/null
+++ b/spec/lutaml/xsd/liquid_methods/attribute_group_spec.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+RSpec.describe Lutaml::Xsd::LiquidMethods::AttributeGroup do
+ let(:schema_xml) do
+ <<~XML
+
+
+
+
+
+
+
+
+
+
+ XML
+ end
+
+ let(:schema) { Lutaml::Xsd.parse(schema_xml) }
+ let(:attr_group) { schema.attribute_group.find { |ag| ag.name == "TestAttributeGroup" } }
+ let(:root_type) { schema.complex_type.find { |ct| ct.name == "RootType" } }
+
+ before do
+ attr_group.instance_variable_set(:@__root, schema)
+ root_type.instance_variable_set(:@__root, schema)
+ end
+
+ describe "#used_by" do
+ it "returns complex types that use this attribute group" do
+ used_by = attr_group.used_by
+ expect(used_by).to include(root_type)
+ end
+ end
+
+ describe "#attribute_elements" do
+ it "returns all attributes from the attribute group" do
+ attrs = attr_group.attribute_elements
+ expect(attrs.length).to eq(2)
+ expect(attrs.map(&:name)).to contain_exactly("GroupAttr1", "GroupAttr2")
+ end
+
+ it "accumulates attributes in the provided array" do
+ array = []
+ attr_group.attribute_elements(array)
+ expect(array.length).to eq(2)
+ end
+ end
+
+ describe "#find_used_by" do
+ it "returns true when attribute group is used by the object" do
+ expect(attr_group.find_used_by(root_type)).to be true
+ end
+
+ it "returns false when attribute group is not used" do
+ other_type = Lutaml::Xsd::ComplexType.new(__register: Lutaml::Xsd.register)
+ expect(attr_group.find_used_by(other_type)).to be nil
+ end
+ end
+
+ describe "#referenced_object" do
+ it "returns self when attribute group has a name" do
+ expect(attr_group.referenced_object).to eq(attr_group)
+ end
+
+ it "returns the referenced attribute group when using ref" do
+ ref_group = Lutaml::Xsd::AttributeGroup.new(__register: Lutaml::Xsd.register)
+ ref_group.ref = "TestAttributeGroup"
+ ref_group.instance_variable_set(:@__root, schema)
+ expect(ref_group.referenced_object).to eq(attr_group)
+ end
+ end
+end
diff --git a/spec/lutaml/xsd/liquid_methods/attribute_spec.rb b/spec/lutaml/xsd/liquid_methods/attribute_spec.rb
new file mode 100644
index 0000000..65ae254
--- /dev/null
+++ b/spec/lutaml/xsd/liquid_methods/attribute_spec.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+RSpec.describe Lutaml::Xsd::LiquidMethods::Attribute do
+ let(:schema_xml) do
+ <<~XML
+
+
+
+
+
+ XML
+ end
+
+ let(:schema) { Lutaml::Xsd.parse(schema_xml) }
+ let(:required_attr) { schema.attribute.find { |a| a.name == "RequiredAttribute" } }
+ let(:optional_attr) { schema.attribute.find { |a| a.name == "OptionalAttribute" } }
+ let(:test_attr) { schema.attribute.find { |a| a.name == "TestAttribute" } }
+
+ before do
+ [required_attr, optional_attr, test_attr].each do |attr|
+ attr&.instance_variable_set(:@__root, schema)
+ end
+ end
+
+ describe "#cardinality" do
+ it "returns '1' for required attributes" do
+ required_attr.use = "required"
+ expect(required_attr.cardinality).to eq("1")
+ end
+
+ it "returns '0..1' for optional attributes" do
+ optional_attr.use = "optional"
+ expect(optional_attr.cardinality).to eq("0..1")
+ end
+
+ it "returns nil for attributes without use specified" do
+ test_attr.use = nil
+ expect(test_attr.cardinality).to be_nil
+ end
+ end
+
+ describe "#referenced_type" do
+ it "returns the type of the attribute when it has a name" do
+ test_attr.type = "xs:string"
+ expect(test_attr.referenced_type).to eq("xs:string")
+ end
+
+ it "returns the type of the referenced attribute when using ref" do
+ ref_attr = Lutaml::Xsd::Attribute.new(__register: Lutaml::Xsd.register)
+ ref_attr.ref = "TestAttribute"
+ ref_attr.instance_variable_set(:@__root, schema)
+ test_attr.type = "xs:int"
+ expect(ref_attr.referenced_type).to eq("xs:int")
+ end
+ end
+
+ describe "#referenced_name" do
+ it "returns the name when attribute has a name" do
+ expect(test_attr.referenced_name).to eq("TestAttribute")
+ end
+
+ it "returns the name of referenced attribute when using ref" do
+ ref_attr = Lutaml::Xsd::Attribute.new(__register: Lutaml::Xsd.register)
+ ref_attr.ref = "TestAttribute"
+ ref_attr.instance_variable_set(:@__root, schema)
+ expect(ref_attr.referenced_name).to eq("TestAttribute")
+ end
+
+ it "returns ref when referenced object is not found" do
+ ref_attr = Lutaml::Xsd::Attribute.new(__register: Lutaml::Xsd.register)
+ ref_attr.ref = "NonExistent"
+ ref_attr.instance_variable_set(:@__root, schema)
+ expect(ref_attr.referenced_name).to eq("NonExistent")
+ end
+ end
+
+ describe "#referenced_object" do
+ it "returns self when attribute has a name" do
+ expect(test_attr.referenced_object).to eq(test_attr)
+ end
+
+ it "returns the referenced attribute when using ref" do
+ ref_attr = Lutaml::Xsd::Attribute.new(__register: Lutaml::Xsd.register)
+ ref_attr.ref = "TestAttribute"
+ ref_attr.instance_variable_set(:@__root, schema)
+ expect(ref_attr.referenced_object).to eq(test_attr)
+ end
+ end
+end
diff --git a/spec/lutaml/xsd/liquid_methods/choice_spec.rb b/spec/lutaml/xsd/liquid_methods/choice_spec.rb
new file mode 100644
index 0000000..a02c1a8
--- /dev/null
+++ b/spec/lutaml/xsd/liquid_methods/choice_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+RSpec.describe Lutaml::Xsd::LiquidMethods::Choice do
+ let(:schema_xml) do
+ <<~XML
+
+
+
+
+
+
+
+
+
+
+ XML
+ end
+
+ let(:schema) { Lutaml::Xsd.parse(schema_xml) }
+ let(:root_type) { schema.complex_type.find { |ct| ct.name == "RootType" } }
+ let(:choice) { root_type.sequence.choice.first }
+
+ describe "#child_elements" do
+ it "returns all element children" do
+ elements = choice.child_elements
+ expect(elements.length).to eq(2)
+ expect(elements.map(&:name)).to contain_exactly("ChoiceElement1", "ChoiceElement2")
+ end
+
+ it "accumulates elements in the provided array" do
+ expect(choice.child_elements.length).to eq(2)
+ end
+
+ it "handles nested structures" do
+ nested_schema_xml = <<~XML
+
+
+
+
+
+
+
+
+
+ XML
+ nested_schema = Lutaml::Xsd.parse(nested_schema_xml)
+ nested_choice = nested_schema.complex_type&.first&.choice&.sequence&.first
+ elements = nested_choice.child_elements
+ expect(elements.map(&:name)).to include("NestedElement")
+ end
+ end
+
+ describe "#find_elements_used" do
+ let(:schema_xml_ref_element) do
+ <<~XML
+
+
+
+
+
+
+
+
+
+
+
+
+ XML
+ end
+
+ it "returns true when element is used in choice" do
+ choice = Lutaml::Xsd.parse(schema_xml_ref_element).complex_type.first.sequence.choice.first
+ expect(choice.find_elements_used("Element1")).to be true
+ end
+
+ it "returns false when element is not used" do
+ expect(choice.find_elements_used("NonExistent")).to be false
+ end
+ end
+end
diff --git a/spec/lutaml/xsd/liquid_methods/complex_type_spec.rb b/spec/lutaml/xsd/liquid_methods/complex_type_spec.rb
new file mode 100644
index 0000000..2feabf9
--- /dev/null
+++ b/spec/lutaml/xsd/liquid_methods/complex_type_spec.rb
@@ -0,0 +1,129 @@
+# frozen_string_literal: true
+
+RSpec.describe Lutaml::Xsd::LiquidMethods::ComplexType do
+ let(:schema_xml) do
+ <<~XML
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ XML
+ end
+
+ let(:schema) { Lutaml::Xsd.parse(schema_xml) }
+ let(:root_type) { schema.complex_type.find { |ct| ct.name == "RootType" } }
+ let(:child_type) { schema.complex_type.find { |ct| ct.name == "ChildType" } }
+
+ describe "#used_by" do
+ it "returns elements that use this complex type" do
+ used_by = root_type.used_by
+ expect(used_by).to be_an(Array)
+ expect(used_by.any? { |el| el.type == "RootType" }).to be true
+ end
+ end
+
+ describe "#attribute_elements" do
+ it "returns all attributes including from attribute groups" do
+ attrs = root_type.attribute_elements
+ expect(attrs.length).to be >= 1
+ expect(attrs.map(&:name)).to include("RootAttr")
+ end
+
+ it "includes attributes from attribute groups" do
+ attrs = root_type.attribute_elements
+ attr_names = attrs.map(&:name)
+ expect(attr_names).to include("GroupAttr1")
+ end
+
+ it "includes attributes from simple content" do
+ simple_content_schema = <<~XML
+
+
+
+
+
+
+
+
+
+ XML
+ sc_schema = Lutaml::Xsd.parse(simple_content_schema)
+ sc_type = sc_schema.complex_type.first
+ sc_type.__root = sc_schema
+ attrs = sc_type.attribute_elements
+ expect(attrs.map(&:name)).to include("ExtendedAttr")
+ end
+ end
+
+ describe "#direct_child_elements" do
+ it "returns direct child elements excluding attributes and annotations" do
+ elements = root_type.direct_child_elements
+ expect(elements.any? { |el| el.is_a?(Lutaml::Xsd::Sequence) }).to be true
+ expect(elements.none? { |el| el.is_a?(Lutaml::Xsd::Attribute) }).to be true
+ expect(elements.first.element.any? { |el| el.is_a?(Lutaml::Xsd::Element) }).to be true
+ end
+
+ it "allows custom exception list" do
+ elements = root_type.direct_child_elements(except: ["Element"])
+ expect(elements.none? { |el| el.is_a?(Lutaml::Xsd::Element) }).to be true
+ end
+ end
+
+ describe "#child_elements" do
+ it "returns all nested child elements" do
+ elements = root_type.child_elements
+ expect(elements.length).to be >= 3
+ expect(elements.map(&:referenced_name)).to include("DirectChild", "ChoiceElement1", "GroupElement1")
+ end
+ end
+
+ describe "#find_elements_used" do
+ it "returns true when element is used" do
+ expect(root_type.find_elements_used("DirectChild")).to be true
+ end
+
+ it "returns false when element is not used" do
+ expect(root_type.find_elements_used("NonExistent")).to be false
+ end
+ end
+
+ describe "#find_used_by" do
+ it "returns true when complex type is used by the object" do
+ # This would require setting up a more complex scenario
+ # For now, we test the method exists and returns boolean
+ result = root_type.find_used_by(child_type)
+ expect([true, false]).to include(result)
+ end
+ end
+end
diff --git a/spec/lutaml/xsd/liquid_methods/element_spec.rb b/spec/lutaml/xsd/liquid_methods/element_spec.rb
new file mode 100644
index 0000000..e285011
--- /dev/null
+++ b/spec/lutaml/xsd/liquid_methods/element_spec.rb
@@ -0,0 +1,124 @@
+# frozen_string_literal: true
+
+RSpec.describe Lutaml::Xsd::LiquidMethods::Element do
+ let(:schema_xml) do
+ <<~XML
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ XML
+ end
+
+ let(:schema) { Lutaml::Xsd.parse(schema_xml) }
+ let(:root_element) { schema.element.find { |e| e.name == "RootElement" } }
+ let(:referenced_element) { schema.element.find { |e| e.name == "ReferencedElement" } }
+ let(:simple_element) { schema.element.find { |e| e.name == "SimpleElement" } }
+
+ before do
+ [root_element, referenced_element, simple_element].each do |el|
+ el&.instance_variable_set(:@__root, schema)
+ end
+ schema.complex_type.each { |ct| ct.instance_variable_set(:@__root, schema) }
+ end
+
+ describe "#used_by" do
+ it "returns complex types that use this element" do
+ used_by = root_element.used_by
+ expect(used_by).to be_an(Array)
+ end
+ end
+
+ describe "#attributes" do
+ it "returns attributes from the referenced complex type" do
+ attrs = root_element.attributes
+ expect(attrs).to be_an(Array)
+ expect(attrs.map(&:name)).to include("RootAttr")
+ end
+
+ it "returns empty array when element does not reference a complex type" do
+ attrs = simple_element.attributes
+ expect(attrs).to be_nil
+ end
+ end
+
+ describe "#child_elements" do
+ it "returns child elements from the referenced complex type" do
+ elements = root_element.child_elements
+ expect(elements).to be_an(Array)
+ expect(elements.map(&:name)).to include("DirectChild")
+ end
+
+ it "returns empty array when element does not reference a complex type" do
+ elements = simple_element.child_elements
+ expect(elements).to be_nil
+ end
+ end
+
+ describe "#referenced_name" do
+ it "returns the name when element has a name" do
+ expect(root_element.referenced_name).to eq("RootElement")
+ end
+
+ it "returns the name of referenced element when using ref" do
+ ref_element = Lutaml::Xsd::Element.new(__register: Lutaml::Xsd.register)
+ ref_element.ref = "ReferencedElement"
+ ref_element.instance_variable_set(:@__root, schema)
+ expect(ref_element.referenced_name).to eq("ReferencedElement")
+ end
+ end
+
+ describe "#referenced_type" do
+ it "returns the type when element has a type" do
+ expect(root_element.referenced_type).to eq("RootType")
+ end
+
+ it "returns the type of referenced element when using ref" do
+ ref_element = Lutaml::Xsd::Element.new(__register: Lutaml::Xsd.register)
+ ref_element.ref = "ReferencedElement"
+ ref_element.instance_variable_set(:@__root, schema)
+ expect(ref_element.referenced_type).to eq("xs:string")
+ end
+ end
+
+ describe "#referenced_object" do
+ it "returns self when element has a name" do
+ expect(root_element.referenced_object).to eq(root_element)
+ end
+
+ it "returns the referenced element when using ref" do
+ ref_element = Lutaml::Xsd::Element.new(__register: Lutaml::Xsd.register)
+ ref_element.ref = "ReferencedElement"
+ ref_element.instance_variable_set(:@__root, schema)
+ expect(ref_element.referenced_object).to eq(referenced_element)
+ end
+ end
+
+ describe "#referenced_complex_type" do
+ it "returns the complex type when element references one" do
+ ct = root_element.referenced_complex_type
+ expect(ct).to be_a(Lutaml::Xsd::ComplexType)
+ expect(ct.name).to eq("RootType")
+ end
+
+ it "returns nil when element does not reference a complex type" do
+ expect(simple_element.referenced_complex_type).to be_nil
+ end
+ end
+end
diff --git a/spec/lutaml/xsd/liquid_methods/extension_spec.rb b/spec/lutaml/xsd/liquid_methods/extension_spec.rb
new file mode 100644
index 0000000..414b080
--- /dev/null
+++ b/spec/lutaml/xsd/liquid_methods/extension_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+RSpec.describe Lutaml::Xsd::LiquidMethods::Extension do
+ let(:schema_xml) do
+ <<~XML
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ XML
+ end
+
+ let(:schema) { Lutaml::Xsd.parse(schema_xml) }
+ let(:simple_content_type) { schema.complex_type.find { |ct| ct.name == "SimpleContentType" } }
+ let(:extension) { simple_content_type.simple_content.extension }
+
+ before do
+ extension.attribute_group.each { |ag| ag.instance_variable_set(:@__root, schema) }
+ end
+
+ describe "#attribute_elements" do
+ it "returns attributes from the extension" do
+ attrs = extension.attribute_elements
+ expect(attrs.length).to be >= 1
+ expect(attrs.map(&:name)).to include("ExtendedAttr")
+ end
+
+ it "includes attributes from attribute groups" do
+ attrs = extension.attribute_elements
+ attr_names = attrs.map(&:name)
+ expect(attr_names).to include("GroupAttr1", "GroupAttr2")
+ end
+
+ it "accumulates attributes in the provided array" do
+ array = []
+ extension.attribute_elements(array)
+ expect(array.length).to be >= 3
+ end
+ end
+end
diff --git a/spec/lutaml/xsd/liquid_methods/group_spec.rb b/spec/lutaml/xsd/liquid_methods/group_spec.rb
new file mode 100644
index 0000000..55143c9
--- /dev/null
+++ b/spec/lutaml/xsd/liquid_methods/group_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+RSpec.describe Lutaml::Xsd::LiquidMethods::Group do
+ let(:schema_xml) do
+ <<~XML
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ XML
+ end
+
+ let(:schema) { Lutaml::Xsd.parse(schema_xml) }
+ let(:test_group) { schema.group.find { |g| g.name == "TestGroup" } }
+ let(:nested_group) { schema.group.find { |g| g.name == "NestedGroup" } }
+
+ describe "#child_elements" do
+ it "returns all element children" do
+ elements = test_group.child_elements
+ expect(elements.length).to eq(2)
+ expect(elements.map(&:referenced_name)).to contain_exactly("GroupElement1", "GroupElement2")
+ end
+
+ it "handles nested structures" do
+ elements = nested_group.child_elements
+ expect(elements.map(&:referenced_name)).to include("NestedElement", "NestedSeqElement")
+ end
+
+ it "accumulates elements in the provided array" do
+ array = []
+ test_group.child_elements(array)
+ expect(array.length).to eq(2)
+ end
+ end
+
+ describe "#find_elements_used" do
+ it "returns true when element is used in group" do
+ expect(test_group.find_elements_used("GroupElement1")).to be true
+ end
+
+ it "returns false when element is not used" do
+ expect(test_group.find_elements_used("NonExistent")).to be false
+ end
+
+ it "finds elements in nested structures" do
+ expect(nested_group.find_elements_used("NestedElement")).to be true
+ expect(nested_group.find_elements_used("NestedSeqElement")).to be true
+ end
+ end
+
+ describe "#referenced_object" do
+ it "returns self when group has a name" do
+ expect(test_group.referenced_object).to eq(test_group)
+ end
+
+ it "returns the referenced group when using ref" do
+ ref_group = Lutaml::Xsd::Group.new(__register: Lutaml::Xsd.register)
+ ref_group.ref = "TestGroup"
+ ref_group.instance_variable_set(:@__root, schema)
+ expect(ref_group.referenced_object).to eq(test_group)
+ end
+ end
+end
diff --git a/spec/lutaml/xsd/liquid_methods/schema_spec.rb b/spec/lutaml/xsd/liquid_methods/schema_spec.rb
new file mode 100644
index 0000000..a8bd99b
--- /dev/null
+++ b/spec/lutaml/xsd/liquid_methods/schema_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+RSpec.describe Lutaml::Xsd::LiquidMethods::Schema do
+ let(:schema_xml) do
+ <<~XML
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ XML
+ end
+
+ let(:schema) { Lutaml::Xsd.parse(schema_xml) }
+
+ describe "#elements_sorted_by_name" do
+ it "returns elements sorted alphabetically by name" do
+ element_names = schema.elements_sorted_by_name.map(&:name)
+ expect(element_names).to eq(%w[AlphaElement BetaElement GammaElement])
+ end
+ end
+
+ describe "#complex_types_sorted_by_name" do
+ it "returns complex types sorted alphabetically by name" do
+ complex_type_names = schema.complex_types_sorted_by_name.map(&:name)
+ expect(complex_type_names).to eq(%w[AlphaType BetaType WidgetType])
+ end
+ end
+
+ describe "#attribute_groups_sorted_by_name" do
+ it "returns attribute groups sorted alphabetically by name" do
+ group_names = schema.attribute_groups_sorted_by_name.map(&:name)
+ expect(group_names).to eq(%w[BaseAttributes CoreAttributes ZetaAttributes])
+ end
+ end
+end
diff --git a/spec/lutaml/xsd/liquid_methods/sequence_spec.rb b/spec/lutaml/xsd/liquid_methods/sequence_spec.rb
new file mode 100644
index 0000000..b9628b7
--- /dev/null
+++ b/spec/lutaml/xsd/liquid_methods/sequence_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+RSpec.describe Lutaml::Xsd::LiquidMethods::Sequence do
+ let(:schema_xml) do
+ <<~XML
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ XML
+ end
+
+ let(:schema) { Lutaml::Xsd.parse(schema_xml) }
+ let(:root_type) { schema.complex_type.find { |ct| ct.name == "RootType" } }
+ let(:sequence) { root_type.sequence }
+
+ describe "#child_elements" do
+ it "returns all element children" do
+ elements = sequence.child_elements
+ expected_elements = %w[
+ DirectChild
+ ChoiceElement1
+ ChoiceElement2
+ NestedSeqElement
+ ]
+ expect(elements.length).to be >= 3
+ expect(elements.map(&:referenced_name)).to include(*expected_elements)
+ end
+
+ it "handles nested sequences" do
+ elements = sequence.child_elements
+ expect(elements.map(&:referenced_name)).to include("NestedSeqElement")
+ end
+
+ it "accumulates elements in the provided array" do
+ array = []
+ sequence.child_elements(array)
+ expect(array.length).to be >= 3
+ end
+ end
+
+ describe "#find_elements_used" do
+ it "returns true when element is used in sequence" do
+ expect(sequence.find_elements_used("DirectChild")).to be true
+ end
+
+ it "returns false when element is not used" do
+ expect(sequence.find_elements_used("NonExistent")).to be false
+ end
+
+ it "finds elements in nested structures" do
+ expect(sequence.find_elements_used("ChoiceElement1")).to be true
+ expect(sequence.find_elements_used("NestedSeqElement")).to be true
+ end
+ end
+end
diff --git a/spec/lutaml/xsd/liquid_methods/simple_content_spec.rb b/spec/lutaml/xsd/liquid_methods/simple_content_spec.rb
new file mode 100644
index 0000000..a33dd18
--- /dev/null
+++ b/spec/lutaml/xsd/liquid_methods/simple_content_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+RSpec.describe Lutaml::Xsd::LiquidMethods::SimpleContent do
+ let(:schema_xml) do
+ <<~XML
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ XML
+ end
+
+ let(:schema) { Lutaml::Xsd.parse(schema_xml) }
+ let(:complex_types) { schema.complex_type }
+ let(:simple_content) { complex_types.find { |ct| ct.name == "SimpleContentType" }.simple_content }
+
+ describe "#attribute_elements" do
+ it "returns attributes from the extension" do
+ attrs = simple_content.attribute_elements
+ expect(attrs.length).to be >= 1
+ expect(attrs.map(&:name)).to include("ExtendedAttr")
+ end
+
+ it "includes attributes from attribute groups in extension" do
+ attrs = simple_content.attribute_elements
+ attr_names = attrs.map(&:name)
+ expect(attr_names).to include("GroupAttr1")
+ end
+ end
+
+ describe "#base_type" do
+ let(:simple_content_with_base) { complex_types.find { |ct| ct.name == "SimpleContentWithBase" } }
+ let(:simple_content_with_restriction) { complex_types.find { |ct| ct.name == "SimpleContentWithRestriction" } }
+
+ it "returns base from extension when present" do
+ expect(simple_content.base_type).to eq("xs:string")
+ end
+
+ it "returns base from restriction when present" do
+ expect(simple_content_with_restriction.simple_content.base_type).to eq("xs:string")
+ end
+
+ it "returns base attribute when extension and restriction are nil" do
+ expect(simple_content_with_base.simple_content.base_type).to eq("xs:int")
+ end
+
+ it "returns nil when no base is specified" do
+ sc = Lutaml::Xsd::SimpleContent.new(__register: Lutaml::Xsd.register)
+ expect(sc.base_type).to be_nil
+ end
+ end
+end
diff --git a/spec/lutaml/xsd_spec.rb b/spec/lutaml/xsd_spec.rb
index 3fd021e..d7d18db 100644
--- a/spec/lutaml/xsd_spec.rb
+++ b/spec/lutaml/xsd_spec.rb
@@ -10,6 +10,15 @@
"unitsml-v1.0-csd03": nil
}.freeze
+SCHEMA_ELEMENTS_REGEXES = {
+ imports: /<\w+:import /,
+ includes: /<\w+:include /,
+ group: /<\w+:group name=/,
+ simple_type: /<\w+:simpleType /,
+ element: /^\s{0,2}<\w+:element /,
+ complex_type: /<\w+:complexType /
+}.freeze
+
RSpec.describe Lutaml::Xsd do
subject(:parsed_schema) { described_class.parse(schema, location: location) }
@@ -25,15 +34,7 @@
end
it "matches count of direct child elements of the root" do
- expected_counts = {
- imports: /<\w+:import /,
- includes: /<\w+:include /,
- group: /<\w+:group name=/,
- simple_type: /<\w+:simpleType /,
- element: /^\s{0,2}<\w+:element /,
- complex_type: /<\w+:complexType /
- }
- expected_counts.each_pair do |key, regex|
+ SCHEMA_ELEMENTS_REGEXES.each_pair do |key, regex|
value = parsed_schema.send(key)
count = value.nil? ? 0 : value.count
expect(count).to eql(schema.scan(regex).count)
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 1f34aae..76cf9dd 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -2,7 +2,7 @@
require "liquid"
require "lutaml/xsd"
-require "xml/c14n"
+require "canon"
RSpec.configure do |config|
# Enable flags like --only-failures and --next-failure
@@ -17,7 +17,7 @@
end
def schema_to_xml(xml, escape_content_tags: false)
- xml = Xml::C14n.format(xml)
+ xml = Canon.format_xml(xml)
xml.gsub!(/<([^&]+)>/, '<\1>') if escape_content_tags
xml
end