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
@@ -19,22 +19,19 @@ module Uniword
19
19
  Wordprocessingml::Table
20
20
  end
21
21
 
22
- # Create and add a row to the table
23
- #
24
- # @yield [TableRowBuilder] Builder for row configuration
25
- # @return [TableRowBuilder] The row builder
22
+ def initialize(model = nil)
23
+ super
24
+ ensure_table_structure
25
+ end
26
+
26
27
  def row(&block)
27
28
  r = TableRowBuilder.new
28
29
  yield(r) if block
29
30
  @model.rows << r.build
31
+ finalize_table_structure
30
32
  r
31
33
  end
32
34
 
33
- # Set table width
34
- #
35
- # @param value [Integer] Width in twips
36
- # @param rule [String] Width rule ('auto', 'exact', 'pct')
37
- # @return [self]
38
35
  def width(value, rule: nil)
39
36
  ensure_properties
40
37
  @model.properties.table_width ||= Properties::TableWidth.new
@@ -43,10 +40,6 @@ module Uniword
43
40
  self
44
41
  end
45
42
 
46
- # Set table indentation
47
- #
48
- # @param value [Integer] Indent in twips
49
- # @return [self]
50
43
  def indent(value)
51
44
  ensure_properties
52
45
  @model.properties.table_indent ||= Properties::TableIndent.new
@@ -62,10 +55,36 @@ module Uniword
62
55
 
63
56
  private
64
57
 
58
+ def properties_tag
59
+ "tblPr"
60
+ end
61
+
65
62
  def ensure_properties
66
63
  @model.properties ||= Wordprocessingml::TableProperties.new
64
+ ensure_properties_in_order
67
65
  @model.properties
68
66
  end
67
+
68
+ def ensure_table_structure
69
+ ensure_properties
70
+ @model.properties.table_width ||= Properties::TableWidth.new(w: 0, type: "auto")
71
+ @model.properties.table_look ||= Wordprocessingml::TableDefaults.default_table_look
72
+ end
73
+
74
+ def finalize_table_structure
75
+ col_count = @model.rows.map(&:cell_count).max || 0
76
+ return if col_count.zero?
77
+
78
+ @model.grid ||= Wordprocessingml::TableGrid.new
79
+ existing = @model.grid.columns.size
80
+ if existing < col_count
81
+ (col_count - existing).times do
82
+ @model.grid.columns << Wordprocessingml::GridCol.new
83
+ end
84
+ elsif existing > col_count
85
+ @model.grid.columns = @model.grid.columns.first(col_count)
86
+ end
87
+ end
69
88
  end
70
89
  end
71
90
  end
@@ -17,6 +17,11 @@ module Uniword
17
17
  Wordprocessingml::TableCell
18
18
  end
19
19
 
20
+ def initialize(model = nil)
21
+ super
22
+ ensure_cell_structure
23
+ end
24
+
20
25
  # Append content to the cell. Routes by type:
21
26
  # - String -> creates a Paragraph with a Run
22
27
  # - Paragraph -> appends to paragraphs
@@ -30,12 +35,16 @@ module Uniword
30
35
  para = Wordprocessingml::Paragraph.new
31
36
  para.runs << Wordprocessingml::Run.new(text: element)
32
37
  @model.paragraphs << para
38
+ track_element_order("p")
33
39
  when Wordprocessingml::Paragraph
34
40
  @model.paragraphs << element
41
+ track_element_order("p")
35
42
  when Wordprocessingml::Table
36
43
  @model.tables << element
44
+ track_element_order("tbl")
37
45
  when ParagraphBuilder
38
46
  @model.paragraphs << element.build
47
+ track_element_order("p")
39
48
  else
40
49
  raise ArgumentError, "Cannot add #{element.class} to table cell"
41
50
  end
@@ -85,10 +94,20 @@ module Uniword
85
94
 
86
95
  private
87
96
 
97
+ def properties_tag
98
+ "tcPr"
99
+ end
100
+
88
101
  def ensure_properties
89
102
  @model.properties ||= Wordprocessingml::TableCellProperties.new
