rng 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.
- checksums.yaml +7 -0
- data/.github/workflows/rake.yml +15 -0
- data/.github/workflows/release.yml +23 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/Gemfile +16 -0
- data/Rakefile +12 -0
- data/lib/rng/attribute.rb +13 -0
- data/lib/rng/builder.rb +158 -0
- data/lib/rng/define.rb +14 -0
- data/lib/rng/element.rb +26 -0
- data/lib/rng/rnc_parser.rb +136 -0
- data/lib/rng/rng_parser.rb +107 -0
- data/lib/rng/schema.rb +18 -0
- data/lib/rng/start.rb +14 -0
- data/lib/rng/version.rb +5 -0
- data/lib/rng.rb +12 -0
- data/rng.gemspec +38 -0
- data/sig/rng.rbs +4 -0
- data/spec/rng/rnc_parser_spec.rb +66 -0
- data/spec/rng/rng_parser_spec.rb +102 -0
- data/spec/rng/schema_spec.rb +193 -0
- data/spec/rng_spec.rb +7 -0
- data/spec/spec_helper.rb +23 -0
- metadata +114 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA256:
         | 
| 3 | 
            +
              metadata.gz: 3b70e1b28d17dd9d6a287a404f218151f5a25ab38eba65aba55330d1e5692a08
         | 
| 4 | 
            +
              data.tar.gz: d8524a2367eb0c73b64f9040555a2269ad2e6f8121a5ed5d05021c20ad00042b
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: 06bc5ef5f3d56f356730e71b42ee2080ab449ed1c769446a315a8077a015327bea94635e172ab9dcbd1d2559eacbb21ad519f3c5dc48fc45c5c7c7ee577326e2
         | 
| 7 | 
            +
              data.tar.gz: 2eca01084c177a824f02c5f30c03fc2463de4fee55fab370c9fc951bc51cbf56fadebee4f326c1472754f394361a4e932979d269bf130b1a23312af20feb6483
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            # Auto-generated by Cimas: Do not edit it manually!
         | 
| 2 | 
            +
            # See https://github.com/metanorma/cimas
         | 
| 3 | 
            +
            name: rake
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            on:
         | 
| 6 | 
            +
              push:
         | 
| 7 | 
            +
                branches: [ master, main ]
         | 
| 8 | 
            +
                tags: [ v* ]
         | 
| 9 | 
            +
              pull_request:
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            jobs:
         | 
| 12 | 
            +
              rake:
         | 
| 13 | 
            +
                uses: metanorma/ci/.github/workflows/generic-rake.yml@main
         | 
| 14 | 
            +
                secrets:
         | 
| 15 | 
            +
                  pat_token: ${{ secrets.LUTAML_CI_PAT_TOKEN }}
         | 
| @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            # Auto-generated by Cimas: Do not edit it manually!
         | 
| 2 | 
            +
            # See https://github.com/metanorma/cimas
         | 
| 3 | 
            +
            name: release
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            on:
         | 
| 6 | 
            +
              workflow_dispatch:
         | 
| 7 | 
            +
                inputs:
         | 
| 8 | 
            +
                  next_version:
         | 
| 9 | 
            +
                    description: |
         | 
| 10 | 
            +
                      Next release version. Possible values: x.y.z, major, minor, patch or pre|rc|etc
         | 
| 11 | 
            +
                    required: true
         | 
| 12 | 
            +
                    default: 'skip'
         | 
| 13 | 
            +
              repository_dispatch:
         | 
| 14 | 
            +
                types: [ do-release ]
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            jobs:
         | 
| 17 | 
            +
              release:
         | 
| 18 | 
            +
                uses: metanorma/ci/.github/workflows/rubygems-release.yml@main
         | 
| 19 | 
            +
                with:
         | 
| 20 | 
            +
                  next_version: ${{ github.event.inputs.next_version }}
         | 
| 21 | 
            +
                secrets:
         | 
| 22 | 
            +
                  rubygems-api-key: ${{ secrets.LUTAML_CI_RUBYGEMS_API_KEY }}
         | 
| 23 | 
            +
                  pat_token: ${{ secrets.LUTAML_CI_PAT_TOKEN }}
         | 
    
        data/.gitignore
    ADDED
    
    
    
        data/.rspec
    ADDED
    
    
    
        data/.rubocop.yml
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    | @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            source "https://rubygems.org"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            # Specify your gem's dependencies in rng.gemspec
         | 
| 6 | 
            +
            gemspec
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            gem "equivalent-xml"
         | 
| 9 | 
            +
            gem "nokogiri"
         | 
| 10 | 
            +
            gem "xml-c14n"
         | 
| 11 | 
            +
            gem "rake", "~> 13.0"
         | 
| 12 | 
            +
            gem "rspec", "~> 3.0"
         | 
| 13 | 
            +
            gem "rubocop", "~> 1.21"
         | 
| 14 | 
            +
            gem "rubocop-performance", require: false
         | 
| 15 | 
            +
            gem "rubocop-rake", require: false
         | 
| 16 | 
            +
            gem "rubocop-rspec", require: false
         | 
    
        data/Rakefile
    ADDED
    
    
| @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            require "lutaml/model"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Rng
         | 
| 4 | 
            +
              class Attribute < Lutaml::Model::Serializable
         | 
| 5 | 
            +
                attribute :name, :string
         | 
| 6 | 
            +
                attribute :type, :string, collection: true
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                xml do
         | 
| 9 | 
            +
                  map_attribute "name", to: :name
         | 
| 10 | 
            +
                  map_element "data", to: :type
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
            end
         | 
    
        data/lib/rng/builder.rb
    ADDED
    
    | @@ -0,0 +1,158 @@ | |
| 1 | 
            +
            module Rng
         | 
| 2 | 
            +
              class Builder
         | 
| 3 | 
            +
                def build(schema, format:)
         | 
| 4 | 
            +
                  case format
         | 
| 5 | 
            +
                  when :rng
         | 
| 6 | 
            +
                    build_rng(schema)
         | 
| 7 | 
            +
                  when :rnc
         | 
| 8 | 
            +
                    build_rnc(schema)
         | 
| 9 | 
            +
                  else
         | 
| 10 | 
            +
                    raise ArgumentError, "Unsupported format: #{format}"
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                private
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def build_rng(schema)
         | 
| 17 | 
            +
                  doc = Nokogiri::XML::Document.new
         | 
| 18 | 
            +
                  doc.encoding = "UTF-8"
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  if schema.is_a?(Rng::Schema)
         | 
| 21 | 
            +
                    root = Nokogiri::XML::Node.new("grammar", doc)
         | 
| 22 | 
            +
                    root["xmlns"] = "http://relaxng.org/ns/structure/1.0"
         | 
| 23 | 
            +
                    doc.root = root
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    start = Nokogiri::XML::Node.new("start", doc)
         | 
| 26 | 
            +
                    root.add_child(start)
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    if schema.start.ref
         | 
| 29 | 
            +
                      ref = Nokogiri::XML::Node.new("ref", doc)
         | 
