uniword 1.0.6 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5dfe381dc7497d65a3df658a648fd09f7f3f5f1f66d68c27578cb3c1a8e226b3
4
- data.tar.gz: c3ae36a30164bb6161a4a7b6d00525ae129b1230ee215941fa8deda61fdea557
3
+ metadata.gz: a62022223f8643943947659aae4d0e27b5d8566cab7486e0c688566cc0428208
4
+ data.tar.gz: 61727290e9fac98b2e4cf3b43bb1fd70f09c4b9648206eb861d1bc3766b964d9
5
5
  SHA512:
6
- metadata.gz: 5942b178fdc0d078e2db920574940daf79dd8fe84fab759bb9265cf4ba4cefee28ed15129c876192d9ff923bd3888f2a598f456787b90360ee2d72b234e393a8
7
- data.tar.gz: b15407d92eb46a750a842f0d38998d4ff49fcb374e5ed8c24ec94b0037fcf6cd10ac64855c490e07ded64381ad867fa1241867303192d09213e5da2b419b35aa
6
+ metadata.gz: f99053000864fae5910ee3c34f07504daf4afb061093aced4a5589f0a80c335cca44fa48a72bad5f1725d87a82346dc5abc577c14e6238875a9a175bfd33c788
7
+ data.tar.gz: cf9546e5ad9b15a3d4973785b922589d29bbd8bea8a34c75b1012f10a2a9190988abaccae1c5e0f41217589fc9f27151342210373ca9a00c355f86ad2218b15c
@@ -11,8 +11,8 @@ module Uniword
11
11
  class CommentBuilder
12
12
  attr_reader :model
13
13
 
14
- def initialize(author:, comment_id: nil, date: nil, initials: nil)
15
- @model = Comment.new(
14
+ def initialize(model: nil, author: nil, comment_id: nil, date: nil, initials: nil)
15
+ @model = model || Comment.new(
16
16
  author: author,
17
17
  comment_id: comment_id,
18
18
  date: date,
@@ -22,9 +22,7 @@ module Uniword
22
22
 
23
23
  # Wrap an existing Comment model
24
24
  def self.from_model(model)
25
- builder = allocate
26
- builder.instance_variable_set(:@model, model)
27
- builder
25
+ new(model: model)
28
26
  end
29
27
 
30
28
  # Append text to the comment (creates a paragraph)
@@ -148,15 +148,25 @@ module Uniword
148
148
  run
149
149
  end
150
150
 
151
- # Factory: creates a Run with a tab character
151
+ # Factory: creates a Run containing a tab character.
152
+ # Use this with ParagraphBuilder#<< to append a tab to a paragraph.
152
153
  #
153
154
  # @return [Wordprocessingml::Run]
155
+ # @see .tab_element for the bare Tab element
154
156
  def self.tab
155
157
  run = Wordprocessingml::Run.new
156
- run.tab = Wordprocessingml::Tab.new
158
+ run.tab = tab_element
157
159
  run
158
160
  end
159
161
 
162
+ # Factory: creates a bare Tab element for direct assignment to Run#tab.
163
+ # Most callers should use .tab instead, which wraps the Tab in a Run.
164
+ #
165
+ # @return [Wordprocessingml::Tab]
166
+ def self.tab_element
167
+ Wordprocessingml::Tab.new
168
+ end
169
+
160
170
  # Factory: creates a Run containing a line break
161
171
  #
162
172
  # @return [Wordprocessingml::Run]
@@ -33,18 +33,14 @@ document_rels)
33
33
  document_rels)
34
34
  # Package infrastructure
35
35
  content["[Content_Types].xml"] =
36
- content_types.to_xml(encoding: "UTF-8", declaration: true,
37
- standalone: true)
36
+ serialize_infrastructure(content_types)
38
37
  content["_rels/.rels"] =
39
- package_rels.to_xml(encoding: "UTF-8", declaration: true,
40
- standalone: true)
38
+ serialize_infrastructure(package_rels)
41
39
 
42
40
  # Document properties
43
41
  if core_properties
44
42
  content["docProps/core.xml"] =
