moxml 0.5.131 → 0.5.132
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/lib/moxml/builder.rb +79 -1
- data/lib/moxml/config.rb +6 -0
- data/lib/moxml/context.rb +5 -2
- data/lib/moxml/version.rb +1 -1
- data/spec/moxml/builder_namespaces_spec.rb +95 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6e9c4fec61c2e21eec26368889b888c8cdf1b3c553161bafc0334861cef93529
|
|
4
|
+
data.tar.gz: 0d32dcd44ab21957f09078af75e147005d1da6a67a72a17626d488757c6a305c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1d8da6f131247d4a9b61bc119e85ec19096bdc8769e35ec545f35da657d23556fb22b9d7b8e00c68684aa875a46134fb3d8845526d123f7d4379ef174ff70a8e
|
|
7
|
+
data.tar.gz: a8d0a826db5dd0de0febc05189048a86f7c23a2f8cb028395d6add3500f331b18d104498a1880c7e28e8786b81aab8beb2b9af8ae9e101b1c13755a3fdbe03ac
|
data/lib/moxml/builder.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Moxml
|
|
|
7
7
|
attr_reader :document
|
|
8
8
|
alias_method :doc, :document
|
|
9
9
|
|
|
10
|
-
def initialize(context)
|
|
10
|
+
def initialize(context, strict: nil)
|
|
11
11
|
@context = context
|
|
12
12
|
@current = @document = context.create_document
|
|
13
13
|
@namespaces = {}
|
|
@@ -22,6 +22,22 @@ module Moxml
|
|
|
22
22
|
@face_counts = Hash.new(0)
|
|
23
23
|
@face_deferred = []
|
|
24
24
|
@face_entity_markers = false
|
|
25
|
+
# #371: strict mode — unknown methods raise instead of
|
|
26
|
+
# silently becoming empty elements. Per-build override
|
|
27
|
+
# (Context#build(strict:)), else the context's
|
|
28
|
+
# config.builder_strict.
|
|
29
|
+
@strict = strict.nil? ? context.config.builder_strict : strict
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def strict?
|
|
33
|
+
@strict == true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The tag-creation face NamespacedTags calls — the private
|
|
37
|
+
# create_element_node stays private.
|
|
38
|
+
def create_tag(tag_name, attrs = {}, text_content: nil, block: nil)
|
|
39
|
+
create_element_node(tag_name, attrs, text_content: text_content,
|
|
40
|
+
block: block, eval_block: false)
|
|
25
41
|
end
|
|
26
42
|
|
|
27
43
|
def build(&block)
|
|
@@ -30,6 +46,61 @@ module Moxml
|
|
|
30
46
|
@document
|
|
31
47
|
end
|
|
32
48
|
|
|
49
|
+
# Namespace-scoped tag syntax (#371, Nokogiri's xml["p"].tag
|
|
50
|
+
# parity): xml["w"].p creates <w:p>. The proxy creates through
|
|
51
|
+
# the same face as plain method_missing — caller-context
|
|
52
|
+
# blocks, text/attributes arguments, trailing-underscore
|
|
53
|
+
# escape — and honors the strict mode.
|
|
54
|
+
def [](namespace_prefix)
|
|
55
|
+
raise ArgumentError, "namespace prefix required" if namespace_prefix.nil? || namespace_prefix.to_s.empty?
|
|
56
|
+
|
|
57
|
+
NamespacedTags.new(self, namespace_prefix.to_s)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Tag proxy bound to one namespace prefix. method_missing maps
|
|
61
|
+
# tag names to prefixed element creation; explicit Builder
|
|
62
|
+
# methods (text, element, ...) stay reachable by holding the
|
|
63
|
+
# builder — the proxy is tags-only by design.
|
|
64
|
+
class NamespacedTags
|
|
65
|
+
def initialize(builder, prefix)
|
|
66
|
+
@builder = builder
|
|
67
|
+
@prefix = prefix
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def [](nested_prefix)
|
|
71
|
+
NamespacedTags.new(@builder, "#{@prefix}:#{nested_prefix}")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def method_missing(method_name, *args, &block)
|
|
75
|
+
# Trailing-underscore escape, same as plain Builder tags
|
|
76
|
+
# (type_ → type under the prefix).
|
|
77
|
+
tag = "#{@prefix}:#{method_name.to_s.chomp('_')}"
|
|
78
|
+
if @builder.strict?
|
|
79
|
+
raise NoMethodError,
|
|
80
|
+
"builder strict mode: unknown tag #{tag.inspect} " \
|
|
81
|
+
"would have silently created an empty element (#371)"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
text_content = args.first.is_a?(String) ? args.shift : nil
|
|
85
|
+
attrs = args.first.is_a?(Hash) ? args.shift : {}
|
|
86
|
+
unless args.empty?
|
|
87
|
+
raise ArgumentError,
|
|
88
|
+
"unexpected arguments for #{tag}: #{args.inspect}"
|
|
89
|
+
end
|
|
90
|
+
if text_content && block
|
|
91
|
+
raise ArgumentError,
|
|
92
|
+
"#{tag}: cannot combine text content with a block"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
@builder.create_tag(tag, attrs,
|
|
96
|
+
text_content: text_content, block: block)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def respond_to_missing?(_method_name, _include_private = false)
|
|
100
|
+
true
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
33
104
|
# Element ops inside a face-accumulated subtree cannot touch the
|
|
34
105
|
# tree yet (nothing exists until the flush): PIs ride the raw
|
|
35
106
|
# hatch in exact position; namespace/entity/declaration/doctype
|
|
@@ -139,6 +210,13 @@ module Moxml
|
|
|
139
210
|
def method_missing(method_name, *args, &block)
|
|
140
211
|
return super if RESERVED_METHOD_PATTERN.match?(method_name.to_s)
|
|
141
212
|
|
|
213
|
+
if @strict
|
|
214
|
+
raise NoMethodError,
|
|
215
|
+
"builder strict mode: unknown method #{method_name.inspect} " \
|
|
216
|
+
"would have silently created an empty <#{method_name}> element " \
|
|
217
|
+
"(#371); call a Builder method or use the explicit element face"
|
|
218
|
+
end
|
|
219
|
+
|
|
142
220
|
text_content = args.first.is_a?(String) ? args.shift : nil
|
|
143
221
|
attrs = args.first.is_a?(Hash) ? args.shift : {}
|
|
144
222
|
|
data/lib/moxml/config.rb
CHANGED
|
@@ -114,6 +114,7 @@ module Moxml
|
|
|
114
114
|
|
|
115
115
|
attr_reader :adapter_name, :default_line_ending
|
|
116
116
|
attr_accessor :strict_parsing,
|
|
117
|
+
:builder_strict,
|
|
117
118
|
:default_encoding,
|
|
118
119
|
:default_indent,
|
|
119
120
|
:restore_entities,
|
|
@@ -173,6 +174,11 @@ module Moxml
|
|
|
173
174
|
@entity_provider = nil
|
|
174
175
|
@namespace_validation_mode = :strict
|
|
175
176
|
@entity_restoration_mode = :lenient
|
|
177
|
+
# #371: Builder method_missing creates elements for unknown
|
|
178
|
+
# methods (the DSL contract). builder_strict flips that to
|
|
179
|
+
# raising NoMethodError — external helper calls stop
|
|
180
|
+
# silently becoming empty elements.
|
|
181
|
+
@builder_strict = false
|
|
176
182
|
@entity_mode = :expand
|
|
177
183
|
end
|
|
178
184
|
|
data/lib/moxml/context.rb
CHANGED
|
@@ -275,8 +275,11 @@ module Moxml
|
|
|
275
275
|
config.adapter.sax_parse(xml, handler)
|
|
276
276
|
end
|
|
277
277
|
|
|
278
|
-
|
|
279
|
-
|
|
278
|
+
# strict: (issue #371) flips unknown method_missing calls to
|
|
279
|
+
# NoMethodError instead of silently creating empty elements —
|
|
280
|
+
# nil defers to config.builder_strict.
|
|
281
|
+
def build(strict: nil, &block)
|
|
282
|
+
Builder.new(self, strict: strict).build(&block)
|
|
280
283
|
end
|
|
281
284
|
|
|
282
285
|
# Frozen serialization defaults, memoized per context — Node#to_xml
|
data/lib/moxml/version.rb
CHANGED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# #371: Builder sharp edges — the namespace-scoped tag proxy
|
|
6
|
+
# (Nokogiri's xml["p"].tag parity) and the strict mode where
|
|
7
|
+
# unknown methods raise instead of silently becoming empty
|
|
8
|
+
# elements.
|
|
9
|
+
RSpec.describe "Builder namespaces + strict mode (#371)" do
|
|
10
|
+
describe "Builder#[] namespace-scoped tags" do
|
|
11
|
+
it "creates prefixed elements with text and attributes" do
|
|
12
|
+
doc = Moxml.new.build do
|
|
13
|
+
element("root") do
|
|
14
|
+
self["w"].p("text", "a" => 1)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
expect(doc.root.children.first.name).to eq("w:p")
|
|
18
|
+
expect(doc.to_xml).to include(%(<w:p a="1">text</w:p>))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "nests blocks in the caller context" do
|
|
22
|
+
doc = Moxml.new.build do
|
|
23
|
+
element("root") do
|
|
24
|
+
self["w"].sub { self["w"].deep("x") }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
sub = doc.root.children.first
|
|
28
|
+
expect(sub.name).to eq("w:sub")
|
|
29
|
+
expect(sub.children.first.name).to eq("w:deep")
|
|
30
|
+
expect(sub.children.first.text).to eq("x")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "strips the trailing-underscore escape under the prefix" do
|
|
34
|
+
doc = Moxml.new.build do
|
|
35
|
+
element("root") { self["epub"].type_ }
|
|
36
|
+
end
|
|
37
|
+
expect(doc.to_xml).to include("<epub:type>")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "composes prefixes through nested []" do
|
|
41
|
+
doc = Moxml.new.build do
|
|
42
|
+
element("root") { self["w"]["ext"].node }
|
|
43
|
+
end
|
|
44
|
+
expect(doc.to_xml).to include("<w:ext:node>")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "rejects a missing prefix" do
|
|
48
|
+
expect { Moxml.new.build { self[nil] } }
|
|
49
|
+
.to raise_error(ArgumentError, /prefix/)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
describe "strict mode" do
|
|
54
|
+
it "raises NoMethodError for unknown methods (the silent-corruption fix)" do
|
|
55
|
+
expect do
|
|
56
|
+
Moxml.new.build(strict: true) do
|
|
57
|
+
element("root") { build_chart_type_unused }
|
|
58
|
+
end
|
|
59
|
+
end.to raise_error(NoMethodError, /build_chart_type_unused/)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "raises for unknown namespaced tags too" do
|
|
63
|
+
expect do
|
|
64
|
+
Moxml.new.build(strict: true) do
|
|
65
|
+
element("root") { self["w"].chart_helper }
|
|
66
|
+
end
|
|
67
|
+
end.to raise_error(NoMethodError, /w:chart_helper/)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "still builds known tags under strict" do
|
|
71
|
+
doc = Moxml.new.build(strict: true) do
|
|
72
|
+
element("root") { text "ok" }
|
|
73
|
+
end
|
|
74
|
+
expect(doc.to_xml).to include("<root>ok</root>")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "defaults off and honors config.builder_strict" do
|
|
78
|
+
expect(Moxml.new.config.builder_strict).to be(false)
|
|
79
|
+
ctx = Moxml.new
|
|
80
|
+
ctx.config.builder_strict = true
|
|
81
|
+
expect do
|
|
82
|
+
ctx.build { element("root") { helperish } }
|
|
83
|
+
end.to raise_error(NoMethodError)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "a per-build false overrides a strict config" do
|
|
87
|
+
ctx = Moxml.new
|
|
88
|
+
ctx.config.builder_strict = true
|
|
89
|
+
doc = ctx.build(strict: false) do
|
|
90
|
+
element("root") { helperish }
|
|
91
|
+
end
|
|
92
|
+
expect(doc.to_xml).to include("<helperish>")
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: moxml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.132
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: |
|
|
14
14
|
Moxml is a unified XML manipulation library that provides a common API
|
|
@@ -473,6 +473,7 @@ files:
|
|
|
473
473
|
- spec/moxml/at_search_spec.rb
|
|
474
474
|
- spec/moxml/attribute_resolver_spec.rb
|
|
475
475
|
- spec/moxml/attribute_spec.rb
|
|
476
|
+
- spec/moxml/builder_namespaces_spec.rb
|
|
476
477
|
- spec/moxml/builder_spec.rb
|
|
477
478
|
- spec/moxml/c14n/adapter_delegation_spec.rb
|
|
478
479
|
- spec/moxml/c14n/api_spec.rb
|