uniword 1.5.4 → 1.6.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/exe/uniword +17 -0
  3. data/lib/uniword/accessibility/accessibility_report.rb +8 -0
  4. data/lib/uniword/cli/completions.rb +99 -0
  5. data/lib/uniword/cli/generate_cli.rb +5 -0
  6. data/lib/uniword/cli/help_switch.rb +45 -0
  7. data/lib/uniword/cli/helpers.rb +1 -0
  8. data/lib/uniword/cli/main.rb +118 -21
  9. data/lib/uniword/cli/no_color.rb +21 -0
  10. data/lib/uniword/document_factory.rb +2 -2
  11. data/lib/uniword/document_writer.rb +32 -10
  12. data/lib/uniword/find_replace/paragraph_walker.rb +49 -12
  13. data/lib/uniword/find_replace/scope.rb +7 -4
  14. data/lib/uniword/mhtml/xml_part.rb +3 -3
  15. data/lib/uniword/ooxml/additional_characteristics.rb +4 -4
  16. data/lib/uniword/ooxml/schema_library.rb +2 -2
  17. data/lib/uniword/ooxml/types/characteristics_val.rb +16 -0
  18. data/lib/uniword/ooxml/types/schema_library_val.rb +17 -0
  19. data/lib/uniword/ooxml/types/variant_types.rb +10 -2
  20. data/lib/uniword/ooxml/types/vt_attr.rb +19 -0
  21. data/lib/uniword/ooxml/types/wml_int.rb +17 -0
  22. data/lib/uniword/ooxml/types/wml_val.rb +19 -0
  23. data/lib/uniword/ooxml/types.rb +12 -0
  24. data/lib/uniword/properties/alignment.rb +4 -0
  25. data/lib/uniword/properties/boolean_element_factory.rb +1 -1
  26. data/lib/uniword/properties/font_size.rb +3 -0
  27. data/lib/uniword/review/review_manager.rb +34 -5
  28. data/lib/uniword/review/revision_resolver.rb +190 -0
  29. data/lib/uniword/review.rb +1 -0
  30. data/lib/uniword/revision.rb +3 -3
  31. data/lib/uniword/serialization/ooxml_serializer.rb +3 -3
  32. data/lib/uniword/theme/theme_xml_parser.rb +1 -3
  33. data/lib/uniword/validation/opc_validator.rb +6 -7
  34. data/lib/uniword/validation/rules/document_context.rb +3 -3
  35. data/lib/uniword/validation/rules/images_rule.rb +1 -1
  36. data/lib/uniword/validation/rules/theme_rule.rb +2 -2
  37. data/lib/uniword/validation/schema_registry.rb +1 -1
  38. data/lib/uniword/version.rb +1 -1
  39. data/lib/uniword/wordprocessingml/deletion.rb +41 -0
  40. data/lib/uniword/wordprocessingml/insertion.rb +41 -0
  41. data/lib/uniword/wordprocessingml/page_margins.rb +7 -7
  42. data/lib/uniword/wordprocessingml/paragraph.rb +30 -4
  43. data/lib/uniword/wordprocessingml/settings.rb +3 -4
  44. data/lib/uniword/wordprocessingml/style_cleanup.rb +5 -14
  45. data/lib/uniword/wordprocessingml/table_borders.rb +1 -1
  46. data/lib/uniword/wordprocessingml/table_cell_borders.rb +1 -1
  47. data/lib/uniword/wordprocessingml/text.rb +0 -1
  48. data/lib/uniword/wordprocessingml.rb +5 -0
  49. data/lib/uniword.rb +9 -3
  50. metadata +15 -10
@@ -59,16 +59,19 @@ module Uniword
59
59
 
60
60
  # Walk every paragraph in `containers` and yield each run's
61
61
  # text elements. Shared by body / headers / footers / footnotes
62
- # / endnotes / comments scopes.
62
+ # / endnotes / comments scopes. Runs inside tracked insertions
63
+ # are live content and included; runs inside deletions are
64
+ # deleted content and left untouched.
63
65
  #
