uniword 1.0.11 → 1.2.4

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 (200) hide show
  1. checksums.yaml +4 -4
  2. data/config/font_metadata.yml +56 -0
  3. data/lib/uniword/accessibility/rules/descriptive_headings_rule.rb +1 -1
  4. data/lib/uniword/assembly/toc.rb +4 -14
  5. data/lib/uniword/assembly/variable_substitutor.rb +3 -4
  6. data/lib/uniword/batch/stages/normalize_styles_stage.rb +4 -2
  7. data/lib/uniword/builder/base_builder.rb +25 -0
  8. data/lib/uniword/builder/chart_builder.rb +33 -14
  9. data/lib/uniword/builder/deterministic_id.rb +17 -0
  10. data/lib/uniword/builder/document_builder.rb +129 -34
  11. data/lib/uniword/builder/footnote_builder.rb +22 -23
  12. data/lib/uniword/builder/header_footer_builder.rb +10 -10
  13. data/lib/uniword/builder/image_builder.rb +24 -16
  14. data/lib/uniword/builder/list_builder.rb +2 -1
  15. data/lib/uniword/builder/paragraph_builder.rb +95 -20
  16. data/lib/uniword/builder/run_utils.rb +97 -0
  17. data/lib/uniword/builder/section_builder.rb +40 -67
  18. data/lib/uniword/builder/table_builder.rb +32 -13
  19. data/lib/uniword/builder/table_cell_builder.rb +19 -0
  20. data/lib/uniword/builder.rb +27 -19
  21. data/lib/uniword/cli/diff_cli.rb +0 -1
  22. data/lib/uniword/cli/generate_cli.rb +0 -1
  23. data/lib/uniword/cli/headers_cli.rb +0 -1
  24. data/lib/uniword/cli/images_cli.rb +0 -1
  25. data/lib/uniword/cli/main.rb +6 -7
  26. data/lib/uniword/cli/protect_cli.rb +0 -1
  27. data/lib/uniword/cli/resources_cli.rb +0 -1
  28. data/lib/uniword/cli/review_cli.rb +0 -1
  29. data/lib/uniword/cli/spellcheck_cli.rb +0 -1
  30. data/lib/uniword/cli/styleset_cli.rb +0 -1
  31. data/lib/uniword/cli/template_cli.rb +0 -1
  32. data/lib/uniword/cli/theme_cli.rb +0 -1
  33. data/lib/uniword/cli/toc_cli.rb +0 -1
  34. data/lib/uniword/cli/watermark_cli.rb +0 -1
  35. data/lib/uniword/cli.rb +6 -17
  36. data/lib/uniword/diff/document_differ.rb +23 -5
  37. data/lib/uniword/diff/package_differ.rb +0 -1
  38. data/lib/uniword/diff.rb +4 -0
  39. data/lib/uniword/document_factory.rb +16 -25
  40. data/lib/uniword/docx/document_statistics.rb +42 -54
  41. data/lib/uniword/docx/id_allocator.rb +143 -0
  42. data/lib/uniword/docx/package.rb +42 -6
  43. data/lib/uniword/docx/package_defaults.rb +15 -25
  44. data/lib/uniword/docx/package_serialization.rb +131 -127
  45. data/lib/uniword/docx/reconciler/body.rb +197 -0
  46. data/lib/uniword/docx/reconciler/fix_codes.rb +74 -0
  47. data/lib/uniword/docx/reconciler/helpers.rb +337 -0
  48. data/lib/uniword/docx/reconciler/notes.rb +301 -0
  49. data/lib/uniword/docx/reconciler/package_structure.rb +322 -0
  50. data/lib/uniword/docx/reconciler/parts.rb +484 -0
  51. data/lib/uniword/docx/reconciler/referential_integrity.rb +449 -0
  52. data/lib/uniword/docx/reconciler/tables.rb +162 -0
  53. data/lib/uniword/docx/reconciler/theme.rb +141 -0
  54. data/lib/uniword/docx/reconciler.rb +44 -1097
  55. data/lib/uniword/docx.rb +3 -0
  56. data/lib/uniword/drawingml/color_scheme.rb +38 -5
  57. data/lib/uniword/hyperlink.rb +27 -26
  58. data/lib/uniword/infrastructure/zip_packager.rb +41 -38
  59. data/lib/uniword/lazy_loader.rb +36 -62
  60. data/lib/uniword/math.rb +3 -86
  61. data/lib/uniword/mhtml/document.rb +1 -3
  62. data/lib/uniword/model_attribute_access.rb +27 -0
  63. data/lib/uniword/ooxml/namespaces.rb +62 -2
  64. data/lib/uniword/ooxml/relationships/package_relationships.rb +7 -0
  65. data/lib/uniword/presentationml/slide_id.rb +0 -1
  66. data/lib/uniword/presentationml/slide_master_id.rb +0 -1
  67. data/lib/uniword/properties/bold.rb +0 -2
  68. data/lib/uniword/properties/boolean_formatting.rb +0 -2
  69. data/lib/uniword/properties/borders.rb +1 -1
  70. data/lib/uniword/properties/italic.rb +0 -2
  71. data/lib/uniword/properties/numbering_properties.rb +1 -1
  72. data/lib/uniword/properties/style_reference.rb +13 -0
  73. data/lib/uniword/quality/rules/heading_hierarchy_rule.rb +1 -1
  74. data/lib/uniword/quality/rules/style_consistency_rule.rb +1 -1
  75. data/lib/uniword/review/review_manager.rb +0 -2
  76. data/lib/uniword/template/helpers/loop_helper.rb +2 -2
  77. data/lib/uniword/template/variable_resolver.rb +14 -1
  78. data/lib/uniword/theme/theme_xml_parser.rb +1 -1
  79. data/lib/uniword/toc/toc_generator.rb +3 -14
  80. data/lib/uniword/transformation/mhtml_element_renderer.rb +94 -37
  81. data/lib/uniword/transformation/mhtml_metadata_builder.rb +1 -1
  82. data/lib/uniword/transformation/ooxml_to_html_converter.rb +3 -5
  83. data/lib/uniword/validation/checkers/internal_link_checker.rb +2 -3
  84. data/lib/uniword/version.rb +1 -1
  85. data/lib/uniword/visitor/text_extractor.rb +2 -1
  86. data/lib/uniword/watermark/manager.rb +2 -2
  87. data/lib/uniword/wordprocessingml/allow_png.rb +14 -0
  88. data/lib/uniword/wordprocessingml/body.rb +51 -34
  89. data/lib/uniword/wordprocessingml/body_div.rb +17 -0
  90. data/lib/uniword/wordprocessingml/div_border.rb +23 -0
  91. data/lib/uniword/wordprocessingml/div_borders.rb +24 -0
  92. data/lib/uniword/wordprocessingml/divs_child.rb +17 -0
  93. data/lib/uniword/wordprocessingml/document_root.rb +4 -3
  94. data/lib/uniword/wordprocessingml/endnote.rb +1 -1
  95. data/lib/uniword/wordprocessingml/endnotes.rb +4 -0
  96. data/lib/uniword/wordprocessingml/footer.rb +4 -0
  97. data/lib/uniword/wordprocessingml/footer_reference.rb +0 -1
  98. data/lib/uniword/wordprocessingml/footnote.rb +1 -1
  99. data/lib/uniword/wordprocessingml/footnotes.rb +4 -0
  100. data/lib/uniword/wordprocessingml/header.rb +4 -0
  101. data/lib/uniword/wordprocessingml/header_reference.rb +0 -1
  102. data/lib/uniword/wordprocessingml/hyperlink.rb +2 -2
  103. data/lib/uniword/wordprocessingml/instr_text.rb +20 -5
  104. data/lib/uniword/wordprocessingml/level.rb +2 -0
  105. data/lib/uniword/wordprocessingml/mar_bottom.rb +17 -0
  106. data/lib/uniword/wordprocessingml/mar_left.rb +17 -0
  107. data/lib/uniword/wordprocessingml/mar_right.rb +17 -0
  108. data/lib/uniword/wordprocessingml/mar_top.rb +17 -0
  109. data/lib/uniword/wordprocessingml/num.rb +4 -0
  110. data/lib/uniword/wordprocessingml/numbering.rb +1 -22
  111. data/lib/uniword/wordprocessingml/optimize_for_browser.rb +14 -0
  112. data/lib/uniword/wordprocessingml/page_defaults.rb +39 -0
  113. data/lib/uniword/wordprocessingml/paragraph.rb +16 -13
  114. data/lib/uniword/wordprocessingml/paragraph_properties.rb +12 -8
  115. data/lib/uniword/wordprocessingml/run.rb +32 -36
  116. data/lib/uniword/wordprocessingml/run_properties/merging.rb +45 -0
  117. data/lib/uniword/wordprocessingml/run_properties.rb +8 -5
  118. data/lib/uniword/wordprocessingml/section_properties.rb +3 -3
  119. data/lib/uniword/wordprocessingml/separator_char.rb +21 -0
  120. data/lib/uniword/wordprocessingml/settings.rb +19 -17
  121. data/lib/uniword/wordprocessingml/styles/style_definition.rb +1 -1
  122. data/lib/uniword/wordprocessingml/styles/style_library.rb +16 -0
  123. data/lib/uniword/wordprocessingml/table_defaults.rb +44 -0
  124. data/lib/uniword/wordprocessingml/text.rb +8 -2
  125. data/lib/uniword/wordprocessingml/web_div.rb +41 -0
  126. data/lib/uniword/wordprocessingml/web_divs.rb +18 -0
  127. data/lib/uniword/wordprocessingml/web_encoding.rb +17 -0
  128. data/lib/uniword/wordprocessingml/web_settings.rb +5 -21
  129. data/lib/uniword/wordprocessingml/zoom.rb +2 -0
  130. data/lib/uniword/wordprocessingml.rb +23 -0
  131. data/lib/uniword.rb +27 -0
  132. metadata +46 -70
  133. data/lib/uniword/math/accent.rb +0 -25
  134. data/lib/uniword/math/accent_properties.rb +0 -25
  135. data/lib/uniword/math/argument_properties.rb +0 -22
  136. data/lib/uniword/math/bar.rb +0 -25
  137. data/lib/uniword/math/bar_properties.rb +0 -25
  138. data/lib/uniword/math/begin_char.rb +0 -22
  139. data/lib/uniword/math/border_box.rb +0 -25
  140. data/lib/uniword/math/border_box_properties.rb +0 -39
  141. data/lib/uniword/math/box.rb +0 -25
  142. data/lib/uniword/math/box_properties.rb +0 -33
  143. data/lib/uniword/math/char.rb +0 -22
  144. data/lib/uniword/math/control_properties.rb +0 -27
  145. data/lib/uniword/math/degree.rb +0 -26
  146. data/lib/uniword/math/delimiter.rb +0 -25
  147. data/lib/uniword/math/delimiter_properties.rb +0 -33
  148. data/lib/uniword/math/denominator.rb +0 -26
  149. data/lib/uniword/math/element.rb +0 -61
  150. data/lib/uniword/math/end_char.rb +0 -22
  151. data/lib/uniword/math/equation_array.rb +0 -25
  152. data/lib/uniword/math/equation_array_properties.rb +0 -33
  153. data/lib/uniword/math/fraction.rb +0 -27
  154. data/lib/uniword/math/fraction_properties.rb +0 -25
  155. data/lib/uniword/math/function.rb +0 -27
  156. data/lib/uniword/math/function_name.rb +0 -26
  157. data/lib/uniword/math/function_properties.rb +0 -23
  158. data/lib/uniword/math/group_char.rb +0 -25
  159. data/lib/uniword/math/group_char_properties.rb +0 -29
  160. data/lib/uniword/math/lim.rb +0 -26
  161. data/lib/uniword/math/lower_limit.rb +0 -27
  162. data/lib/uniword/math/lower_limit_properties.rb +0 -23
  163. data/lib/uniword/math/math_break.rb +0 -22
  164. data/lib/uniword/math/math_font.rb +0 -22
  165. data/lib/uniword/math/math_properties.rb +0 -53
  166. data/lib/uniword/math/math_run.rb +0 -42
  167. data/lib/uniword/math/math_run_properties.rb +0 -37
  168. data/lib/uniword/math/math_simple_int_val.rb +0 -24
  169. data/lib/uniword/math/math_simple_val.rb +0 -24
  170. data/lib/uniword/math/math_style.rb +0 -22
  171. data/lib/uniword/math/math_text.rb +0 -23
  172. data/lib/uniword/math/matrix.rb +0 -25
  173. data/lib/uniword/math/matrix_column.rb +0 -23
  174. data/lib/uniword/math/matrix_column_properties.rb +0 -24
  175. data/lib/uniword/math/matrix_columns.rb +0 -23
  176. data/lib/uniword/math/matrix_properties.rb +0 -37
  177. data/lib/uniword/math/matrix_row.rb +0 -23
  178. data/lib/uniword/math/nary.rb +0 -29
  179. data/lib/uniword/math/nary_properties.rb +0 -33
  180. data/lib/uniword/math/numerator.rb +0 -26
  181. data/lib/uniword/math/o_math.rb +0 -65
  182. data/lib/uniword/math/o_math_para.rb +0 -25
  183. data/lib/uniword/math/o_math_para_properties.rb +0 -22
  184. data/lib/uniword/math/phantom.rb +0 -25
  185. data/lib/uniword/math/phantom_properties.rb +0 -33
  186. data/lib/uniword/math/pre_sub_superscript.rb +0 -29
  187. data/lib/uniword/math/pre_sub_superscript_properties.rb +0 -23
  188. data/lib/uniword/math/radical.rb +0 -27
  189. data/lib/uniword/math/radical_properties.rb +0 -25
  190. data/lib/uniword/math/separator_char.rb +0 -22
  191. data/lib/uniword/math/sub.rb +0 -26
  192. data/lib/uniword/math/sub_superscript.rb +0 -29
  193. data/lib/uniword/math/sub_superscript_properties.rb +0 -25
  194. data/lib/uniword/math/subscript.rb +0 -27
  195. data/lib/uniword/math/subscript_properties.rb +0 -23
  196. data/lib/uniword/math/sup.rb +0 -26
  197. data/lib/uniword/math/superscript.rb +0 -27
  198. data/lib/uniword/math/superscript_properties.rb +0 -23
  199. data/lib/uniword/math/upper_limit.rb +0 -27
  200. data/lib/uniword/math/upper_limit_properties.rb +0 -23
