Module: Idml::Schema::Rnc

Defined in:
lib/idml/schema/rnc.rb

Overview

Parses RelaxNG Compact element definitions into data: element name → wire attribute names. Handles the Adobe package schemas' shape (NAME_Object = element Name { ... } with balanced braces; attributes as attribute X { type }). Two consumers over this seam: the class generator script and the schema-conformance spec.

Constant Summary collapse

ELEMENT_DEF =
/(\w+)\s*+=\s*+element\s*+([\w:]+)\s*+\{/
ATTRIBUTE_DEF =
/attribute\s++(\w+)\s*+\{([^}]*)\}/

Class Method Summary collapse

Class Method Details

.balanced_body(src, from) ⇒ Object

The balanced-brace body starting just after from (which points past the opening brace); nil when unterminated.



72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/idml/schema/rnc.rb', line 72

def balanced_body(src, from)
  depth = 1
  i = from
  while i < src.length && depth.positive?
    case src[i]
    when "{" then depth += 1
    when "}" then depth -= 1
    end
    i += 1
  end
  return nil if depth.positive?

  src[from...(i - 1)]
end

.body_attributes(body) ⇒ Object



48
49
50
# File 'lib/idml/schema/rnc.rb', line 48

def body_attributes(body)
  body.scan(ATTRIBUTE_DEF).map(&:first).uniq
end

.each_element(paths, &block) ⇒ Object



52
53
54
55
56
# File 'lib/idml/schema/rnc.rb', line 52

def each_element(paths, &block)
  paths.each do |path|
    scan_file(path, &block)
  end
end

.element_attribute_map(paths) ⇒ Object

{ "Story" => [["Self", ...]], ... } for every element definition across the given RNC files. An element defined in several files keeps each definition as an alternative (array of attribute-name arrays) — a class conforms when its wire attributes equal ANY one alternative.



22
23
24
25
26
27
28
# File 'lib/idml/schema/rnc.rb', line 22

def element_attribute_map(paths)
  definitions = Hash.new { |h, k| h[k] = [] }
  each_element(paths) do |element_name, body|
    definitions[element_name] << body_attributes(body)
  end
  definitions.to_h
end

.element_definition(path, definition_name) ⇒ Object

[xml_element_name, attrs_with_types] for one named definition (Story_Object) in one file; nil when absent. attrs_with_types: [["Self", "xsd:string"], ...].



33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/idml/schema/rnc.rb', line 33

def element_definition(path, definition_name)
  src = File.read(path)
  prefix = /#{Regexp.escape(definition_name)}\s*=\s*element\s+/
  pattern = /#{prefix}([\w:]+)\s*\{/
  match = src.match(pattern)
  return nil unless match

  body = balanced_body(src, match.end(0))
  return nil unless body

  attrs_with_types = body.scan(ATTRIBUTE_DEF)
    .map { |name, type| [name, type.strip] }
  [match[1], attrs_with_types]
end

.scan_file(path) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
# File 'lib/idml/schema/rnc.rb', line 58

def scan_file(path)
  src = File.read(path)
  src.scan(ELEMENT_DEF) do
    match = Regexp.last_match
    body = balanced_body(src, match.end(0))
    next unless body

    element_name = match[2].sub(/^idPkg:/, "")
    yield(element_name, body)
  end
end