uniword 1.6.1 → 1.7.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 +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/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
|
@@ -92,19 +92,12 @@ module Uniword
|
|
|
92
92
|
# Empty line separating headers from body
|
|
93
93
|
lines << ""
|
|
94
94
|
|
|
95
|
-
# Body — encode if needed
|
|
95
|
+
# Body — encode if needed (part content is always decoded)
|
|
96
96
|
encoding = part.content_transfer_encoding
|
|
97
97
|
if encoding == "quoted-printable"
|
|
98
|
-
|
|
99
|
-
body = quoted_printable_encode(part.decoded_content)
|
|
98
|
+
body = quoted_printable_encode(part.raw_content)
|
|
100
99
|
elsif encoding == "base64"
|
|
101
|
-
|
|
102
|
-
# Decoded content (round-trip: raw_content is already base64)
|
|
103
|
-
body = Base64.strict_encode64(part.decoded_content)
|
|
104
|
-
rescue ArgumentError
|
|
105
|
-
# New part: raw_content is raw binary data
|
|
106
|
-
body = Base64.strict_encode64(part.raw_content)
|
|
107
|
-
end
|
|
100
|
+
body = Base64.strict_encode64(part.raw_content)
|
|
108
101
|
body = body.gsub(/.{1,76}/, "\\0\r\n").rstrip
|
|
109
102
|
else
|
|
110
103
|
body = part.raw_content
|
|
@@ -96,11 +96,33 @@ module Uniword
|
|
|
96
96
|
mime_part.content_location = content_location
|
|
97
97
|
mime_part.content_transfer_encoding = encoding
|
|
98
98
|
mime_part.content_id = content_id&.gsub(/[<>]/, "")
|
|
99
|
-
mime_part.
|
|
99
|
+
mime_part.decoded_content = decode_body(body.rstrip, encoding)
|
|
100
100
|
|
|
101
101
|
mime_part
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
+
# Decode a wire body into its content, per the declared
|
|
105
|
+
# Content-Transfer-Encoding. Stored content is always decoded.
|
|
106
|
+
# Tolerant of real-world corruption (Word truncates some base64
|
|
107
|
+
# tails), so base64 uses the non-strict decoder.
|
|
108
|
+
def decode_body(body, encoding)
|
|
109
|
+
case encoding&.downcase
|
|
110
|
+
when "base64"
|
|
111
|
+
Base64.decode64(body)
|
|
112
|
+
when "quoted-printable"
|
|
113
|
+
# Decode in BINARY so hex escapes map 1:1 to bytes; single-pass
|
|
114
|
+
# gsub, so an already-decoded byte is never re-decoded.
|
|
115
|
+
body.force_encoding("BINARY")
|
|
116
|
+
.gsub(/=\r?\n/, "")
|
|
117
|
+
.gsub(/=([0-9A-Fa-f]{2})/) do
|
|
118
|
+
[::Regexp.last_match(1).hex].pack("C")
|
|
119
|
+
end
|
|
120
|
+
.force_encoding("UTF-8")
|
|
121
|
+
else
|
|
122
|
+
body&.force_encoding("UTF-8") || body
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
104
126
|
# Create a typed MimePart based on content type.
|
|
105
127
|
def create_typed_part(content_type)
|
|
106
128
|
return nil unless content_type
|
|
@@ -34,7 +34,7 @@ module Uniword
|
|
|
34
34
|
|
|
35
35
|
# Create a MathEquation from OMML XML
|
|
36
36
|
#
|
|
37
|
-
# @param omml_xml [String,
|
|
37
|
+
# @param omml_xml [String, Moxml::Node] OMML XML content
|
|
38
38
|
# @return [MathEquation] New equation instance with parsed formula
|
|
39
39
|
#
|
|
40
40
|
# @example Parse OMML
|
|
@@ -94,7 +94,7 @@ module Uniword
|
|
|
94
94
|
|
|
95
95
|
# Parse OMML to Plurimath formula
|
|
96
96
|
#
|
|
97
|
-
# @param node [
|
|
97
|
+
# @param node [Moxml::Node] OMML node
|
|
98
98
|
# @return [Plurimath::Math::Formula] Parsed formula
|
|
99
99
|
#
|
|
100
100
|
# @api private
|
|
@@ -131,7 +131,7 @@ module Uniword
|
|
|
131
131
|
|
|
132
132
|
# Determine if equation is inline or block display
|
|
133
133
|
#
|
|
134
|
-
# @param node [
|
|
134
|
+
# @param node [Moxml::Node] OMML node
|
|
135
135
|
# @return [String] "inline" or "block"
|
|
136
136
|
#
|
|
137
137
|
# @api private
|
|
@@ -143,7 +143,7 @@ module Uniword
|
|
|
143
143
|
|
|
144
144
|
# Extract equation properties from OMML
|
|
145
145
|
#
|
|
146
|
-
# @param node [
|
|
146
|
+
# @param node [Moxml::Node] OMML node
|
|
147
147
|
# @return [Hash] Extracted properties
|
|
148
148
|
#
|
|
149
149
|
# @api private
|
|
@@ -220,21 +220,26 @@ module Uniword
|
|
|
220
220
|
#
|
|
221
221
|
# @api private
|
|
222
222
|
def self.wrap_block_equation(omml_content, equation)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
xml["m"].oMathParaPr do
|
|
228
|
-
xml["m"].jc("m:val" => equation.alignment) if equation.alignment
|
|
229
|
-
end
|
|
230
|
-
end
|
|
223
|
+
doc = Moxml.new.create_document
|
|
224
|
+
para = doc.create_element("m:oMathPara")
|
|
225
|
+
para.add_namespace("m", OMML_NAMESPACE)
|
|
226
|
+
doc.root = para
|
|
231
227
|
|
|
232
|
-
|
|
233
|
-
|
|
228
|
+
# Add paragraph properties if needed
|
|
229
|
+
if equation.alignment || equation.break_enabled
|
|
230
|
+
props = doc.create_element("m:oMathParaPr")
|
|
231
|
+
if equation.alignment
|
|
232
|
+
jc = doc.create_element("m:jc")
|
|
233
|
+
jc["m:val"] = equation.alignment
|
|
234
|
+
props.add_child(jc)
|
|
234
235
|
end
|
|
236
|
+
para.add_child(props)
|
|
235
237
|
end
|
|
236
238
|
|
|
237
|
-
|
|
239
|
+
# Add the math content
|
|
240
|
+
para.add_child(Moxml.parse(omml_content).root)
|
|
241
|
+
|
|
242
|
+
doc.to_xml
|
|
238
243
|
end
|
|
239
244
|
|
|
240
245
|
# Format OMML output
|
|
@@ -248,33 +253,32 @@ module Uniword
|
|
|
248
253
|
def self.format_omml(omml, options = {})
|
|
249
254
|
return omml unless options[:pretty]
|
|
250
255
|
|
|
251
|
-
|
|
252
|
-
doc.to_xml(indent: 2)
|
|
256
|
+
Moxml.parse(omml).to_xml(indent: 2)
|
|
253
257
|
end
|
|
254
258
|
|
|
255
259
|
# Parse XML node from string or node
|
|
256
260
|
#
|
|
257
|
-
# @param xml [String,
|
|
258
|
-
# @return [
|
|
261
|
+
# @param xml [String, Moxml::Node] XML input
|
|
262
|
+
# @return [Moxml::Node] Parsed node
|
|
259
263
|
#
|
|
260
264
|
# @api private
|
|
261
265
|
def self.parse_xml_node(xml)
|
|
262
266
|
case xml
|
|
263
|
-
when
|
|
267
|
+
when Moxml::Node
|
|
264
268
|
xml
|
|
265
269
|
when String
|
|
266
|
-
doc =
|
|
270
|
+
doc = Moxml.parse(xml)
|
|
267
271
|
doc.root || raise(ArgumentError, "Invalid XML: no root element")
|
|
268
272
|
else
|
|
269
273
|
raise ArgumentError,
|
|
270
|
-
"Expected String or
|
|
274
|
+
"Expected String or Moxml::Node, got #{xml.class}"
|
|
271
275
|
end
|
|
272
276
|
end
|
|
273
277
|
|
|
274
278
|
# Extract the math node (m:oMath) from container
|
|
275
279
|
#
|
|
276
|
-
# @param node [
|
|
277
|
-
# @return [
|
|
280
|
+
# @param node [Moxml::Node] OMML node (could be oMath or oMathPara)
|
|
281
|
+
# @return [Moxml::Node] The m:oMath node
|
|
278
282
|
#
|
|
279
283
|
# @api private
|
|
280
284
|
def self.extract_math_node(node)
|
|
@@ -52,7 +52,7 @@ module Uniword
|
|
|
52
52
|
|
|
53
53
|
# Create a MathEquation from OMML XML
|
|
54
54
|
#
|
|
55
|
-
# @param omml_xml [String,
|
|
55
|
+
# @param omml_xml [String, Moxml::Node] OMML XML content
|
|
56
56
|
# @return [MathEquation] New equation instance
|
|
57
57
|
#
|
|
58
58
|
# @example
|
|
@@ -7,26 +7,26 @@ module Uniword
|
|
|
7
7
|
# Contains the Word HTML document with embedded XML metadata
|
|
8
8
|
# (DocumentProperties, WordDocument settings, LatentStyles).
|
|
9
9
|
class HtmlPart < MimePart
|
|
10
|
-
# Parse the decoded HTML with
|
|
10
|
+
# Parse the decoded HTML with Moxml (Leptris HTML parser)
|
|
11
11
|
def html_document
|
|
12
|
-
@html_document ||=
|
|
12
|
+
@html_document ||= Moxml.new.parse_html(decoded_content)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
# Extract the <head> element as string
|
|
16
16
|
def head_html
|
|
17
|
-
node = html_document.
|
|
18
|
-
node ? node.
|
|
17
|
+
node = html_document.at_xpath(".//head")
|
|
18
|
+
node ? node.to_xml : ""
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
# Extract the <body> element inner HTML
|
|
22
22
|
def body_html
|
|
23
|
-
node = html_document.
|
|
24
|
-
node ? node.
|
|
23
|
+
node = html_document.at_xpath(".//body")
|
|
24
|
+
node ? node.children.map(&:to_xml).join : ""
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# Extract inline CSS styles from <style> tags
|
|
28
28
|
def css_styles
|
|
29
|
-
html_document.
|
|
29
|
+
html_document.xpath(".//style").map(&:content).join("\n")
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
# Extract DocumentProperties XML from HTML head comments.
|
|
@@ -56,9 +56,15 @@ module Uniword
|
|
|
56
56
|
"urn:schemas-microsoft-com:office:word", "w")
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
+
# The document head element.
|
|
60
|
+
def head_element
|
|
61
|
+
html_document.at_xpath(".//head")
|
|
62
|
+
end
|
|
63
|
+
|
|
59
64
|
# Extract all <xml> blocks from head
|
|
60
65
|
def xml_blocks
|
|
61
|
-
|
|
66
|
+
head = head_element
|
|
67
|
+
head&.xpath("comment()")&.filter_map do |comment|
|
|
62
68
|
text = comment.text
|
|
63
69
|
::Regexp.last_match(1).strip if text =~ %r{<xml>(.*?)</xml>}m
|
|
64
70
|
end || []
|
|
@@ -82,23 +88,23 @@ module Uniword
|
|
|
82
88
|
# This method finds the requested root element and wraps it
|
|
83
89
|
# with proper namespace declarations for lutaml-model.
|
|
84
90
|
def extract_office_xml(element_name, namespace_uri, prefix)
|
|
85
|
-
head =
|
|
91
|
+
head = head_element
|
|
86
92
|
return nil unless head
|
|
87
93
|
|
|
88
94
|
head.xpath("comment()").each do |comment|
|
|
89
95
|
text = comment.text
|
|
90
|
-
|
|
96
|
+
tag = "#{prefix}:#{element_name}"
|
|
97
|
+
next unless text.include?("<#{tag}")
|
|
91
98
|
|
|
92
99
|
# Extract the specific element with its content
|
|
93
|
-
pattern = %r{(<#{
|
|
100
|
+
pattern = %r{(<#{tag}[^>]*>.*?</#{tag}>)}m
|
|
94
101
|
next unless text =~ pattern
|
|
95
102
|
|
|
96
103
|
xml = ::Regexp.last_match(1)
|
|
97
104
|
# Ensure namespace declaration is present
|
|
98
105
|
ns_decl = "xmlns:#{prefix}=\"#{namespace_uri}\""
|
|
99
106
|
unless xml.include?(ns_decl)
|
|
100
|
-
xml = xml.sub(/(<#{
|
|
101
|
-
"\\1 #{ns_decl}")
|
|
107
|
+
xml = xml.sub(/(<#{tag})/, "\\1 #{ns_decl}")
|
|
102
108
|
end
|
|
103
109
|
return xml
|
|
104
110
|
end
|
|
@@ -111,45 +111,51 @@ module Uniword
|
|
|
111
111
|
#
|
|
112
112
|
# Identifies math elements by tag name or special markers.
|
|
113
113
|
#
|
|
114
|
-
# @param element [
|
|
114
|
+
# @param element [Moxml::Element] The HTML element
|
|
115
115
|
# @return [Boolean] true if element contains math content
|
|
116
116
|
#
|
|
117
117
|
# @example Check if element is math
|
|
118
118
|
# is_math = MathConverter.math_element?(element)
|
|
119
119
|
def self.math_element?(element)
|
|
120
|
-
return false unless element.is_a?(
|
|
120
|
+
return false unless element.is_a?(Moxml::Element)
|
|
121
121
|
|
|
122
122
|
math_tags = %w[math mml:math m:oMath m:oMathPara]
|
|
123
123
|
return true if math_tags.include?(element.name)
|
|
124
|
-
return true if element["class"]&.include?("math")
|
|
125
|
-
return true if element["data-mathml"]
|
|
126
124
|
|
|
127
|
-
|
|
125
|
+
css_class = attribute_value(element, "class").to_s
|
|
126
|
+
return true if css_class.include?("math")
|
|
127
|
+
|
|
128
|
+
!attribute_value(element, "data-mathml").nil?
|
|
128
129
|
end
|
|
129
130
|
|
|
130
131
|
# Extract math content from element.
|
|
131
132
|
#
|
|
132
133
|
# Extracts the mathematical content from various element types.
|
|
133
134
|
#
|
|
134
|
-
# @param element [
|
|
135
|
+
# @param element [Moxml::Element] The HTML element
|
|
135
136
|
# @return [Hash] Hash with :type and :content
|
|
136
137
|
#
|
|
137
138
|
# @example Extract math from element
|
|
138
139
|
# math = MathConverter.extract_math(element)
|
|
139
140
|
# # => { type: :mathml, content: "<math>...</math>" }
|
|
140
141
|
def self.extract_math(element)
|
|
141
|
-
if element
|
|
142
|
-
{ type: :mathml, content:
|
|
142
|
+
if (mathml = attribute_value(element, "data-mathml"))
|
|
143
|
+
{ type: :mathml, content: mathml }
|
|
143
144
|
elsif /^(math|mml:math)$/.match?(element.name)
|
|
144
|
-
{ type: :mathml, content: element.
|
|
145
|
+
{ type: :mathml, content: element.to_xml }
|
|
145
146
|
elsif /^m:(oMath|oMathPara)$/.match?(element.name)
|
|
146
|
-
{ type: :omml, content: element.
|
|
147
|
-
elsif element
|
|
147
|
+
{ type: :omml, content: element.to_xml }
|
|
148
|
+
elsif attribute_value(element, "class").to_s.include?("asciimath")
|
|
148
149
|
{ type: :asciimath, content: element.text }
|
|
149
150
|
else
|
|
150
|
-
{ type: :unknown, content: element.
|
|
151
|
+
{ type: :unknown, content: element.to_xml }
|
|
151
152
|
end
|
|
152
153
|
end
|
|
154
|
+
|
|
155
|
+
# Read an attribute value from a Moxml element.
|
|
156
|
+
def self.attribute_value(element, name)
|
|
157
|
+
element.attribute(name)&.value
|
|
158
|
+
end
|
|
153
159
|
end
|
|
154
160
|
end
|
|
155
161
|
end
|
|
@@ -7,7 +7,10 @@ module Uniword
|
|
|
7
7
|
# Base class for MIME parts in an MHTML document.
|
|
8
8
|
#
|
|
9
9
|
# Each MIME part has headers (Content-Type, Content-Location,
|
|
10
|
-
# Content-Transfer-Encoding) and a body (
|
|
10
|
+
# Content-Transfer-Encoding) and a body (raw_content, always in
|
|
11
|
+
# DECODED form: the wire encoding declared by
|
|
12
|
+
# content_transfer_encoding is applied by MimePackager and removed
|
|
13
|
+
# by MimeParser, never here).
|
|
11
14
|
#
|
|
12
15
|
# Subclasses: HtmlPart, XmlPart, ImagePart, FileListPart, HeaderPart
|
|
13
16
|
class MimePart < Lutaml::Model::Serializable
|
|
@@ -18,21 +21,7 @@ module Uniword
|
|
|
18
21
|
attribute :raw_content, :string
|
|
19
22
|
|
|
20
23
|
def decoded_content
|
|
21
|
-
|
|
22
|
-
when "base64"
|
|
23
|
-
Base64.strict_decode64(raw_content.gsub(/\s+/, ""))
|
|
24
|
-
when "quoted-printable"
|
|
25
|
-
# Work in BINARY encoding to avoid gsub encoding conflicts,
|
|
26
|
-
# then force UTF-8 at the end. Use .b to get BINARY encoding
|
|
27
|
-
# without altering bytes.
|
|
28
|
-
content = raw_content.dup.force_encoding("BINARY")
|
|
29
|
-
content
|
|
30
|
-
.gsub(/=\r?\n/, "")
|
|
31
|
-
.gsub(/=([0-9A-Fa-f]{2})/) { [::Regexp.last_match(1).hex].pack("C") }
|
|
32
|
-
.force_encoding("UTF-8")
|
|
33
|
-
else
|
|
34
|
-
raw_content
|
|
35
|
-
end
|
|
24
|
+
raw_content
|
|
36
25
|
end
|
|
37
26
|
|
|
38
27
|
def decoded_content=(value)
|
|
@@ -77,6 +77,27 @@ module Uniword
|
|
|
77
77
|
end
|
|
78
78
|
module_function :insert_once
|
|
79
79
|
|
|
80
|
+
# Reposition an existing singleton entry after (or before) the
|
|
81
|
+
# given anchor. No-op when the entry or model order is absent.
|
|
82
|
+
# Falls back to end (after: missing) or start (before: missing).
|
|
83
|
+
#
|
|
84
|
+
# @param model [Lutaml::Model::Serializable] model to mutate
|
|
85
|
+
# @param name [String] element name to move
|
|
86
|
+
# @param after [String, nil] place after this element name
|
|
87
|
+
# @param before [String, nil] place before this element name
|
|
88
|
+
# @return [Array, nil] the mutated order
|
|
89
|
+
def move_once(model, name, after: nil, before: nil)
|
|
90
|
+
order = mutable_order(model)
|
|
91
|
+
return unless order
|
|
92
|
+
|
|
93
|
+
entry = order.find { |e| e.name == name }
|
|
94
|
+
return unless entry
|
|
95
|
+
|
|
96
|
+
order.delete_at(order.index { |e| e.name == name })
|
|
97
|
+
order.insert(insertion_index(order, after, before, nil), entry)
|
|
98
|
+
end
|
|
99
|
+
module_function :move_once
|
|
100
|
+
|
|
80
101
|
# @return [Integer] index for the insert
|
|
81
102
|
def insertion_index(order, after, before, position)
|
|
82
103
|
if after
|
|
@@ -67,7 +67,7 @@ module Uniword
|
|
|
67
67
|
schema_def = @schema.definition_for(element.class)
|
|
68
68
|
|
|
69
69
|
# Build XML document
|
|
70
|
-
doc =
|
|
70
|
+
doc = Moxml.new.create_document
|
|
71
71
|
|
|
72
72
|
# Create root element with proper namespace
|
|
73
73
|
root = serialize_element_to_node(doc, element, schema_def, options)
|
|
@@ -105,21 +105,21 @@ module Uniword
|
|
|
105
105
|
OoxmlSchema.load(schema_path)
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
-
# Serialize element to
|
|
108
|
+
# Serialize element to Moxml node
|
|
109
109
|
#
|
|
110
|
-
# @param doc [
|
|
110
|
+
# @param doc [Moxml::Document] XML document
|
|
111
111
|
# @param element [Element] Element to serialize
|
|
112
112
|
# @param schema_def [ElementDefinition] Schema definition
|
|
113
113
|
# @param options [Hash] Serialization options
|
|
114
|
-
# @return [
|
|
114
|
+
# @return [Moxml::Element] Created node
|
|
115
115
|
def serialize_element_to_node(doc, element, schema_def, options)
|
|
116
116
|
# Create element node with namespace
|
|
117
117
|
prefix = schema_def.prefix
|
|
118
118
|
local_name = schema_def.local_name
|
|
119
119
|
namespace_uri = @schema.namespace[:uri]
|
|
120
120
|
|
|
121
|
-
node =
|
|
122
|
-
node.
|
|
121
|
+
node = doc.create_element(local_name)
|
|
122
|
+
node.add_namespace(prefix, namespace_uri)
|
|
123
123
|
|
|
124
124
|
# Serialize attributes according to schema
|
|
125
125
|
serialize_attributes_to_node(node, element, schema_def)
|
|
@@ -132,7 +132,7 @@ module Uniword
|
|
|
132
132
|
|
|
133
133
|
# Serialize element attributes to node
|
|
134
134
|
#
|
|
135
|
-
# @param node [
|
|
135
|
+
# @param node [Moxml::Element] Node to add attributes to
|
|
136
136
|
# @param element [Element] Element with attributes
|
|
137
137
|
# @param schema_def [ElementDefinition] Schema definition
|
|
138
138
|
def serialize_attributes_to_node(node, element, schema_def)
|
|
@@ -157,8 +157,8 @@ module Uniword
|
|
|
157
157
|
|
|
158
158
|
# Serialize element children to node
|
|
159
159
|
#
|
|
160
|
-
# @param doc [
|
|
161
|
-
# @param node [
|
|
160
|
+
# @param doc [Moxml::Document] XML document
|
|
161
|
+
# @param node [Moxml::Element] Parent node
|
|
162
162
|
# @param element [Element] Element with children
|
|
163
163
|
# @param schema_def [ElementDefinition] Schema definition
|
|
164
164
|
# @param options [Hash] Serialization options
|
|
@@ -190,11 +190,11 @@ module Uniword
|
|
|
190
190
|
|
|
191
191
|
# Serialize a single child element to node
|
|
192
192
|
#
|
|
193
|
-
# @param doc [
|
|
193
|
+
# @param doc [Moxml::Document] XML document
|
|
194
194
|
# @param child [Element, Object] Child element or value
|
|
195
195
|
# @param child_def [ChildDefinition] Child schema definition
|
|
196
196
|
# @param options [Hash] Serialization options
|
|
197
|
-
# @return [
|
|
197
|
+
# @return [Moxml::Element, nil] Created node
|
|
198
198
|
def serialize_child_to_node(doc, child, child_def, options)
|
|
199
199
|
# Get namespace prefix from tag (e.g., 'w:p' -> 'w')
|
|
200
200
|
tag_parts = child_def.tag.split(":")
|
|
@@ -204,24 +204,21 @@ module Uniword
|
|
|
204
204
|
|
|
205
205
|
if child_def.presence_only?
|
|
206
206
|
# Presence-only element (e.g., <w:b/> for bold)
|
|
207
|
-
node =
|
|
208
|
-
node.
|
|
209
|
-
namespace_uri)
|
|
207
|
+
node = doc.create_element(local_name)
|
|
208
|
+
node.add_namespace(prefix, namespace_uri)
|
|
210
209
|
node
|
|
211
210
|
elsif child.is_a?(String)
|
|
212
211
|
# Simple string value (e.g., text content)
|
|
213
|
-
node =
|
|
214
|
-
node.
|
|
215
|
-
namespace_uri)
|
|
212
|
+
node = doc.create_element(local_name)
|
|
213
|
+
node.add_namespace(prefix, namespace_uri)
|
|
216
214
|
node.content = child
|
|
217
215
|
node
|
|
218
216
|
elsif child.is_a?(Uniword::Element)
|
|
219
217
|
# Check if this is a TextElement that needs special handling
|
|
220
218
|
if child.instance_of?(::Uniword::TextElement) && child.is_a?(Uniword::TextElement)
|
|
221
219
|
# TextElement: serialize as element with text content
|
|
222
|
-
node =
|
|
223
|
-
node.
|
|
224
|
-
namespace_uri)
|
|
220
|
+
node = doc.create_element(local_name)
|
|
221
|
+
node.add_namespace(prefix, namespace_uri)
|
|
225
222
|
node.content = child.content.to_s if child.content
|
|
226
223
|
node
|
|
227
224
|
else
|
|
@@ -238,9 +235,8 @@ module Uniword
|
|
|
238
235
|
else
|
|
239
236
|
# For properties without schema, create empty element
|
|
240
237
|
# (will be filled with children if schema definition exists for element type)
|
|
241
|
-
node =
|
|
242
|
-
node.
|
|
243
|
-
namespace_uri)
|
|
238
|
+
node = doc.create_element(local_name)
|
|
239
|
+
node.add_namespace(prefix, namespace_uri)
|
|
244
240
|
|
|
245
241
|
if child_def.has_attributes?
|
|
246
242
|
child_def.attributes.each do |attr_config|
|
|
@@ -256,9 +252,8 @@ module Uniword
|
|
|
256
252
|
end
|
|
257
253
|
elsif child_def.has_attributes?
|
|
258
254
|
# Element with attributes but no child elements
|
|
259
|
-
node =
|
|
260
|
-
node.
|
|
261
|
-
namespace_uri)
|
|
255
|
+
node = doc.create_element(local_name)
|
|
256
|
+
node.add_namespace(prefix, namespace_uri)
|
|
262
257
|
|
|
263
258
|
child_def.attributes.each do |attr_config|
|
|
264
259
|
attr_def = AttributeDefinition.new(attr_config)
|
|
@@ -271,23 +266,20 @@ module Uniword
|
|
|
271
266
|
node
|
|
272
267
|
elsif child.is_a?(String)
|
|
273
268
|
# Simple string value
|
|
274
|
-
node =
|
|
275
|
-
node.
|
|
276
|
-
namespace_uri)
|
|
269
|
+
node = doc.create_element(local_name)
|
|
270
|
+
node.add_namespace(prefix, namespace_uri)
|
|
277
271
|
node.content = child
|
|
278
272
|
node
|
|
279
273
|
elsif child.is_a?(Uniword::TextElement)
|
|
280
274
|
# TextElement or similar - get the content
|
|
281
|
-
node =
|
|
282
|
-
node.
|
|
283
|
-
namespace_uri)
|
|
275
|
+
node = doc.create_element(local_name)
|
|
276
|
+
node.add_namespace(prefix, namespace_uri)
|
|
284
277
|
node.content = child.content.to_s if child.content
|
|
285
278
|
node
|
|
286
279
|
elsif child
|
|
287
280
|
# Other value element - convert to string
|
|
288
|
-
node =
|
|
289
|
-
node.
|
|
290
|
-
namespace_uri)
|
|
281
|
+
node = doc.create_element(local_name)
|
|
282
|
+
node.add_namespace(prefix, namespace_uri)
|
|
291
283
|
node.content = child.to_s
|
|
292
284
|
node
|
|
293
285
|
end
|