45
- core_properties.to_xml(encoding: "UTF-8",
46
- prefix: true,
47
- standalone: true)
43
+ serialize_part(core_properties)
48
44
  end
49
45
  if app_properties
50
46
  content["docProps/app.xml"] =
@@ -78,75 +74,57 @@ document_rels)
78
74
  # Document parts
79
75
  if document
80
76
  content["word/document.xml"] =
81
- document.to_xml(encoding: "UTF-8", prefix: true,
82
- fix_boolean_elements: true,
83
- standalone: true)
77
+ serialize_part(document)
84
78
  end
85
79
  if styles
86
80
  content["word/styles.xml"] =
87
- styles.to_xml(encoding: "UTF-8", prefix: true,
88
- fix_boolean_elements: true,
89
- standalone: true)
81
+ serialize_part(styles)
90
82
  end
91
83
  if numbering
92
84
  content["word/numbering.xml"] =
93
- numbering.to_xml(encoding: "UTF-8", prefix: true,
94
- fix_boolean_elements: true,
95
- standalone: true)
85
+ serialize_part(numbering)
96
86
  end
97
87
  if settings
98
88
  content["word/settings.xml"] =
99
- settings.to_xml(encoding: "UTF-8", prefix: true,
100
- standalone: true)
89
+ serialize_part(settings)
101
90
  end
102
91
  if font_table
103
92
  content["word/fontTable.xml"] =
104
- font_table.to_xml(encoding: "UTF-8", prefix: true,
105
- standalone: true)
93
+ serialize_part(font_table)
106
94
  end
107
95
  if web_settings
108
96
  content["word/webSettings.xml"] =
109
- web_settings.to_xml(encoding: "UTF-8", prefix: true,
110
- standalone: true)
97
+ serialize_part(web_settings)
111
98
  end
112
99
  if document_rels
113
100
  content["word/_rels/document.xml.rels"] =
114
- document_rels.to_xml(encoding: "UTF-8", declaration: true,
115
- standalone: true)
101
+ serialize_infrastructure(document_rels)
116
102
  end
117
103
 
118
104
  # Theme
119
105
  if theme
120
106
  content["word/theme/theme1.xml"] =
121
- theme.to_xml(encoding: "UTF-8", prefix: true,
122
- standalone: true)
107
+ serialize_part(theme)
123
108
  end
124
109
  if theme_rels
125
110
  content["word/theme/_rels/theme1.xml.rels"] =
126
- theme_rels.to_xml(encoding: "UTF-8", declaration: true,
127
- standalone: true)
111
+ serialize_infrastructure(theme_rels)
128
112
  end
129
113
 
130
114
  # Notes
131
115
  if footnotes
132
116
  content["word/footnotes.xml"] =
133
- footnotes.to_xml(encoding: "UTF-8", prefix: true,
134
- fix_boolean_elements: true,
135
- standalone: true)
117
+ serialize_part(footnotes)
136
118
  end
137
119
  if endnotes
138
120
  content["word/endnotes.xml"] =
139
- endnotes.to_xml(encoding: "UTF-8", prefix: true,
140
- fix_boolean_elements: true,
141
- standalone: true)
121
+ serialize_part(endnotes)
142
122
  end
143
123
 
144
124
  # Bibliography sources
145
125
  if document&.bibliography_sources
146
126
  content["word/sources.xml"] =
147
- document.bibliography_sources.to_xml(encoding: "UTF-8",
148
- declaration: true,
149
- standalone: true)
127
+ serialize_infrastructure(document.bibliography_sources)
150
128
  end
151
129
 
152
130
  # Headers and footers
@@ -155,6 +133,16 @@ document_rels)
155
133
  serialize_header_footer_parts(content)
156
134
  end
157
135
 
136
+ # Serialize an OOXML document part with standard encoding
137
+ def serialize_part(model)
138
+ model.to_xml(encoding: "UTF-8", prefix: true, standalone: true)
139
+ end
140
+
141
+ # Serialize package infrastructure (rels, content types) with declaration
142
+ def serialize_infrastructure(model)
143
+ model.to_xml(encoding: "UTF-8", declaration: true, standalone: true)
144
+ end
145
+
158
146
  private