64
- # @param containers [Enumerable<#paragraphs, #tables,
65
- # #structured_document_tags>]
66
+ # @param containers [Enumerable<#paragraphs>]
66
67
  # @yieldparam text_element [Wordprocessingml::Text]
67
68
  # @yieldparam accessor [TextAccessor]
68
69
  # @return [void]
69
70
  def each_text_in_containers(containers)
70
71
  ParagraphWalker.each_paragraph(containers) do |paragraph|
71
- paragraph.runs&.each do |run|
72
+ runs = paragraph.runs +
73
+ paragraph.insertions.flat_map(&:runs)
74
+ runs.each do |run|
72
75
  each_text_in_run(run) { |*a| yield(*a) }
73
76
  end
74
77
  end
@@ -5,9 +5,9 @@ module Uniword
5
5
  # XML MIME part — filelist.xml, props*.xml, colorschememapping.xml, etc.
6
6
  class XmlPart < MimePart
7
7
  def xml_content
8
- @xml_content ||= Nokogiri::XML(decoded_content) { |config| config.strict.noblanks }
9
- rescue Nokogiri::XML::SyntaxError
10
- @xml_content ||= Nokogiri::XML(decoded_content)
8
+ @xml_content ||= Moxml.parse(decoded_content)
9
+ rescue Moxml::ParseError
10
+ @xml_content ||= Moxml.parse(decoded_content)
11
11
  end
12
12
 
13
13
  def to_xml
@@ -11,10 +11,10 @@ module Uniword
11
11
  #
12
12
  # Namespace: http://schemas.openxmlformats.org/officeDocument/2006/characteristics
13
13
  class Characteristic < Lutaml::Model::Serializable
14
- attribute :name, :string
15
- attribute :relation, :string
16
- attribute :val, :string
17
- attribute :vocabulary, :string
14
+ attribute :name, Types::CharacteristicsVal
15
+ attribute :relation, Types::CharacteristicsVal
16
+ attribute :val, Types::CharacteristicsVal
17
+ attribute :vocabulary, Types::CharacteristicsVal
18
18
 
19
19
  xml do
20
20
  element "characteristic"
@@ -12,8 +12,8 @@ module Uniword
12
12
  # Namespace: http://schemas.openxmlformats.org/schemaLibrary/2006/main
13
13
  # Prefix: sl
14
14
  class SchemaLibEntry < Lutaml::Model::Serializable
15
- attribute :uri, :string, default: -> { "" }
16
- attribute :manifest_location, :string
15
+ attribute :uri, Types::SchemaLibraryVal, default: -> { "" }
16
+ attribute :manifest_location, Types::SchemaLibraryVal
17
17
 
18
18
  xml do
19
19
  element "schema"
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Uniword
6
+ module Ooxml
7
+ module Types
8
+ # String type in the Additional Characteristics namespace.
9
+ class CharacteristicsVal < Lutaml::Model::Type::String
10
+ xml do
11
+ namespace Uniword::Ooxml::Namespaces::Characteristics
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Uniword
6
+ module Ooxml
7
+ module Types
8
+ # String type in the schemaLibrary namespace (sl:uri,
9
+ # sl:manifestLocation qualified attributes).
10
+ class SchemaLibraryVal < Lutaml::Model::Type::String
11
+ xml do
12
+ namespace Uniword::Ooxml::Namespaces::SchemaLibrary
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -13,6 +13,14 @@ module Uniword
13
13
  module VariantTypes
14
14
  VT_NS = Uniword::Ooxml::Namespaces::VariantTypes
15
15
 
16
+ # String type for qualified vt: attributes (e.g. baseType on
17
+ # vector elements).
18
+ class VtAttr < Lutaml::Model::Type::String
19
+ xml do
20
+ namespace VT_NS
21
+ end
22
+ end
23
+
16
24
  # Base class for simple variant type values (text content only)
17
25
  class VTValue < Lutaml::Model::Serializable
18
26
  attribute :value, :string
@@ -259,7 +267,7 @@ module Uniword
259
267
 
260
268
  # vt:vector - Typed array of values
261
269
  class VtVector < Lutaml::Model::Serializable