@@ -10,9 +10,9 @@ module Uniword
10
10
  # Element: <w:r>
11
11
  class Run < Lutaml::Model::Serializable
12
12
  attribute :properties, RunProperties
13
- attribute :text, Text
13
+ attribute :text, Text, collection: true
14
14
  attribute :tab, Tab
15
- attribute :break, Break
15
+ attribute :break, Break, collection: true
16
16
  attribute :drawings, Drawing, collection: true, initialize_empty: true
17
17
  attribute :pictures, Picture, collection: true, initialize_empty: true
18
18
  attribute :alternate_content, AlternateContent, default: nil
@@ -25,6 +25,8 @@ module Uniword
25
25
  attribute :no_break_hyphen, NoBreakHyphen
26
26
  attribute :sym, Symbol
27
27
  attribute :last_rendered_page_break, LastRenderedPageBreak
28
+ attribute :separator_char, SeparatorChar
29
+ attribute :continuation_separator_char, ContinuationSeparatorChar
28
30
 
29
31
  # Revision tracking attributes
30
32
  attribute :rsid_r, :string # Revision ID for run
@@ -61,6 +63,9 @@ module Uniword
61
63
  map_element "sym", to: :sym, render_nil: false
62
64
  map_element "lastRenderedPageBreak", to: :last_rendered_page_break,
