class Lutaml::Model::Schema::RngCompiler::ElementVisitor

for individual children is delegated to ValueTypeResolver.
union) is delegated to DefineClassifier. Value-shape resolution
Shape classification (define-wraps-data, define-wraps-enum-choice,
interleave
optional, zeroOrMore, oneOrMore, group, choice, mixed, list,
element, attribute, ref, text, data, value, empty,
Supported constructs:
objects, registered in the shared ‘classes` hash.
Definitions::Model / Definitions::RestrictedType / Definitions::UnionType
Walks an RNG <element> / <define> subtree and produces

def attribute_type_symbol(child)

subclass — so wrap the value in a generated namespaced type.
namespace, which lutaml-model expresses only through a Type::Value
namespace (a prefix other than the built-in "xml") must carry that
The attribute's value type, as a Symbol. An attribute in a foreign
def attribute_type_symbol(child)
  base = @value_type_resolver.resolve(child) || :string
  uri = foreign_namespace_uri(child)
  return base unless uri
  namespaced_attribute_type(child, base, @register_namespace.call(uri))
end

def build_attribute(child, type_ref, kind, ctx, doc, default: nil)

def build_attribute(child, type_ref, kind, ctx, doc, default: nil)
  xml_name = xml_name_for(child)
  Definitions::Attribute.new(
    name: Utils.snake_case(xml_name.tr(":", "_")),
    type: type_ref,
    xml_name: xml_name,
    kind: kind,
    collection: ctx[:collection] || false,
    initialize_empty: ctx[:initialize_empty] || false,
    documentation: doc,
    default: default,
  )
end

def build_complex_define(define, class_name)

def build_complex_define(define, class_name)
  wrapping = define.element.size == 1 ? define.element.first : nil
  xml_root = if wrapping
               Definitions::XmlRoot.new(kind: :element, name: wrapping.attr_name)
             else
               Definitions::XmlRoot.new(kind: :fragment)
             end
  model = Definitions::Model.new(
    class_name: class_name,
    xml_root: xml_root,
    documentation: documentation_text(define) || documentation_text(wrapping),
    namespace_class_name: namespace_class_for(wrapping || define),
  )
  register_class!(model)
  visit_content(wrapping || define, model)
  model
end

def build_ref_attribute(ref, type_ref, xml_name, ctx)

def build_ref_attribute(ref, type_ref, xml_name, ctx)
  Definitions::Attribute.new(
    name: Utils.snake_case(ref.name),
    type: type_ref,
    xml_name: xml_name,
    kind: :element,
    collection: ctx[:collection] || false,
    initialize_empty: ctx[:initialize_empty] || false,
  )
end

def compile_define(define)

- Multiple or zero wrapped elements -> fragment Definitions::Model.
- Wraps exactly one element -> rooted Definitions::Model.
- Define body matches a simple-type shape -> RestrictedType/UnionType.
Compile a into a class.
def compile_define(define)
  class_name = Utils.camel_case(define.name)
  return @classes[class_name] if @classes.key?(class_name)
  if (simple = DefineClassifier.build(define, class_name))
    register_class!(simple)
    return simple
  end
  build_complex_define(define, class_name)
end

def compile_element(element)

Compile a top-level . Always becomes a rooted model.
def compile_element(element)
  class_name = Utils.camel_case(element.attr_name)
  model = @classes[class_name] ||= Definitions::Model.new(
    class_name: class_name,
    xml_root: Definitions::XmlRoot.new(kind: :element, name: element.attr_name),
    documentation: documentation_text(element),
    namespace_class_name: namespace_class_for(element),
  )
  visit_content(element, model)
  model
end

def default_ctx

def default_ctx
  { collection: nil, initialize_empty: false }
end

def dispatch(kind, child, parent, ctx)

def dispatch(kind, child, parent, ctx)
  handler = HANDLERS[kind] or return
  send(handler, kind, child, parent, ctx)
end

def documentation_text(node)

def documentation_text(node)
  return nil unless node.respond_to?(:documentation)
  docs = Array(node.documentation).map(&:to_s).reject(&:empty?)
  docs.empty? ? nil : docs.join("\n")
end

def element_order_entries(node)

def element_order_entries(node)
  return [] unless node.respond_to?(:element_order)
  Array(node.element_order).select do |e|
    e.respond_to?(:node_type) && e.node_type == :element
  end
end

def fallback_each_child_kind(node)

def fallback_each_child_kind(node)
  DISPATCHABLE_KINDS.each_with_object([]) do |attr_name, pairs|
    next unless node.respond_to?(attr_name)
    Array(node.public_send(attr_name)).each do |child|
      pairs << [attr_name, child] unless child.nil?
    end
  end
end

def fixed_value_default(container)

the value round-trips even when omitted.
Match XSD compiler's behavior — emit `default: -> { "X" }` so
X = fixed value.
def fixed_value_default(container)
  return nil unless container.respond_to?(:value)
  values = Array(container.value)
  return nil if values.size != 1
  return nil if RngHelpers.structural_content?(container)
  values.first.value.to_s
end

def foreign_namespace_uri(node)

empty-URI namespace that would clobber the grammar default.
honored -- excluding "" here just avoids registering a bogus
represented distinctly from the default and is not separately
only at class level, so a per-node `ns=""` override cannot be
node the grammar default namespace. lutaml expresses namespaces
both treated as not foreign, so `namespace_class_for` hands the
`ns=""` no-namespace override) and the built-in "xml" prefix are
A node's foreign XML namespace URI, or nil. An empty `ns` (RNC's
def foreign_namespace_uri(node)
  uri = node.respond_to?(:ns) ? node.ns : nil
  uri if uri.is_a?(String) && !uri.empty? && uri != "xml"
end

def generated_type_for(base)

the primitive `:date_time`).
even if a define shares its name (a custom `dateTime` must not shadow
built-in symbol. A built-in symbol always means the built-in type,
by symbol identity (so acronym class names round-trip) — or nil for a
The generated RestrictedType a resolved type symbol refers to, found
def generated_type_for(base)
  return nil if Lutaml::Model::Type::TYPE_CODES.key?(base)
  @classes.values.find do |klass|
    klass.is_a?(Definitions::RestrictedType) &&
      RngHelpers.type_symbol(klass.class_name) == base
  end
end

def handle_attribute(_kind, child, parent, ctx)

def handle_attribute(_kind, child, parent, ctx)
  doc = documentation_text(child)
  symbol = attribute_type_symbol(child)
  type_ref = Definitions::TypeRef.new(kind: :symbol, value: symbol.to_s)
  fixed = fixed_value_default(child)
  push_attribute(parent, build_attribute(child, type_ref, :attribute, ctx, doc, default: fixed))
end

def handle_choice(_kind, choice, parent, ctx)

def handle_choice(_kind, choice, parent, ctx)
  return if RngHelpers.pure_value_choice?(choice)
  collector = MemberCollector.new
  visit_content(choice, collector, ctx)
  return if collector.members.empty?
  parent.members << Definitions::Choice.new(
    alternatives: collector.members,
    header: "choice",
  )
  collector.imports.each { |name| push_import(parent, name) }
end

def handle_element(_kind, child, parent, ctx)

def handle_element(_kind, child, parent, ctx)
  doc = documentation_text(child)
  value_type = @value_type_resolver.resolve(child)
  type_ref = type_ref_for_element(child, value_type)
  push_attribute(parent, build_attribute(child, type_ref, :element, ctx, doc))
end

def handle_empty(_kind, _child, _parent, _ctx)

def handle_empty(_kind, _child, _parent, _ctx)
  # <empty/> contributes no content — intentionally no-op.
end

def handle_group(_kind, group, parent, ctx)

= ordered sequence of items.
def handle_group(_kind, group, parent, ctx)
  collector = MemberCollector.new
  visit_content(group, collector, ctx)
  return if collector.members.empty?
  parent.members << Definitions::Sequence.new(members: collector.members)
  collector.imports.each { |name| push_import(parent, name) }
end

def handle_interleave(_kind, interleave, parent, ctx)

def handle_interleave(_kind, interleave, parent, ctx)
  visit_content(interleave, parent, ctx)
end

def handle_optional(_kind, opt, parent, ctx)

def handle_optional(_kind, opt, parent, ctx)
  visit_content(opt, parent, ctx)
end

def handle_ref(_kind, ref, parent, ctx)

def handle_ref(_kind, ref, parent, ctx)
  target_define = @defines[ref.name]
  raise Lutaml::Model::Error, "ref to unknown define: #{ref.name}" unless target_define
  target_class = compile_define(target_define)
  if RngHelpers.simple_type?(target_class)
    type_ref = Definitions::TypeRef.new(kind: :symbol, value: RngHelpers.type_symbol(target_class.class_name).to_s)
    push_attribute(parent, build_ref_attribute(ref, type_ref, ref.name, ctx))
  elsif RngHelpers.fragment_model?(target_class) && ctx[:collection].nil?
    push_import(parent, target_class.class_name)
  else
    type_ref = Definitions::TypeRef.new(kind: :class_ref, value: target_class.class_name)
    xml_name = target_class.xml_root.name || ref.name
    push_attribute(parent, build_ref_attribute(ref, type_ref, xml_name, ctx))
  end
end

def handle_repeating(kind, node, parent, ctx)

def handle_repeating(kind, node, parent, ctx)
  visit_content(node, parent, ctx.merge(REPETITION_CTX.fetch(kind)))
end

def initialize(defines, classes, default_namespace_class: nil,

def initialize(defines, classes, default_namespace_class: nil,
               register_namespace: nil)
  @defines = defines
  @classes = classes
  @default_namespace_class = default_namespace_class
  @register_namespace = register_namespace
  @value_type_resolver = ValueTypeResolver.new(
    defines, classes,
    compile_define: method(:compile_define),
    register_class: method(:register_class!)
  )
end

def mixed?(node)

def mixed?(node)
  node.respond_to?(:mixed) && Utils.present?(node.mixed)
end

def namespace_class_for(node)

class-level namespace; otherwise it inherits the grammar default.
An element's own ns (from `element ex:name`) becomes its model's
def namespace_class_for(node)
  uri = foreign_namespace_uri(node)
  uri ? @register_namespace.call(uri) : @default_namespace_class
end

def namespaced_attribute_type(child, base, ns_class)

its constraints are inherited; otherwise subclass the built-in base.
resolved base is itself a generated RestrictedType, subclass it so
define or an inline type shared with unqualified members. When the
type — never by mutating the resolved type, which may be a named
Give the attribute's value a namespace via a dedicated generated
def namespaced_attribute_type(child, base, ns_class)
  resolved = generated_type_for(base)
  parent = resolved ? resolved.class_name : RngHelpers.parent_class_for(base)
  type = Definitions::RestrictedType.new(
    class_name: RngHelpers.unique_class_name(
      @classes, "#{Utils.camel_case(child.attr_name)}Type"
    ),
    parent_class: parent,
    facets: Definitions::Facet.new,
    namespace_class_name: ns_class,
  )
  register_class!(type)
  RngHelpers.type_symbol(type.class_name)
end

def ordered_children(node)

def ordered_children(node)
  element_entries = element_order_entries(node)
  return fallback_each_child_kind(node) if element_entries.empty?
  arrays = {}
  indices = ::Hash.new(0)
  element_entries.each_with_object([]) do |entry, pairs|
    kind = entry.name.to_sym
    next unless DISPATCHABLE_KINDS.include?(kind) && node.respond_to?(kind)
    children = arrays[kind] ||= Array(node.public_send(kind))
    child = children[indices[kind]]
    indices[kind] += 1
    pairs << [kind, child] if child
  end
end

def push_attribute(parent, attr)

def push_attribute(parent, attr)
  parent.members.reject! do |m|
    m.is_a?(Definitions::Attribute) && m.name == attr.name
  end
  parent.members << attr
end

def push_import(parent, name)

def push_import(parent, name)
  parent.imports << name unless parent.imports.include?(name)
end

def register_class!(klass)

def register_class!(klass)
  @classes[klass.class_name] = klass
end

def text?(node)

def text?(node)
  node.respond_to?(:text) && Utils.present?(node.text)
end

def type_ref_for_element(child, value_type)

def type_ref_for_element(child, value_type)
  return Definitions::TypeRef.new(kind: :symbol, value: value_type.to_s) if value_type
  compiled = compile_element(child)
  Definitions::TypeRef.new(kind: :class_ref, value: compiled.class_name)
end

def visit_content(node, model, ctx = default_ctx)

Generic content walker.
def visit_content(node, model, ctx = default_ctx)
  ordered_children(node).each do |kind, child|
    dispatch(kind, child, model, ctx)
  end
  model.mixed = true if mixed?(node)
  model.text_content = true if text?(node)
end

def xml_name_for(child)

def xml_name_for(child)
  name = child.attr_name
  ns = child.respond_to?(:ns) ? child.ns : nil
  return name unless ns == "xml"
  "#{ns}:#{name}"
end