262
- attribute :base_type, :string
270
+ attribute :base_type, VtAttr
263
271
  attribute :size, :string
264
272
  attribute :lpwstr_values, VtLpwstr, collection: true
265
273
  attribute :lpstr_values, VtLpstr, collection: true
@@ -287,7 +295,7 @@ module Uniword
287
295
 
288
296
  # vt:array - Typed array with bounds
289
297
  class VtArray < Lutaml::Model::Serializable
290
- attribute :base_type, :string
298
+ attribute :base_type, VtAttr
291
299
  attribute :size, :string
292
300
  attribute :l_bound, :string
293
301
  attribute :u_bound, :string
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Uniword
6
+ module Ooxml
7
+ module Types
8
+ module VariantTypes
9
+ # String type for qualified vt: attributes (e.g. baseType on
10
+ # vector elements).
11
+ class VtAttr < Lutaml::Model::Type::String
12
+ xml do
13
+ namespace VT_NS
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Uniword
6
+ module Ooxml
7
+ module Types
8
+ # Integer type in the WordProcessingML namespace (w:top, w:gutter,
9
+ # w:sz et al. qualified attributes).
10
+ class WmlInt < Lutaml::Model::Type::Integer
11
+ xml do
12
+ namespace Uniword::Ooxml::Namespaces::WordProcessingML
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Uniword
6
+ module Ooxml
7
+ module Types
8
+ # String type in the WordProcessingML namespace.
9
+ # Binds mapped attribute rules to their qualified (URI, local)
10
+ # identity — e.g. w:val on boolean/formatting elements — so
11
+ # strict adapters (Leptris) match exactly what DOCX emits.
12
+ class WmlVal < Lutaml::Model::Type::String
13
+ xml do
14
+ namespace Uniword::Ooxml::Namespaces::WordProcessingML
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -53,6 +53,18 @@ module Uniword
53
53
  # Relationships namespace type for r:embed/r:link cross-namespace attrs
54
54
  autoload :RelationshipId, "#{__dir__}/types/relationship_id"
55
55
 
56
+ # WordProcessingML namespace type for qualified w:val attributes
57
+ autoload :WmlVal, "#{__dir__}/types/wml_val"
58
+
59
+ # WordProcessingML namespace type for qualified integer attributes
60
+ autoload :WmlInt, "#{__dir__}/types/wml_int"
61
+
62
+ # schemaLibrary namespace type for sl: qualified attributes
63
+ autoload :SchemaLibraryVal, "#{__dir__}/types/schema_library_val"
64
+
65
+ # Additional Characteristics namespace type
66
+ autoload :CharacteristicsVal, "#{__dir__}/types/characteristics_val"
67
+
56
68
  # Variant Types (vt: namespace) for OLE property values
57
69
  autoload :VariantTypes, "#{__dir__}/types/variant_types"
58
70
 
@@ -6,6 +6,10 @@ module Uniword
6
6
  module Properties
7
7
  # Namespaced custom type for alignment value
8
8
  class AlignmentValue < Lutaml::Model::Type::String
9
+ xml do
10
+ namespace Ooxml::Namespaces::WordProcessingML
11
+ end
12
+
9
13
  # Full ST_Jc enumeration from ECMA-376 (wml.xsd)