| 30 | 
            +
                      ref["name"] = schema.start.ref
         | 
| 31 | 
            +
                      start.add_child(ref)
         | 
| 32 | 
            +
                    end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    schema.start.elements.each do |element|
         | 
| 35 | 
            +
                      start.add_child(build_rng_element(element, doc))
         | 
| 36 | 
            +
                    end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    schema.define&.each do |define|
         | 
| 39 | 
            +
                      define_node = Nokogiri::XML::Node.new("define", doc)
         | 
| 40 | 
            +
                      define_node["name"] = define.name
         | 
| 41 | 
            +
                      define.elements.each do |element|
         | 
| 42 | 
            +
                        define_node.add_child(build_rng_element(element, doc))
         | 
| 43 | 
            +
                      end
         | 
| 44 | 
            +
                      root.add_child(define_node)
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
                  elsif schema.is_a?(Rng::Element)
         | 
| 47 | 
            +
                    el = build_rng_element(schema, doc)
         | 
| 48 | 
            +
                    el["xmlns"] = "http://relaxng.org/ns/structure/1.0"
         | 
| 49 | 
            +
                    doc.root = el
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  doc.to_xml
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def build_rng_element(element, doc)
         | 
| 56 | 
            +
                  if element.zero_or_more&.any?
         | 
| 57 | 
            +
                    zero_or_more = Nokogiri::XML::Node.new("zeroOrMore", doc)
         | 
| 58 | 
            +
                    el = Nokogiri::XML::Node.new("element", doc)
         | 
| 59 | 
            +
                    el["name"] = element.name
         | 
| 60 | 
            +
                    add_element_content(element, el, doc)
         | 
| 61 | 
            +
                    zero_or_more.add_child(el)
         | 
| 62 | 
            +
                    return zero_or_more
         | 
| 63 | 
            +
                  elsif element.one_or_more&.any?
         | 
| 64 | 
            +
                    one_or_more = Nokogiri::XML::Node.new("oneOrMore", doc)
         | 
| 65 | 
            +
                    el = Nokogiri::XML::Node.new("element", doc)
         | 
| 66 | 
            +
                    el["name"] = element.name
         | 
| 67 | 
            +
                    add_element_content(element, el, doc)
         | 
| 68 | 
            +
                    one_or_more.add_child(el)
         | 
| 69 | 
            +
                    return one_or_more
         | 
| 70 | 
            +
                  elsif element.optional&.any?
         | 
| 71 | 
            +
                    optional = Nokogiri::XML::Node.new("optional", doc)
         | 
| 72 | 
            +
                    el = Nokogiri::XML::Node.new("element", doc)
         | 
| 73 | 
            +
                    el["name"] = element.name
         | 
| 74 | 
            +
                    add_element_content(element, el, doc)
         | 
| 75 | 
            +
                    optional.add_child(el)
         | 
| 76 | 
            +
                    return optional
         | 
| 77 | 
            +
                  else
         | 
| 78 | 
            +
                    el = Nokogiri::XML::Node.new("element", doc)
         | 
| 79 | 
            +
                    el["name"] = element.name
         | 
| 80 | 
            +
                    add_element_content(element, el, doc)
         | 
| 81 | 
            +
                    return el
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                def add_element_content(element, el, doc)
         | 
| 86 | 
            +
                  element.attributes&.each do |attr|
         | 
| 87 | 
            +
                    attr_node = Nokogiri::XML::Node.new("attribute", doc)
         | 
| 88 | 
            +
                    attr_node["name"] = attr.name
         | 
| 89 | 
            +
                    if attr.type&.any?
         | 
| 90 | 
            +
                      data = Nokogiri::XML::Node.new("data", doc)
         | 
| 91 | 
            +
                      data["type"] = attr.type.first
         | 
| 92 | 
            +
                      attr_node.add_child(data)
         | 
| 93 | 
            +
                    else
         | 
| 94 | 
            +
                      text = Nokogiri::XML::Node.new("text", doc)
         | 
| 95 | 
            +
                      attr_node.add_child(text)
         | 
| 96 | 
            +
                    end
         | 
| 97 | 
            +
                    el.add_child(attr_node)
         | 
| 98 | 
            +
                  end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                  element.elements&.each do |child|
         | 
| 101 | 
            +
                    el.add_child(build_rng_element(child, doc))
         | 
| 102 | 
            +
                  end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                  if element.text
         | 
| 105 | 
            +
                    text = Nokogiri::XML::Node.new("text", doc)
         | 
| 106 | 
            +
                    el.add_child(text)
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                def build_rnc(schema)
         | 
| 111 | 
            +
                  result = ""
         | 
| 112 | 
            +
                  elements = schema.is_a?(Rng::Schema) ? schema.start.elements : [schema]
         | 
| 113 | 
            +
                  elements.each do |element|
         | 
| 114 | 
            +
                    result += build_rnc_element(element)
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
                  result
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                def build_rnc_element(element, indent = 0)
         | 
| 120 | 
            +
                  return "" unless element # Handle nil elements
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                  result = "  " * indent
         | 
| 123 | 
            +
                  result += "element #{element.name} {\n"
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                  element.attributes&.each do |attr|
         | 
| 126 | 
            +
                    result += "  " * (indent + 1)
         | 
| 127 | 
            +
                    result += "attribute #{attr.name} { text }"
         | 
| 128 | 
            +
                    result += ",\n" unless element.attributes.last == attr && !element.elements&.any? && !element.text
         | 
| 129 | 
            +
                  end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                  element.elements&.each_with_index do |child, index|
         | 
| 132 | 
            +
                    child_result = build_rnc_element(child, indent + 1)
         | 
| 133 | 
            +
                    result += child_result
         | 
| 134 | 
            +
                    result += "," unless index == element.elements.size - 1 && !element.text
         | 
| 135 | 
            +
                    result += "\n"
         | 
| 136 | 
            +
                  end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                  if element.text
         | 
| 139 | 
            +
                    result += "  " * (indent + 1)
         | 
| 140 | 
            +
                    result += "text"
         | 
| 141 | 
            +
                    result += "\n"
         | 
| 142 | 
            +
                  end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                  result += "  " * indent
         | 
| 145 | 
            +
                  result += "}"
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                  if element.zero_or_more&.any?
         | 
| 148 | 
            +
                    result += "*"
         | 
| 149 | 
            +
                  elsif element.one_or_more&.any?
         | 
| 150 | 
            +
                    result += "+"
         | 
| 151 | 
            +
                  elsif element.optional&.any?
         | 
| 152 | 
            +
                    result += "?"
         | 
| 153 | 
            +
                  end
         | 
| 154 | 
            +
             | 
| 155 | 
            +
                  result
         | 
| 156 | 
            +
                end
         | 
| 157 | 
            +
              end
         | 
| 158 | 
            +
            end
         | 
    
        data/lib/rng/define.rb
    ADDED
    
    | @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            require "lutaml/model"
         | 
| 2 | 
            +
            require_relative "element"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Rng
         | 
