RNG provides Ruby tools for working with RELAX NG schemas, supporting both the XML syntax (RNG) and the compact syntax (RNC). It allows parsing, manipulation, and generation of RELAX NG schemas through an intuitive Ruby API.
Key features:
-
Parse RELAX NG XML (.rng) and Compact (.rnc) syntax
-
Programmatically build RELAX NG schemas
-
Convert between XML and compact notations
-
Object model representing RELAX NG concepts
-
Integration with the LutaML ecosystem
Install the gem:
# In your Gemfile
gem 'rng'
require 'rng'
# Parse from XML syntax
schema = Rng.parse(File.read('example.rng'))
# Access schema components
if schema.element
# Simple element pattern
puts "Root element: #{schema.element.name}"
else
# Grammar with named patterns
start_element = schema.start.element
puts "Root element: #{start_element.name}"
end
require 'rng'
# Parse from compact syntax
schema = Rng.parse_rnc(File.read('example.rnc'))
# Access schema components
if schema.element
# Simple element pattern
puts "Root element: #{schema.element.name}"
else
# Grammar with named patterns
start_element = schema.start.element
puts "Root element: #{start_element.name}"
end
require 'rng'
# Parse RNG and convert to RNC
schema = Rng.parse(File.read('example.rng'))
rnc = Rng.to_rnc(schema)
File.write('example.rnc', rnc)
# Parse RNC and get RNG XML
schema = Rng.parse_rnc(File.read('example.rnc'))
rng_xml = Rng::RncParser.parse(File.read('example.rnc'))
File.write('example.rng', rng_xml)
require 'rng'
# Create a schema with an address element
schema = Rng::Grammar.new
schema.element = Rng::Element.new(
name: "address"
)
# Add attributes
schema.element.attribute = Rng::Attribute.new(
name: "id"
)
schema.element.attribute.data = Rng::Data.new(
type: "ID"
)
# Add child elements
name_element = Rng::Element.new(name: "name")
name_element.text = Rng::Text.new
street_element = Rng::Element.new(name: "street")
street_element.text = Rng::Text.new
city_element = Rng::Element.new(name: "city")
city_element.text = Rng::Text.new
# Add child elements to parent
schema.element.element = [name_element, street_element, city_element]
# Convert to RNC format
rnc = Rng.to_rnc(schema)
File.write('address.rnc', rnc)
The Grammar class represents a complete RELAX NG schema:
# Simple element pattern
schema = Rng::Grammar.new(
element: Rng::Element.new(...)
)
# Grammar with named patterns
schema = Rng::Grammar.new(
start: Rng::Start.new(...),
define: [Rng::Define.new(...), ...],
datatypeLibrary: "http://www.w3.org/2001/XMLSchema-datatypes"
)
The Start class defines the entry point of a schema:
start = Rng::Start.new(
ref: Rng::Ref.new(name: "addressDef"), # Reference to a named pattern
element: Rng::Element.new(...), # Inline element definition
choice: Rng::Choice.new(...), # Choice pattern
group: Rng::Group.new(...) # Group pattern
)
Define represents named pattern definitions:
define = Rng::Define.new(
name: "addressDef",
element: Rng::Element.new(...),
choice: Rng::Choice.new(...),
group: Rng::Group.new(...)
)
Element represents XML elements in the schema:
element = Rng::Element.new(
name: "address",
attribute: Rng::Attribute.new(...), # Attribute definition
element: Rng::Element.new(...), # Child element definition
text: Rng::Text.new, # Text content
zeroOrMore: Rng::ZeroOrMore.new(...), # Elements that can appear zero or more times
oneOrMore: Rng::OneOrMore.new(...), # Elements that must appear at least once
optional: Rng::Optional.new(...) # Optional elements
)
Attribute defines attributes for elements:
attribute = Rng::Attribute.new(
name: "id",
data: Rng::Data.new(type: "ID") # XML Schema datatype
)
The library includes classes for all RELAX NG patterns:
-
Rng::Choice
- Represents a choice between patterns -
Rng::Group
- Represents a sequence of patterns -
Rng::Interleave
- Represents patterns that can be interleaved -
Rng::Mixed
- Represents mixed content (text and elements) -
Rng::Optional
- Represents an optional pattern -
Rng::ZeroOrMore
- Represents a pattern that can occur zero or more times -
Rng::OneOrMore
- Represents a pattern that must occur at least once -
Rng::Text
- Represents text content -
Rng::Empty
- Represents empty content -
Rng::Value
- Represents a specific value -
Rng::Data
- Represents a datatype -
Rng::List
- Represents a list of values -
Rng::Ref
- Represents a reference to a named pattern -
Rng::ParentRef
- Represents a reference to a pattern in a parent grammar -
Rng::ExternalRef
- Represents a reference to a pattern in an external grammar -
Rng::NotAllowed
- Represents a pattern that is not allowed
XML syntax is the canonical form of RELAX NG schemas:
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<start>
<element name="address">
<attribute name="id">
<data type="ID"/>
</attribute>
<element name="name">
<text/>
</element>
<element name="street">
<text/>
</element>
<element name="city">
<text/>
</element>
</element>
</start>
</grammar>
require 'rng'
# Create a schema with choice patterns
schema = Rng::Grammar.new
schema.start = Rng::Start.new
# Create a choice between two elements
choice = Rng::Choice.new
choice.element = []
# First option: name element
name_element = Rng::Element.new(name: "name")
name_element.text = Rng::Text.new
choice.element << name_element
# Second option: first name and last name elements
first_name = Rng::Element.new(name: "firstName")
first_name.text = Rng::Text.new
last_name = Rng::Element.new(name: "lastName")
last_name.text = Rng::Text.new
# Group the first name and last name elements
group = Rng::Group.new
group.element = [first_name, last_name]
# Add the group as the second choice
choice.group = [group]
# Add the choice to the start element
schema.start.choice = choice
# Convert to RNC format
rnc = Rng.to_rnc(schema)
puts rnc
require 'rng'
# Create a schema with named patterns
schema = Rng::Grammar.new
schema.start = Rng::Start.new
# Create a reference to a named pattern
ref = Rng::Ref.new(name: "addressDef")
schema.start.ref = ref
# Define the named pattern
define = Rng::Define.new(name: "addressDef")
schema.define = [define]
# Add an element to the named pattern
element = Rng::Element.new(name: "address")
element.attribute = Rng::Attribute.new(name: "id")
element.attribute.data = Rng::Data.new(type: "ID")
# Add child elements
name_element = Rng::Element.new(name: "name")
name_element.text = Rng::Text.new
element.element = [name_element]
# Add the element to the named pattern
define.element = element
# Convert to RNC format
rnc = Rng.to_rnc(schema)
puts rnc
require 'rng'
# Create a schema with cardinality constraints
schema = Rng::Grammar.new
schema.element = Rng::Element.new(name: "addressBook")
# Create a card element that can appear zero or more times
zero_or_more = Rng::ZeroOrMore.new
card_element = Rng::Element.new(name: "card")
# Add child elements to the card element
name_element = Rng::Element.new(name: "name")
name_element.text = Rng::Text.new
email_element = Rng::Element.new(name: "email")
email_element.text = Rng::Text.new
# Create an optional note element
optional = Rng::Optional.new
note_element = Rng::Element.new(name: "note")
note_element.text = Rng::Text.new
optional.element = [note_element]
# Add the child elements to the card element
card_element.element = [name_element, email_element]
card_element.optional = optional
# Add the card element to the zero_or_more pattern
zero_or_more.element = [card_element]
# Add the zero_or_more pattern to the address book element
schema.element.zeroOrMore = zero_or_more
# Convert to RNC format
rnc = Rng.to_rnc(schema)
puts rnc
-
Fork the repository
-
Create your feature branch (
git checkout -b feature/my-new-feature
) -
Commit your changes (
git commit -am 'Add some feature'
) -
Push to the branch (
git push origin feature/my-new-feature
) -
Create a new Pull Request