xsd_struct 0.1.0
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/AI_POLICY.md +5 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +89 -0
- data/LICENSE.txt +17 -0
- data/README.md +65 -0
- data/lib/xsd_struct/attribute_method.rb +33 -0
- data/lib/xsd_struct/attribute_valuer.rb +23 -0
- data/lib/xsd_struct/cache.rb +33 -0
- data/lib/xsd_struct/document.rb +36 -0
- data/lib/xsd_struct/element.rb +62 -0
- data/lib/xsd_struct/element_method.rb +36 -0
- data/lib/xsd_struct/element_valuer.rb +39 -0
- data/lib/xsd_struct/schema.rb +75 -0
- data/lib/xsd_struct.rb +17 -0
- data/sig/xsd_struct.rbs +52 -0
- metadata +115 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 1c1c5f92fb6a7e7b8b6200de5ab3af4b28b6c53deac97d3d7afe1320fe6eb33b
|
|
4
|
+
data.tar.gz: f9463188c81bb62633693623c552ecbf96dddca1f6cd8257a12276da4a8b0b69
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 9ec4efce2cb37f56222a6f3ae26fd0615447506b16e768877aa1dec95be1d078fe1c885101e0fb228a31082f508bff4f3ec4dde11076f1ead56bd643c15e7a9f
|
|
7
|
+
data.tar.gz: 68568be909b88169c0006ce36bfd900f5538319c144e0a456287cf72abc6cb5d26528221e3818c1a24442547a886f7ce32301351fbb8fa8665000f9ea60eba87
|
data/AI_POLICY.md
ADDED
data/CHANGELOG.md
ADDED
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Contributor Covenant 3.0 Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We pledge to make our community welcoming, safe, and equitable for all.
|
|
6
|
+
|
|
7
|
+
We are committed to fostering an environment that respects and promotes the dignity, rights, and contributions of all individuals, regardless of characteristics including race, ethnicity, caste, colour, age, physical characteristics, neurodiversity, disability, sex or gender, gender identity or expression, sexual orientation, language, philosophy or religion, national or social origin, socio-economic position, level of education, or other status. The same privileges of participation are extended to everyone who participates in good faith and in accordance with this Covenant.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Encouraged Behaviours
|
|
11
|
+
|
|
12
|
+
While acknowledging differences in social norms, we all strive to meet our community's expectations for positive behaviour. We also understand that our words and actions may be interpreted differently than we intend based on culture, background, or native language.
|
|
13
|
+
|
|
14
|
+
With these considerations in mind, we agree to behave mindfully toward each other and act in ways that centre our shared values, including:
|
|
15
|
+
|
|
16
|
+
1. Respecting the **purpose of our community**, our activities, and our ways of gathering.
|
|
17
|
+
2. Engaging **kindly and honestly** with others.
|
|
18
|
+
3. Respecting **different viewpoints** and experiences.
|
|
19
|
+
4. **Taking responsibility** for our actions and contributions.
|
|
20
|
+
5. Gracefully giving and accepting **constructive feedback**.
|
|
21
|
+
6. Committing to **repairing harm** when it occurs.
|
|
22
|
+
7. Behaving in other ways that promote and sustain the **well-being of our community**.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## Restricted Behaviours
|
|
26
|
+
|
|
27
|
+
We agree to restrict the following behaviours in our community. Instances, threats, and promotion of these behaviours are violations of this Code of Conduct.
|
|
28
|
+
|
|
29
|
+
1. **Harassment.** Violating explicitly expressed boundaries or engaging in unnecessary personal attention after any clear request to stop.
|
|
30
|
+
2. **Character attacks.** Making insulting, demeaning, or pejorative comments directed at a community member or group of people.
|
|
31
|
+
3. **Stereotyping or discrimination.** Characterising anyone’s personality or behaviour on the basis of immutable identities or traits.
|
|
32
|
+
4. **Sexualization.** Behaving in a way that would generally be considered inappropriately intimate in the context or purpose of the community.
|
|
33
|
+
5. **Violating confidentiality**. Sharing or acting on someone's personal or private information without their permission.
|
|
34
|
+
6. **Endangerment.** Causing, encouraging, or threatening violence or other harm toward any person or group.
|
|
35
|
+
7. Behaving in other ways that **threaten the well-being** of our community.
|
|
36
|
+
|
|
37
|
+
### Other Restrictions
|
|
38
|
+
|
|
39
|
+
1. **Misleading identity.** Impersonating someone else for any reason, or pretending to be someone else to evade enforcement actions.
|
|
40
|
+
2. **Failing to credit sources.** Not properly crediting the sources of content you contribute.
|
|
41
|
+
3. **Promotional materials**. Sharing marketing or other commercial content in a way that is outside the norms of the community.
|
|
42
|
+
4. **Irresponsible communication.** Failing to responsibly present content which includes, links or describes any other restricted behaviours.
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## Reporting an Issue
|
|
46
|
+
|
|
47
|
+
Tensions can occur between community members even when they are trying their best to collaborate. Not every conflict represents a code of conduct violation, and this Code of Conduct reinforces encouraged behaviours and norms that can help avoid conflicts and minimise harm.
|
|
48
|
+
|
|
49
|
+
When an incident does occur, it is important to report it promptly. To report a possible violation, please email pat@freelancing-gods.com.
|
|
50
|
+
|
|
51
|
+
Community Moderators take reports of violations seriously and will make every effort to respond in a timely manner. They will investigate all reports of code of conduct violations, reviewing messages, logs, and recordings, or interviewing witnesses and other participants. Community Moderators will keep investigation and enforcement actions as transparent as possible while prioritising safety and confidentiality. In order to honour these values, enforcement actions are carried out in private with the involved parties, but communicating to the whole community may be part of a mutually agreed upon resolution.
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
## Addressing and Repairing Harm
|
|
55
|
+
|
|
56
|
+
If an investigation by the Community Moderators finds that this Code of Conduct has been violated, the following enforcement ladder may be used to determine how best to repair harm, based on the incident's impact on the individuals involved and the community as a whole. Depending on the severity of a violation, lower rungs on the ladder may be skipped.
|
|
57
|
+
|
|
58
|
+
1) Warning
|
|
59
|
+
1) Event: A violation involving a single incident or series of incidents.
|
|
60
|
+
2) Consequence: A private, written warning from the Community Moderators.
|
|
61
|
+
3) Repair: Examples of repair include a private written apology, acknowledgement of responsibility, and seeking clarification on expectations.
|
|
62
|
+
2) Temporarily Limited Activities
|
|
63
|
+
1) Event: A repeated incidence of a violation that previously resulted in a warning, or the first incidence of a more serious violation.
|
|
64
|
+
2) Consequence: A private, written warning with a time-limited cooldown period designed to underscore the seriousness of the situation and give the community members involved time to process the incident. The cooldown period may be limited to particular communication channels or interactions with particular community members.
|
|
65
|
+
3) Repair: Examples of repair may include making an apology, using the cooldown period to reflect on actions and impact, and being thoughtful about re-entering community spaces after the period is over.
|
|
66
|
+
3) Temporary Suspension
|
|
67
|
+
1) Event: A pattern of repeated violation which the Community Moderators have tried to address with warnings, or a single serious violation.
|
|
68
|
+
2) Consequence: A private written warning with conditions for return from suspension. In general, temporary suspensions give the person being suspended time to reflect upon their behaviour and possible corrective actions.
|
|
69
|
+
3) Repair: Examples of repair include respecting the spirit of the suspension, meeting the specified conditions for return, and being thoughtful about how to reintegrate with the community when the suspension is lifted.
|
|
70
|
+
4) Permanent Ban
|
|
71
|
+
1) Event: A pattern of repeated code of conduct violations that other steps on the ladder have failed to resolve, or a violation so serious that the Community Moderators determine there is no way to keep the community safe with this person as a member.
|
|
72
|
+
2) Consequence: Access to all community spaces, tools, and communication channels is removed. In general, permanent bans should be rarely used, should have strong reasoning behind them, and should only be resorted to if working through other remedies has failed to change the behaviour.
|
|
73
|
+
3) Repair: There is no possible repair in cases of this severity.
|
|
74
|
+
|
|
75
|
+
This enforcement ladder is intended as a guideline. It does not limit the ability of Community Managers to use their discretion and judgment, in keeping with the best interests of our community.
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
## Scope
|
|
79
|
+
|
|
80
|
+
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public or other spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
## Attribution
|
|
84
|
+
|
|
85
|
+
This Code of Conduct is adapted from the Contributor Covenant, version 3.0, permanently available at [https://www.contributor-covenant.org/version/3/0/](https://www.contributor-covenant.org/version/3/0/).
|
|
86
|
+
|
|
87
|
+
Contributor Covenant is stewarded by the Organization for Ethical Source and licensed under CC BY-SA 4.0. To view a copy of this license, visit [https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/)
|
|
88
|
+
|
|
89
|
+
For answers to common questions about Contributor Covenant, see the FAQ at [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are provided at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations). Additional enforcement and community guideline resources can be found at [https://www.contributor-covenant.org/resources](https://www.contributor-covenant.org/resources). The enforcement ladder was inspired by the work of [Mozilla’s code of conduct team](https://github.com/mozilla/inclusion).
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
MIT NON-AI License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Pat Allan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of the software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions.
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
In addition, the following restrictions apply:
|
|
10
|
+
|
|
11
|
+
1. The Software and any modifications made to it may not be used for the purpose of training or improving machine learning algorithms, including but not limited to artificial intelligence, natural language processing, or data mining. This condition applies to any derivatives, modifications, or updates based on the Software code. Any usage of the Software in an AI-training dataset is considered a breach of this License.
|
|
12
|
+
|
|
13
|
+
2. The Software may not be included in any dataset used for training or improving machine learning algorithms, including but not limited to artificial intelligence, natural language processing, or data mining.
|
|
14
|
+
|
|
15
|
+
3. Any person or organisation found to be in violation of these restrictions will be subject to legal action and may be held liable for any damages resulting from such use.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# XSD Struct
|
|
2
|
+
|
|
3
|
+
Take an XSD schema and turn XML documents following that schema into Ruby object trees.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bundle add xsd_struct
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
gem install xsd_struct
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
To load a new schema in, you can either refer to the raw string XSD (which is itself an XML document):
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
schema = XSDStruct::Schema.new(
|
|
25
|
+
"<?xml version="1.0" encoding="UTF-8" ?> ..."
|
|
26
|
+
)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or, you can load the schema from a file. This has the additional benefit that the schema can refer to other files to build out its full understanding.
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
schema = XSDStruct::Schema.from_path("./path/to/schema.xsd")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
It's also possible to refer to a gzipped schema, which is useful when the schema files are extremely large:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
schema = XSDStruct::Schema.from_path(
|
|
39
|
+
"./path/to/schema.gz", gzip: true
|
|
40
|
+
)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Once a schema is loaded, then you can pass in the raw string of an XML document which conforms to that schema, to retrieve an object tree representing the document. It responds to methods matching any root element names, downcased and underscored to match usual Ruby naming styles. So, as a contrived example:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
document = schema.build("<Page><Item id="7" /></Page>")
|
|
47
|
+
# presuming a page may have many items:
|
|
48
|
+
document.page.items.first.id #=> 7
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Development
|
|
52
|
+
|
|
53
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
54
|
+
|
|
55
|
+
## Contributing
|
|
56
|
+
|
|
57
|
+
Bug reports and pull requests are welcome on Codeberg at https://codeberg.org/patallan/xsd_struct. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://codeberg.org/patallan/xsd_struct/src/branch/main/CODE_OF_CONDUCT.md) and [AI/LLMs policy](https://codeberg.org/patallan/xsd_struct/src/branch/main/AI_POLICY.md).
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
The gem is available as open source under the terms of a [modified MIT License](https://codeberg.org/patallan/xsd_struct/src/branch/main/LICENSE.txt) - mostly the same, but cannot be used for training/improving machine learning algorithms (AI/LLMs) directly or indirectly.
|
|
62
|
+
|
|
63
|
+
## Code of Conduct
|
|
64
|
+
|
|
65
|
+
Everyone interacting in the xsd_struct project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://codeberg.org/patallan/xsd_struct/src/branch/main/CODE_OF_CONDUCT.md).
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "attribute_valuer"
|
|
4
|
+
|
|
5
|
+
module XSDStruct
|
|
6
|
+
# Appends a method to an element subclass for a given attribute. The method
|
|
7
|
+
# name will be the attribute's name, but translated to idiomatic Ruby - so,
|
|
8
|
+
# downcased and underscored.
|
|
9
|
+
#
|
|
10
|
+
class AttributeMethod
|
|
11
|
+
def self.call(...)
|
|
12
|
+
new(...).call
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(klass, attribute)
|
|
16
|
+
@klass = klass
|
|
17
|
+
@attribute = attribute
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call
|
|
21
|
+
valuer = AttributeValuer.new(attribute)
|
|
22
|
+
name = valuer.accessor_name
|
|
23
|
+
|
|
24
|
+
klass.define_method(name) do
|
|
25
|
+
attributes[name] ||= valuer.call(node)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
attr_reader :klass, :attribute
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module XSDStruct
|
|
4
|
+
# Generates a attribute's value for a given element.
|
|
5
|
+
#
|
|
6
|
+
class AttributeValuer
|
|
7
|
+
def initialize(attribute)
|
|
8
|
+
@attribute = attribute
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call(parent)
|
|
12
|
+
parent.attributes[attribute.name].value
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def accessor_name
|
|
16
|
+
XSDStruct.inflector.underscore(attribute.name)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
attr_reader :attribute
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "singleton"
|
|
4
|
+
|
|
5
|
+
require_relative "element"
|
|
6
|
+
|
|
7
|
+
module XSDStruct
|
|
8
|
+
# XSDStruct::Cache maintains a known list of Element classes, representing
|
|
9
|
+
# each element in the given XSD document reader.
|
|
10
|
+
#
|
|
11
|
+
# The cache is lazily loaded, meaning that only elements that are traversed
|
|
12
|
+
# are generated.
|
|
13
|
+
class Cache
|
|
14
|
+
def initialize(reader)
|
|
15
|
+
@reader = reader
|
|
16
|
+
@cache = {}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def [](tree)
|
|
20
|
+
cache.fetch(tree) do
|
|
21
|
+
Element.generate(definition(tree), self, tree)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
attr_reader :reader, :cache
|
|
28
|
+
|
|
29
|
+
def definition(tree)
|
|
30
|
+
tree.inject(reader) { |parent, name| parent[name] } || reader[tree.last]
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "element_method"
|
|
4
|
+
|
|
5
|
+
module XSDStruct
|
|
6
|
+
# XSDStruct::Document represents the root of an XML document that has been
|
|
7
|
+
# parsed according to a given XSD schema.
|
|
8
|
+
#
|
|
9
|
+
# It responds to methods matching any root element names, downcased and
|
|
10
|
+
# underscored to match usual Ruby naming styles. So, for example:
|
|
11
|
+
#
|
|
12
|
+
# document = schema.build("<Page><Item id="7" /></Page>")
|
|
13
|
+
# # presuming a page may have many items:
|
|
14
|
+
# document.page.items.first.id #=> 7
|
|
15
|
+
#
|
|
16
|
+
class Document
|
|
17
|
+
def self.generate(xsd, cache)
|
|
18
|
+
klass = Class.new(Document)
|
|
19
|
+
|
|
20
|
+
xsd.elements.each do |element|
|
|
21
|
+
ElementMethod.call(klass, element, cache, [])
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
klass
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def initialize(node)
|
|
28
|
+
@node = node
|
|
29
|
+
@elements = {}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
attr_reader :node, :elements
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/inflector"
|
|
4
|
+
|
|
5
|
+
require_relative "attribute_method"
|
|
6
|
+
require_relative "element_method"
|
|
7
|
+
|
|
8
|
+
module XSDStruct
|
|
9
|
+
# Represents an element/node in an XML object tree. Attributes and inner
|
|
10
|
+
# elements can be accessed via methods (downcased, underscored, and pluralised
|
|
11
|
+
# where appropriate).
|
|
12
|
+
#
|
|
13
|
+
# If this element does not have any attributes or inner/sub-elements, then
|
|
14
|
+
# the value can be returned directly - and will be typed beyond raw strings if
|
|
15
|
+
# possible.
|
|
16
|
+
#
|
|
17
|
+
class Element
|
|
18
|
+
def self.generate(xsd, cache, tree)
|
|
19
|
+
klass = Class.new(Element)
|
|
20
|
+
|
|
21
|
+
xsd.collect_attributes.each do |attribute|
|
|
22
|
+
AttributeMethod.call(klass, attribute)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
xsd.collect_elements.each do |element|
|
|
26
|
+
ElementMethod.call(klass, element, cache, tree)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
klass
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def initialize(node, schema)
|
|
33
|
+
@node = node
|
|
34
|
+
@schema = schema
|
|
35
|
+
@attributes = {}
|
|
36
|
+
@elements = {}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def value
|
|
40
|
+
case schema.data_type
|
|
41
|
+
when "positiveInteger"
|
|
42
|
+
node.text.to_i
|
|
43
|
+
when "decimal"
|
|
44
|
+
node.text.to_f
|
|
45
|
+
else
|
|
46
|
+
node.text
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def value_or_self
|
|
51
|
+
direct? ? value : self
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
attr_reader :node, :schema, :attributes, :elements
|
|
57
|
+
|
|
58
|
+
def direct?
|
|
59
|
+
schema.collect_elements.empty? && schema.collect_attributes.empty?
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "element_valuer"
|
|
4
|
+
|
|
5
|
+
module XSDStruct
|
|
6
|
+
# Appends a method to an element subclass for a given sub-element. The method
|
|
7
|
+
# name will be the sub-element's name, but translated to idiomatic Ruby - so,
|
|
8
|
+
# downcased and underscored - and will also be plural if multiple instances of
|
|
9
|
+
# the sub-element are allowed.
|
|
10
|
+
#
|
|
11
|
+
class ElementMethod
|
|
12
|
+
def self.call(...)
|
|
13
|
+
new(...).call
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(klass, element, cache, tree)
|
|
17
|
+
@klass = klass
|
|
18
|
+
@element = element
|
|
19
|
+
@cache = cache
|
|
20
|
+
@tree = tree
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def call
|
|
24
|
+
valuer = ElementValuer.new(element, cache, tree)
|
|
25
|
+
name = valuer.accessor_name
|
|
26
|
+
|
|
27
|
+
klass.define_method(name) do
|
|
28
|
+
elements[name] ||= valuer.call(node)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
attr_reader :klass, :element, :cache, :tree
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module XSDStruct
|
|
4
|
+
# Generates a sub-element's value for a given parent. In the situation where
|
|
5
|
+
# the parent can have multiple sub-elements of the same type, this will return
|
|
6
|
+
# an array, but otherwise a single value.
|
|
7
|
+
#
|
|
8
|
+
# If the element has no possible children or attributes, then the raw text of
|
|
9
|
+
# that element is instead returned, typed accordingly where possible.
|
|
10
|
+
#
|
|
11
|
+
class ElementValuer
|
|
12
|
+
def initialize(element, cache, parent_tree)
|
|
13
|
+
@element = element
|
|
14
|
+
@cache = cache
|
|
15
|
+
@tree = parent_tree + [element.name]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call(parent)
|
|
19
|
+
values = parent
|
|
20
|
+
.children
|
|
21
|
+
.select { |child| child.name == element.name }
|
|
22
|
+
.collect { |child| cache[tree].new(child, element).value_or_self }
|
|
23
|
+
|
|
24
|
+
element.multiple_allowed? ? values : values.first
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def accessor_name
|
|
28
|
+
if element.multiple_allowed?
|
|
29
|
+
XSDStruct.inflector.pluralize(XSDStruct.inflector.underscore(element.name))
|
|
30
|
+
else
|
|
31
|
+
XSDStruct.inflector.underscore(element.name)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
attr_reader :element, :cache, :tree
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "nokogiri"
|
|
4
|
+
require "xsd"
|
|
5
|
+
require "zlib"
|
|
6
|
+
|
|
7
|
+
require_relative "cache"
|
|
8
|
+
require_relative "document"
|
|
9
|
+
|
|
10
|
+
module XSDStruct
|
|
11
|
+
# XSDStruct::Schema performs the initial parsing of a raw XSD string, which
|
|
12
|
+
# can then in turn be used to generate object trees from an XML document that
|
|
13
|
+
# conforms to the schema.
|
|
14
|
+
#
|
|
15
|
+
# To load a new schema in, you can either refer to the raw string XSD (which
|
|
16
|
+
# is itself an XML document):
|
|
17
|
+
#
|
|
18
|
+
# schema = XSDStruct::Schema.new(
|
|
19
|
+
# "<<?xml version="1.0" encoding="UTF-8" ?> ..."
|
|
20
|
+
# )
|
|
21
|
+
#
|
|
22
|
+
# Or, you can load the schema from a file. This has the additional benefit
|
|
23
|
+
# that the schema can refer to other files to build out its full
|
|
24
|
+
# understanding.
|
|
25
|
+
#
|
|
26
|
+
# schema = XSDStruct::Schema.from_path("./path/to/schema.xsd")
|
|
27
|
+
#
|
|
28
|
+
# It's also possible to refer to a gzipped schema, which is useful when the
|
|
29
|
+
# schema files are extremely large:
|
|
30
|
+
#
|
|
31
|
+
# schema = XSDStruct::Schema.from_path(
|
|
32
|
+
# "./path/to/schema.gz", gzip: true
|
|
33
|
+
# )
|
|
34
|
+
#
|
|
35
|
+
# Once a schema is loaded, then you can pass in the raw string of an XML
|
|
36
|
+
# document which conforms to that schema, to retrieve an object tree
|
|
37
|
+
# representing the document:
|
|
38
|
+
#
|
|
39
|
+
# document = schema.build("<?xml version="1.0" encoding="UTF-8" ?> ...")
|
|
40
|
+
#
|
|
41
|
+
# This document is an instance of XSDStruct::Document.
|
|
42
|
+
#
|
|
43
|
+
class Schema
|
|
44
|
+
def self.from_path(path, gzip: false)
|
|
45
|
+
raw = gzip ? Zlib::Inflate.inflate(File.binread(path)) : File.read(path)
|
|
46
|
+
|
|
47
|
+
new(raw, location: path)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def initialize(raw, location: nil)
|
|
51
|
+
@raw = raw
|
|
52
|
+
@location = location
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def build(raw)
|
|
56
|
+
XSDStruct::Document
|
|
57
|
+
.generate(reader, cache)
|
|
58
|
+
.new(Nokogiri::XML(raw))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
attr_reader :raw, :location
|
|
64
|
+
|
|
65
|
+
def cache
|
|
66
|
+
@cache ||= XSDStruct::Cache.new(reader)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def reader
|
|
70
|
+
@reader ||= XSD::XML.new.tap do |xml_reader|
|
|
71
|
+
xml_reader.add_schema_xml(raw, location:)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
data/lib/xsd_struct.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/inflector"
|
|
4
|
+
require "nokogiri"
|
|
5
|
+
|
|
6
|
+
# XSDStruct transforms XML documents into object trees, based on a schema
|
|
7
|
+
# defined by an XSD document.
|
|
8
|
+
#
|
|
9
|
+
# See XSDStruct::Schema for usage.
|
|
10
|
+
#
|
|
11
|
+
module XSDStruct
|
|
12
|
+
def self.inflector
|
|
13
|
+
@inflector ||= Dry::Inflector.new
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
require_relative "xsd_struct/schema"
|
data/sig/xsd_struct.rbs
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module XSDStruct
|
|
2
|
+
type elementValue = (XSDStruct::Element | String | Integer | Float)
|
|
3
|
+
|
|
4
|
+
def self.inflector: () -> Dry::Inflector
|
|
5
|
+
|
|
6
|
+
class AttributeMethod
|
|
7
|
+
def self.call: (::Class klass, XSD::Attribute attribute) -> ::Symbol
|
|
8
|
+
def initialize: (::Class klass, XSD::Attribute attribute) -> void
|
|
9
|
+
def call: () -> ::Symbol
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class AttributeValuer
|
|
13
|
+
def initialize: (XSD::Attribute attribute) -> void
|
|
14
|
+
def call: (untyped parent) -> String
|
|
15
|
+
def accessor_name: () -> String
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class Cache
|
|
19
|
+
def initialize: (XSD::XML reader) -> void
|
|
20
|
+
def []: (Array[String] tree) -> Class
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Document
|
|
24
|
+
def self.generate: (XSD::XML xsd, XSDStruct::Cache cache) -> Class
|
|
25
|
+
def initialize: (Nokogiri::XML::Document node) -> void
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class ElementMethod
|
|
29
|
+
def self.call: (Class klass, XSD::Element element, XSDStruct::Cache cache, Array[String] tree) -> Symbol
|
|
30
|
+
def initialize: (Class klass, XSD::Element element, XSDStruct::Cache cache, Array[String] tree) -> void
|
|
31
|
+
def call: () -> Symbol
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class ElementValuer
|
|
35
|
+
def initialize: (XSD::Element element, XSDStruct::Cache cache, Array[String] parent_tree) -> void
|
|
36
|
+
def call: (untyped parent) -> (elementValue | Array[elementValue] | nil)
|
|
37
|
+
def accessor_name: () -> String
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class Element
|
|
41
|
+
def self.generate: (XSD::Element xsd, XSDStruct::Cache cache, Array[String] tree) -> Class
|
|
42
|
+
def initialize: (Nokogiri::XML::Node node, XSD::Element schema) -> void
|
|
43
|
+
def value: () -> (String | Integer | Float)
|
|
44
|
+
def value_or_self: () -> elementValue
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class Schema
|
|
48
|
+
def self.from_path: (String path, ?gzip: bool) -> XSDStruct::Schema
|
|
49
|
+
def initialize: (String raw, ?location: (nil | String)) -> void
|
|
50
|
+
def build: (String raw) -> XSDStruct::Document
|
|
51
|
+
end
|
|
52
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: xsd_struct
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Pat Allan
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: dry-inflector
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.2'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.2'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: nokogiri
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.19'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.19'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: xsd
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '2.9'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '2.9'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: zlib
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '3.2'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '3.2'
|
|
68
|
+
description: Uses XSD schemas to turn an XML document into a collection of Ruby objects
|
|
69
|
+
email:
|
|
70
|
+
- pat@freelancing-gods.com
|
|
71
|
+
executables: []
|
|
72
|
+
extensions: []
|
|
73
|
+
extra_rdoc_files: []
|
|
74
|
+
files:
|
|
75
|
+
- AI_POLICY.md
|
|
76
|
+
- CHANGELOG.md
|
|
77
|
+
- CODE_OF_CONDUCT.md
|
|
78
|
+
- LICENSE.txt
|
|
79
|
+
- README.md
|
|
80
|
+
- lib/xsd_struct.rb
|
|
81
|
+
- lib/xsd_struct/attribute_method.rb
|
|
82
|
+
- lib/xsd_struct/attribute_valuer.rb
|
|
83
|
+
- lib/xsd_struct/cache.rb
|
|
84
|
+
- lib/xsd_struct/document.rb
|
|
85
|
+
- lib/xsd_struct/element.rb
|
|
86
|
+
- lib/xsd_struct/element_method.rb
|
|
87
|
+
- lib/xsd_struct/element_valuer.rb
|
|
88
|
+
- lib/xsd_struct/schema.rb
|
|
89
|
+
- sig/xsd_struct.rbs
|
|
90
|
+
homepage: https://codeberg.org/patallan/xsd_struct
|
|
91
|
+
licenses:
|
|
92
|
+
- MIT NON-AI
|
|
93
|
+
metadata:
|
|
94
|
+
homepage_uri: https://codeberg.org/patallan/xsd_struct
|
|
95
|
+
source_code_uri: https://codeberg.org/patallan/xsd_struct
|
|
96
|
+
changelog_uri: https://codeberg.org/patallan/xsd_struct/src/branch/main/CHANGELOG.md
|
|
97
|
+
rubygems_mfa_required: 'true'
|
|
98
|
+
rdoc_options: []
|
|
99
|
+
require_paths:
|
|
100
|
+
- lib
|
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
102
|
+
requirements:
|
|
103
|
+
- - ">="
|
|
104
|
+
- !ruby/object:Gem::Version
|
|
105
|
+
version: 3.3.0
|
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
requirements: []
|
|
112
|
+
rubygems_version: 4.0.20
|
|
113
|
+
specification_version: 4
|
|
114
|
+
summary: Combine XSD and XML documents to build Ruby object trees
|
|
115
|
+
test_files: []
|