| 5 | 
            +
              class Define < Lutaml::Model::Serializable
         | 
| 6 | 
            +
                attribute :name, :string
         | 
| 7 | 
            +
                attribute :elements, Element, collection: true
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                xml do
         | 
| 10 | 
            +
                  map_attribute "name", to: :name
         | 
| 11 | 
            +
                  map_element "element", to: :elements
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
            end
         | 
    
        data/lib/rng/element.rb
    ADDED
    
    | @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            require "lutaml/model"
         | 
| 2 | 
            +
            require_relative "attribute"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Rng
         | 
| 5 | 
            +
              class Element < Lutaml::Model::Serializable
         | 
| 6 | 
            +
                attribute :name, :string
         | 
| 7 | 
            +
                attribute :attributes, Attribute, collection: true
         | 
| 8 | 
            +
                attribute :elements, Element, collection: true
         | 
| 9 | 
            +
                attribute :text, :boolean
         | 
| 10 | 
            +
                attribute :zero_or_more, Element, collection: true
         | 
| 11 | 
            +
                attribute :one_or_more, Element, collection: true
         | 
| 12 | 
            +
                attribute :optional, Element, collection: true
         | 
| 13 | 
            +
                attribute :choice, Element, collection: true
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                xml do
         | 
| 16 | 
            +
                  map_attribute "name", to: :name
         | 
| 17 | 
            +
                  map_element "attribute", to: :attributes
         | 
| 18 | 
            +
                  map_element "element", to: :elements
         | 
| 19 | 
            +
                  map_element "text", to: :text
         | 
| 20 | 
            +
                  map_element "zeroOrMore", to: :zero_or_more
         | 
| 21 | 
            +
                  map_element "oneOrMore", to: :one_or_more
         | 
| 22 | 
            +
                  map_element "optional", to: :optional
         | 
| 23 | 
            +
                  map_element "choice", to: :choice
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
            end
         | 
| @@ -0,0 +1,136 @@ | |
| 1 | 
            +
            require "parslet"
         | 
| 2 | 
            +
            require_relative "schema"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Rng
         | 
| 5 | 
            +
              class RncParser < Parslet::Parser
         | 
| 6 | 
            +
                rule(:space) { match('\s').repeat(1) }
         | 
| 7 | 
            +
                rule(:space?) { space.maybe }
         | 
| 8 | 
            +
                rule(:newline) { (str("\r").maybe >> str("\n")).repeat(1) }
         | 
| 9 | 
            +
                rule(:newline?) { newline.maybe }
         | 
| 10 | 
            +
                rule(:whitespace) { (space | newline).repeat }
         | 
| 11 | 
            +
                rule(:comma) { str(",") }
         | 
| 12 | 
            +
                rule(:comma?) { (whitespace >> comma >> whitespace).maybe }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                rule(:identifier) { match("[a-zA-Z0-9_]").repeat(1).as(:identifier) }
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                rule(:element_def) {
         | 
| 17 | 
            +
                  str("element") >> space >>
         | 
| 18 | 
            +
                  identifier >>
         | 
| 19 | 
            +
                  whitespace >>
         | 
| 20 | 
            +
                  str("{") >>
         | 
| 21 | 
            +
                  whitespace >>
         | 
| 22 | 
            +
                  content.maybe.as(:content) >>
         | 
| 23 | 
            +
                  whitespace >>
         | 
| 24 | 
            +
                  str("}") >>
         | 
| 25 | 
            +
                  (str("*") | str("+") | str("?")).maybe.as(:occurrence)
         | 
| 26 | 
            +
                }
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                rule(:attribute_def) {
         | 
| 29 | 
            +
                  str("attribute") >> space >>
         | 
| 30 | 
            +
                  identifier.as(:name) >>
         | 
| 31 | 
            +
                  whitespace >>
         | 
| 32 | 
            +
                  str("{") >>
         | 
| 33 | 
            +
                  whitespace >>
         | 
| 34 | 
            +
                  (str("text")).as(:type) >>
         | 
| 35 | 
            +
                  whitespace >>
         | 
| 36 | 
            +
                  str("}")
         | 
| 37 | 
            +
                }
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                rule(:text_def) { str("text").as(:text) }
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                rule(:content_item) {
         | 
| 42 | 
            +
                  ((element_def | attribute_def | text_def).as(:item) >> comma?).repeat(1).as(:items)
         | 
| 43 | 
            +
                }
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                rule(:content) { content_item }
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                rule(:grammar) { whitespace >> element_def.as(:element) >> whitespace }
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                root(:grammar)
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def parse(input)
         | 
| 52 | 
            +
                  tree = super(input.strip)
         | 
| 53 | 
            +
                  build_schema(tree)
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                private
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                def build_schema(tree)
         | 
| 59 | 
            +
                  element = tree[:element]
         | 
| 60 | 
            +
                  Schema.new(
         | 
| 61 | 
            +
                    start: Start.new(
         | 
| 62 | 
            +
                      elements: [build_element(element)],
         | 
| 63 | 
            +
                    ),
         | 
| 64 | 
            +
                  )
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                def build_element(element)
         | 
| 68 | 
            +
                  name = element[:identifier].to_s
         | 
| 69 | 
            +
                  content = element[:content]&.[](:items)
         | 
| 70 | 
            +
                  occurrence = element[:occurrence]
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  # Create base element
         | 
| 73 | 
            +
                  el = Element.new(
         | 
| 74 | 
            +
                    name: name,
         | 
| 75 | 
            +
                    attributes: [],
         | 
| 76 | 
            +
                    elements: [],
         | 
| 77 | 
            +
                    text: false,
         | 
| 78 | 
            +
                  )
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                  if content
         | 
| 81 | 
            +
                    current_elements = []
         | 
| 82 | 
            +
                    current_attributes = []
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                    content.each do |item|
         | 
| 85 | 
            +
                      case
         | 
| 86 | 
            +
                      when item[:item][:name] || (item[:item][:identifier] && item[:item][:type])
         | 
| 87 | 
            +
                        attr_name = item[:item][:name] || item[:item][:identifier]
         | 
| 88 | 
            +
                        attr = Attribute.new(
         | 
| 89 | 
            +
                          name: attr_name.to_s,
         | 
| 90 | 
            +
                          type: ["string"],
         | 
| 91 | 
            +
                        )
         | 
| 92 | 
            +
                        current_attributes << attr
         | 
| 93 | 
            +
                      when item[:item][:identifier]
         | 
| 94 | 
            +
                        current_elements << build_element(item[:item])
         | 
| 95 | 
            +
                      when item[:item][:text]
         | 
| 96 | 
            +
                        el.text = true
         | 
| 97 | 
            +
                      end
         | 
| 98 | 
            +
                    end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                    el.attributes = current_attributes
         | 
| 101 | 
            +
                    el.elements = current_elements
         | 
| 102 | 
            +
                  end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                  # Handle occurrence modifiers
         | 
| 105 | 
            +
                  result = el
         | 
| 106 | 
            +
                  case occurrence
         | 