63
65
  render_nil: false
66
+ map_element "separator", to: :separator_char, render_nil: false
67
+ map_element "continuationSeparator", to: :continuation_separator_char,
68
+ render_nil: false
64
69
  end
65
70
 
66
71
  # Initialize with text normalization
@@ -78,15 +83,24 @@ module Uniword
78
83
  visitor.visit_run(self)
79
84
  end
80
85
 
86
+ # Combined string content of all text elements in the run
87
+ #
88
+ # @return [String] Concatenated content of every w:t element
89
+ def text_string
90
+ Array(text).join
91
+ end
92
+
81
93
  # Substitute text in run
82
94
  #
83
95
  # @param pattern [Regexp, String] Pattern to match
84
96
  # @param replacement [String] Replacement text
85
97
  # @return [self] For method chaining
86
98
  def substitute(pattern, replacement)
87
- return self unless text
88
-
89
- self.text = text.to_s.gsub(pattern, replacement)
99
+ Array(text).each do |t|
100
+ content = t.content.to_s.gsub(pattern, replacement)
101
+ t.content = content
102
+ t.xml_space = Text.preserve_whitespace?(content) ? "preserve" : nil
103
+ end
90
104
  self
91
105
  end
92
106
 
@@ -96,10 +110,12 @@ module Uniword
96
110
  # @yield [MatchData] Block receives match data object