159
147
 
160
148
  def inject_image_parts(content, content_types, document_rels)
@@ -456,9 +444,7 @@ document_rels)
456
444
  document.headers.each_value do |header_obj|
457
445
  idx += 1
458
446
  content["word/header#{idx}.xml"] =
459
- header_obj.to_xml(encoding: "UTF-8", prefix: true,
460
- fix_boolean_elements: true,
461
- standalone: true)
447
+ serialize_part(header_obj)
462
448
  end
463
449
  end
464
450
 
@@ -469,9 +455,7 @@ document_rels)
469
455
  document.footers.each_value do |footer_obj|
470
456
  idx += 1
471
457
  content["word/footer#{idx}.xml"] =
472
- footer_obj.to_xml(encoding: "UTF-8", prefix: true,
473
- fix_boolean_elements: true,
474
- standalone: true)
458
+ serialize_part(footer_obj)
475
459
  end
476
460
  end
477
461
 
@@ -480,9 +464,7 @@ document_rels)
480
464
 
481
465
  document.header_footer_parts.each do |part|
482
466
  content["word/#{part[:target]}"] =
483
- part[:content].to_xml(encoding: "UTF-8", prefix: true,
484
- fix_boolean_elements: true,
485
- standalone: true)
467
+ serialize_part(part[:content])
486
468
  end
487
469
  end
488
470
  end
@@ -85,14 +85,14 @@ module Uniword
85
85
  ].compact
86
86
 
87
87
  parts.each do |part|
88
- part.instance_variable_set(:@pending_namespace_data, nil)
89
- part.instance_variable_set(:@import_declaration_plan, nil)
88
+ part.pending_namespace_data = nil
89
+ part.import_declaration_plan = nil
90
+ part.pending_plan_root_element = nil
91
+ # TODO: @xml_input_namespaces has no public accessor in lutaml-model.
92
+ # Replace with public API once lutaml-model exposes one.
90
93
  part.instance_variable_set(:@xml_input_namespaces, nil)
91
94
  end
92
95
  end
93
-
94
- # -- Section Properties --
95
-
96
96
  def reconcile_section_properties
97
97
  return unless package.document&.body
98
98
 
@@ -313,7 +313,7 @@ module Uniword
313
313
  color_name = color_name.to_sym
314
314
  # Map folHlink to fol_hlink for attribute access
315
315
  attr_name = color_name == :folHlink ? :fol_hlink : color_name
316
- color_obj = instance_variable_get("@#{attr_name}")
316
+ color_obj = public_send(attr_name)
317
317
  color_obj&.value
318
318
  end
319
319
 
@@ -345,7 +345,7 @@ module Uniword
345
345
 
346
346
  color_obj = color_class.new
347
347
  color_obj.rgb = value
348
- instance_variable_set("@#{attr_name}", color_obj)
348
+ public_send(:"#{attr_name}=", color_obj)
349
349
  @colors_hash[color_name] = value
350
350
  end
351
351
 
@@ -32,6 +32,9 @@ module Uniword
32
32
  attribute :equation_arrays, EquationArray, collection: true,
33
33
  initialize_empty: true
34
34
 
35
+ # Transient: position among sibling runs (used by MHTML renderer)
36
+ attribute :run_position, :integer
37
+
35
38
  xml do
36
39
  element "oMath"
37
40
  namespace Uniword::Ooxml::Namespaces::MathML
@@ -9,11 +9,7 @@ module Uniword
9
9
  # the generated setter.
10
10
  module BooleanElement
11
11
  def value
12
- @val != "false"
13
- end
14
-
15
- def value=(v)
16
- @val = v ? nil : "false"
12
+ val != "false"
17
13
  end
18
14
  end
19
15
 
@@ -35,7 +31,7 @@ module Uniword
35
31
  else
36
32
  v
37
33
  end
38
- value_set_for(:val) if @val
34
+ value_set_for(:val)
39
35
  end
40
36
  end
41
37
  end
@@ -15,8 +15,7 @@ module Uniword
15
15
  # @param document [DocumentRoot] The document to serialize