| 107 | 
            +
                  when "*"
         | 
| 108 | 
            +
                    result = Element.new(
         | 
| 109 | 
            +
                      name: el.name,
         | 
| 110 | 
            +
                      attributes: el.attributes,
         | 
| 111 | 
            +
                      elements: el.elements,
         | 
| 112 | 
            +
                      text: el.text,
         | 
| 113 | 
            +
                    )
         | 
| 114 | 
            +
                    result.zero_or_more = [el]
         | 
| 115 | 
            +
                  when "+"
         | 
| 116 | 
            +
                    result = Element.new(
         | 
| 117 | 
            +
                      name: el.name,
         | 
| 118 | 
            +
                      attributes: el.attributes,
         | 
| 119 | 
            +
                      elements: el.elements,
         | 
| 120 | 
            +
                      text: el.text,
         | 
| 121 | 
            +
                    )
         | 
| 122 | 
            +
                    result.one_or_more = [el]
         | 
| 123 | 
            +
                  when "?"
         | 
| 124 | 
            +
                    result = Element.new(
         | 
| 125 | 
            +
                      name: el.name,
         | 
| 126 | 
            +
                      attributes: el.attributes,
         | 
| 127 | 
            +
                      elements: el.elements,
         | 
| 128 | 
            +
                      text: el.text,
         | 
| 129 | 
            +
                    )
         | 
| 130 | 
            +
                    result.optional = [el]
         | 
| 131 | 
            +
                  end
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                  result
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
              end
         | 
| 136 | 
            +
            end
         | 
| @@ -0,0 +1,107 @@ | |
| 1 | 
            +
            require "nokogiri"
         | 
| 2 | 
            +
            require_relative "schema"
         | 
| 3 | 
            +
            require_relative "element"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Rng
         | 
| 6 | 
            +
              class RngParser
         | 
| 7 | 
            +
                RELAXNG_NS = "http://relaxng.org/ns/structure/1.0"
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def parse(input)
         | 
| 10 | 
            +
                  doc = Nokogiri::XML(input)
         | 
| 11 | 
            +
                  doc.remove_namespaces! # This simplifies namespace handling
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  root = doc.root
         | 
| 14 | 
            +
                  case root.name
         | 
| 15 | 
            +
                  when "grammar"
         | 
| 16 | 
            +
                    parse_grammar(doc)
         | 
| 17 | 
            +
                  when "element"
         | 
| 18 | 
            +
                    parse_element(doc)
         | 
| 19 | 
            +
                  else
         | 
| 20 | 
            +
                    raise Rng::Error, "Unexpected root element: #{root.name}"
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                private
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def parse_grammar(doc)
         | 
| 27 | 
            +
                  Schema.new(
         | 
| 28 | 
            +
                    start: parse_start(doc.at_xpath("//start")),
         | 
| 29 | 
            +
                    define: doc.xpath("//define").map { |define| parse_define(define) },
         | 
| 30 | 
            +
                  )
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def parse_start(node)
         | 
| 34 | 
            +
                  return nil unless node
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  Start.new(
         | 
| 37 | 
            +
                    ref: node.at_xpath(".//ref")&.attr("name"),
         | 
| 38 | 
            +
                    elements: node.xpath(".//element").map { |element| parse_element(element) },
         | 
| 39 | 
            +
                  )
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                def parse_define(node)
         | 
| 43 | 
            +
                  Define.new(
         | 
| 44 | 
            +
                    name: node["name"],
         | 
| 45 | 
            +
                    elements: node.xpath(".//element").map { |element| parse_element(element) },
         | 
| 46 | 
            +
                  )
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def parse_element(node)
         | 
| 50 | 
            +
                  return nil unless node["name"]
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  element = Element.new(
         | 
| 53 | 
            +
                    name: node["name"],
         | 
| 54 | 
            +
                    attributes: [],
         | 
| 55 | 
            +
                    elements: [],
         | 
| 56 | 
            +
                    text: false,
         | 
| 57 | 
            +
                  )
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  node.children.each do |child|
         | 
| 60 | 
            +
                    parse_child(child, element)
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  element
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                def parse_child(node, element)
         | 
| 67 | 
            +
                  case node.name
         | 
| 68 | 
            +
                  when "attribute"
         | 
| 69 | 
            +
                    element.attributes << parse_attribute(node)
         | 
| 70 | 
            +
                  when "element"
         | 
| 71 | 
            +
                    element.elements << parse_element(node)
         | 
| 72 | 
            +
                  when "text"
         | 
| 73 | 
            +
                    element.text = true
         | 
| 74 | 
            +
                  when "zeroOrMore"
         | 
| 75 | 
            +
                    parse_zero_or_more(node).each { |el| element.zero_or_more << el }
         | 
| 76 | 
            +
                  when "oneOrMore"
         | 
| 77 | 
            +
                    parse_one_or_more(node).each { |el| element.one_or_more << el }
         | 
| 78 | 
            +
                  when "optional"
         | 
| 79 | 
            +
                    parse_optional(node).each { |el| element.optional << el }
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                def parse_attribute(node)
         | 
| 84 | 
            +
                  data_node = node.at_xpath(".//data")
         | 
| 85 | 
            +
                  Attribute.new(
         | 
| 86 | 
            +
                    name: node["name"],
         | 
| 87 | 
            +
                    type: data_node ? [data_node["type"]] : ["string"],
         | 
| 88 | 
            +
                  )
         | 
| 89 | 
            +
                end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                def parse_zero_or_more(node)
         | 
| 92 | 
            +
                  node.xpath("./element").map { |el| parse_element(el) }
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                def parse_one_or_more(node)
         | 
| 96 | 
            +
                  node.xpath("./element").map { |el| parse_element(el) }
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                def parse_optional(node)
         | 
| 100 | 
            +
                  node.xpath("./element").map { |el| parse_element(el) }
         | 
| 101 | 
            +
                end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                def parse_choice(node)
         | 
| 104 | 
            +
                  node.xpath(".//choice/element").map { |el| parse_element(el) }
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
              end
         | 
| 107 | 
            +
            end
         | 
    
        data/lib/rng/schema.rb
    ADDED
    
    | @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            require "lutaml/model"
         | 
| 2 | 
            +
            require_relative "start"
         | 
| 3 | 
            +
            require_relative "define"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Rng
         | 
| 6 | 
            +
              class Schema < Lutaml::Model::Serializable
         | 
| 7 | 
            +
                attribute :start, Start
         | 
| 8 | 
            +
                attribute :define, Define, collection: true
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                xml do
         | 
| 11 | 
            +
                  root "grammar"
         | 
| 12 | 
            +
                  namespace "http://relaxng.org/ns/structure/1.0"
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  map_element "start", to: :start
         | 
| 15 | 
            +
                  map_element "define", to: :define
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
    
        data/lib/rng/start.rb
    ADDED
    
    | @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            require "lutaml/model"
         | 
| 2 | 
            +
            require_relative "element"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Rng
         | 