97
111
  # @return [self] For method chaining
98
112
  def substitute_with_block(pattern, &block)
99
- return self unless text
100
-
101
- self.text = text.to_s.gsub(pattern) do |_match_str|
102
- yield(Regexp.last_match)
113
+ Array(text).each do |t|
114
+ content = t.content.to_s.gsub(pattern) do |_match_str|
115
+ yield(Regexp.last_match)
116
+ end
117
+ t.content = content
118
+ t.xml_space = Text.preserve_whitespace?(content) ? "preserve" : nil
103
119
  end
104
120
  self
105
121
  end
@@ -108,7 +124,7 @@ module Uniword
108
124
  #
109
125
  # @return [String] Human-readable representation
110
126
  def inspect
111
- text_preview = text.to_s
127
+ text_preview = text_string
112
128
  text_preview = "#{text_preview[0, 37]}..." if text_preview.length > 40
113
129
 
114
130
  flags = []
@@ -167,36 +183,16 @@ module Uniword
167
183
 
168
184
  private
169
185
 
170
- # Merge two RunProperties objects with override taking precedence
186
+ # Merge two RunProperties objects with override taking precedence.
171
187
  #
172
- # Creates a new RunProperties where each attribute is taken from
173
- # `override` if set, otherwise from `base`.
188
+ # Delegates to RunProperties#merged_over which uses the model's
189
+ # declared attributes — fully model-driven, no dynamic dispatch.
174
190
  #
175
191
  # @param base [RunProperties] Base (inherited) properties
176
192
  # @param override [RunProperties] Override (explicit) properties
177
193
  # @return [RunProperties] Merged properties
178
194
  def merge_properties(base, override)
179
- merged = RunProperties.new
180
-
181
- RunProperties.attributes.each_key do |attr_name|
182
- override_val = override.public_send(attr_name)
183
- base_val = base.public_send(attr_name)
184
-
185
- use_override = if override_val.is_a?(Lutaml::Model::Serializable)
186
- override_val.class.attributes.any? do |k, _|
187
- !override_val.using_default?(k)
188
- end
189
- else
190
- !override_val.nil?
191
- end
192
-
193
- value = use_override ? override_val : base_val
194
- next unless value
195
-
196
- merged.public_send(:"#{attr_name}=", value)
197
- end
198
-
199
- merged
195
+ override.merged_over(base)
200
196
  end
201
197
 
202
198
  # Get inherited run properties from paragraph style chain
@@ -209,7 +205,7 @@ module Uniword
209
205
  return nil unless parent_paragraph
210
206
 
211
207
  paragraph = parent_paragraph
212
- style_id = paragraph.style
208
+ style_id = paragraph.style&.to_s
213
209
 
214
210
  return nil unless style_id
215
211
  return nil unless paragraph.parent_document
@@ -240,7 +236,7 @@ module Uniword
240
236
  # Create Text object with xml:space="preserve" when needed
241
237
  def create_text_object(string)
242
238
  text_obj = Text.new(content: string)
243
- text_obj.xml_space = "preserve" if string.start_with?(" ") || string.end_with?(" ") || string.include?("\t")
239
+ text_obj.xml_space = "preserve" if Text.preserve_whitespace?(string)
244
240
  text_obj
245
241
  end
246
242
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uniword
4
+ module Wordprocessingml
5
+ class RunProperties < Lutaml::Model::Serializable
6
+ # Merge semantics for RunProperties.
7
+ #
8
+ # Provides style-inheritance merging where override properties take
9
+ # precedence over base properties. Uses the model's declared attributes
10
+ # via read_attribute/write_attribute — fully model-driven.
11
+ module Merging
12
+ # Merge with another RunProperties, using self as override.
13
+ #
14
+ # For each declared attribute: if self has a non-default value,
15
+ # use it; otherwise use the other's value.
16
+ #
17
+ # @param base [RunProperties] Base (inherited) properties
18
+ # @return [RunProperties] New merged properties
19
+ def merged_over(base)
20
+ merged = RunProperties.new
21
+
22
+ self.class.attributes.each_key do |attr_name|
23
+ override_val = read_attribute(attr_name)
24
+ base_val = base.read_attribute(attr_name)
25
+
26
+ use_override = if override_val.is_a?(Lutaml::Model::Serializable)
27
+ override_val.class.attributes.any? do |k, _|
28
+ !override_val.using_default?(k)
29
+ end
30
+ else
31
+ !override_val.nil?
32
+ end
33
+
34
+ value = use_override ? override_val : base_val
35
+ next unless value
36
+
37
+ merged.write_attribute(attr_name, value)
38
+ end
39
+
40
+ merged
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -2,11 +2,6 @@
2
2
 