103
+ ensure_properties_in_order
90
104
  @model.properties
91
105
  end
106
+
107
+ def ensure_cell_structure
108
+ ensure_properties
109
+ @model.properties.cell_width ||= Properties::CellWidth.new(w: 0, type: "auto")
110
+ end
92
111
  end
93
112
  end
94
113
  end
@@ -39,6 +39,7 @@ module Uniword
39
39
  autoload :SdtBuilder, "uniword/builder/sdt_builder"
40
40
  autoload :BibliographyBuilder, "uniword/builder/bibliography_builder"
41
41
  autoload :ChartBuilder, "uniword/builder/chart_builder"
42
+ autoload :DeterministicId, "uniword/builder/deterministic_id"
42
43
 
43
44
  # Factory: creates a Run with optional formatting
44
45
  #
@@ -216,28 +217,35 @@ module Uniword
216
217
  def build_field_paragraph(instruction)
217
218
  para = Wordprocessingml::Paragraph.new
218
219
 
219
- # Begin character (inside a run wrapper)
220
- fc_begin = Wordprocessingml::FieldChar.new
221
- fc_begin.fldCharType = "begin"
222
- para.field_chars << fc_begin
223
-
224
- # Instruction text
225
- it = Wordprocessingml::InstrText.new
226
- it.text = instruction
227
- para.instr_text << it
228
-
229
- # Separate character
230
- fc_sep = Wordprocessingml::FieldChar.new
231
- fc_sep.fldCharType = "separate"
232
- para.field_chars << fc_sep
233
-
234
- # End character
235
- fc_end = Wordprocessingml::FieldChar.new
236
- fc_end.fldCharType = "end"
237
- para.field_chars << fc_end
220
+ # Begin character inside a run
221
+ run_begin = Wordprocessingml::Run.new
222
+ run_begin.field_char = Wordprocessingml::FieldChar.new
223
+ run_begin.field_char.fldCharType = "begin"
224
+ para.runs << run_begin
225
+
226
+ # Instruction text inside a run
227
+ run_instr = Wordprocessingml::Run.new
228
+ run_instr.instr_text = Wordprocessingml::InstrText.new
229
+ run_instr.instr_text.text = instruction
230
+ para.runs << run_instr
231
+
232
+ # Separate character inside a run
233
+ run_sep = Wordprocessingml::Run.new
234
+ run_sep.field_char = Wordprocessingml::FieldChar.new
235
+ run_sep.field_char.fldCharType = "separate"
236
+ para.runs << run_sep
237
+
238
+ # End character inside a run
239
+ run_end = Wordprocessingml::Run.new
240
+ run_end.field_char = Wordprocessingml::FieldChar.new
241
+ run_end.field_char.fldCharType = "end"
242
+ para.runs << run_end
238
243
 
239
244
  para
240
245
  end
241
246
  end
247
+
248
+ # Shared run utilities
249
+ autoload :RunUtils, "uniword/builder/run_utils"
242
250
  end
243
251
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "thor"
4
4
  require "json"
5
- require_relative "helpers"
6
5
 
7
6
  module Uniword
8
7
  # Diff subcommands for Uniword CLI.
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "thor"
4
- require_relative "helpers"
5
4
 
6
5
  module Uniword
7
6
  # Generate subcommand for Uniword CLI.
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "thor"
4
- require_relative "helpers"
5
4
 
6
5
  module Uniword
7
6
  # Headers/Footers subcommand for Uniword CLI.
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "thor"
4
4
  require "json"
5
- require_relative "helpers"
6
5
 
7
6
  module Uniword
8
7
  # Images subcommands for Uniword CLI.
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "thor"
4
4
  require "rainbow"
5
- require_relative "helpers"
6
5
  require_relative "styleset_cli"
7
6
  require_relative "resources_cli"
8
7
  require_relative "theme_cli"
@@ -501,17 +500,17 @@ module Uniword
501
500
 
502
501
  def apply_metadata_updates(core_properties)
