Skip to content

Commit

Permalink
creating and/or loading files arguments XMLSchema compiler
Browse files Browse the repository at this point in the history
  • Loading branch information
suleman-uzair committed Jan 24, 2025
1 parent b1f41bb commit dd811d1
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 13 deletions.
16 changes: 15 additions & 1 deletion README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -3598,6 +3598,16 @@ provided, a default directory named `lutaml_models_<timestamp>` is created.
[example]
`"path/to/directory"`

`create_files`::: A `boolean` argument (`false` by default) to create files directly in the specified directory as defined by the `output_dir` option.
+
[example]
`create_files: (true | false)`

`load_classes`::: A `boolean` argument (`false` by default) to load generated classes before returning them.
+
[example]
`load_classes: (true | false)`

`namespace`::: The namespace of the schema. This will be added in the
`Lutaml::Model::Serializable` file's `xml do` block.
+
Expand All @@ -3619,6 +3629,7 @@ link:https://www.w3.org/TR/xmlschema-1/#include[XML Schema specification].
[example]
`"path/to/schema/directory"`

NOTE: If both `create_files` and `load_classes` are provided, the `create_files` argument will take priority and generate files without loading them!

The generated LutaML models consists of two different kind of Ruby classes
depending on the XSD schema:
Expand Down Expand Up @@ -3652,9 +3663,12 @@ options = {
output_dir: 'path/to/directory',
namespace: 'http://example.com/namespace',
prefix: "example-prefix",
location: "http://example.com/example.xsd"
location: "http://example.com/example.xsd",
# or
# location: "path/to/schema/directory"
create_files: true, # Default: false
# OR
load_classes: true, # Default: false
}
# generates the files in the output_dir | default_dir
Expand Down
31 changes: 24 additions & 7 deletions lib/lutaml/model/schema/xml_compiler.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require "erb"
require "tmpdir"
require "lutaml/xsd"
require_relative "templates/simple_type"

Expand Down Expand Up @@ -107,13 +108,20 @@ class <%= Utils.camel_case(name) %> < <%= resolve_parent_class(content) %>

def to_models(schema, options = {})
as_models(schema, options: options)
dir = options.fetch(:output_dir, "lutaml_models_#{Time.now.to_i}")
FileUtils.mkdir_p(dir)

@data_types_classes = Templates::SimpleType.create_simple_types(@simple_types)
@data_types_classes.each { |name, content| create_file(name, content, dir) }
@complex_types.each { |name, content| create_file(name, MODEL_TEMPLATE.result(binding), dir) }
nil
if options[:create_files]
dir = options.fetch(:output_dir, "lutaml_models_#{Time.now.to_i}")
FileUtils.mkdir_p(dir)
@data_types_classes.each { |name, content| create_file(name, content, dir) }
@complex_types.each { |name, content| create_file(name, MODEL_TEMPLATE.result(binding), dir) }
nil
else
simple_types = @data_types_classes.transform_keys { |key| Utils.camel_case(key.to_s) }
complex_types = @complex_types.to_h { |name, content| [Utils.camel_case(name), MODEL_TEMPLATE.result(binding)] }
classes_hash = simple_types.merge(complex_types)
require_classes(classes_hash) if options[:load_classes]
classes_hash
end
end

private
Expand All @@ -122,6 +130,15 @@ def create_file(name, content, dir)
File.write("#{dir}/#{Utils.snake_case(name)}.rb", content)
end

def require_classes(classes_hash)
Dir.mktmpdir do |dir|
classes_hash.each do |name, klass|
create_file(name, klass, dir)
require "#{dir}/#{Utils.snake_case(name)}"
end
end
end

# START: STRUCTURE SETUP METHODS

def as_models(schema, options: {})
Expand All @@ -136,7 +153,7 @@ def as_models(schema, options: {})
@complex_types = MappingHash.new
@attribute_groups = MappingHash.new