3
3
  require "lutaml/model"
4
4
 
5
- require_relative "run_properties/yaml_transforms"
6
- require_relative "run_properties/predicates"
7
- require_relative "run_properties/accessors"
8
- require_relative "run_properties/conversion"
9
-
10
5
  module Uniword
11
6
  module Wordprocessingml
12
7
  # Run (character) formatting properties
@@ -14,9 +9,17 @@ module Uniword
14
9
  # Represents w:rPr element containing character-level formatting.
15
10
  # Used in StyleSets and run elements.
16
11
  class RunProperties < Lutaml::Model::Serializable
12
+ autoload :YamlTransforms, "#{__dir__}/run_properties/yaml_transforms"
13
+ autoload :Predicates, "#{__dir__}/run_properties/predicates"
14
+ autoload :Accessors, "#{__dir__}/run_properties/accessors"
15
+ autoload :Conversion, "#{__dir__}/run_properties/conversion"
16
+ autoload :Merging, "#{__dir__}/run_properties/merging"
17
+
18
+ include Uniword::ModelAttributeAccess
17
19
  include YamlTransforms
18
20
  include Predicates
19
21
  include Accessors
22
+ include Merging
20
23
  prepend Conversion
21
24
 
22
25
  # Pattern 0: ATTRIBUTES FIRST, then XML mappings
@@ -48,13 +48,13 @@ module Uniword
48
48
  map_attribute "paraId", to: :para_id, render_nil: false
49
49
  map_attribute "textId", to: :text_id, render_nil: false
50
50
 
51
- map_element "pgSz", to: :page_size, render_nil: false
52
- map_element "pgMar", to: :page_margins, render_nil: false
53
- map_element "cols", to: :columns, render_nil: false
54
51
  map_element "headerReference", to: :header_references, render_nil: false
55
52
  map_element "footerReference", to: :footer_references, render_nil: false
53
+ map_element "pgSz", to: :page_size, render_nil: false
54
+ map_element "pgMar", to: :page_margins, render_nil: false
56
55
  map_attribute "val", to: :type
57
56
  map_element "pgNumType", to: :page_numbering, render_nil: false
57
+ map_element "cols", to: :columns, render_nil: false
58
58
  map_element "docGrid", to: :doc_grid, render_nil: false
59
59
  map_element "footnotePr", to: :footnote_pr, render_nil: false
60
60
  map_element "titlePg", to: :title_pg, render_nil: false
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Uniword
6
+ module Wordprocessingml
7
+ class SeparatorChar < Lutaml::Model::Serializable
8
+ xml do
9
+ element "separator"
10
+ namespace Uniword::Ooxml::Namespaces::WordProcessingML
11
+ end
12
+ end
13
+
14
+ class ContinuationSeparatorChar < Lutaml::Model::Serializable
15
+ xml do
16
+ element "continuationSeparator"
17
+ namespace Uniword::Ooxml::Namespaces::WordProcessingML
18
+ end
19
+ end
20
+ end
21
+ end
@@ -106,40 +106,42 @@ module Uniword
106
106
  ]
107
107
 
108
108
  map_attribute "Ignorable", to: :mc_ignorable, render_nil: false
109
+ # Element order MUST match OOXML CT_Settings schema sequence.
110
+ # Out-of-order elements cause Word "unreadable content" errors.
109
111
  map_element "zoom", to: :zoom, render_nil: false
110
112
  map_element "doNotDisplayPageBoundaries",
111
113
  to: :do_not_display_page_boundaries, render_nil: false
112
- map_element "compat", to: :compat, render_nil: false
114
+ map_element "mirrorMargins", to: :mirror_margins, render_nil: false
115
+ map_element "attachedTemplate", to: :attached_template,
116
+ render_nil: false
113
117
  map_element "proofState", to: :proof_state, render_nil: false
114
118
  map_element "stylePaneFormatFilter", to: :style_pane_format_filter,
115
119
  render_nil: false
120
+ map_element "stylePaneSortMethod", to: :style_pane_sort_method,
121
+ render_nil: false
116
122
  map_element "defaultTabStop", to: :default_tab_stop, render_nil: false
123
+ map_element "hyphenationZone", to: :hyphenation_zone,
124
+ render_nil: false
125
+ map_element "evenAndOddHeaders", to: :even_and_odd_headers,
126
+ render_nil: false
117
127
  map_element "characterSpacingControl", to: :character_spacing_control,
118
128
  render_nil: false
129
+ map_element "footnotePr", to: :footnote_pr, render_nil: false
130
+ map_element "endnotePr", to: :endnote_pr, render_nil: false
131
+ map_element "compat", to: :compat, render_nil: false
132
+ map_element "docVars", to: :doc_vars, render_nil: false
119
133
  map_element "rsids", to: :rsids, render_nil: false
120
134
  map_element "mathPr", to: :math_pr, render_nil: false
121
135
  map_element "themeFontLang", to: :theme_font_lang, render_nil: false
122
136
  map_element "clrSchemeMapping", to: :clr_scheme_mapping,
123
137
  render_nil: false
138
+ map_element "doNotIncludeSubdocsInStats",
139
+ to: :do_not_include_subdocs_in_stats, render_nil: false
140
+ map_element "hdrShapeDefaults", to: :hdr_shape_defaults,
141
+ render_nil: false
124
142
  map_element "shapeDefaults", to: :shape_defaults, render_nil: false
