uniword 1.6.1 → 1.7.1
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/uniword/builder/chart_builder.rb +129 -134
- data/lib/uniword/diff/package_differ.rb +16 -15
- data/lib/uniword/docx/package_integrity_checker.rb +10 -11
- data/lib/uniword/docx/reconciler/body.rb +1 -0
- data/lib/uniword/docx/reconciler/helpers.rb +27 -12
- data/lib/uniword/docx/reconciler/parts.rb +5 -0
- data/lib/uniword/infrastructure/mime_packager.rb +3 -10
- data/lib/uniword/infrastructure/mime_parser.rb +23 -1
- data/lib/uniword/math/plurimath_adapter.rb +28 -24
- data/lib/uniword/math_equation.rb +1 -1
- data/lib/uniword/mhtml/html_part.rb +19 -13
- data/lib/uniword/mhtml/math_converter.rb +18 -12
- data/lib/uniword/mhtml/mime_part.rb +5 -16
- data/lib/uniword/ooxml/element_order.rb +21 -0
- data/lib/uniword/ooxml/schema/element_serializer.rb +27 -35
- data/lib/uniword/streaming_parser.rb +22 -20
- data/lib/uniword/theme/theme_xml_parser.rb +3 -3
- data/lib/uniword/transformation/html_element_builder.rb +62 -48
- data/lib/uniword/transformation/html_formatting_mapper.rb +3 -3
- data/lib/uniword/transformation/html_to_ooxml_converter.rb +11 -11
- data/lib/uniword/transformation/mhtml_element_renderer.rb +1 -2
- data/lib/uniword/validation/opc_validator.rb +3 -3
- data/lib/uniword/validation/schema_registry.rb +9 -16
- data/lib/uniword/validation/validators/xml_schema_validator.rb +75 -26
- data/lib/uniword/validation/verify_orchestrator.rb +1 -1
- data/lib/uniword/version.rb +1 -1
- data/lib/uniword/wp_drawing/position_h.rb +1 -1
- data/lib/uniword/wp_drawing/position_v.rb +1 -1
- data/lib/uniword/wp_drawing/size_rel_h.rb +1 -1
- data/lib/uniword/wp_drawing/size_rel_v.rb +1 -1
- data/lib/uniword.rb +4 -8
- metadata +8 -9
- data/lib/uniword/infrastructure/xml_namespace_normalizer.rb +0 -107
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "moxml"
|
|
4
|
-
require "
|
|
4
|
+
require "leptris/xml/xsd"
|
|
5
5
|
|
|
6
6
|
module Uniword
|
|
7
7
|
module Validation
|
|
8
8
|
# Maps XML namespace URIs to XSD schema files and loads schemas for validation.
|
|
9
9
|
#
|
|
10
10
|
# Uses Moxml for namespace detection (parsing XML to find declared namespaces)
|
|
11
|
-
# and
|
|
11
|
+
# and Moxml::XSD (Leptris engine) for actual XSD validation.
|
|
12
12
|
#
|
|
13
13
|
# The registry knows which OOXML namespace URIs correspond to which XSD files
|
|
14
14
|
# bundled in data/schemas/.
|
|
@@ -17,7 +17,7 @@ module Uniword
|
|
|
17
17
|
# registry = SchemaRegistry.new
|
|
18
18
|
# ns_uris = registry.detect_namespaces(xml_string)
|
|
19
19
|
# schema = registry.schema_for_namespaces(ns_uris)
|
|
20
|
-
# errors = schema.validate(
|
|
20
|
+
# errors = schema.validate(xml_string)
|
|
21
21
|
class SchemaRegistry
|
|
22
22
|
# Map of namespace URI => XSD file (relative to data/schemas/)
|
|
23
23
|
NAMESPACE_XSD_MAP = {
|
|
@@ -145,7 +145,7 @@ module Uniword
|
|
|
145
145
|
# Load and cache an XSD schema for validation.
|
|
146
146
|
#
|
|
147
147
|
# @param xsd_relative_path [String] Path relative to schemas_dir
|
|
148
|
-
# @return [
|
|
148
|
+
# @return [Moxml::XSD::Schema] Compiled schema
|
|
149
149
|
def load_schema(xsd_relative_path)
|
|
150
150
|
return @schema_cache[xsd_relative_path] if @schema_cache.key?(xsd_relative_path)
|
|
151
151
|
|
|
@@ -155,18 +155,11 @@ module Uniword
|
|
|
155
155
|
"XSD schema not found: #{xsd_path}"
|
|
156
156
|
end
|
|
157
157
|
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
#
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
schema_dir = File.dirname(xsd_path)
|
|
164
|
-
begin
|
|
165
|
-
Dir.chdir(schema_dir)
|
|
166
|
-
schema = Nokogiri::XML::Schema(File.read(File.basename(xsd_path)))
|
|
167
|
-
ensure
|
|
168
|
-
Dir.chdir(original_dir)
|
|
169
|
-
end
|
|
158
|
+
# Path-based compilation resolves xsd:import chains relative
|
|
159
|
+
# to the schema file's own directory; string compilation does
|
|
160
|
+
# not resolve them.
|
|
161
|
+
handle = Leptris::XML::XSD.compile_file(xsd_path)
|
|
162
|
+
schema = Moxml::XSD::Schema.new(handle, @moxml)
|
|
170
163
|
|
|
171
164
|
@schema_cache[xsd_relative_path] = schema
|
|
172
165
|
schema
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "zip"
|
|
4
|
-
require "
|
|
4
|
+
require "moxml"
|
|
5
5
|
# LayerValidator autoloaded via lib/uniword/validation.rb
|
|
6
6
|
|
|
7
7
|
module Uniword
|
|
@@ -14,7 +14,8 @@ module Uniword
|
|
|
14
14
|
# - XSD validation: parts validated against OOXML XSD schemas
|
|
15
15
|
#
|
|
16
16
|
# XSD validation uses SchemaRegistry for namespace-aware schema selection.
|
|
17
|
-
# Uses Moxml
|
|
17
|
+
# Uses Moxml throughout: parse, namespace detection, and the
|
|
18
|
+
# Moxml::XSD (Leptris engine) validator.
|
|
18
19
|
#
|
|
19
20
|
# @example Validate with XSD
|
|
20
21
|
# validator = XmlSchemaValidator.new("xml_schema" => { "xsd_validation" => true })
|
|
@@ -66,8 +67,8 @@ module Uniword
|
|
|
66
67
|
|
|
67
68
|
def validate_entry(entry, issues)
|
|
68
69
|
content = entry.get_input_stream.read
|
|
69
|
-
|
|
70
|
-
rescue
|
|
70
|
+
moxml.parse(content, strict: true)
|
|
71
|
+
rescue Moxml::ParseError => e
|
|
71
72
|
issues << Report::ValidationIssue.new(
|
|
72
73
|
severity: "error",
|
|
73
74
|
code: "XSD-001",
|
|
@@ -87,16 +88,15 @@ module Uniword
|
|
|
87
88
|
begin
|
|
88
89
|
content = entry.get_input_stream.read
|
|
89
90
|
schema = registry.load_schema(schema_path)
|
|
90
|
-
doc =
|
|
91
|
+
doc = moxml.parse(content)
|
|
91
92
|
preprocess_mce(doc)
|
|
92
93
|
|
|
93
94
|
schema.validate(doc).each do |error|
|
|
94
95
|
issues << Report::ValidationIssue.new(
|
|
95
96
|
severity: "error",
|
|
96
97
|
code: "XSD-002",
|
|
97
|
-
message: error.
|
|
98
|
+
message: error.to_s.gsub("\n", " ").strip,
|
|
98
99
|
part: entry.name,
|
|
99
|
-
line: error.line,
|
|
100
100
|
)
|
|
101
101
|
end
|
|
102
102
|
|
|
@@ -120,42 +120,87 @@ module Uniword
|
|
|
120
120
|
end
|
|
121
121
|
end
|
|
122
122
|
|
|
123
|
-
#
|
|
124
|
-
# declaration
|
|
125
|
-
#
|
|
126
|
-
#
|
|
123
|
+
# Apply MCE preprocessing per the part's own mc:Ignorable
|
|
124
|
+
# declaration so strict XSD validation is not drowned in errors
|
|
125
|
+
# for extension content:
|
|
126
|
+
#
|
|
127
|
+
# - mc:AlternateContent is unwrapped to the first understood
|
|
128
|
+
# mc:Choice (else the mc:Fallback) per MCE branch selection;
|
|
129
|
+
# - elements in ignorable namespaces are removed (w15:docId,
|
|
130
|
+
# w16se:* settings extensions);
|
|
131
|
+
# - attributes in ignorable namespaces are removed (w14:paraId,
|
|
132
|
+
# w14:textId), plus the mc:Ignorable attribute itself, which
|
|
133
|
+
# is undeclared on most roots in the transitional XSDs.
|
|
127
134
|
#
|
|
128
|
-
# Extension ELEMENTS are deliberately kept: removing them changes
|
|
129
|
-
# libxml2's content-model evaluation and can expose pre-existing
|
|
130
|
-
# Word-vs-schema quirks (e.g. rsids placed late, tolerated via the
|
|
131
|
-
# sequence's trailing xsd:any) as false baseline errors.
|
|
132
135
|
# Operates in place on the parsed document; never touches the
|
|
133
136
|
# packaged bytes.
|
|
134
137
|
#
|
|
135
|
-
# @param doc [
|
|
138
|
+
# @param doc [Moxml::Document] parsed part to clean
|
|
136
139
|
# @return [void]
|
|
137
140
|
def preprocess_mce(doc)
|
|
138
141
|
root = doc.root
|
|
139
142
|
return unless root
|
|
140
143
|
|
|
141
|
-
|
|
142
|
-
|
|
144
|
+
uris = ignorable_namespace_uris(root)
|
|
145
|
+
unwrap_alternate_content(doc, uris)
|
|
146
|
+
remove_ignorable_elements(doc, uris) unless uris.empty?
|
|
147
|
+
strip_mce_attributes(root, uris + [MCE_NAMESPACE_URI])
|
|
148
|
+
end
|
|
143
149
|
|
|
144
|
-
|
|
145
|
-
|
|
150
|
+
# Namespace URIs the root declares ignorable via mc:Ignorable.
|
|
151
|
+
def ignorable_namespace_uris(root)
|
|
152
|
+
ignorable = root["mc:Ignorable"]
|
|
153
|
+
return [] if ignorable.nil? || ignorable.empty?
|
|
154
|
+
|
|
155
|
+
prefix_uris = root.namespaces.to_h { |ns| [ns.prefix, ns.uri] }
|
|
156
|
+
ignorable.split(/\s+/).filter_map { |prefix| prefix_uris[prefix] }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Replace each mc:AlternateContent with the content of its first
|
|
160
|
+
# understood mc:Choice, or its mc:Fallback when no Choice applies.
|
|
161
|
+
def unwrap_alternate_content(doc, ignorable_uris)
|
|
162
|
+
doc.xpath("//mc:AlternateContent", "mc" => MCE_NAMESPACE_URI)
|
|
163
|
+
.to_a.reverse.each do |alternate|
|
|
164
|
+
branch = chosen_branch(alternate, ignorable_uris)
|
|
165
|
+
if branch
|
|
166
|
+
anchor = alternate
|
|
167
|
+
branch.children.each do |child|
|
|
168
|
+
anchor.add_next_sibling(child)
|
|
169
|
+
anchor = child
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
alternate.remove
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# The first mc:Choice whose required namespaces are all
|
|
177
|
+
# non-ignorable, else the mc:Fallback, else nil.
|
|
178
|
+
def chosen_branch(alternate, ignorable_uris)
|
|
179
|
+
choices = alternate.xpath("./mc:Choice", "mc" => MCE_NAMESPACE_URI)
|
|
180
|
+
choices.find do |choice|
|
|
181
|
+
requires = choice.attributes
|
|
182
|
+
.find { |attr| attr.name == "Requires" }
|
|
183
|
+
&.value.to_s.split(/\s+/)
|
|
184
|
+
requires.all? { |prefix| !ignorable_uris.include?(prefix) }
|
|
185
|
+
end || alternate.at_xpath("./mc:Fallback", "mc" => MCE_NAMESPACE_URI)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Remove elements whose namespace is declared ignorable.
|
|
189
|
+
def remove_ignorable_elements(doc, uris)
|
|
190
|
+
doc.traverse do |node|
|
|
191
|
+
next unless node.is_a?(Moxml::Element)
|
|
192
|
+
|
|
193
|
+
node.remove if uris.include?(node.namespace&.uri)
|
|
146
194
|
end
|
|
147
|
-
# The mc:Ignorable attribute itself is MCE machinery and is
|
|
148
|
-
# undeclared on most root elements in the transitional XSDs.
|
|
149
|
-
strip_mce_attributes(root, uris + [MCE_NAMESPACE_URI])
|
|
150
195
|
end
|
|
151
196
|
|
|
152
197
|
# Remove attributes in ignorable namespaces from every element.
|
|
153
198
|
def strip_mce_attributes(doc, uris)
|
|
154
199
|
doc.traverse do |node|
|
|
155
|
-
next unless node.
|
|
200
|
+
next unless node.is_a?(Moxml::Element)
|
|
156
201
|
|
|
157
|
-
node.
|
|
158
|
-
attr.remove if uris.include?(attr.namespace&.
|
|
202
|
+
node.attributes.each do |attr|
|
|
203
|
+
attr.remove if uris.include?(attr.namespace&.uri)
|
|
159
204
|
end
|
|
160
205
|
end
|
|
161
206
|
end
|
|
@@ -192,6 +237,10 @@ module Uniword
|
|
|
192
237
|
layer_config["xsd_validation"] == true
|
|
193
238
|
end
|
|
194
239
|
|
|
240
|
+
def moxml
|
|
241
|
+
@moxml ||= Moxml.new
|
|
242
|
+
end
|
|
243
|
+
|
|
195
244
|
def layer_config
|
|
196
245
|
@config["xml_schema"] || {}
|
|
197
246
|
end
|
|
@@ -8,7 +8,7 @@ module Uniword
|
|
|
8
8
|
#
|
|
9
9
|
# Coordinates:
|
|
10
10
|
# 1. OPC Package validation (ZIP, content types, relationships)
|
|
11
|
-
# 2. XSD Schema validation (namespace-aware,
|
|
11
|
+
# 2. XSD Schema validation (namespace-aware, via Moxml::XSD)
|
|
12
12
|
# 3. Word Document semantic validation (extensible rule system)
|
|
13
13
|
#
|
|
14
14
|
# Returns a VerificationReport that can be serialized to JSON/YAML
|
data/lib/uniword/version.rb
CHANGED
|
@@ -18,7 +18,7 @@ module Uniword
|
|
|
18
18
|
namespace Uniword::Ooxml::Namespaces::WordProcessingDrawing
|
|
19
19
|
mixed_content
|
|
20
20
|
|
|
21
|
-
map_attribute "
|
|
21
|
+
map_attribute "relativeFrom", to: :relative_from
|
|
22
22
|
map_element "align", to: :align, render_nil: false
|
|
23
23
|
map_element "posOffset", to: :pos_offset, render_nil: false
|
|
24
24
|
end
|
|
@@ -18,7 +18,7 @@ module Uniword
|
|
|
18
18
|
namespace Uniword::Ooxml::Namespaces::WordProcessingDrawing
|
|
19
19
|
mixed_content
|
|
20
20
|
|
|
21
|
-
map_attribute "
|
|
21
|
+
map_attribute "relativeFrom", to: :relative_from
|
|
22
22
|
map_element "align", to: :align, render_nil: false
|
|
23
23
|
map_element "posOffset", to: :pos_offset, render_nil: false
|
|
24
24
|
end
|
|
@@ -17,7 +17,7 @@ module Uniword
|
|
|
17
17
|
namespace Uniword::Ooxml::Namespaces::WordProcessingDrawing
|
|
18
18
|
mixed_content
|
|
19
19
|
|
|
20
|
-
map_attribute "
|
|
20
|
+
map_attribute "relativeFrom", to: :relative_from
|
|
21
21
|
map_element "pctWidth", to: :pct_width, render_nil: false
|
|
22
22
|
end
|
|
23
23
|
end
|
|
@@ -17,7 +17,7 @@ module Uniword
|
|
|
17
17
|
namespace Uniword::Ooxml::Namespaces::WordProcessingDrawing
|
|
18
18
|
mixed_content
|
|
19
19
|
|
|
20
|
-
map_attribute "
|
|
20
|
+
map_attribute "relativeFrom", to: :relative_from
|
|
21
21
|
map_element "pctHeight", to: :pct_height, render_nil: false
|
|
22
22
|
end
|
|
23
23
|
end
|
data/lib/uniword.rb
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
require "lutaml/model"
|
|
4
4
|
require "zip"
|
|
5
5
|
|
|
6
|
-
# XML hydration
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
6
|
+
# XML hydration, XSD validation, and HTML parsing all route through
|
|
7
|
+
# lutaml-model's Moxml layer with the runtime default adapter
|
|
8
|
+
# (Leptris). Nokogiri remains available as an alternative Moxml
|
|
9
|
+
# adapter for development and testing.
|
|
10
10
|
|
|
11
11
|
# Uniword is a comprehensive Ruby library for reading and writing Microsoft Word
|
|
12
12
|
# documents in DOCX format using a schema-driven architecture.
|
|
@@ -378,10 +378,6 @@ module Uniword
|
|
|
378
378
|
doc
|
|
379
379
|
end
|
|
380
380
|
|
|
381
|
-
# Convert HTML to a document with paragraphs.
|
|
382
|
-
# Alias for {#from_html} for backward compatibility.
|
|
383
|
-
alias html_to_doc from_html
|
|
384
|
-
|
|
385
381
|
# Convert HTML directly to a DOCX file
|
|
386
382
|
#
|
|
387
383
|
# @param html [String] HTML content
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: uniword
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.7.1
|
|
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-
|
|
11
|
+
date: 2026-10-18 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: logger
|
|
@@ -25,33 +25,33 @@ dependencies:
|
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '0'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
28
|
+
name: leptris
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
31
|
- - "~>"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version:
|
|
33
|
+
version: 1.9.368
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version:
|
|
40
|
+
version: 1.9.368
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
42
|
+
name: lutaml-model
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
45
|
- - "~>"
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
47
|
+
version: '0.8'
|
|
48
48
|
type: :runtime
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
52
|
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
54
|
+
version: '0.8'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
56
|
name: omml
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -1072,7 +1072,6 @@ files:
|
|
|
1072
1072
|
- lib/uniword/infrastructure.rb
|
|
1073
1073
|
- lib/uniword/infrastructure/mime_packager.rb
|
|
1074
1074
|
- lib/uniword/infrastructure/mime_parser.rb
|
|
1075
|
-
- lib/uniword/infrastructure/xml_namespace_normalizer.rb
|
|
1076
1075
|
- lib/uniword/infrastructure/zip_extractor.rb
|
|
1077
1076
|
- lib/uniword/infrastructure/zip_packager.rb
|
|
1078
1077
|
- lib/uniword/lazy_loader.rb
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "nokogiri"
|
|
4
|
-
|
|
5
|
-
module Uniword
|
|
6
|
-
module Infrastructure
|
|
7
|
-
# XML normalization utilities for round-trip fidelity
|
|
8
|
-
#
|
|
9
|
-
# Ensures that serialized XML matches the expected namespace declaration format
|
|
10
|
-
# by adding missing prefixed namespace declarations to root elements.
|
|
11
|
-
module XmlNamespaceNormalizer
|
|
12
|
-
# Prefixed namespaces that should be declared at root level in OOXML documents
|
|
13
|
-
PREFIXED_NAMESPACES = {
|
|
14
|
-
"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" => "wp",
|
|
15
|
-
"http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" => "wp14",
|
|
16
|
-
}.freeze
|
|
17
|
-
|
|
18
|
-
# Normalize XML to ensure prefixed namespace declarations are at root level
|
|
19
|
-
#
|
|
20
|
-
# @param xml [String] XML content
|
|
21
|
-
# @return [String] Normalized XML content
|
|
22
|
-
def self.normalize(xml)
|
|
23
|
-
doc = Nokogiri::XML(xml)
|
|
24
|
-
return xml unless doc.root
|
|
25
|
-
|
|
26
|
-
# Check if any prefixed namespaces are used but not declared at root
|
|
27
|
-
root = doc.root
|
|
28
|
-
root_ns = root.namespace_definitions.to_h { |ns| [ns.prefix, ns.href] }
|
|
29
|
-
|
|
30
|
-
PREFIXED_NAMESPACES.each do |uri, expected_prefix|
|
|
31
|
-
# Check if this namespace URI is used in the document
|
|
32
|
-
elements_with_ns = doc.xpath("//*[namespace-uri()='#{uri}']")
|
|
33
|
-
next if elements_with_ns.empty?
|
|
34
|
-
|
|
35
|
-
# Check if the expected prefix is already declared at root
|
|
36
|
-
if root_ns.key?(expected_prefix) && (root_ns[expected_prefix] == uri)
|
|
37
|
-
# Check if the URI matches
|
|
38
|
-
next
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Check if default namespace with this URI exists
|
|
42
|
-
default_ns = root_ns.find { |k, v| k.nil? && v == uri }
|
|
43
|
-
|
|
44
|
-
if default_ns
|
|
45
|
-
# Replace default namespace with prefixed namespace
|
|
46
|
-
# Find the default xmlns attribute and rename it
|
|
47
|
-
root.attributes.each do |name, attr|
|
|
48
|
-
next unless name == "xmlns" && attr.value == uri
|
|
49
|
-
|
|
50
|
-
# Remove the default namespace
|
|
51
|
-
root.remove_attribute("xmlns")
|
|
52
|
-
# Add the prefixed namespace
|
|
53
|
-
root["xmlns:#{expected_prefix}"] = uri
|
|
54
|
-
|
|
55
|
-
# Update all elements that use this namespace to use the prefix
|
|
56
|
-
update_elements_namespace(elements_with_ns, expected_prefix)
|
|
57
|
-
break
|
|
58
|
-
end
|
|
59
|
-
else
|
|
60
|
-
# Namespace is used but not declared at root - add it
|
|
61
|
-
root["xmlns:#{expected_prefix}"] = uri
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
doc.to_xml(indent: 0, save_with: Nokogiri::XML::Node::SaveOptions::AS_XML)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Update elements to use the correct namespace prefix
|
|
69
|
-
#
|
|
70
|
-
# @param elements [Nokogiri::XML::NodeSet] Elements to update
|
|
71
|
-
# @param prefix [String] The prefix to use
|
|
72
|
-
def self.update_elements_namespace(elements, prefix)
|
|
73
|
-
elements.each do |el|
|
|
74
|
-
# Update the element's namespace
|
|
75
|
-
ns = el.document.root.namespaces.find do |_k, v|
|
|
76
|
-
v == el.namespace.href
|
|
77
|
-
end
|
|
78
|
-
next unless ns
|
|
79
|
-
|
|
80
|
-
# Change namespace key from default to prefixed
|
|
81
|
-
el.document.root.namespaces.delete(ns[0])
|
|
82
|
-
el.document.root["xmlns:#{prefix}"] = ns[1]
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# Normalize namespace declarations in a ZIP content hash
|
|
87
|
-
#
|
|
88
|
-
# @param zip_content [Hash] File path => content mapping
|
|
89
|
-
# @param target_files [Array<String>] Files to normalize
|
|
90
|
-
# @return [Hash] Updated zip content
|
|
91
|
-
def self.normalize_zip_content(zip_content, target_files = nil)
|
|
92
|
-
result = zip_content.dup
|
|
93
|
-
target_files ||= result.keys.select do |k|
|
|
94
|
-
k.end_with?(".xml", ".rels")
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
target_files.each do |path|
|
|
98
|
-
next unless result[path]
|
|
99
|
-
|
|
100
|
-
result[path] = normalize(result[path])
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
result
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
end
|