| 5 | 
            +
              class Start < Lutaml::Model::Serializable
         | 
| 6 | 
            +
                attribute :ref, :string
         | 
| 7 | 
            +
                attribute :elements, Element, collection: true
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                xml do
         | 
| 10 | 
            +
                  map_attribute "ref", to: :ref
         | 
| 11 | 
            +
                  map_element "element", to: :elements
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
            end
         | 
    
        data/lib/rng/version.rb
    ADDED
    
    
    
        data/lib/rng.rb
    ADDED
    
    | @@ -0,0 +1,12 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative "rng/version"
         | 
| 4 | 
            +
            require_relative "rng/rnc_parser"
         | 
| 5 | 
            +
            require_relative "rng/rng_parser"
         | 
| 6 | 
            +
            require_relative "rng/builder"
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            module Rng
         | 
| 9 | 
            +
              class Error < StandardError; end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              # Your code goes here...
         | 
| 12 | 
            +
            end
         | 
    
        data/rng.gemspec
    ADDED
    
    | @@ -0,0 +1,38 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative "lib/rng/version"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            all_files_in_git = Dir.chdir(File.expand_path(__dir__)) do
         | 
| 6 | 
            +
              `git ls-files -z`.split("\x0")
         | 
| 7 | 
            +
            end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            Gem::Specification.new do |spec|
         | 
| 10 | 
            +
              spec.name = "rng"
         | 
| 11 | 
            +
              spec.version = Rng::VERSION
         | 
| 12 | 
            +
              spec.authors = ["Ribose"]
         | 
| 13 | 
            +
              spec.email = ["open.source@ribose.com"]
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              spec.summary = "Library to parse and build RELAX NG (RNG) and RELAX NG Compact Syntax (RNC) schemas."
         | 
| 16 | 
            +
              spec.homepage = "https://github.com/lutaml/rng"
         | 
| 17 | 
            +
              spec.license = "BSD-2-Clause"
         | 
| 18 | 
            +
              spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              spec.metadata["homepage_uri"] = spec.homepage
         | 
| 21 | 
            +
              spec.metadata["source_code_uri"] = spec.homepage
         | 
| 22 | 
            +
              spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues"
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              # Specify which files should be added to the gem when it is released.
         | 
| 25 | 
            +
              spec.files = all_files_in_git
         | 
| 26 | 
            +
                .reject { |f| f.match(%r{\A(?:test|features|bin|\.)/}) }
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              spec.bindir = "exe"
         | 
| 29 | 
            +
              spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
         | 
| 30 | 
            +
              spec.require_paths = ["lib"]
         | 
| 31 | 
            +
             | 
| 32 | 
            +
              spec.add_dependency "lutaml-model"
         | 
| 33 | 
            +
              spec.add_dependency "parslet"
         | 
| 34 | 
            +
              spec.add_dependency "nokogiri"
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              # spec.add_dependency "thor"
         | 
| 37 | 
            +
              spec.metadata["rubygems_mfa_required"] = "true"
         | 
| 38 | 
            +
            end
         | 
    
        data/sig/rng.rbs
    ADDED
    
    
| @@ -0,0 +1,66 @@ | |
| 1 | 
            +
            require "spec_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            RSpec.describe Rng::RncParser do
         | 
| 4 | 
            +
              let(:parser) { described_class.new }
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              describe "#parse" do
         | 
| 7 | 
            +
                context "with a simple RNC schema" do
         | 
| 8 | 
            +
                  let(:input) do
         | 
| 9 | 
            +
                    <<~RNC
         | 
| 10 | 
            +
                      element addressBook {
         | 
| 11 | 
            +
                        element card {
         | 
| 12 | 
            +
                          element name { text },
         | 
| 13 | 
            +
                          element email { text }
         | 
| 14 | 
            +
                        }*
         | 
| 15 | 
            +
                      }
         | 
| 16 | 
            +
                    RNC
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  it "correctly parses the schema" do
         | 
| 20 | 
            +
                    result = parser.parse(input)
         | 
| 21 | 
            +
                    expect(result).to be_a(Rng::Schema)
         | 
| 22 | 
            +
                    expect(result.start.elements.first.name).to eq("addressBook")
         | 
| 23 | 
            +
                    expect(result.start.elements.first.elements.first.name).to eq("card")
         | 
| 24 | 
            +
                    expect(result.start.elements.first.elements.first.elements.map(&:name)).to eq(["name", "email"])
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                context "with attributes" do
         | 
| 29 | 
            +
                  let(:input) do
         | 
| 30 | 
            +
                    <<~RNC
         | 
| 31 | 
            +
                      element person {
         | 
| 32 | 
            +
                        attribute id { text },
         | 
| 33 | 
            +
                        element name { text }
         | 
| 34 | 
            +
                      }
         | 
| 35 | 
            +
                    RNC
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  it "correctly parses attributes" do
         | 
| 39 | 
            +
                    result = parser.parse(input)
         | 
| 40 | 
            +
                    expect(result.start.elements.first.name).to eq("person")
         | 
| 41 | 
            +
                    expect(result.start.elements.first.elements.first).to be_a(Rng::Attribute)
         | 
| 42 | 
            +
                    expect(result.start.elements.first.elements.first.name).to eq("id")
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                context "with nested elements" do
         | 
| 47 | 
            +
                  let(:input) do
         | 
| 48 | 
            +
                    <<~RNC
         | 
| 49 | 
            +
                      element root {
         | 
| 50 | 
            +
                        element child1 {
         | 
| 51 | 
            +
                          element grandchild { text }
         | 
| 52 | 
            +
                        },
         | 
| 53 | 
            +
                        element child2 { text }
         | 
| 54 | 
            +
                      }
         | 
| 55 | 
            +
                    RNC
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  it "correctly parses nested elements" do
         | 
| 59 | 
            +
                    result = parser.parse(input)
         | 
| 60 | 
            +
                    expect(result.start.elements.first.name).to eq("root")
         | 
| 61 | 
            +
                    expect(result.start.elements.first.elements.map(&:name)).to eq(["child1", "child2"])
         | 
| 62 | 
            +
                    expect(result.start.elements.first.elements.first.elements.first.name).to eq("grandchild")
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
            end
         | 
| @@ -0,0 +1,102 @@ | |
| 1 | 
            +
            require "spec_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            RSpec.describe Rng::RngParser do
         | 
| 4 | 
            +
              let(:parser) { described_class.new }
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              describe "#parse" do
         | 
| 7 | 
            +
                context "with a simple RNG schema" do
         | 
| 8 | 
            +
                  let(:input) do
         | 
| 9 | 
            +
                    <<~RNG
         | 
| 10 | 
            +
                      <grammar xmlns="http://relaxng.org/ns/structure/1.0">
         | 
| 11 | 
            +
                        <start>
         | 
| 12 | 
            +
                          <element name="addressBook">
         | 
| 13 | 
            +
                            <zeroOrMore>
         | 
| 14 | 
            +
                              <element name="card">
         | 