125
143
  map_element "decimalSymbol", to: :decimal_symbol, render_nil: false
126
144
  map_element "listSeparator", to: :list_separator, render_nil: false
127
- map_element "attachedTemplate", to: :attached_template,
128
- render_nil: false
129
- map_element "footnotePr", to: :footnote_pr, render_nil: false
130
- map_element "endnotePr", to: :endnote_pr, render_nil: false
131
- map_element "hdrShapeDefaults", to: :hdr_shape_defaults,
132
- render_nil: false
133
- map_element "evenAndOddHeaders", to: :even_and_odd_headers,
134
- render_nil: false
135
- map_element "mirrorMargins", to: :mirror_margins, render_nil: false
136
- map_element "doNotIncludeSubdocsInStats",
137
- to: :do_not_include_subdocs_in_stats, render_nil: false
138
- map_element "hyphenationZone", to: :hyphenation_zone,
139
- render_nil: false
140
- map_element "stylePaneSortMethod", to: :style_pane_sort_method,
141
- render_nil: false
142
- map_element "docVars", to: :doc_vars, render_nil: false
143
145
  # Both w14:docId and w15:docId use the same element name 'docId'
144
146
  # Separate map_element entries needed for namespace-aware matching
145
147
  # The namespace_scope ensures w14 and w15 namespaces are declared on root
@@ -25,7 +25,7 @@ module Uniword
25
25
  def resolve_inheritance(library = nil)
26
26
  return properties unless @base_style && library
27
27
 
28
- base_def = library.public_send(style_type, @base_style)
28
+ base_def = library.lookup(style_type, @base_style)
29
29
  base_props = base_def.resolve_inheritance(library)
30
30
 
31
31
  merge_properties(base_props, properties)
@@ -106,6 +106,22 @@ module Uniword
106
106
  raise(ArgumentError, "Table style not found: #{style_name}")
107
107
  end
108
108
 
109
+ # Lookup a style by kind. Single dispatch point for all style types.
110
+ # @param kind [Symbol] One of :paragraph_style, :character_style,
111
+ # :list_style, :table_style, :semantic_style
112
+ # @param name [String, Symbol] Style name
113
+ def lookup(kind, name)
114
+ case kind
115
+ when :paragraph_style then paragraph_style(name)
116
+ when :character_style then character_style(name)
117
+ when :list_style then list_style(name)
118
+ when :table_style then table_style(name)
119
+ when :semantic_style then semantic_style(name)
120
+ else
121
+ raise ArgumentError, "Unknown style kind: #{kind.inspect}"
122
+ end
123
+ end
124
+
109
125
  # Get semantic style definition
110
126
  #
111
127
  # @param style_name [String, Symbol] Style name
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uniword
4
+ module Wordprocessingml
5
+ # Shared table structure defaults.
6
+ #
7
+ # Provides canonical values for table look, grid, and cell width defaults
8
+ # used by both builder and reconciler.
9
+ module TableDefaults
10
+ DEFAULT_TABLE_LOOK = Properties::TableLook.new(
11
+ val: "04A0",
12
+ first_row: 1,
13
+ last_row: 0,
14
+ first_column: 1,
15
+ last_column: 0,
16
+ no_h_band: 0,
17
+ no_v_band: 1,
18
+ ).freeze
19
+
20
+ def self.default_table_look
21
+ Properties::TableLook.new(
22
+ val: DEFAULT_TABLE_LOOK.val,
23
+ first_row: DEFAULT_TABLE_LOOK.first_row,
24
+ last_row: DEFAULT_TABLE_LOOK.last_row,
25
+ first_column: DEFAULT_TABLE_LOOK.first_column,
26
+ last_column: DEFAULT_TABLE_LOOK.last_column,
27
+ no_h_band: DEFAULT_TABLE_LOOK.no_h_band,
28
+ no_v_band: DEFAULT_TABLE_LOOK.no_v_band,
29
+ )
30
+ end
31
+
32
+ def self.fill_missing_table_look(look)
33
+ look.val ||= DEFAULT_TABLE_LOOK.val
34
+ look.first_row ||= DEFAULT_TABLE_LOOK.first_row
35
+ look.last_row ||= DEFAULT_TABLE_LOOK.last_row
36
+ look.first_column ||= DEFAULT_TABLE_LOOK.first_column
37
+ look.last_column ||= DEFAULT_TABLE_LOOK.last_column
38
+ look.no_h_band ||= DEFAULT_TABLE_LOOK.no_h_band
39
+ look.no_v_band ||= DEFAULT_TABLE_LOOK.no_v_band
40
+ look
41
+ end
42
+ end
43
+ end
44
+ end
@@ -35,12 +35,18 @@ module Uniword
35
35
  else
36
36
  content_str = value.to_s
37
37
  text_obj = new(content: content_str)
38
- # Preserve whitespace for XML serialization
39
- text_obj.xml_space = "preserve" if content_str.start_with?(" ") || content_str.end_with?(" ") || content_str.include?("\t")
38
+ text_obj.xml_space = "preserve" if preserve_whitespace?(content_str)
40
39
  text_obj
41
40
  end
42
41
  end
43
42
 