503
502
  setters = {
504
- "set-title" => :title=,
505
- "set-author" => :creator=,
506
- "set-subject" => :subject=,
507
- "set-keywords" => :keywords=,
508
- "set-description" => :description=,
503
+ "set-title" => ->(cp, v) { cp.title = v },
504
+ "set-author" => ->(cp, v) { cp.creator = v },
505
+ "set-subject" => ->(cp, v) { cp.subject = v },
506
+ "set-keywords" => ->(cp, v) { cp.keywords = v },
507
+ "set-description" => ->(cp, v) { cp.description = v },
509
508
  }
510
509
  updated = false
511
510
  setters.each do |opt, setter|
512
511
  next unless options[opt]
513
512
 
514
- core_properties&.public_send(setter, options[opt])
513
+ setter.call(core_properties, options[opt]) if core_properties
515
514
  updated = true
516
515
  end
517
516
  updated
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "thor"
4
- require_relative "helpers"
5
4
 
6
5
  module Uniword
7
6
  # Protect subcommand for Uniword CLI.
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "thor"
4
- require_relative "helpers"
5
4
 
6
5
  module Uniword
7
6
  # Word Resources subcommands for Uniword CLI.
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "thor"
4
- require_relative "helpers"
5
4
 
6
5
  module Uniword
7
6
  # Review subcommands for Uniword CLI.
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "thor"
4
4
  require "json"
5
- require_relative "helpers"
6
5
 
7
6
  module Uniword
8
7
  # Spellcheck subcommands for Uniword CLI.
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "thor"
4
- require_relative "helpers"
5
4
 
6
5
  module Uniword
7
6
  # StyleSet subcommands for Uniword CLI.
@@ -3,7 +3,6 @@
3
3
  require "thor"
4
4
  require "json"
5
5
  require "yaml"
6
- require_relative "helpers"
7
6
 
8
7
  module Uniword
9
8
  # Template subcommands for Uniword CLI.
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "thor"
4
- require_relative "helpers"
5
4
 
6
5
  module Uniword
7
6
  # Theme subcommands for Uniword CLI.
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "thor"
4
4
  require "json"
5
- require_relative "helpers"
6
5
 
7
6
  module Uniword
8
7
  # TOC subcommands for Uniword CLI.
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "thor"
4
- require_relative "helpers"
5
4
 
6
5
  module Uniword
7
6
  # Watermark subcommand for Uniword CLI.
data/lib/uniword/cli.rb CHANGED
@@ -1,20 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # CLI entry point loads decomposed classes from lib/uniword/cli/.
4
- # Uniword::CLI is the main Thor class. Subcommands are in separate files.
3
+ # CLI namespace. All CLI classes are autoloaded from lib/uniword.rb.
4
+ # This file exists for backwards compatibility with code that
5
+ # requires "uniword/cli" to load the CLI namespace.
5
6
 
6
- require_relative "cli/helpers"
7
- require_relative "cli/styleset_cli"
8
- require_relative "cli/resources_cli"
9
- require_relative "cli/theme_cli"
10
- require_relative "cli/generate_cli"
11
- require_relative "cli/review_cli"
12
- require_relative "cli/diff_cli"
13
- require_relative "cli/toc_cli"
14
- require_relative "cli/images_cli"
15
- require_relative "cli/spellcheck_cli"
16
- require_relative "cli/template_cli"
17
- require_relative "cli/headers_cli"
18
- require_relative "cli/watermark_cli"
19
- require_relative "cli/protect_cli"
20
- require_relative "cli/main"
7
+ module Uniword
8
+ # All CLI autoload entries live in lib/uniword.rb.
9
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "diff_result"
4
-
5
3
  module Uniword
6
4
  module Diff
7
5
  # Compares two DocumentRoot instances and produces a DiffResult.
@@ -442,7 +440,10 @@ module Uniword
442
440
  def format_value(props, method)
443
441
  return nil unless props
444
442
 
445
- val = props.public_send(method)
443
+ val = case method
444
+ when :alignment then props.alignment
445
+ when :style then Array(props.style).first
446
+ end
446
447
  unwrap_value(val)