| 15 | 
            +
                                <element name="name">
         | 
| 16 | 
            +
                                  <text/>
         | 
| 17 | 
            +
                                </element>
         | 
| 18 | 
            +
                                <element name="email">
         | 
| 19 | 
            +
                                  <text/>
         | 
| 20 | 
            +
                                </element>
         | 
| 21 | 
            +
                              </element>
         | 
| 22 | 
            +
                            </zeroOrMore>
         | 
| 23 | 
            +
                          </element>
         | 
| 24 | 
            +
                        </start>
         | 
| 25 | 
            +
                      </grammar>
         | 
| 26 | 
            +
                    RNG
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  it "correctly parses the schema" do
         | 
| 30 | 
            +
                    result = parser.parse(input)
         | 
| 31 | 
            +
                    expect(result).to be_a(Rng::Schema)
         | 
| 32 | 
            +
                    expect(result.start.elements.first.name).to eq("addressBook")
         | 
| 33 | 
            +
                    expect(result.start.elements.first.zero_or_more.first.name).to eq("card")
         | 
| 34 | 
            +
                    expect(result.start.elements.first.zero_or_more.first.elements.map(&:name)).to eq(["name", "email"])
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                context "with a complex RNG schema" do
         | 
| 39 | 
            +
                  let(:input) do
         | 
| 40 | 
            +
                    <<~RNG
         | 
| 41 | 
            +
                      <grammar xmlns="http://relaxng.org/ns/structure/1.0">
         | 
| 42 | 
            +
                        <start>
         | 
| 43 | 
            +
                          <ref name="addressBook"/>
         | 
| 44 | 
            +
                        </start>
         | 
| 45 | 
            +
                        <define name="addressBook">
         | 
| 46 | 
            +
                          <element name="addressBook">
         | 
| 47 | 
            +
                            <zeroOrMore>
         | 
| 48 | 
            +
                              <element name="card">
         | 
| 49 | 
            +
                                <element name="name">
         | 
| 50 | 
            +
                                  <text/>
         | 
| 51 | 
            +
                                </element>
         | 
| 52 | 
            +
                                <element name="email">
         | 
| 53 | 
            +
                                  <text/>
         | 
| 54 | 
            +
                                </element>
         | 
| 55 | 
            +
                                <optional>
         | 
| 56 | 
            +
                                  <element name="note">
         | 
| 57 | 
            +
                                    <text/>
         | 
| 58 | 
            +
                                  </element>
         | 
| 59 | 
            +
                                </optional>
         | 
| 60 | 
            +
                              </element>
         | 
| 61 | 
            +
                            </zeroOrMore>
         | 
| 62 | 
            +
                          </element>
         | 
| 63 | 
            +
                        </define>
         | 
| 64 | 
            +
                      </grammar>
         | 
| 65 | 
            +
                    RNG
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  it "correctly parses the schema" do
         | 
| 69 | 
            +
                    result = parser.parse(input)
         | 
| 70 | 
            +
                    expect(result).to be_a(Rng::Schema)
         | 
| 71 | 
            +
                    expect(result.start.ref).to eq("addressBook")
         | 
| 72 | 
            +
                    expect(result.define.first.name).to eq("addressBook")
         | 
| 73 | 
            +
                    expect(result.define.first.elements.first.name).to eq("addressBook")
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                context "with attributes" do
         | 
| 78 | 
            +
                  let(:input) do
         | 
| 79 | 
            +
                    <<~RNG
         | 
| 80 | 
            +
                      <grammar xmlns="http://relaxng.org/ns/structure/1.0">
         | 
| 81 | 
            +
                        <start>
         | 
| 82 | 
            +
                          <element name="person">
         | 
| 83 | 
            +
                            <attribute name="id">
         | 
| 84 | 
            +
                              <data type="ID"/>
         | 
| 85 | 
            +
                            </attribute>
         | 
| 86 | 
            +
                            <element name="name">
         | 
| 87 | 
            +
                              <text/>
         | 
| 88 | 
            +
                            </element>
         | 
| 89 | 
            +
                          </element>
         | 
| 90 | 
            +
                        </start>
         | 
| 91 | 
            +
                      </grammar>
         | 
| 92 | 
            +
                    RNG
         | 
| 93 | 
            +
                  end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                  it "correctly parses attributes" do
         | 
| 96 | 
            +
                    result = parser.parse(input)
         | 
| 97 | 
            +
                    expect(result.start.elements.first.attributes.first.name).to eq("id")
         | 
| 98 | 
            +
                    expect(result.start.elements.first.attributes.first.type).to eq(["ID"])
         | 
| 99 | 
            +
                  end
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
              end
         | 
| 102 | 
            +
            end
         | 
| @@ -0,0 +1,193 @@ | |
| 1 | 
            +
            require "spec_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            RSpec.describe Rng::Schema do
         | 
| 4 | 
            +
              let(:rng_parser) { Rng::RngParser.new }
         | 
| 5 | 
            +
              let(:rnc_parser) { Rng::RncParser.new }
         | 
| 6 | 
            +
              let(:builder) { Rng::Builder.new }
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              describe "RNG parsing and building" do
         | 
| 9 | 
            +
                let(:rng_input) do
         | 
| 10 | 
            +
                  <<~RNG
         | 
| 11 | 
            +
                    <?xml version="1.0" encoding="UTF-8"?>
         | 
| 12 | 
            +
                    <element name="addressBook" xmlns="http://relaxng.org/ns/structure/1.0">
         | 
| 13 | 
            +
                      <zeroOrMore>
         | 
| 14 | 
            +
                        <element name="card">
         | 
| 15 | 
            +
                          <element name="name">
         | 
| 16 | 
            +
                            <text/>
         | 
| 17 | 
            +
                          </element>
         | 
| 18 | 
            +
                          <element name="email">
         | 
| 19 | 
            +
                            <text/>
         | 
| 20 | 
            +
                          </element>
         | 
| 21 | 
            +
                          <optional>
         | 
| 22 | 
            +
                            <element name="note">
         | 
| 23 | 
            +
                              <text/>
         | 
| 24 | 
            +
                            </element>
         | 
| 25 | 
            +
                          </optional>
         | 
| 26 | 
            +
                        </element>
         | 
| 27 | 
            +
                      </zeroOrMore>
         | 
| 28 | 
            +
                    </element>
         | 
| 29 | 
            +
                  RNG
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                it "correctly parses and rebuilds RNG" do
         | 
| 33 | 
            +
                  parsed = rng_parser.parse(rng_input)
         | 
| 34 | 
            +
                  rebuilt = builder.build(parsed, format: :rng)
         | 
| 35 | 
            +
                  expect(normalize_xml(rebuilt)).to eq(normalize_xml(rng_input))
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              describe "RNC parsing and building" do
         | 
| 40 | 
            +
                let(:rnc_input) do
         | 
| 41 | 
            +
                  <<~RNC
         | 