43
+ # Whether the content requires xml:space="preserve" per OOXML spec.
44
+ # Leading/trailing spaces, tabs, and newlines all need preservation.
45
+ def self.preserve_whitespace?(content)
46
+ content.start_with?(" ") || content.end_with?(" ") ||
47
+ content.include?("\t") || content.include?("\n")
48
+ end
49
+
44
50
  # Get the actual text value
45
51
  def text
46
52
  content
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Uniword
6
+ module Wordprocessingml
7
+ class WebDiv < Lutaml::Model::Serializable
8
+ attribute :id, :string
9
+ attribute :body_div, BodyDiv
10
+ attribute :mar_left, MarLeft
11
+ attribute :mar_right, MarRight
12
+ attribute :mar_top, MarTop
13
+ attribute :mar_bottom, MarBottom
14
+ attribute :div_bdr, DivBorders
15
+ attribute :divs_child, DivsChild
16
+
17
+ xml do
18
+ element "div"
19
+ namespace Uniword::Ooxml::Namespaces::WordProcessingML
20
+ mixed_content
21
+ map_attribute "id", to: :id
22
+ map_element "bodyDiv", to: :body_div, render_nil: false
23
+ map_element "marLeft", to: :mar_left, render_nil: false
24
+ map_element "marRight", to: :mar_right, render_nil: false
25
+ map_element "marTop", to: :mar_top, render_nil: false
26
+ map_element "marBottom", to: :mar_bottom, render_nil: false
27
+ map_element "divBdr", to: :div_bdr, render_nil: false
28
+ map_element "divsChild", to: :divs_child, render_nil: false
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ # Reopen DivsChild to add the recursive WebDiv reference
35
+ Uniword::Wordprocessingml::DivsChild.class_eval do
36
+ attribute :div, Uniword::Wordprocessingml::WebDiv, collection: true
37
+
38
+ xml do
39
+ map_element "div", to: :div, render_nil: false
40
+ end
41
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Uniword
6
+ module Wordprocessingml
7
+ class WebDivs < Lutaml::Model::Serializable
8
+ attribute :div, WebDiv, collection: true
9
+
10
+ xml do
11
+ element "divs"
12
+ namespace Uniword::Ooxml::Namespaces::WordProcessingML
13
+ mixed_content
14
+ map_element "div", to: :div, render_nil: false
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Uniword
6
+ module Wordprocessingml
7
+ class WebEncoding < Lutaml::Model::Serializable
8
+ attribute :val, :string
9
+
10
+ xml do
11
+ element "encoding"
12
+ namespace Uniword::Ooxml::Namespaces::WordProcessingML
13
+ map_attribute "val", to: :val
14
+ end
15
+ end
16
+ end
17
+ end
@@ -4,35 +4,17 @@ require "lutaml/model"
4
4
 
5
5
  module Uniword
6
6
  module Wordprocessingml
7
- # Optimize for browser marker element
8
- # Presence = true, Absence = false
9
- class OptimizeForBrowser < Lutaml::Model::Serializable
10
- xml do
11
- element "optimizeForBrowser"
12
- namespace Uniword::Ooxml::Namespaces::WordProcessingML
13
- end
14
- end
15
-
16
- # Allow PNG marker element
17
- # Presence = true, Absence = false
18
- class AllowPng < Lutaml::Model::Serializable
19
- xml do
20
- element "allowPNG"
21
- namespace Uniword::Ooxml::Namespaces::WordProcessingML
22
- end
23
- end
24
-
25
- # Web settings (word/webSettings.xml)
26
- #
27
- # Contains settings related to web publishing
28
7
  class WebSettings < Lutaml::Model::Serializable
29
8
  attribute :mc_ignorable, Ooxml::Types::McIgnorable
30
9
  attribute :optimize_for_browser, OptimizeForBrowser
31
10
  attribute :allow_png, AllowPng
11
+ attribute :divs, WebDivs
12
+ attribute :web_encoding, WebEncoding
32
13
 
33
14
  xml do
34
15
  element "webSettings"
35
16
  namespace Uniword::Ooxml::Namespaces::WordProcessingML
17
+ mixed_content
36
18
 