schema_to_models([parsed_schema])
schema_to_models(Array(parsed_schema))
end

def schema_to_models(schemas)
Expand Down
10 changes: 10 additions & 0 deletions spec/fixtures/xml/user.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.openxmlformats.org/officeDocument/2006/math">
<xsd:complexType name="User">
<xsd:sequence>
<xsd:element name="id" type="xsd:nonNegativeInteger" minOccurs="1" />
<xsd:element name="age" type="xsd:unsignedLong" minOccurs="0" />
<xsd:element name="token" type="xsd:token" minOccurs="0" />
</xsd:sequence>
</xsd:complexType>
<xsd:element name="User" type="User"/>
</schema>
76 changes: 71 additions & 5 deletions spec/lutaml/model/schema/xml_compiler_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
require "spec_helper"
require "lutaml/model/schema"
require "lutaml/xsd"
require "tmpdir"

RSpec.describe Lutaml::Model::Schema::XmlCompiler do
describe ".to_models" do
Expand All @@ -25,8 +24,8 @@
end

Dir.mktmpdir do |dir|
it "generates the models, requires them, and tests them with valid and invalid xml" do
described_class.to_models(File.read("spec/fixtures/xml/test_schema.xsd"), output_dir: dir)
it "creates the model files, requires them, and tests them with valid and invalid xml" do
described_class.to_models(File.read("spec/fixtures/xml/test_schema.xsd"), output_dir: dir, create_files: true)
expect(File).to exist("#{dir}/ct_math_test.rb")
expect(File).to exist("#{dir}/st_integer255.rb")
expect(File).to exist("#{dir}/long.rb")
Expand All @@ -38,20 +37,21 @@
end
end

context "when processing examples from classes generated by valid xml schema" do
context "when processing examples from classes/files generated by valid xml schema" do
Dir.mktmpdir do |dir|
before do
described_class.to_models(
File.read("spec/fixtures/xml/math_document_schema.xsd"),
output_dir: dir,
create_files: true,
)
require_relative "#{dir}/math_document"
end

let(:valid_example) { File.read("spec/fixtures/xml/valid_math_document.xml") }
let(:invalid_example) { File.read("spec/fixtures/xml/invalid_math_document.xml") }

it "does not raise error with valid example" do
it "does not raise error with valid example and creates files" do
expect(defined?(MathDocument)).to eq("constant")
parsed = MathDocument.from_xml(valid_example)
expect(parsed.title).to eql("Example Title")
Expand All @@ -65,6 +65,72 @@
end
end
end

context "when classes are generated but files are not created" do
let(:schema_classes_hash) do
described_class.to_models(
File.read("spec/fixtures/xml/user.xsd"),
)
end

let(:expected_classes) do
[
"NonNegativeInteger",
"PositiveInteger",
"Base64Binary",
"UnsignedLong",
"UnsignedInt",
"HexBinary",
"Token",
"Long",
"User",
]
end

it "matches the expected class names of the schema" do
expect(schema_classes_hash.keys).to eql(expected_classes)
end
end

context "when classes are generated and loaded but files are not created" do
before do
described_class.to_models(
File.read("spec/fixtures/xml/user.xsd"),
load_classes: true,
)
end

let(:expected_classes) do
%w[
NonNegativeInteger
PositiveInteger
Base64Binary
UnsignedLong
UnsignedInt
HexBinary
Token
Long
User
]
end

let(:xml) do
<<~XML
<User>
<id>1112</id>
<age>29</age>
<token>u9dId901dp13f</token>
</User>
XML
end

it "matches the expected class names of the schema" do
expected_classes.each do |klass|
expect(be_const_defined(klass)).to be_truthy
end
expect(User.from_xml(xml).to_xml).to be_equivalent_to(xml)
end
end
end

describe "structure setup methods" do
Expand Down

0 comments on commit dd811d1

Please sign in to comment.