| 42 | 
            +
                    element addressBook {
         | 
| 43 | 
            +
                      element card {
         | 
| 44 | 
            +
                        element name { text },
         | 
| 45 | 
            +
                        element email { text },
         | 
| 46 | 
            +
                        element note { text }?
         | 
| 47 | 
            +
                      }*
         | 
| 48 | 
            +
                    }
         | 
| 49 | 
            +
                  RNC
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                it "correctly parses and rebuilds RNC" do
         | 
| 53 | 
            +
                  parsed = rnc_parser.parse(rnc_input)
         | 
| 54 | 
            +
                  rebuilt = builder.build(parsed, format: :rnc)
         | 
| 55 | 
            +
                  expect(rebuilt.gsub(/\s+/, "")).to eq(rnc_input.gsub(/\s+/, ""))
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
              describe "RNG to RNC conversion" do
         | 
| 60 | 
            +
                let(:rng_input) do
         | 
| 61 | 
            +
                  <<~RNG
         | 
| 62 | 
            +
                    <?xml version="1.0" encoding="UTF-8"?>
         | 
| 63 | 
            +
                    <element name="addressBook" xmlns="http://relaxng.org/ns/structure/1.0">
         | 
| 64 | 
            +
                      <zeroOrMore>
         | 
| 65 | 
            +
                        <element name="card">
         | 
| 66 | 
            +
                          <element name="name">
         | 
| 67 | 
            +
                            <text/>
         | 
| 68 | 
            +
                          </element>
         | 
| 69 | 
            +
                          <element name="email">
         | 
| 70 | 
            +
                            <text/>
         | 
| 71 | 
            +
                          </element>
         | 
| 72 | 
            +
                        </element>
         | 
| 73 | 
            +
                      </zeroOrMore>
         | 
| 74 | 
            +
                    </element>
         | 
| 75 | 
            +
                  RNG
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                let(:expected_rnc) do
         | 
| 79 | 
            +
                  <<~RNC
         | 
| 80 | 
            +
                    element addressBook {
         | 
| 81 | 
            +
                      element card {
         | 
| 82 | 
            +
                        element name { text },
         | 
| 83 | 
            +
                        element email { text }
         | 
| 84 | 
            +
                      }*
         | 
| 85 | 
            +
                    }
         | 
| 86 | 
            +
                  RNC
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                it "correctly converts RNG to RNC" do
         | 
| 90 | 
            +
                  parsed = rng_parser.parse(rng_input)
         | 
| 91 | 
            +
                  rnc = builder.build(parsed, format: :rnc)
         | 
| 92 | 
            +
                  expect(rnc.gsub(/\s+/, "")).to eq(expected_rnc.gsub(/\s+/, ""))
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
              end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
              describe "RNC to RNG conversion" do
         | 
| 97 | 
            +
                let(:rnc_input) do
         | 
| 98 | 
            +
                  <<~RNC
         | 
| 99 | 
            +
                    element addressBook {
         | 
| 100 | 
            +
                      element card {
         | 
| 101 | 
            +
                        element name { text },
         | 
| 102 | 
            +
                        element email { text }
         | 
| 103 | 
            +
                      }*
         | 
| 104 | 
            +
                    }
         | 
| 105 | 
            +
                  RNC
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                let(:expected_rng) do
         | 
| 109 | 
            +
                  <<~RNG
         | 
| 110 | 
            +
                    <element name="addressBook" xmlns="http://relaxng.org/ns/structure/1.0">
         | 
| 111 | 
            +
                      <zeroOrMore>
         | 
| 112 | 
            +
                        <element name="card">
         | 
| 113 | 
            +
                          <element name="name">
         | 
| 114 | 
            +
                            <text/>
         | 
| 115 | 
            +
                          </element>
         | 
| 116 | 
            +
                          <element name="email">
         | 
| 117 | 
            +
                            <text/>
         | 
| 118 | 
            +
                          </element>
         | 
| 119 | 
            +
                        </element>
         | 
| 120 | 
            +
                      </zeroOrMore>
         | 
| 121 | 
            +
                    </element>
         | 
| 122 | 
            +
                  RNG
         | 
| 123 | 
            +
                end
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                it "correctly converts RNC to RNG" do
         | 
| 126 | 
            +
                  parsed = rnc_parser.parse(rnc_input)
         | 
| 127 | 
            +
                  rng = builder.build(parsed, format: :rng)
         | 
| 128 | 
            +
                  expect(rng.gsub(/\s+/, "")).to eq(expected_rng.gsub(/\s+/, ""))
         | 
| 129 | 
            +
                end
         | 
| 130 | 
            +
              end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
              describe "Complex schema parsing and building" do
         | 
| 133 | 
            +
                let(:complex_rng_input) do
         | 
| 134 | 
            +
                  <<~RNG
         | 
| 135 | 
            +
                    <grammar xmlns="http://relaxng.org/ns/structure/1.0">
         | 
| 136 | 
            +
                      <start>
         | 
| 137 | 
            +
                        <ref name="addressBook"/>
         | 
| 138 | 
            +
                      </start>
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                      <define name="addressBook">
         | 
| 141 | 
            +
                        <element name="addressBook">
         | 
| 142 | 
            +
                          <zeroOrMore>
         | 
| 143 | 
            +
                            <ref name="card"/>
         | 
| 144 | 
            +
                          </zeroOrMore>
         | 
| 145 | 
            +
                        </element>
         | 
| 146 | 
            +
                      </define>
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                      <define name="card">
         | 
| 149 | 
            +
                        <element name="card">
         | 
| 150 | 
            +
                          <ref name="name"/>
         | 
| 151 | 
            +
                          <ref name="email"/>
         | 
| 152 | 
            +
                          <optional>
         | 
| 153 | 
            +
                            <ref name="note"/>
         | 
| 154 | 
            +
                          </optional>
         | 
| 155 | 
            +
                        </element>
         | 
| 156 | 
            +
                      </define>
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                      <define name="name">
         | 
| 159 | 
            +
                        <element name="name">
         | 
| 160 | 
            +
                          <text/>
         | 
| 161 | 
            +
                        </element>
         | 
| 162 | 
            +
                      </define>
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                      <define name="email">
         | 
| 165 | 
            +
                        <element name="email">
         | 
| 166 | 
            +
                          <text/>
         | 
| 167 | 
            +
                        </element>
         | 
| 168 | 
            +
                      </define>
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                      <define name="note">
         | 
| 171 | 
            +
                        <element name="note">
         | 
| 172 | 
            +
                          <text/>
         | 
| 173 | 
            +
                        </element>
         | 
| 174 | 
            +
                      </define>
         | 
| 175 | 
            +
                    </grammar>
         | 
| 176 | 
            +
                  RNG
         | 
| 177 | 
            +
                end
         | 
| 178 | 
            +
             | 
| 179 | 
            +
                it "correctly parses and rebuilds complex RNG" do
         | 
| 180 | 
            +
                  parsed = rng_parser.parse(complex_rng_input)
         | 
| 181 | 
            +
                  rebuilt = builder.build(parsed, format: :rng)
         | 