447
448
  end
448
449
 
@@ -454,7 +455,14 @@ module Uniword
454
455
  def run_prop_value(props, method)
455
456
  return nil unless props
456
457
 
457
- val = props.public_send(method)
458
+ val = case method
459
+ when :bold then props.bold
460
+ when :italic then props.italic
461
+ when :underline then props.underline
462
+ when :fonts then props.fonts
463
+ when :size then props.size
464
+ when :color then props.color
465
+ end
458
466
  return nil if val.nil?
459
467
 
460
468
  unwrap_value(val)
@@ -468,7 +476,17 @@ module Uniword
468
476
  def metadata_value(cp, field)
469
477
  return nil unless cp
470
478
 
471
- val = cp.public_send(field)
479
+ val = case field
480
+ when :title then cp.title
481
+ when :creator then cp.creator
482
+ when :subject then cp.subject
483
+ when :keywords then cp.keywords
484
+ when :description then cp.description
485
+ when :last_modified_by then cp.last_modified_by
486
+ when :revision then cp.revision
487
+ when :created then cp.created
488
+ when :modified then cp.modified
489
+ end
472
490
  return nil if val.nil?
473
491
 
474
492
  unwrap_value(val).to_s
@@ -3,7 +3,6 @@
3
3
  require "zip"
4
4
  require "nokogiri"
5
5
  require "canon"
6
- require_relative "package_diff_result"
7
6
 
8
7
  module Uniword
9
8
  module Diff
data/lib/uniword/diff.rb CHANGED
@@ -14,6 +14,10 @@ module Uniword
14
14
  autoload :DocumentDiffer, "uniword/diff/document_differ"
15
15
  autoload :Formatter, "uniword/diff/formatter"
16
16
  autoload :PackageDiffer, "uniword/diff/package_differ"
17
+ autoload :XmlChange, "uniword/diff/package_diff_result"
18
+ autoload :ZipMetadataChange, "uniword/diff/package_diff_result"
19
+ autoload :OpcIssue, "uniword/diff/package_diff_result"
20
+ autoload :PartChange, "uniword/diff/package_diff_result"
17
21
  autoload :PackageDiffResult, "uniword/diff/package_diff_result"
18
22
  end
19
23
  end
@@ -169,27 +169,6 @@ module Uniword
169
169
  detector.detect(path)
170
170
  end
171
171
 
172
- # Mapping from Docx::Package attribute names to DocumentRoot
173
- # attribute names. Keys where names differ use explicit mapping.
174
- PACKAGE_PART_MAPPINGS = {
175
- styles: :styles_configuration,
176
- numbering: :numbering_configuration,
177
- settings: :settings,
178
- font_table: :font_table,
179
- web_settings: :web_settings,
180
- theme: :theme,
181
- core_properties: :core_properties,
182
- app_properties: :app_properties,
183
- document_rels: :document_rels,
184
- theme_rels: :theme_rels,
185
- package_rels: :package_rels,
186
- content_types: :content_types,
187
- custom_properties: :custom_properties,
188
- custom_xml_items: :custom_xml_items,
189
- footnotes: :footnotes,
190
- endnotes: :endnotes,
191
- embeddings: :embeddings,
192
- }.freeze
193
172
 
194
173
  # Copy package parts to document for round-trip preservation
195
174
  #
@@ -199,10 +178,22 @@ module Uniword
199
178
  def copy_package_parts_to_document(package, document)
200
179
  return unless document.is_a?(Uniword::Wordprocessingml::DocumentRoot)
201
180
 