10
14
  VALUES = %w[
11
15
  start center end both mediumKashida distribute numTab
@@ -47,7 +47,7 @@ module Uniword
47
47
  klass = Class.new(Lutaml::Model::Serializable) do
48
48
  include BooleanElement
49
49
 
50
- attribute :val, :string, default: nil
50
+ attribute :val, Ooxml::Types::WmlVal, default: nil
51
51
  include BooleanValSetter
52
52
 
53
53
  xml do
@@ -6,6 +6,9 @@ module Uniword
6
6
  module Properties
7
7
  # Namespaced custom type for font size value
8
8
  class FontSizeValue < Lutaml::Model::Type::Integer
9
+ xml do
10
+ namespace Ooxml::Namespaces::WordProcessingML
11
+ end
9
12
  end
10
13
 
11
14
  # Font size element
@@ -27,6 +27,8 @@ module Uniword
27
27
  def initialize(document)
28
28
  @document = document
29
29
  @accept_reject = AcceptReject.new
30
+ @resolver = RevisionResolver.new(document)
31
+ @resolver_hydrated = false
30
32
  end
31
33
 
32
34
  # --- Comments ---
@@ -141,26 +143,41 @@ module Uniword
141
143
 
142
144
  # Accept a single revision by ID.
143
145
  #
146
+ # Applies the decision to the document models (tracked-change
147
+ # nodes are spliced or removed) and keeps the facade list in
148
+ # sync.
149
+ #
144
150
  # @param revision_id [String] The revision ID to accept
145
151
  # @return [Boolean] true if accepted, false if not found
146
- def accept(revision_id)
152
+ def accept(revision_id) # rubocop:disable Naming/PredicateMethod
147
153
  revision = tracked_changes.find_revision(revision_id)
148
154
  return false unless revision
149
155
 
150
- @accept_reject.accept(revision)
156
+ if @resolver.ids.include?(revision_id.to_s)
157
+ @resolver.accept(revision_id)
158
+ else
159
+ @accept_reject.accept(revision)
160
+ end
151
161
  tracked_changes.remove_revision(revision_id)
152
162
  true
153
163
  end
154
164
 
155
165
  # Reject a single revision by ID.
156
166
  #
167
+ # Applies the decision to the document models and keeps the
168
+ # facade list in sync.
169
+ #
157
170
  # @param revision_id [String] The revision ID to reject
158
171
  # @return [Boolean] true if rejected, false if not found
159
- def reject(revision_id)
172
+ def reject(revision_id) # rubocop:disable Naming/PredicateMethod
160
173
  revision = tracked_changes.find_revision(revision_id)
161
174
  return false unless revision
162
175
 
163
- @accept_reject.reject(revision)
176
+ if @resolver.ids.include?(revision_id.to_s)
177
+ @resolver.reject(revision_id)
178
+ else
179
+ @accept_reject.reject(revision)
180
+ end
164
181
  tracked_changes.remove_revision(revision_id)
165
182
  true
166
183
  end
@@ -169,6 +186,8 @@ module Uniword
169
186
  #
170
187
  # @return [Integer] Number of changes accepted
171
188
  def accept_all
189
+ tracked_changes # hydrates the resolver's node registry
190
+ @resolver.ids.each { |id| @resolver.accept(id) }
172
191
  tracked_changes.accept_all
173
192
  end
174
193
 
@@ -176,6 +195,8 @@ module Uniword
176
195
  #
177
196
  # @return [Integer] Number of changes rejected
178
197
  def reject_all
198
+ tracked_changes # hydrates the resolver's node registry
199
+ @resolver.ids.each { |id| @resolver.reject(id) }
179
200
  tracked_changes.reject_all
180
201
  end
181
202
 
@@ -242,7 +263,10 @@ module Uniword
242
263
  end
243
264
  end
244
265
 
245
- # Get or initialize TrackedChanges for the document.
266
+ # Get or initialize TrackedChanges for the document. On first
267
+ # access, parsed <w:ins>/<w:del> nodes are registered on the
268
+ # facade (via RevisionResolver) so listing and accept/reject
269
+ # reflect the document's real tracked changes.
246
270
  #
247
271
  # @return [Uniword::TrackedChanges] The tracked changes collection
248
272
  def tracked_changes
@@ -256,6 +280,11 @@ module Uniword
256
280
  tc
257
281
  end
258
282
  end
283
+ unless @resolver_hydrated
284
+ @resolver.hydrate(@tracked_changes)
285
+ @resolver_hydrated = true
286
+ end
287
+ @tracked_changes
259
288
  end
260
289
  end
261
290
  end
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uniword
4
+ module Review
5
+ # Bridges parsed tracked-change nodes (<w:ins>/<w:del> on
6
+ # paragraphs) to the TrackedChanges facade and applies
7
+ # accept/reject decisions to the document models.
8
+ #
9
+ # Hydration walks the body (tables and SDT content included, via
10
+ # ParagraphWalker) and registers one facade Revision per node,
11
+ # keyed by the OOXML revision id (w:id). Decisions then mutate
12
+ # the models:
13
+ #
14
+ # - accept insert → splice wrapped runs into the paragraph
15
+ # - reject insert → remove the node
16
+ # - accept delete → remove the node (deleted content goes away)
17
+ # - reject delete → splice runs back, delText converted to t
18
+ #
19
+ # element_order arrays are updated alongside the collections so
20
+ # serialization keeps the runs at the revision's position.
21
+ class RevisionResolver
22
+ # @param document [Wordprocessingml::DocumentRoot]
23
+ def initialize(document)
24
+ @document = document
25
+ @nodes = {}
26
+ end
27
+
28
+ # Register every parsed ins/del node on the facade.
29
+ #
30
+ # @param tracked_changes [Uniword::TrackedChanges]
31
+ # @return [Uniword::TrackedChanges]
32
+ def hydrate(tracked_changes)
33
+ each_tracked_node do |paragraph, node|
34
+ id = node.id.to_s
35
+ next if id.empty? || @nodes.key?(id)
36
+
37
+ @nodes[id] = [paragraph, node]
38
+ tracked_changes.add_revision(facade_revision_for(node))
39
+ end
40
+ tracked_changes
41
+ end
42
+
43
+ # Registered revision ids, in document order.
44
+ #
45
+ # @return [Array<String>]
46
+ def ids
47
+ @nodes.keys
48
+ end
49
+
50
+ # Accept one revision: keep inserted content / drop deleted
51
+ # content.
52
+ #
53
+ # @param revision_id [String]
54
+ # @return [Boolean] true when the revision was found and applied
55
+ def accept(revision_id) # rubocop:disable Naming/PredicateMethod
56
+ paragraph, node = @nodes[revision_id.to_s]
57
+ return false unless paragraph
58
+
59
+ if insertion?(node)
60
+ splice_into_paragraph(paragraph, node, node.runs)
61
+ else
62
+ remove_node(paragraph, node)
63
+ end
64
+ @nodes.delete(revision_id.to_s)
65
+ true
66
+ end
67
+
68
+ # Reject one revision: drop inserted content / restore deleted
69
+ # content.
70
+ #
71
+ # @param revision_id [String]
72
+ # @return [Boolean] true when the revision was found and applied
73
+ def reject(revision_id) # rubocop:disable Naming/PredicateMethod
74
+ paragraph, node = @nodes[revision_id.to_s]
75
+ return false unless paragraph
76
+
77
+ if insertion?(node)
78
+ remove_node(paragraph, node)
79
+ else
80
+ restored = node.runs.map { |run| restore_run_text(run) }
81
+ splice_into_paragraph(paragraph, node, restored)
82
+ end
83
+ @nodes.delete(revision_id.to_s)
84
+ true
85
+ end
86
+
87
+ private
88
+
89
+ def insertion?(node)
90
+ node.is_a?(Wordprocessingml::Insertion)
91
+ end
92
+
93
+ def facade_revision_for(node)
94
+ Uniword::Revision.new(
95
+ type: insertion?(node) ? :insert : :delete,
96
+ revision_id: node.id.to_s,
97
+ author: node.author,
98
+ date: node.date,
99
+ text: node.text,
100
+ )
101
+ end
102
+
103
+ def each_tracked_node(&block)
104
+ containers = [@document.body].compact
105
+ FindReplace::ParagraphWalker.each_paragraph(containers) do |p|
106
+ p.insertions.each { |node| yield(p, node) }
107
+ p.deletions.each { |node| yield(p, node) }
108
+ end
109
+ end
110
+
111
+ def splice_into_paragraph(paragraph, node, runs)
112
+ tag = node_tag(node)
113
+ collection = tracked_collection(paragraph, node)
114
+ index = collection.index(node)
115
+
116
+ paragraph.runs.concat(runs)
117
+ collection.delete(node)
118
+ replace_order_entry(paragraph, tag, index, runs.size)
119
+ end
120
+
121
+ def remove_node(paragraph, node)
122
+ tag = node_tag(node)
123
+ collection = tracked_collection(paragraph, node)
124
+ index = collection.index(node)
125
+
126
+ collection.delete(node)
127
+ replace_order_entry(paragraph, tag, index, 0)
128
+ end
129
+
130
+ # Convert a deleted run's delText back into live text so a
131
+ # rejected deletion re-enters the document as content.
132
+ def restore_run_text(run)
133
+ deleted = run.del_text
134
+ if deleted
135
+ run.text = [] if run.text.nil?
136
+ run.text << Wordprocessingml::Text.new(content: deleted.content)
137
+ run.del_text = nil
138
+ retag_order_entry(run, "delText", "t")
139
+ end
140
+ run
141
+ end
142
+
143
+ def node_tag(node)
144
+ insertion?(node) ? "ins" : "del"
145
+ end
146
+
147
+ def tracked_collection(paragraph, node)
148
+ insertion?(node) ? paragraph.insertions : paragraph.deletions
149
+ end
150
+
151
+ # Replace the index-th `tag` entry in element_order with `count`
152
+ # run entries (count 0 removes it). Parsed models carry
153
+ # element_order; built models serialize by collection order.
154
+ def replace_order_entry(model, tag, index, count)
155
+ order = Ooxml::ElementOrder.mutable_order(model)
156
+ return unless order
157
+ return unless index
158
+
159
+ position = nth_entry_index(order, tag, index)
160
+ return unless position
161
+
162
+ entries = Array.new(count) { xml_entry("r") }
163
+ order[position, 1] = entries
164
+ end
165
+
166
+ def retag_order_entry(model, old_tag, new_tag)
167
+ order = Ooxml::ElementOrder.mutable_order(model)
168
+ return unless order
169
+
170
+ position = nth_entry_index(order, old_tag, 0)
171
+ order[position] = xml_entry(new_tag) if position
172
+ end
173
+
174
+ def nth_entry_index(order, tag, nth)
175
+ seen = -1
176
+ order.each_with_index do |entry, i|
177
+ next unless entry.name == tag
178
+
179
+ seen += 1
180
+ return i if seen == nth
181
+ end
182
+ nil
183
+ end
184
+
185
+ def xml_entry(name)
186
+ Lutaml::Xml::Element.new("Element", name)
187
+ end
188
+ end
189
+ end
190
+ end
@@ -14,6 +14,7 @@ module Uniword
14
14
  module Review
15
15
  autoload :AcceptReject, "uniword/review/accept_reject"
16
16
  autoload :ReviewManager, "uniword/review/review_manager"
17
+ autoload :RevisionResolver, "uniword/review/revision_resolver"
17
18
  autoload :InteractiveReview, "uniword/review/interactive_review"
18
19
  end
19
20
  end
@@ -35,13 +35,13 @@ module Uniword
35
35
  # @see TrackedChanges For revision collection management
36
36
  class Revision < Lutaml::Model::Serializable
37
37
  # Unique revision identifier
38
- attribute :revision_id, :string
38
+ attribute :revision_id, Ooxml::Types::WmlVal
39
39
 
40
40
  # Author name
41
- attribute :author, :string
41
+ attribute :author, Ooxml::Types::WmlVal
42
42
 
43
43
  # Revision date/time
44
- attribute :date, :string
44
+ attribute :date, Ooxml::Types::WmlVal
45
45
 
46
46
  # OOXML namespace configuration
47
47
  xml do
@@ -18,12 +18,12 @@ module Uniword
18
18
  document.to_xml(encoding: "UTF-8", prefix: true)
19
19
  end
20
20
 
21
- # Serialize a document to XML and return as a Nokogiri document
21
+ # Serialize a document to XML and return as a Moxml document
22
22
  #
23
23
  # @param document [DocumentRoot] The document to serialize
24
- # @return [Nokogiri::XML::Document] The serialized document
24
+ # @return [Moxml::Document] The serialized document
25
25
  def serialize_to_doc(document)
26
- Nokogiri::XML(serialize(document))
26
+ Moxml.parse(serialize(document))
27
27
  end
28
28
  end
29
29
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "nokogiri"
4
-
5
3
  module Uniword
6
4
  module Themes
7
5
  # Parses theme XML files into Theme models
@@ -26,7 +24,7 @@ module Uniword
26
24
  # @return [Theme] Parsed theme
27
25
  # @raise [ArgumentError] if XML is invalid or missing theme element
28
26
  def parse(xml)
29
- doc = Nokogiri::XML(xml)
27
+ doc = Moxml.parse(xml)
30
28
  theme_node = doc.at_xpath("//a:theme", THEME_NS)
31
29
 
32
30
  unless theme_node
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "zip"
4
- require "nokogiri"
5
4
 
6
5
  module Uniword
7
6
  module Validation
@@ -107,7 +106,7 @@ module Uniword
107
106
  ct_entry = zip.find_entry("[Content_Types].xml")
108
107
  return unless ct_entry
109
108
 
110
- ct_doc = Nokogiri::XML(ct_entry.get_input_stream.read, &:strict)
109
+ ct_doc = Moxml.parse(ct_entry.get_input_stream.read)
111
110
 
112
111
  # Get declared extensions
113
112
  declared_exts = ct_doc.xpath("//xmlns:Default", "xmlns" => CT_NS)
@@ -137,7 +136,7 @@ module Uniword
137
136
  suggestion: "Add a Default or Override entry in [Content_Types].xml.",
138
137
  )