16
16
  # @return [String] The serialized XML
17
17
  def serialize(document)
18
- document.to_xml(encoding: "UTF-8", prefix: true,
19
- fix_boolean_elements: true)
18
+ document.to_xml(encoding: "UTF-8", prefix: true)
20
19
  end
21
20
 
22
21
  # Serialize a document to XML and return as a Nokogiri document
@@ -79,7 +79,7 @@ module Uniword
79
79
 
80
80
  # Set the color using the appropriate attribute
81
81
  attr_name = color_name == "folHlink" ? :fol_hlink : color_name.to_sym
82
- scheme.instance_variable_set("@#{attr_name}", color_obj)
82
+ scheme.public_send(:"#{attr_name}=", color_obj)
83
83
  end
84
84
 
85
85
  # Sync hash interface
@@ -543,11 +543,11 @@ module Uniword
543
543
  parts.join
544
544
  end
545
545
 
546
- # Build position-indexed map from elements that have @_run_position markers
546
+ # Build position-indexed map from elements that have run_position markers
547
547
  def build_position_map(elements, fallback_pos)
548
548
  by_pos = {}
549
549
  elements.each do |el|
550
- pos = el.instance_variable_get(:@_run_position)
550
+ pos = el.run_position if el.is_a?(Uniword::Wordprocessingml::Hyperlink) || el.is_a?(Uniword::Math::OMath)
551
551
  pos ||= fallback_pos
552
552
  by_pos[pos] ||= []
553
553
  by_pos[pos] << el
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Uniword
4
- VERSION = "1.0.6"
4
+ VERSION = "1.0.8"
5
5
  end
@@ -15,6 +15,9 @@ module Uniword
15
15
  attribute :history, Uniword::Properties::HistoryValue
16
16
  attribute :runs, Run, collection: true, initialize_empty: true
17
17
 
18
+ # Transient: position among sibling runs (used by MHTML renderer)
19
+ attribute :run_position, :integer
20
+
18
21
  xml do
19
22
  element "hyperlink"
20
23
  namespace Uniword::Ooxml::Namespaces::WordProcessingML
@@ -178,34 +178,22 @@ module Uniword
178
178
  def merge_properties(base, override)
179
179
  merged = RunProperties.new
180
180
 
181
- # Get all attribute names from RunProperties class
182
181
  RunProperties.attributes.each_key do |attr_name|
183
182
  override_val = override.public_send(attr_name)
184
183
  base_val = base.public_send(attr_name)
185
184
 
186
- # Use override if it's non-nil AND not using default value
187
- # For boolean properties like Bold, check if it was explicitly set
188
185
  use_override = if override_val.is_a?(Lutaml::Model::Serializable)
189
- # Check if the property has any non-default/non-nil values
190
186
  override_val.class.attributes.any? do |k, _|
191
- iv = override_val.instance_variable_get(:"@#{k}")
192
- iv && !override_val.using_default?(k)
187
+ !override_val.using_default?(k)
193
188
  end
194
189
  else
195
190
  !override_val.nil?
196
191
  end
197
192
 
198
- begin
199
- if use_override
200
- merged.public_send(:"#{attr_name}=", override_val)
201
- elsif base_val
202
- merged.public_send(:"#{attr_name}=", base_val)
203
- end
204
- rescue StandardError => e
205
- Uniword.logger&.debug do
206
- "Skipping attribute #{attr_name}: #{e.message}"
207
- end
208
- end
193
+ value = use_override ? override_val : base_val
194
+ next unless value
195
+
196
+ merged.public_send(:"#{attr_name}=", value)
209
197
  end
210
198
 
211
199
  merged
@@ -251,10 +251,6 @@ module Uniword
251
251
  def run_properties
252
252
  rPr
253
253
  end
254
-
255
- def to_xml(options = {})
256
- super(options.merge(fix_boolean_elements: true))
257
- end
258
254
  end
259
255
  end
260
256
  end
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.0.6
4
+ version: 1.0.8
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-05-08 00:00:00.000000000 Z
11
+ date: 2026-05-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: logger