37
19
  namespace_scope [
38
20
  { namespace: Uniword::Ooxml::Namespaces::MarkupCompatibility,
@@ -60,6 +42,8 @@ module Uniword
60
42
  map_element "optimizeForBrowser", to: :optimize_for_browser,
61
43
  render_nil: false
62
44
  map_element "allowPNG", to: :allow_png, render_nil: false
45
+ map_element "divs", to: :divs, render_nil: false
46
+ map_element "encoding", to: :web_encoding, render_nil: false
63
47
  end
64
48
  end
65
49
  end
@@ -9,12 +9,14 @@ module Uniword
9
9
  # Generated from OOXML schema: wordprocessingml.yml
10
10
  # Element: <w:zoom>
11
11
  class Zoom < Lutaml::Model::Serializable
12
+ attribute :val, :string
12
13
  attribute :percent, :integer
13
14
 
14
15
  xml do
15
16
  element "zoom"
16
17
  namespace Uniword::Ooxml::Namespaces::WordProcessingML
17
18
 
19
+ map_attribute "val", to: :val
18
20
  map_attribute "percent", to: :percent
19
21
  end
20
22
  end
@@ -34,6 +34,9 @@ module Uniword
34
34
  autoload :LastRenderedPageBreak,
35
35
  "uniword/wordprocessingml/last_rendered_page_break"
36
36
  autoload :CarriageReturn, "uniword/wordprocessingml/carriage_return"
37
+ autoload :SeparatorChar, "uniword/wordprocessingml/separator_char"
38
+ autoload :ContinuationSeparatorChar,
39
+ "uniword/wordprocessingml/separator_char"
37
40
  autoload :SimpleField, "uniword/wordprocessingml/simple_field"
38
41
  autoload :TextBoxContent, "uniword/wordprocessingml/text_box_content"
39
42
  autoload :Object, "uniword/wordprocessingml/object"
@@ -129,7 +132,23 @@ module Uniword
129
132
  autoload :Font, "uniword/wordprocessingml/font"
130
133
  autoload :Fonts, "uniword/wordprocessingml/fonts"
131
134
  autoload :FontTable, "uniword/wordprocessingml/font_table"
135
+
136
+ # Web settings
132
137
  autoload :WebSettings, "uniword/wordprocessingml/web_settings"
138
+ autoload :OptimizeForBrowser,
139
+ "uniword/wordprocessingml/optimize_for_browser"
140
+ autoload :AllowPng, "uniword/wordprocessingml/allow_png"
141
+ autoload :DivBorder, "uniword/wordprocessingml/div_border"
142
+ autoload :DivBorders, "uniword/wordprocessingml/div_borders"
143
+ autoload :MarLeft, "uniword/wordprocessingml/mar_left"
144
+ autoload :MarRight, "uniword/wordprocessingml/mar_right"
145
+ autoload :MarTop, "uniword/wordprocessingml/mar_top"
146
+ autoload :MarBottom, "uniword/wordprocessingml/mar_bottom"
147
+ autoload :BodyDiv, "uniword/wordprocessingml/body_div"
148
+ autoload :DivsChild, "uniword/wordprocessingml/divs_child"
149
+ autoload :WebDiv, "uniword/wordprocessingml/web_div"
150
+ autoload :WebDivs, "uniword/wordprocessingml/web_divs"
151
+ autoload :WebEncoding, "uniword/wordprocessingml/web_encoding"
133
152
 
134
153
  # Numbering
135
154
  autoload :AbstractNum, "uniword/wordprocessingml/abstract_num"
@@ -231,5 +250,9 @@ module Uniword
231
250
  # Mail merge
232
251
  autoload :Recipients, "uniword/wordprocessingml/recipients"
233
252
  autoload :RecipientData, "uniword/wordprocessingml/recipient_data"
253
+
254
+ # Shared defaults
255
+ autoload :PageDefaults, "uniword/wordprocessingml/page_defaults"
256
+ autoload :TableDefaults, "uniword/wordprocessingml/table_defaults"
234
257
  end
235
258
  end
data/lib/uniword.rb CHANGED
@@ -39,8 +39,20 @@ Lutaml::Model::Config.xml_adapter_type = :nokogiri
39
39
  #
40
40
  # @see DocumentFactory Factory for reading documents
41
41
  module Uniword
42
+ # Wire Uniword into Omml's context system so Omml::Models types resolve
43
+ # correctly when used as attribute types on Uniword's serializable classes
44
+ # (Paragraph#o_maths typed as Omml::Models::CTOMath, etc.).
45
+ # register_in creates a :uniword context with all Omml models registered
46
+ # directly, falling back to :default for Uniword's own types. Setting
47
+ # Config.default_register means from_xml/to_xml calls without an explicit
48
+ # register: argument use :uniword automatically.
49
+ require "omml"
50
+ Omml::Configuration.register_in(:uniword)
51
+ Lutaml::Model::Config.default_register = :uniword
52
+
42
53
  # Version constant
43
54
  autoload :VERSION, "uniword/version"
55
+ autoload :ModelAttributeAccess, "uniword/model_attribute_access"
44
56
 
45
57
  # === Namespace Modules (autoload) ===
46
58
  # All autoloads MUST be inside the module they're registering constants for
@@ -125,6 +137,17 @@ module Uniword
125
137
  autoload :GenerateCLI, "uniword/cli/generate_cli"
126
138
  autoload :DiffCLI, "uniword/cli/diff_cli"
127
139
  autoload :TemplateCLI, "uniword/cli/template_cli"
140
+ autoload :CLIHelpers, "uniword/cli/helpers"
141
+ autoload :StyleSetCLI, "uniword/cli/styleset_cli"
142
+ autoload :ResourcesCLI, "uniword/cli/resources_cli"
143
+ autoload :ThemeCLI, "uniword/cli/theme_cli"
144
+ autoload :ReviewCLI, "uniword/cli/review_cli"
145
+ autoload :TocCLI, "uniword/cli/toc_cli"
146
+ autoload :ImagesCLI, "uniword/cli/images_cli"
147
+ autoload :SpellcheckCLI, "uniword/cli/spellcheck_cli"
148
+ autoload :HeadersCLI, "uniword/cli/headers_cli"
149
+ autoload :WatermarkCLI, "uniword/cli/watermark_cli"
150
+ autoload :ProtectCLI, "uniword/cli/protect_cli"
128
151
 
129
152
  # Diff (document comparison)
130
153
  autoload :Diff, "uniword/diff"
@@ -132,6 +155,10 @@ module Uniword
132
155
  # Review (comments and tracked changes)
133
156
  autoload :Review, "uniword/review"
134
157
 
158
+ # Shared tagging modules
159
+ autoload :HasRunPosition, "uniword/has_run_position"
160
+ autoload :DocumentInput, "uniword/document_input"
161
+
135
162
  # Spellcheck (spell and grammar checking)
136
163
  autoload :Spellcheck, "uniword/spellcheck"
137
164