| 182 | 
            +
                  expect(rebuilt.gsub(/\s+/, "")).to eq(complex_rng_input.gsub(/\s+/, ""))
         | 
| 183 | 
            +
                end
         | 
| 184 | 
            +
             | 
| 185 | 
            +
                it "correctly converts complex RNG to RNC" do
         | 
| 186 | 
            +
                  parsed = rng_parser.parse(complex_rng_input)
         | 
| 187 | 
            +
                  rnc = builder.build(parsed, format: :rnc)
         | 
| 188 | 
            +
                  reparsed = rnc_parser.parse(rnc)
         | 
| 189 | 
            +
                  rng_again = builder.build(reparsed, format: :rng)
         | 
| 190 | 
            +
                  expect(rng_again.gsub(/\s+/, "")).to eq(complex_rng_input.gsub(/\s+/, ""))
         | 
| 191 | 
            +
                end
         | 
| 192 | 
            +
              end
         | 
| 193 | 
            +
            end
         | 
    
        data/spec/rng_spec.rb
    ADDED
    
    
    
        data/spec/spec_helper.rb
    ADDED
    
    | @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "rng"
         | 
| 4 | 
            +
            require "xml/c14n"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            RSpec.configure do |config|
         | 
| 7 | 
            +
              # Enable flags like --only-failures and --next-failure
         | 
| 8 | 
            +
              config.example_status_persistence_file_path = ".rspec_status"
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              # Disable RSpec exposing methods globally on `Module` and `main`
         | 
| 11 | 
            +
              config.disable_monkey_patching!
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              config.expect_with :rspec do |c|
         | 
| 14 | 
            +
                c.syntax = :expect
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              # Add helper method for XML comparison
         | 
| 18 | 
            +
              config.include(Module.new do
         | 
| 19 | 
            +
                def normalize_xml(xml)
         | 
| 20 | 
            +
                  Xml::C14n.format(xml)
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              end)
         | 
| 23 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,114 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: rng
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.1.1
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - Ribose
         | 
| 8 | 
            +
            autorequire:
         | 
| 9 | 
            +
            bindir: exe
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2024-11-27 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: lutaml-model
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - ">="
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: '0'
         | 
| 20 | 
            +
              type: :runtime
         | 
| 21 | 
            +
              prerelease: false
         | 
| 22 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            +
                requirements:
         | 
| 24 | 
            +
                - - ">="
         | 
| 25 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            +
                    version: '0'
         | 
| 27 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            +
              name: parslet
         | 
| 29 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 | 
            +
                requirements:
         | 
| 31 | 
            +
                - - ">="
         | 
| 32 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            +
                    version: '0'
         | 
| 34 | 
            +
              type: :runtime
         | 
| 35 | 
            +
              prerelease: false
         | 
| 36 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            +
                requirements:
         | 
| 38 | 
            +
                - - ">="
         | 
| 39 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            +
                    version: '0'
         | 
| 41 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            +
              name: nokogiri
         | 
| 43 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
                requirements:
         | 
| 45 | 
            +
                - - ">="
         | 
| 46 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            +
                    version: '0'
         | 
| 48 | 
            +
              type: :runtime
         | 
| 49 | 
            +
              prerelease: false
         | 
| 50 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - ">="
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: '0'
         | 
| 55 | 
            +
            description:
         | 
| 56 | 
            +
            email:
         | 
| 57 | 
            +
            - open.source@ribose.com
         | 
| 58 | 
            +
            executables: []
         | 
| 59 | 
            +
            extensions: []
         | 
| 60 | 
            +
            extra_rdoc_files: []
         | 
| 61 | 
            +
            files:
         | 
| 62 | 
            +
            - ".github/workflows/rake.yml"
         | 
| 63 | 
            +
            - ".github/workflows/release.yml"
         | 
| 64 | 
            +
            - ".gitignore"
         | 
| 65 | 
            +
            - ".rspec"
         | 
| 66 | 
            +
            - ".rubocop.yml"
         | 
| 67 | 
            +
            - Gemfile
         | 
| 68 | 
            +
            - Rakefile
         | 
| 69 | 
            +
            - lib/rng.rb
         | 
| 70 | 
            +
            - lib/rng/attribute.rb
         | 
| 71 | 
            +
            - lib/rng/builder.rb
         | 
| 72 | 
            +
            - lib/rng/define.rb
         | 
| 73 | 
            +
            - lib/rng/element.rb
         | 
| 74 | 
            +
            - lib/rng/rnc_parser.rb
         | 
| 75 | 
            +
            - lib/rng/rng_parser.rb
         | 
| 76 | 
            +
            - lib/rng/schema.rb
         | 
| 77 | 
            +
            - lib/rng/start.rb
         | 
| 78 | 
            +
            - lib/rng/version.rb
         | 
| 79 | 
            +
            - rng.gemspec
         | 
| 80 | 
            +
            - sig/rng.rbs
         | 
| 81 | 
            +
            - spec/rng/rnc_parser_spec.rb
         | 
| 82 | 
            +
            - spec/rng/rng_parser_spec.rb
         | 
| 83 | 
            +
            - spec/rng/schema_spec.rb
         | 
| 84 | 
            +
            - spec/rng_spec.rb
         | 
| 85 | 
            +
            - spec/spec_helper.rb
         | 
| 86 | 
            +
            homepage: https://github.com/lutaml/rng
         | 
| 87 | 
            +
            licenses:
         | 
| 88 | 
            +
            - BSD-2-Clause
         | 
| 89 | 
            +
            metadata:
         | 
| 90 | 
            +
              homepage_uri: https://github.com/lutaml/rng
         | 
| 91 | 
            +
              source_code_uri: https://github.com/lutaml/rng
         | 
| 92 | 
            +
              bug_tracker_uri: https://github.com/lutaml/rng/issues
         | 
| 93 | 
            +
              rubygems_mfa_required: 'true'
         | 
| 94 | 
            +
            post_install_message:
         | 
| 95 | 
            +
            rdoc_options: []
         | 
| 96 | 
            +
            require_paths:
         | 
| 97 | 
            +
            - lib
         | 
| 98 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 99 | 
            +
              requirements:
         | 
| 100 | 
            +
              - - ">="
         | 
| 101 | 
            +
                - !ruby/object:Gem::Version
         | 
| 102 | 
            +
                  version: 3.0.0
         | 
| 103 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 104 | 
            +
              requirements:
         | 
| 105 | 
            +
              - - ">="
         | 
| 106 | 
            +
                - !ruby/object:Gem::Version
         | 
| 107 | 
            +
                  version: '0'
         | 
| 108 | 
            +
            requirements: []
         | 
| 109 | 
            +
            rubygems_version: 3.5.22
         | 
| 110 | 
            +
            signing_key:
         | 
| 111 | 
            +
            specification_version: 4
         | 
| 112 | 
            +
            summary: Library to parse and build RELAX NG (RNG) and RELAX NG Compact Syntax (RNC)
         | 
| 113 | 
            +
              schemas.
         | 
| 114 | 
            +
            test_files: []
         |