202
- PACKAGE_PART_MAPPINGS.each do |pkg_attr, doc_attr|
203
- value = package.public_send(pkg_attr)
204
- document.public_send(:"#{doc_attr}=", value) if value
205
- end
181
+ document.styles_configuration = package.styles if package.styles
182
+ document.numbering_configuration = package.numbering if package.numbering
183
+ document.settings = package.settings if package.settings
184
+ document.font_table = package.font_table if package.font_table
185
+ document.web_settings = package.web_settings if package.web_settings
186
+ document.theme = package.theme if package.theme
187
+ document.core_properties = package.core_properties if package.core_properties
188
+ document.app_properties = package.app_properties if package.app_properties
189
+ document.document_rels = package.document_rels if package.document_rels
190
+ document.theme_rels = package.theme_rels if package.theme_rels
191
+ document.package_rels = package.package_rels if package.package_rels
192
+ document.content_types = package.content_types if package.content_types
193
+ document.custom_properties = package.custom_properties if package.custom_properties
194
+ document.custom_xml_items = package.custom_xml_items if package.custom_xml_items
195
+ document.footnotes = package.footnotes if package.footnotes
196
+ document.endnotes = package.endnotes if package.endnotes
206
197
  end
207
198
 
208
199
  private
@@ -2,23 +2,8 @@
2
2
 
3
3
  module Uniword
4
4
  module Docx
5
- # Calculates document statistics that appear in docProps/app.xml.
6
- #
7
- # Verified against Microsoft Word 2024. Rules:
8
- # - Words: whitespace-separated tokens; each CJK char = 1 word
9
- # - Characters: total chars minus whitespace (NO paragraph marks)
10
- # - CharactersWithSpaces: total chars including spaces (NO paragraph marks)
11
- # - Paragraphs: count of non-empty paragraphs (empty paras excluded)
12
- # - Lines: same as non-empty paragraph count (no page layout engine)
13
- # - Pages: simple approximation (no page layout engine)
14
- #
15
- # Known limitations:
16
- # - Pages and Lines use paragraph-based approximation (no page layout engine)
17
- # - Footnote/endnote text is not included (unclear if Word includes it)
18
- # - Header/footer text is not included (Word likely excludes it)
19
5
  class DocumentStatistics
20
- # CJK Unified Ideographs and extension ranges
21
- CJK_REGEX = /[\u4E00-\u9FFF\u3400-\u4DBF\uF900-\uFAFF\u2F00-\u2FDF\u2E80-\u2EFF]/
6
+ CJK_REGEX = /[一-鿿㐀-䶿豈-﫿⼀-⿟⺀-⻿]/
22
7
  WHITESPACE_REGEX = /[ \t\r\n]/
23
8
 
24
9
  def initialize(package)
@@ -32,8 +17,9 @@ module Uniword
32
17
 
33
18
  text_per_paragraph = []
34
19
  collect_text(body, text_per_paragraph)
20
+ collect_notes(text_per_paragraph)
21
+ collect_headers_footers(text_per_paragraph)
35
22
 
36
- # Word only counts non-empty paragraphs
37
23
  non_empty = text_per_paragraph.reject { |t| t.strip.empty? }
38
24
 
