moxml 0.1.6 → 0.1.8
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 +4 -4
- data/.github/workflows/dependent-repos.json +5 -0
- data/.github/workflows/dependent-tests.yml +20 -0
- data/.github/workflows/docs.yml +59 -0
- data/.github/workflows/rake.yml +12 -4
- data/.github/workflows/release.yml +5 -3
- data/.gitignore +37 -0
- data/.rubocop.yml +15 -7
- data/.rubocop_todo.yml +238 -40
- data/Gemfile +14 -9
- data/LICENSE.md +6 -2
- data/README.adoc +535 -373
- data/Rakefile +53 -0
- data/benchmarks/.gitignore +6 -0
- data/benchmarks/generate_report.rb +550 -0
- data/docs/Gemfile +13 -0
- data/docs/_config.yml +138 -0
- data/docs/_guides/advanced-features.adoc +87 -0
- data/docs/_guides/development-testing.adoc +165 -0
- data/docs/_guides/index.adoc +45 -0
- data/docs/_guides/modifying-xml.adoc +293 -0
- data/docs/_guides/parsing-xml.adoc +231 -0
- data/docs/_guides/sax-parsing.adoc +603 -0
- data/docs/_guides/working-with-documents.adoc +118 -0
- data/docs/_pages/adapter-compatibility.adoc +369 -0
- data/docs/_pages/adapters/headed-ox.adoc +237 -0
- data/docs/_pages/adapters/index.adoc +98 -0
- data/docs/_pages/adapters/libxml.adoc +286 -0
- data/docs/_pages/adapters/nokogiri.adoc +252 -0
- data/docs/_pages/adapters/oga.adoc +292 -0
- data/docs/_pages/adapters/ox.adoc +55 -0
- data/docs/_pages/adapters/rexml.adoc +293 -0
- data/docs/_pages/best-practices.adoc +430 -0
- data/docs/_pages/compatibility.adoc +468 -0
- data/docs/_pages/configuration.adoc +251 -0
- data/docs/_pages/error-handling.adoc +350 -0
- data/docs/_pages/headed-ox-limitations.adoc +558 -0
- data/docs/_pages/headed-ox.adoc +1025 -0
- data/docs/_pages/index.adoc +35 -0
- data/docs/_pages/installation.adoc +141 -0
- data/docs/_pages/node-api-reference.adoc +50 -0
- data/docs/_pages/performance.adoc +36 -0
- data/docs/_pages/quick-start.adoc +244 -0
- data/docs/_pages/thread-safety.adoc +29 -0
- data/docs/_references/document-api.adoc +408 -0
- data/docs/_references/index.adoc +48 -0
- data/docs/_tutorials/basic-usage.adoc +268 -0
- data/docs/_tutorials/builder-pattern.adoc +343 -0
- data/docs/_tutorials/index.adoc +33 -0
- data/docs/_tutorials/namespace-handling.adoc +325 -0
- data/docs/_tutorials/xpath-queries.adoc +359 -0
- data/docs/index.adoc +122 -0
- data/examples/README.md +124 -0
- data/examples/api_client/README.md +424 -0
- data/examples/api_client/api_client.rb +394 -0
- data/examples/api_client/example_response.xml +48 -0
- data/examples/headed_ox_example/README.md +90 -0
- data/examples/headed_ox_example/headed_ox_demo.rb +71 -0
- data/examples/rss_parser/README.md +194 -0
- data/examples/rss_parser/example_feed.xml +93 -0
- data/examples/rss_parser/rss_parser.rb +189 -0
- data/examples/sax_parsing/README.md +50 -0
- data/examples/sax_parsing/data_extractor.rb +75 -0
- data/examples/sax_parsing/example.xml +21 -0
- data/examples/sax_parsing/large_file.rb +78 -0
- data/examples/sax_parsing/simple_parser.rb +55 -0
- data/examples/web_scraper/README.md +352 -0
- data/examples/web_scraper/example_page.html +201 -0
- data/examples/web_scraper/web_scraper.rb +312 -0
- data/lib/moxml/adapter/base.rb +107 -28
- data/lib/moxml/adapter/customized_libxml/cdata.rb +28 -0
- data/lib/moxml/adapter/customized_libxml/comment.rb +24 -0
- data/lib/moxml/adapter/customized_libxml/declaration.rb +85 -0
- data/lib/moxml/adapter/customized_libxml/element.rb +39 -0
- data/lib/moxml/adapter/customized_libxml/node.rb +44 -0
- data/lib/moxml/adapter/customized_libxml/processing_instruction.rb +31 -0
- data/lib/moxml/adapter/customized_libxml/text.rb +27 -0
- data/lib/moxml/adapter/customized_oga/xml_generator.rb +1 -1
- data/lib/moxml/adapter/customized_ox/attribute.rb +28 -3
- data/lib/moxml/adapter/customized_ox/namespace.rb +0 -2
- data/lib/moxml/adapter/customized_ox/text.rb +0 -2
- data/lib/moxml/adapter/customized_rexml/formatter.rb +11 -6
- data/lib/moxml/adapter/headed_ox.rb +161 -0
- data/lib/moxml/adapter/libxml.rb +1548 -0
- data/lib/moxml/adapter/nokogiri.rb +121 -9
- data/lib/moxml/adapter/oga.rb +123 -12
- data/lib/moxml/adapter/ox.rb +283 -27
- data/lib/moxml/adapter/rexml.rb +127 -20
- data/lib/moxml/adapter.rb +21 -4
- data/lib/moxml/attribute.rb +6 -0
- data/lib/moxml/builder.rb +40 -4
- data/lib/moxml/config.rb +8 -3
- data/lib/moxml/context.rb +39 -1
- data/lib/moxml/doctype.rb +13 -1
- data/lib/moxml/document.rb +39 -6
- data/lib/moxml/document_builder.rb +27 -5
- data/lib/moxml/element.rb +71 -2
- data/lib/moxml/error.rb +175 -6
- data/lib/moxml/node.rb +94 -3
- data/lib/moxml/node_set.rb +34 -0
- data/lib/moxml/sax/block_handler.rb +194 -0
- data/lib/moxml/sax/element_handler.rb +124 -0
- data/lib/moxml/sax/handler.rb +113 -0
- data/lib/moxml/sax.rb +31 -0
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xml_utils/encoder.rb +4 -4
- data/lib/moxml/xml_utils.rb +7 -4
- data/lib/moxml/xpath/ast/node.rb +159 -0
- data/lib/moxml/xpath/cache.rb +91 -0
- data/lib/moxml/xpath/compiler.rb +1768 -0
- data/lib/moxml/xpath/context.rb +26 -0
- data/lib/moxml/xpath/conversion.rb +124 -0
- data/lib/moxml/xpath/engine.rb +52 -0
- data/lib/moxml/xpath/errors.rb +101 -0
- data/lib/moxml/xpath/lexer.rb +304 -0
- data/lib/moxml/xpath/parser.rb +485 -0
- data/lib/moxml/xpath/ruby/generator.rb +269 -0
- data/lib/moxml/xpath/ruby/node.rb +193 -0
- data/lib/moxml/xpath.rb +37 -0
- data/lib/moxml.rb +5 -2
- data/moxml.gemspec +3 -1
- data/old-specs/moxml/adapter/customized_libxml/.gitkeep +6 -0
- data/spec/consistency/README.md +77 -0
- data/spec/{moxml/examples/adapter_spec.rb → consistency/adapter_parity_spec.rb} +4 -4
- data/spec/examples/README.md +75 -0
- data/spec/{support/shared_examples/examples/attribute.rb → examples/attribute_examples_spec.rb} +1 -1
- data/spec/{support/shared_examples/examples/basic_usage.rb → examples/basic_usage_spec.rb} +2 -2
- data/spec/{support/shared_examples/examples/namespace.rb → examples/namespace_examples_spec.rb} +3 -3
- data/spec/{support/shared_examples/examples/readme_examples.rb → examples/readme_examples_spec.rb} +6 -4
- data/spec/{support/shared_examples/examples/xpath.rb → examples/xpath_examples_spec.rb} +10 -6
- data/spec/integration/README.md +71 -0
- data/spec/{moxml/all_with_adapters_spec.rb → integration/all_adapters_spec.rb} +3 -2
- data/spec/integration/headed_ox_integration_spec.rb +326 -0
- data/spec/{support → integration}/shared_examples/edge_cases.rb +37 -10
- data/spec/integration/shared_examples/high_level/.gitkeep +0 -0
- data/spec/{support/shared_examples/context.rb → integration/shared_examples/high_level/context_behavior.rb} +2 -1
- data/spec/{support/shared_examples/integration.rb → integration/shared_examples/integration_workflows.rb} +23 -6
- data/spec/integration/shared_examples/node_wrappers/.gitkeep +0 -0
- data/spec/{support/shared_examples/cdata.rb → integration/shared_examples/node_wrappers/cdata_behavior.rb} +6 -1
- data/spec/{support/shared_examples/comment.rb → integration/shared_examples/node_wrappers/comment_behavior.rb} +2 -1
- data/spec/{support/shared_examples/declaration.rb → integration/shared_examples/node_wrappers/declaration_behavior.rb} +5 -2
- data/spec/{support/shared_examples/doctype.rb → integration/shared_examples/node_wrappers/doctype_behavior.rb} +2 -2
- data/spec/{support/shared_examples/document.rb → integration/shared_examples/node_wrappers/document_behavior.rb} +1 -1
- data/spec/{support/shared_examples/node.rb → integration/shared_examples/node_wrappers/node_behavior.rb} +9 -2
- data/spec/{support/shared_examples/node_set.rb → integration/shared_examples/node_wrappers/node_set_behavior.rb} +1 -18
- data/spec/{support/shared_examples/processing_instruction.rb → integration/shared_examples/node_wrappers/processing_instruction_behavior.rb} +6 -2
- data/spec/moxml/README.md +41 -0
- data/spec/moxml/adapter/.gitkeep +0 -0
- data/spec/moxml/adapter/README.md +61 -0
- data/spec/moxml/adapter/base_spec.rb +27 -0
- data/spec/moxml/adapter/headed_ox_spec.rb +311 -0
- data/spec/moxml/adapter/libxml_spec.rb +14 -0
- data/spec/moxml/adapter/ox_spec.rb +9 -8
- data/spec/moxml/adapter/shared_examples/.gitkeep +0 -0
- data/spec/{support/shared_examples/xml_adapter.rb → moxml/adapter/shared_examples/adapter_contract.rb} +39 -12
- data/spec/moxml/adapter_spec.rb +16 -0
- data/spec/moxml/attribute_spec.rb +30 -0
- data/spec/moxml/builder_spec.rb +33 -0
- data/spec/moxml/cdata_spec.rb +31 -0
- data/spec/moxml/comment_spec.rb +31 -0
- data/spec/moxml/config_spec.rb +3 -3
- data/spec/moxml/context_spec.rb +28 -0
- data/spec/moxml/declaration_spec.rb +36 -0
- data/spec/moxml/doctype_spec.rb +33 -0
- data/spec/moxml/document_builder_spec.rb +30 -0
- data/spec/moxml/document_spec.rb +105 -0
- data/spec/moxml/element_spec.rb +143 -0
- data/spec/moxml/error_spec.rb +266 -22
- data/spec/{moxml_spec.rb → moxml/moxml_spec.rb} +9 -9
- data/spec/moxml/namespace_spec.rb +32 -0
- data/spec/moxml/node_set_spec.rb +39 -0
- data/spec/moxml/node_spec.rb +37 -0
- data/spec/moxml/processing_instruction_spec.rb +34 -0
- data/spec/moxml/sax_spec.rb +1067 -0
- data/spec/moxml/text_spec.rb +31 -0
- data/spec/moxml/version_spec.rb +14 -0
- data/spec/moxml/xml_utils/.gitkeep +0 -0
- data/spec/moxml/xml_utils/encoder_spec.rb +27 -0
- data/spec/moxml/xml_utils_spec.rb +49 -0
- data/spec/moxml/xpath/ast/node_spec.rb +83 -0
- data/spec/moxml/xpath/axes_spec.rb +296 -0
- data/spec/moxml/xpath/cache_spec.rb +358 -0
- data/spec/moxml/xpath/compiler_spec.rb +406 -0
- data/spec/moxml/xpath/context_spec.rb +210 -0
- data/spec/moxml/xpath/conversion_spec.rb +365 -0
- data/spec/moxml/xpath/fixtures/sample.xml +25 -0
- data/spec/moxml/xpath/functions/boolean_functions_spec.rb +114 -0
- data/spec/moxml/xpath/functions/node_functions_spec.rb +145 -0
- data/spec/moxml/xpath/functions/numeric_functions_spec.rb +164 -0
- data/spec/moxml/xpath/functions/position_functions_spec.rb +93 -0
- data/spec/moxml/xpath/functions/special_functions_spec.rb +89 -0
- data/spec/moxml/xpath/functions/string_functions_spec.rb +381 -0
- data/spec/moxml/xpath/lexer_spec.rb +488 -0
- data/spec/moxml/xpath/parser_integration_spec.rb +210 -0
- data/spec/moxml/xpath/parser_spec.rb +364 -0
- data/spec/moxml/xpath/ruby/generator_spec.rb +421 -0
- data/spec/moxml/xpath/ruby/node_spec.rb +291 -0
- data/spec/moxml/xpath_capabilities_spec.rb +199 -0
- data/spec/moxml/xpath_spec.rb +77 -0
- data/spec/performance/README.md +83 -0
- data/spec/performance/benchmark_spec.rb +64 -0
- data/spec/{support/shared_examples/examples/memory.rb → performance/memory_usage_spec.rb} +3 -1
- data/spec/{support/shared_examples/examples/thread_safety.rb → performance/thread_safety_spec.rb} +3 -1
- data/spec/performance/xpath_benchmark_spec.rb +259 -0
- data/spec/spec_helper.rb +58 -1
- data/spec/support/xml_matchers.rb +1 -1
- metadata +176 -35
- data/lib/ox/node.rb +0 -9
- data/spec/support/shared_examples/examples/benchmark_spec.rb +0 -51
- /data/spec/{support/shared_examples/builder.rb → integration/shared_examples/high_level/builder_behavior.rb} +0 -0
- /data/spec/{support/shared_examples/document_builder.rb → integration/shared_examples/high_level/document_builder_behavior.rb} +0 -0
- /data/spec/{support/shared_examples/attribute.rb → integration/shared_examples/node_wrappers/attribute_behavior.rb} +0 -0
- /data/spec/{support/shared_examples/element.rb → integration/shared_examples/node_wrappers/element_behavior.rb} +0 -0
- /data/spec/{support/shared_examples/namespace.rb → integration/shared_examples/node_wrappers/namespace_behavior.rb} +0 -0
- /data/spec/{support/shared_examples/text.rb → integration/shared_examples/node_wrappers/text_behavior.rb} +0 -0
data/docs/_config.yml
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Site settings
|
|
2
|
+
title: Moxml
|
|
3
|
+
description: >-
|
|
4
|
+
Modern XML processing for Ruby with unified adapter interface
|
|
5
|
+
baseurl: "/moxml"
|
|
6
|
+
url: "https://lutaml.github.io"
|
|
7
|
+
|
|
8
|
+
# Theme
|
|
9
|
+
theme: just-the-docs
|
|
10
|
+
remote_theme: just-the-docs/just-the-docs@v0.7.0
|
|
11
|
+
|
|
12
|
+
# Color scheme
|
|
13
|
+
color_scheme: light
|
|
14
|
+
|
|
15
|
+
# Search
|
|
16
|
+
search_enabled: true
|
|
17
|
+
search:
|
|
18
|
+
heading_level: 2
|
|
19
|
+
previews: 3
|
|
20
|
+
preview_words_before: 5
|
|
21
|
+
preview_words_after: 10
|
|
22
|
+
tokenizer_separator: /[\s/]+/
|
|
23
|
+
rel_url: true
|
|
24
|
+
button: false
|
|
25
|
+
|
|
26
|
+
# Heading anchors
|
|
27
|
+
heading_anchors: true
|
|
28
|
+
|
|
29
|
+
# Aux links for the upper right navigation
|
|
30
|
+
aux_links:
|
|
31
|
+
"GitHub":
|
|
32
|
+
- "https://github.com/lutaml/moxml"
|
|
33
|
+
"RubyGems":
|
|
34
|
+
- "https://rubygems.org/gems/moxml"
|
|
35
|
+
|
|
36
|
+
# Makes Aux links open in a new tab
|
|
37
|
+
aux_links_new_tab: true
|
|
38
|
+
|
|
39
|
+
# Back to top link
|
|
40
|
+
back_to_top: true
|
|
41
|
+
back_to_top_text: "Back to top"
|
|
42
|
+
|
|
43
|
+
# Footer content
|
|
44
|
+
footer_content: >-
|
|
45
|
+
Copyright © 2025 Ribose. Distributed by a
|
|
46
|
+
<a href="https://github.com/lutaml/moxml/blob/main/LICENSE.md">BSD-2-Clause license</a>.
|
|
47
|
+
|
|
48
|
+
# Footer last edit timestamp
|
|
49
|
+
last_edit_timestamp: true
|
|
50
|
+
last_edit_time_format: "%b %e %Y at %I:%M %p"
|
|
51
|
+
|
|
52
|
+
# Navigation structure
|
|
53
|
+
nav_sort: order
|
|
54
|
+
|
|
55
|
+
# Collections for organizing content
|
|
56
|
+
collections:
|
|
57
|
+
pages:
|
|
58
|
+
permalink: "/:path/"
|
|
59
|
+
output: true
|
|
60
|
+
tutorials:
|
|
61
|
+
permalink: "/:collection/:path/"
|
|
62
|
+
output: true
|
|
63
|
+
guides:
|
|
64
|
+
permalink: "/:collection/:path/"
|
|
65
|
+
output: true
|
|
66
|
+
references:
|
|
67
|
+
permalink: "/:collection/:path/"
|
|
68
|
+
output: true
|
|
69
|
+
|
|
70
|
+
# Just the Docs collection configuration
|
|
71
|
+
just_the_docs:
|
|
72
|
+
collections:
|
|
73
|
+
pages:
|
|
74
|
+
name: Core topics
|
|
75
|
+
nav_fold: false
|
|
76
|
+
tutorials:
|
|
77
|
+
name: Tutorials
|
|
78
|
+
nav_fold: true
|
|
79
|
+
guides:
|
|
80
|
+
name: Guides
|
|
81
|
+
nav_fold: true
|
|
82
|
+
references:
|
|
83
|
+
name: Reference
|
|
84
|
+
nav_fold: true
|
|
85
|
+
|
|
86
|
+
# Plugins
|
|
87
|
+
plugins:
|
|
88
|
+
- jekyll-asciidoc
|
|
89
|
+
- jekyll-seo-tag
|
|
90
|
+
- jekyll-sitemap
|
|
91
|
+
|
|
92
|
+
# AsciiDoc configuration
|
|
93
|
+
asciidoc: {}
|
|
94
|
+
asciidoctor:
|
|
95
|
+
attributes:
|
|
96
|
+
- source-highlighter=rouge
|
|
97
|
+
- rouge-style=github
|
|
98
|
+
- icons=font
|
|
99
|
+
- sectanchors=true
|
|
100
|
+
- idprefix=
|
|
101
|
+
- idseparator=-
|
|
102
|
+
- experimental=true
|
|
103
|
+
|
|
104
|
+
include:
|
|
105
|
+
- '*.adoc'
|
|
106
|
+
|
|
107
|
+
# Exclude from processing
|
|
108
|
+
exclude:
|
|
109
|
+
- Gemfile
|
|
110
|
+
- Gemfile.lock
|
|
111
|
+
- _site
|
|
112
|
+
- node_modules
|
|
113
|
+
- vendor/bundle/
|
|
114
|
+
- vendor/cache/
|
|
115
|
+
- vendor/gems/
|
|
116
|
+
- vendor/ruby/
|
|
117
|
+
- Rakefile
|
|
118
|
+
|
|
119
|
+
# Enable code copy button
|
|
120
|
+
enable_copy_code_button: true
|
|
121
|
+
|
|
122
|
+
# Callouts
|
|
123
|
+
callouts_level: quiet
|
|
124
|
+
callouts:
|
|
125
|
+
highlight:
|
|
126
|
+
color: yellow
|
|
127
|
+
important:
|
|
128
|
+
title: Important
|
|
129
|
+
color: blue
|
|
130
|
+
new:
|
|
131
|
+
title: New
|
|
132
|
+
color: green
|
|
133
|
+
note:
|
|
134
|
+
title: Note
|
|
135
|
+
color: purple
|
|
136
|
+
warning:
|
|
137
|
+
title: Warning
|
|
138
|
+
color: red
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Advanced features
|
|
3
|
+
parent: Guides
|
|
4
|
+
nav_order: 8
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
== Advanced features
|
|
8
|
+
|
|
9
|
+
== Advanced features
|
|
10
|
+
|
|
11
|
+
=== XPath querying and node mapping
|
|
12
|
+
|
|
13
|
+
==== Nokogiri, Oga, REXML, LibXML
|
|
14
|
+
|
|
15
|
+
Moxml provides efficient XPath querying by leveraging the native XML library's
|
|
16
|
+
implementation while maintaining consistent node mapping:
|
|
17
|
+
|
|
18
|
+
[source,ruby]
|
|
19
|
+
----
|
|
20
|
+
# Find all book elements
|
|
21
|
+
books = doc.xpath('//book')
|
|
22
|
+
# Returns Moxml::Element objects mapped to native nodes
|
|
23
|
+
|
|
24
|
+
# Find with namespaces
|
|
25
|
+
titles = doc.xpath('//dc:title',
|
|
26
|
+
'dc' => 'http://purl.org/dc/elements/1.1/')
|
|
27
|
+
|
|
28
|
+
# Find first matching node
|
|
29
|
+
first_book = doc.at_xpath('//book')
|
|
30
|
+
|
|
31
|
+
# Chain queries
|
|
32
|
+
doc.xpath('//book').each do |book|
|
|
33
|
+
# Each book is a mapped Moxml::Element
|
|
34
|
+
title = book.at_xpath('.//title')
|
|
35
|
+
puts "#{book['id']}: #{title.text}"
|
|
36
|
+
end
|
|
37
|
+
----
|
|
38
|
+
|
|
39
|
+
==== Ox
|
|
40
|
+
|
|
41
|
+
The native Ox's query method
|
|
42
|
+
https://www.ohler.com/ox/Ox/Element.html#method-i-locate[`locate`] resembles
|
|
43
|
+
XPath but has a different syntax.
|
|
44
|
+
|
|
45
|
+
==== HeadedOx
|
|
46
|
+
|
|
47
|
+
HeadedOx provides comprehensive (but not fully) XPath 1.0 support via a pure
|
|
48
|
+
Ruby XPath engine layered on top of Ox.
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
=== Namespace handling
|
|
52
|
+
|
|
53
|
+
[source,ruby]
|
|
54
|
+
----
|
|
55
|
+
# Add namespace to element
|
|
56
|
+
element.add_namespace('dc', 'http://purl.org/dc/elements/1.1/')
|
|
57
|
+
|
|
58
|
+
# Create element in namespace
|
|
59
|
+
title = doc.create_element('dc:title')
|
|
60
|
+
title.text = 'Document Title'
|
|
61
|
+
|
|
62
|
+
# Query with namespaces
|
|
63
|
+
doc.xpath('//dc:title',
|
|
64
|
+
'dc' => 'http://purl.org/dc/elements/1.1/')
|
|
65
|
+
----
|
|
66
|
+
|
|
67
|
+
=== Accessing native implementation
|
|
68
|
+
|
|
69
|
+
While not typically needed, you can access the underlying XML library's nodes:
|
|
70
|
+
|
|
71
|
+
[source,ruby]
|
|
72
|
+
----
|
|
73
|
+
# Get native node
|
|
74
|
+
native_node = element.native
|
|
75
|
+
|
|
76
|
+
# Get adapter being used
|
|
77
|
+
adapter = element.context.config.adapter
|
|
78
|
+
|
|
79
|
+
# Create from native node
|
|
80
|
+
element = Moxml::Element.new(native_node, context)
|
|
81
|
+
----
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
See also:
|
|
85
|
+
|
|
86
|
+
* link:../pages/compatibility[Adapter compatibility]
|
|
87
|
+
* link:../pages/adapters/[Individual adapter documentation]
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Development and testing
|
|
3
|
+
parent: Guides
|
|
4
|
+
nav_order: 10
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
== Development and testing
|
|
9
|
+
|
|
10
|
+
=== Skipping benchmarks
|
|
11
|
+
|
|
12
|
+
Benchmark tests can be slow and are not needed for regular test runs. To
|
|
13
|
+
speed up local development, you can skip benchmark tests using the
|
|
14
|
+
`SKIP_BENCHMARKS` environment variable.
|
|
15
|
+
|
|
16
|
+
Syntax:
|
|
17
|
+
|
|
18
|
+
[source,shell]
|
|
19
|
+
----
|
|
20
|
+
SKIP_BENCHMARKS=1 bundle exec rspec
|
|
21
|
+
----
|
|
22
|
+
|
|
23
|
+
This will skip all benchmark tests while running the regular test suite.
|
|
24
|
+
|
|
25
|
+
To run benchmarks explicitly:
|
|
26
|
+
|
|
27
|
+
[source,shell]
|
|
28
|
+
----
|
|
29
|
+
bundle exec rspec spec/moxml/examples/xpath_benchmark_spec.rb
|
|
30
|
+
----
|
|
31
|
+
|
|
32
|
+
Or use the rake task:
|
|
33
|
+
|
|
34
|
+
[source,shell]
|
|
35
|
+
----
|
|
36
|
+
rake benchmark:xpath
|
|
37
|
+
----
|
|
38
|
+
|
|
39
|
+
NOTE: The `rake benchmark:xpath` task always runs benchmarks regardless of the
|
|
40
|
+
`SKIP_BENCHMARKS` environment variable setting.
|
|
41
|
+
|
|
42
|
+
=== Running tests with coverage
|
|
43
|
+
|
|
44
|
+
To run the test suite with code coverage tracking:
|
|
45
|
+
|
|
46
|
+
[source,shell]
|
|
47
|
+
----
|
|
48
|
+
COVERAGE=true bundle exec rspec
|
|
49
|
+
----
|
|
50
|
+
|
|
51
|
+
After running, view the coverage report:
|
|
52
|
+
|
|
53
|
+
[source,shell]
|
|
54
|
+
----
|
|
55
|
+
open coverage/index.html
|
|
56
|
+
----
|
|
57
|
+
|
|
58
|
+
The coverage configuration includes:
|
|
59
|
+
|
|
60
|
+
* Minimum overall coverage: 90%
|
|
61
|
+
* Minimum per-file coverage: 80%
|
|
62
|
+
* Organized groups for Core, Adapters, and Utilities
|
|
63
|
+
* Filters for spec and vendor directories
|
|
64
|
+
|
|
65
|
+
=== Generating performance benchmark reports
|
|
66
|
+
|
|
67
|
+
Moxml provides a comprehensive benchmark reporting system that measures and
|
|
68
|
+
compares all adapters across multiple dimensions.
|
|
69
|
+
|
|
70
|
+
==== Running the benchmark report
|
|
71
|
+
|
|
72
|
+
To generate a complete performance report for all adapters:
|
|
73
|
+
|
|
74
|
+
[source,shell]
|
|
75
|
+
----
|
|
76
|
+
rake benchmark:report
|
|
77
|
+
----
|
|
78
|
+
|
|
79
|
+
Or run the script directly:
|
|
80
|
+
|
|
81
|
+
[source,shell]
|
|
82
|
+
----
|
|
83
|
+
bundle exec ruby benchmarks/generate_report.rb
|
|
84
|
+
----
|
|
85
|
+
|
|
86
|
+
This will benchmark all available adapters and generate a detailed report at
|
|
87
|
+
[`benchmarks/PERFORMANCE_REPORT.md`](benchmarks/PERFORMANCE_REPORT.md).
|
|
88
|
+
|
|
89
|
+
==== Benchmark categories
|
|
90
|
+
|
|
91
|
+
The report includes the following benchmark categories:
|
|
92
|
+
|
|
93
|
+
**Parsing benchmarks:**
|
|
94
|
+
|
|
95
|
+
* Simple XML (< 1KB)
|
|
96
|
+
* Medium XML (10KB, 50 elements with namespaces)
|
|
97
|
+
* Large XML (145KB, 500 elements)
|
|
98
|
+
* Complex nested structures
|
|
99
|
+
|
|
100
|
+
**Serialization benchmarks:**
|
|
101
|
+
|
|
102
|
+
* Simple documents
|
|
103
|
+
* Documents with namespaces
|
|
104
|
+
* Documents with mixed content
|
|
105
|
+
|
|
106
|
+
**XPath benchmarks:**
|
|
107
|
+
|
|
108
|
+
* Simple queries (`//element`)
|
|
109
|
+
* Complex queries with predicates (`//element[@attribute]`)
|
|
110
|
+
* Namespace-aware queries (`//ns:element`)
|
|
111
|
+
|
|
112
|
+
**Memory benchmarks:**
|
|
113
|
+
|
|
114
|
+
* Memory usage per document parse
|
|
115
|
+
* Memory usage for large documents
|
|
116
|
+
|
|
117
|
+
==== Report contents
|
|
118
|
+
|
|
119
|
+
The generated report includes:
|
|
120
|
+
|
|
121
|
+
* Summary table comparing all adapters with grades
|
|
122
|
+
* Detailed performance metrics for each benchmark
|
|
123
|
+
* ASCII performance visualization charts
|
|
124
|
+
* Adapter selection recommendations based on results
|
|
125
|
+
* Complete test environment details (Ruby version, platform, gem versions)
|
|
126
|
+
* Best performers for each category
|
|
127
|
+
|
|
128
|
+
==== Viewing the report
|
|
129
|
+
|
|
130
|
+
After generation, the report is available at:
|
|
131
|
+
|
|
132
|
+
[source,shell]
|
|
133
|
+
----
|
|
134
|
+
cat benchmarks/PERFORMANCE_REPORT.md
|
|
135
|
+
----
|
|
136
|
+
|
|
137
|
+
Or open it in your preferred Markdown viewer.
|
|
138
|
+
|
|
139
|
+
NOTE: The generated report is machine-specific and excluded from git via
|
|
140
|
+
`.gitignore`. Results will vary based on your hardware, OS, and Ruby version.
|
|
141
|
+
|
|
142
|
+
==== Example output
|
|
143
|
+
|
|
144
|
+
The summary table shows comparative performance:
|
|
145
|
+
|
|
146
|
+
[source,markdown]
|
|
147
|
+
----
|
|
148
|
+
| Adapter | Parse (ips) | Serialize (ips) | XPath (ips) | Memory (MB) | Grade |
|
|
149
|
+
|---------|-------------|-----------------|-------------|-------------|-------|
|
|
150
|
+
| Nokogiri | 76 | 13900 | 64958 | -0.1 ⭐ | A |
|
|
151
|
+
| Ox | 289 | 39203 | 9640 | 0.0 ⭐⭐⭐⭐⭐ | A+ |
|
|
152
|
+
...
|
|
153
|
+
----
|
|
154
|
+
|
|
155
|
+
Performance visualizations help quickly identify the best adapter for specific
|
|
156
|
+
needs:
|
|
157
|
+
|
|
158
|
+
[source]
|
|
159
|
+
----
|
|
160
|
+
Parsing (Medium XML):
|
|
161
|
+
Nokogiri █████████████ 76 ips
|
|
162
|
+
Ox ██████████████████████████████████████████████████ 289 ips
|
|
163
|
+
...
|
|
164
|
+
----
|
|
165
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Overview
|
|
3
|
+
nav_order: 1
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
== Guides
|
|
7
|
+
|
|
8
|
+
Task-oriented guides for common XML processing operations with Moxml.
|
|
9
|
+
|
|
10
|
+
=== Document operations
|
|
11
|
+
|
|
12
|
+
link:parsing-xml[Parsing XML]::
|
|
13
|
+
Parse XML from strings, files, and IO streams with different adapters.
|
|
14
|
+
|
|
15
|
+
link:creating-documents[Creating documents]::
|
|
16
|
+
Build XML documents from scratch using the builder pattern or direct
|
|
17
|
+
manipulation.
|
|
18
|
+
|
|
19
|
+
link:modifying-xml[Modifying XML]::
|
|
20
|
+
Add, remove, and modify elements, attributes, and content in XML documents.
|
|
21
|
+
|
|
22
|
+
link:serializing-xml[Serializing XML]::
|
|
23
|
+
Convert XML documents to strings with formatting and encoding options.
|
|
24
|
+
|
|
25
|
+
=== Querying and traversal
|
|
26
|
+
|
|
27
|
+
link:xpath-queries[XPath queries]::
|
|
28
|
+
Query XML documents using XPath expressions with namespaces and predicates.
|
|
29
|
+
|
|
30
|
+
link:node-traversal[Node traversal]::
|
|
31
|
+
Navigate XML document structure using parent, child, and sibling methods.
|
|
32
|
+
|
|
33
|
+
link:finding-nodes[Finding nodes]::
|
|
34
|
+
Different approaches to locate specific nodes in XML documents.
|
|
35
|
+
|
|
36
|
+
=== Advanced operations
|
|
37
|
+
|
|
38
|
+
link:namespace-management[Namespace management]::
|
|
39
|
+
Work with XML namespaces including declarations, prefixes, and resolution.
|
|
40
|
+
|
|
41
|
+
link:error-handling[Error handling]::
|
|
42
|
+
Handle XML processing errors effectively with Moxml's error classes.
|
|
43
|
+
|
|
44
|
+
link:performance-optimization[Performance optimization]::
|
|
45
|
+
Tips and techniques for optimizing XML processing performance.
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Modifying XML
|
|
3
|
+
parent: Overview
|
|
4
|
+
nav_order: 3
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
== Modifying XML
|
|
8
|
+
|
|
9
|
+
=== Purpose
|
|
10
|
+
|
|
11
|
+
Learn how to modify existing XML documents by adding, updating, and removing
|
|
12
|
+
elements, attributes, and content.
|
|
13
|
+
|
|
14
|
+
=== Modifying element content
|
|
15
|
+
|
|
16
|
+
Update text content of elements:
|
|
17
|
+
|
|
18
|
+
[source,ruby]
|
|
19
|
+
----
|
|
20
|
+
xml = '<library><book><title>Old Title</title></book></library>'
|
|
21
|
+
doc = Moxml.new.parse(xml)
|
|
22
|
+
|
|
23
|
+
# Find and update
|
|
24
|
+
title = doc.at_xpath('//title')
|
|
25
|
+
title.text = 'New Title'
|
|
26
|
+
|
|
27
|
+
puts doc.to_xml
|
|
28
|
+
# => <library><book><title>New Title</title></book></library>
|
|
29
|
+
----
|
|
30
|
+
|
|
31
|
+
=== Adding elements
|
|
32
|
+
|
|
33
|
+
Add new elements to existing documents:
|
|
34
|
+
|
|
35
|
+
[source,ruby]
|
|
36
|
+
----
|
|
37
|
+
xml = '<library><book id="1"><title>Ruby Basics</title></book></library>'
|
|
38
|
+
doc = Moxml.new.parse(xml)
|
|
39
|
+
|
|
40
|
+
book = doc.at_xpath('//book[@id="1"]')
|
|
41
|
+
|
|
42
|
+
# Add author element
|
|
43
|
+
author = doc.create_element('author')
|
|
44
|
+
author.text = 'Jane Smith'
|
|
45
|
+
book.add_child(author)
|
|
46
|
+
|
|
47
|
+
# Add price element
|
|
48
|
+
price = doc.create_element('price')
|
|
49
|
+
price.text = '29.99'
|
|
50
|
+
price['currency'] = 'USD'
|
|
51
|
+
book.add_child(price)
|
|
52
|
+
|
|
53
|
+
# Add ISBN element
|
|
54
|
+
isbn = doc.create_element('isbn')
|
|
55
|
+
isbn.text = '978-0-123456-78-9'
|
|
56
|
+
book.add_child(isbn)
|
|
57
|
+
|
|
58
|
+
puts doc.to_xml(indent: 2)
|
|
59
|
+
----
|
|
60
|
+
|
|
61
|
+
=== Removing elements
|
|
62
|
+
|
|
63
|
+
Remove elements from documents:
|
|
64
|
+
|
|
65
|
+
[source,ruby]
|
|
66
|
+
----
|
|
67
|
+
xml = <<~XML
|
|
68
|
+
<library>
|
|
69
|
+
<book id="1">
|
|
70
|
+
<title>Ruby Basics</title>
|
|
71
|
+
<author>Jane Smith</author>
|
|
72
|
+
<draft>true</draft>
|
|
73
|
+
</book>
|
|
74
|
+
</library>
|
|
75
|
+
XML
|
|
76
|
+
|
|
77
|
+
doc = Moxml.new.parse(xml)
|
|
78
|
+
|
|
79
|
+
# Find and remove element
|
|
80
|
+
draft = doc.at_xpath('//draft')
|
|
81
|
+
draft.remove
|
|
82
|
+
|
|
83
|
+
# Remove by parent
|
|
84
|
+
book = doc.at_xpath('//book')
|
|
85
|
+
author = book.at_xpath('.//author')
|
|
86
|
+
book.remove_child(author)
|
|
87
|
+
|
|
88
|
+
puts doc.to_xml
|
|
89
|
+
----
|
|
90
|
+
|
|
91
|
+
=== Modifying attributes
|
|
92
|
+
|
|
93
|
+
Update, add, and remove attributes:
|
|
94
|
+
|
|
95
|
+
[source,ruby]
|
|
96
|
+
----
|
|
97
|
+
xml = '<book id="1" status="draft">Ruby Basics</book>'
|
|
98
|
+
doc = Moxml.new.parse(xml)
|
|
99
|
+
|
|
100
|
+
book = doc.root
|
|
101
|
+
|
|
102
|
+
# Update existing attribute
|
|
103
|
+
book['id'] = '100'
|
|
104
|
+
|
|
105
|
+
# Add new attribute
|
|
106
|
+
book['edition'] = '2nd'
|
|
107
|
+
book['category'] = 'programming'
|
|
108
|
+
|
|
109
|
+
# Remove attribute
|
|
110
|
+
book.remove_attribute('status')
|
|
111
|
+
|
|
112
|
+
# Get all attributes
|
|
113
|
+
book.attributes.each do |attr|
|
|
114
|
+
puts "#{attr.name}=#{attr.value}"
|
|
115
|
+
end
|
|
116
|
+
# => id=100
|
|
117
|
+
# => edition=2nd
|
|
118
|
+
# => category=programming
|
|
119
|
+
----
|
|
120
|
+
|
|
121
|
+
=== Replacing nodes
|
|
122
|
+
|
|
123
|
+
Replace elements with new content:
|
|
124
|
+
|
|
125
|
+
[source,ruby]
|
|
126
|
+
----
|
|
127
|
+
xml = '<book><title>Old Title</title><author>Old Author</author></book>'
|
|
128
|
+
doc = Moxml.new.parse(xml)
|
|
129
|
+
|
|
130
|
+
# Replace title element
|
|
131
|
+
old_title = doc.at_xpath('//title')
|
|
132
|
+
new_title = doc.create_element('title')
|
|
133
|
+
new_title.text = 'New Title'
|
|
134
|
+
new_title['lang'] = 'en'
|
|
135
|
+
|
|
136
|
+
old_title.replace(new_title)
|
|
137
|
+
|
|
138
|
+
# Replace text node
|
|
139
|
+
author = doc.at_xpath('//author')
|
|
140
|
+
author.children.first.replace(doc.create_text('New Author'))
|
|
141
|
+
|
|
142
|
+
puts doc.to_xml
|
|
143
|
+
----
|
|
144
|
+
|
|
145
|
+
=== Adding siblings
|
|
146
|
+
|
|
147
|
+
Insert elements relative to existing nodes:
|
|
148
|
+
|
|
149
|
+
[source,ruby]
|
|
150
|
+
----
|
|
151
|
+
xml = <<~XML
|
|
152
|
+
<book>
|
|
153
|
+
<title>Ruby Programming</title>
|
|
154
|
+
<price>29.99</price>
|
|
155
|
+
</book>
|
|
156
|
+
XML
|
|
157
|
+
|
|
158
|
+
doc = Moxml.new.parse(xml)
|
|
159
|
+
|
|
160
|
+
# Add before price
|
|
161
|
+
price = doc.at_xpath('//price')
|
|
162
|
+
author = doc.create_element('author')
|
|
163
|
+
author.text = 'Jane Smith'
|
|
164
|
+
price.add_previous_sibling(author)
|
|
165
|
+
|
|
166
|
+
# Add after title
|
|
167
|
+
title = doc.at_xpath('//title')
|
|
168
|
+
subtitle = doc.create_element('subtitle')
|
|
169
|
+
subtitle.text = 'A Comprehensive Guide'
|
|
170
|
+
title.add_next_sibling(subtitle)
|
|
171
|
+
|
|
172
|
+
puts doc.to_xml(indent: 2)
|
|
173
|
+
----
|
|
174
|
+
|
|
175
|
+
Output:
|
|
176
|
+
|
|
177
|
+
[source,xml]
|
|
178
|
+
----
|
|
179
|
+
<book>
|
|
180
|
+
<title>Ruby Programming</title>
|
|
181
|
+
<subtitle>A Comprehensive Guide</subtitle>
|
|
182
|
+
<author>Jane Smith</author>
|
|
183
|
+
<price>29.99</price>
|
|
184
|
+
</book>
|
|
185
|
+
----
|
|
186
|
+
|
|
187
|
+
=== Batch modifications
|
|
188
|
+
|
|
189
|
+
Update multiple elements at once:
|
|
190
|
+
|
|
191
|
+
[source,ruby]
|
|
192
|
+
----
|
|
193
|
+
xml = <<~XML
|
|
194
|
+
<library>
|
|
195
|
+
<book><price currency="USD">29.99</price></book>
|
|
196
|
+
<book><price currency="USD">39.99</price></book>
|
|
197
|
+
<book><price currency="USD">19.99</price></book>
|
|
198
|
+
</library>
|
|
199
|
+
XML
|
|
200
|
+
|
|
201
|
+
doc = Moxml.new.parse(xml)
|
|
202
|
+
|
|
203
|
+
# Apply 10% discount to all books
|
|
204
|
+
doc.xpath('//price').each do |price|
|
|
205
|
+
current = price.text.to_f
|
|
206
|
+
discounted = (current * 0.9).round(2)
|
|
207
|
+
price.text = discounted.to_s
|
|
208
|
+
price['original'] = current.to_s
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
puts doc.to_xml(indent: 2)
|
|
212
|
+
----
|
|
213
|
+
|
|
214
|
+
=== Preserving structure
|
|
215
|
+
|
|
216
|
+
Maintain document structure during modifications:
|
|
217
|
+
|
|
218
|
+
[source,ruby]
|
|
219
|
+
----
|
|
220
|
+
xml = <<~XML
|
|
221
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
222
|
+
<library>
|
|
223
|
+
<book id="1">Ruby Basics</book>
|
|
224
|
+
</library>
|
|
225
|
+
XML
|
|
226
|
+
|
|
227
|
+
doc = Moxml.new.parse(xml)
|
|
228
|
+
|
|
229
|
+
# Modifications preserve declaration
|
|
230
|
+
book = doc.at_xpath('//book')
|
|
231
|
+
book['edition'] = '2nd'
|
|
232
|
+
|
|
233
|
+
# Original structure maintained
|
|
234
|
+
puts doc.to_xml(indent: 2)
|
|
235
|
+
# => Still has <?xml ... ?> declaration
|
|
236
|
+
----
|
|
237
|
+
|
|
238
|
+
=== Common modification patterns
|
|
239
|
+
|
|
240
|
+
==== Update or create pattern
|
|
241
|
+
|
|
242
|
+
[source,ruby]
|
|
243
|
+
----
|
|
244
|
+
def ensure_element(parent, name, text)
|
|
245
|
+
elem = parent.at_xpath(".//#{name}")
|
|
246
|
+
|
|
247
|
+
if elem
|
|
248
|
+
# Update existing
|
|
249
|
+
elem.text = text
|
|
250
|
+
else
|
|
251
|
+
# Create new
|
|
252
|
+
elem = parent.document.create_element(name)
|
|
253
|
+
elem.text = text
|
|
254
|
+
parent.add_child(elem)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
elem
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
book = doc.at_xpath('//book')
|
|
261
|
+
ensure_element(book, 'author', 'Jane Smith')
|
|
262
|
+
ensure_element(book, 'price', '29.99')
|
|
263
|
+
----
|
|
264
|
+
|
|
265
|
+
==== Conditional modification
|
|
266
|
+
|
|
267
|
+
[source,ruby]
|
|
268
|
+
----
|
|
269
|
+
doc.xpath('//book').each do |book|
|
|
270
|
+
price = book.at_xpath('.//price')
|
|
271
|
+
next unless price
|
|
272
|
+
|
|
273
|
+
# Add discount for expensive books
|
|
274
|
+
if price.text.to_f > 30
|
|
275
|
+
book['discount'] = '10%'
|
|
276
|
+
price.text = (price.text.to_f * 0.9).to_s
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
----
|
|
280
|
+
|
|
281
|
+
=== Best practices
|
|
282
|
+
|
|
283
|
+
. **Find before modifying** - always locate elements first
|
|
284
|
+
. **Check element exists** before calling methods on it
|
|
285
|
+
. **Use transactions** for complex modifications if needed
|
|
286
|
+
. **Validate structure** after major changes
|
|
287
|
+
. **Preserve document metadata** (declarations, encoding)
|
|
288
|
+
|
|
289
|
+
=== See also
|
|
290
|
+
|
|
291
|
+
* link:creating-documents[Creating documents] - Build from scratch
|
|
292
|
+
* link:../tutorials/basic-usage[Basic usage] - Fundamentals
|
|
293
|
+
* link:../references/element-api[Element API] - Complete method reference
|