139
138
  end
140
- rescue Nokogiri::XML::SyntaxError => e
139
+ rescue Moxml::ParseError => e
141
140
  issues << Report::ValidationIssue.new(
142
141
  severity: "error",
143
142
  code: "OPC-008",
@@ -155,7 +154,7 @@ module Uniword
155
154
  end
156
155
 
157
156
  def check_relationship_file(zip, entry, issues)
158
- doc = Nokogiri::XML(entry.get_input_stream.read)
157
+ doc = Moxml.parse(entry.get_input_stream.read)
159
158
 
160
159
  base_dir = compute_base_dir(entry.name)
161
160
 
@@ -181,7 +180,7 @@ module Uniword
181
180
  "from the package. Remove the relationship or add the part.",
182
181
  )
183
182
  end
184
- rescue Nokogiri::XML::SyntaxError => e
183
+ rescue Moxml::ParseError => e
185
184
  issues << Report::ValidationIssue.new(
186
185
  severity: "error",
187
186
  code: "OPC-008",
@@ -195,8 +194,8 @@ module Uniword
195
194
 
196
195
  xml_entries.each do |entry|
197
196
  content = entry.get_input_stream.read
198
- Nokogiri::XML(content, &:strict)
199
- rescue Nokogiri::XML::SyntaxError => e
197
+ Moxml.parse(content)
198
+ rescue Moxml::ParseError => e
200
199
  issues << Report::ValidationIssue.new(
201
200
  severity: "error",
202
201
  code: "OPC-008",
@@ -29,7 +29,7 @@ module Uniword
29
29
  @path = path
30
30
  @zip = nil
31
31
  @parsed_parts = {}
32
- @moxml = Moxml.new(:nokogiri)
32
+ @moxml = Moxml.new
33
33
  end
34
34
 
35
35
  # Context type used by the Engine to select rules.
@@ -139,7 +139,7 @@ module Uniword
139
139
  raw = part_raw(rels_path)
140
140
  return [] unless raw
141
141
 
142
- doc = Nokogiri::XML(raw)
142
+ doc = Moxml.parse(raw)
143
143
  doc.xpath("//xmlns:Relationship", "xmlns" => RELS_NS).map do |rel|
144
144
  {
145
145
  id: rel["Id"],
@@ -157,7 +157,7 @@ module Uniword
157
157
  raw = part_raw("[Content_Types].xml")
158
158
  return {} unless raw
159
159
 
160
- doc = Nokogiri::XML(raw)
160
+ doc = Moxml.parse(raw)
161
161
  types = {}
162
162
 
163
163
  doc.xpath("//xmlns:Default", "xmlns" => CT_NS).each do |node|