39
25
  {
@@ -48,45 +34,58 @@ module Uniword
48
34
 
49
35
  private
50
36
 
51
- # Recursively collect text from paragraphs, tables, and SDTs.
52
- def collect_text(container, text_per_paragraph)
53
- if container.is_a?(Uniword::Wordprocessingml::Body) || container.is_a?(Uniword::Wordprocessingml::DocumentRoot) || container.is_a?(Uniword::Wordprocessingml::TableCell)
54
- container.paragraphs.each do |para|
55
- text_per_paragraph << para.text
56
- end
57
- end
37
+ def collect_text(body, text_per_paragraph)
38
+ collect_paragraphs(body.paragraphs, text_per_paragraph)
39
+ collect_table_text(body.tables, text_per_paragraph)
40
+ collect_sdt_text(body.structured_document_tags, text_per_paragraph)
41
+ end
42
+
43
+ def collect_paragraphs(paragraphs, text_per_paragraph)
44
+ paragraphs.each { |para| text_per_paragraph << para.text }
45
+ end
58
46
 
59
- if container.is_a?(Uniword::Wordprocessingml::Body) || container.is_a?(Uniword::Wordprocessingml::DocumentRoot)
60
- container.tables.each do |table|
61
- table.rows.each do |row|
62
- row.cells.each do |cell|
63
- collect_text(cell, text_per_paragraph)
64
- end
47
+ def collect_table_text(tables, text_per_paragraph)
48
+ tables.each do |table|
49
+ table.rows.each do |row|
50
+ row.cells.each do |cell|
51
+ collect_paragraphs(cell.paragraphs, text_per_paragraph)
52
+ collect_table_text(cell.tables, text_per_paragraph)
65
53
  end
66
54
  end
67
55
  end
56
+ end
68
57
 
69
- if container.is_a?(Uniword::Wordprocessingml::Body)
70
- container.structured_document_tags.each do |sdt|
71
- sdt.content&.paragraphs&.each do |para|
72
- text = para.text
73
- text_per_paragraph << text
74
- end
75
- end
58
+ def collect_sdt_text(sdts, text_per_paragraph)
59
+ sdts.each do |sdt|
60
+ next unless sdt.content
61
+ collect_paragraphs(sdt.content.paragraphs, text_per_paragraph)
62
+ end
63
+ end
64
+
65
+ def collect_notes(text_per_paragraph)
66
+ @package.footnotes&.footnote_entries&.each do |entry|
67
+ entry.paragraphs.each { |p| text_per_paragraph << p.text }
68
+ end
69
+ @package.endnotes&.endnote_entries&.each do |entry|
70
+ entry.paragraphs.each { |p| text_per_paragraph << p.text }
71
+ end
72
+ end
73
+
74
+ def collect_headers_footers(text_per_paragraph)
75
+ @package.document&.headers&.each_value do |header|
76
+ header.paragraphs.each { |p| text_per_paragraph << p.text }
77
+ end
78
+ @package.document&.footers&.each_value do |footer|
79
+ footer.paragraphs.each { |p| text_per_paragraph << p.text }
76
80
  end
77
81
  end
78
82
 
79
- # Count words using Word's algorithm:
80
- # - Whitespace-separated tokens for Latin text
81
- # - Each CJK character counts as 1 word individually
82
- # - Mixed CJK/Latin segments are counted appropriately
83
83
  def count_words(text_per_paragraph)
84
84
  total = 0
85
85
  text_per_paragraph.each do |text|
86
86
  next if text.strip.empty?
87
87
 
88
- # Split text into CJK and non-CJK segments
89
- segments = text.scan(/[\u4E00-\u9FFF\u3400-\u4DBF\uF900-\uFAFF\u2F00-\u2FDF\u2E80-\u2EFF]+|[^ \t\r\n]+/)
88
+ segments = text.scan(/[一-鿿㐀-䶿豈-﫿⼀-⿟⺀-⻿]+|[^ \t\r\n]+/)
90
89
  segments.each do |seg|
91
90
  total += if seg.match?(CJK_REGEX)
92
91
  seg.length
@@ -98,31 +97,20 @@ module Uniword
98
97
  total
99
98
  end
100
99
 
101
- # Characters (no spaces) = total chars minus whitespace.
102
- #
103
- # Verified against Word 2024:
104
- # "Hello World" → 10 (not 11, no paragraph marks)
105
100
  def count_characters_no_spaces(text_per_paragraph)
106
101
  total_chars = text_per_paragraph.sum(&:length)
107
102
  whitespace_count = text_per_paragraph.sum { |t| t.count(" \t") }
108
103
  total_chars - whitespace_count
109
104
  end
110
105
 
111
- # Characters (with spaces) = total chars including spaces.
112
- #
113
- # Verified against Word 2024:
114
- # "Hello World" → 11
115
106
  def count_characters_with_spaces(text_per_paragraph)
116
107
  text_per_paragraph.sum(&:length)
117
108
  end
118
109
 
119
- # Page estimation without page layout engine.
120
- # Uses a simple heuristic: ~45 paragraphs per page.
121
110
  def estimate_pages(paragraph_count)
122
111
  [1, (paragraph_count / 45.0).ceil].max
123
112
  end
124
113
 
125
- # Line estimation without page layout engine.
126
114
  def estimate_lines(paragraph_count)
127
115
  [1, paragraph_count].max
128
116
  end