uniword 1.2.5 → 1.4.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.
Files changed (209) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +313 -0
  3. data/CONTRIBUTING.md +1 -1
  4. data/config/ooxml/schemas/shared_types.yml +5 -4
  5. data/data/schemas/ecma/dc.xsd +118 -0
  6. data/data/schemas/ecma/dcmitype.xsd +50 -0
  7. data/data/schemas/ecma/dcterms.xsd +331 -0
  8. data/data/schemas/ecma/opc-coreProperties.xsd +2 -2
  9. data/data/schemas/ecma/xml.xsd +117 -0
  10. data/lib/uniword/builder/bibliography_builder.rb +1 -1
  11. data/lib/uniword/builder/chart_builder.rb +22 -14
  12. data/lib/uniword/builder/comment_anchorer.rb +221 -0
  13. data/lib/uniword/builder/document_builder.rb +97 -6
  14. data/lib/uniword/builder/image_builder.rb +11 -10
  15. data/lib/uniword/builder/run_utils.rb +2 -0
  16. data/lib/uniword/builder.rb +1 -0
  17. data/lib/uniword/cli/fonts_cli.rb +52 -0
  18. data/lib/uniword/cli/main.rb +110 -11
  19. data/lib/uniword/cli/page_cli.rb +85 -0
  20. data/lib/uniword/cli/styles_cli.rb +204 -0
  21. data/lib/uniword/cli/theme_cli.rb +88 -0
  22. data/lib/uniword/cli/toc_cli.rb +22 -2
  23. data/lib/uniword/comment.rb +20 -15
  24. data/lib/uniword/comment_range.rb +3 -3
  25. data/lib/uniword/comments_part.rb +51 -19
  26. data/lib/uniword/configuration/configuration_loader.rb +5 -3
  27. data/lib/uniword/configuration.rb +93 -1
  28. data/lib/uniword/content_types.rb +19 -39
  29. data/lib/uniword/document_factory.rb +12 -18
  30. data/lib/uniword/document_writer.rb +16 -5
  31. data/lib/uniword/docx/chart_part.rb +34 -0
  32. data/lib/uniword/docx/custom_xml_item.rb +91 -0
  33. data/lib/uniword/docx/header_footer_part.rb +140 -0
  34. data/lib/uniword/docx/header_footer_part_collection.rb +135 -0
  35. data/lib/uniword/docx/header_footer_view.rb +225 -0
  36. data/lib/uniword/docx/id_allocator.rb +130 -36
  37. data/lib/uniword/docx/image_part.rb +83 -0
  38. data/lib/uniword/docx/package.rb +141 -333
  39. data/lib/uniword/docx/package_defaults.rb +83 -99
  40. data/lib/uniword/docx/package_integrity_checker.rb +312 -0
  41. data/lib/uniword/docx/package_serialization.rb +144 -233
  42. data/lib/uniword/docx/part.rb +125 -0
  43. data/lib/uniword/docx/part_collection.rb +131 -0
  44. data/lib/uniword/docx/part_loader/chart_loader.rb +50 -0
  45. data/lib/uniword/docx/part_loader/custom_xml_loader.rb +53 -0
  46. data/lib/uniword/docx/part_loader/embedding_loader.rb +26 -0
  47. data/lib/uniword/docx/part_loader/header_footer_loader.rb +101 -0
  48. data/lib/uniword/docx/part_loader/image_loader.rb +105 -0
  49. data/lib/uniword/docx/part_loader/load_context.rb +85 -0
  50. data/lib/uniword/docx/part_loader/raw_part_loader.rb +183 -0
  51. data/lib/uniword/docx/part_loader/theme_media_loader.rb +39 -0
  52. data/lib/uniword/docx/part_loader/xml_model_loader.rb +56 -0
  53. data/lib/uniword/docx/part_loader.rb +92 -0
  54. data/lib/uniword/docx/raw_part.rb +75 -0
  55. data/lib/uniword/docx/reconciler/body.rb +59 -68
  56. data/lib/uniword/docx/reconciler/fix.rb +46 -0
  57. data/lib/uniword/docx/reconciler/fix_codes.rb +37 -30
  58. data/lib/uniword/docx/reconciler/helpers.rb +18 -31
  59. data/lib/uniword/docx/reconciler/notes.rb +17 -8
  60. data/lib/uniword/docx/reconciler/package_structure.rb +168 -221
  61. data/lib/uniword/docx/reconciler/parts.rb +36 -13
  62. data/lib/uniword/docx/reconciler/referential_integrity.rb +222 -123
  63. data/lib/uniword/docx/reconciler/tables.rb +78 -16
  64. data/lib/uniword/docx/reconciler/theme.rb +7 -4
  65. data/lib/uniword/docx/reconciler.rb +36 -14
  66. data/lib/uniword/docx.rb +13 -0
  67. data/lib/uniword/drawingml/blip.rb +0 -1
  68. data/lib/uniword/drawingml/color_scheme.rb +5 -6
  69. data/lib/uniword/drawingml/font_scheme.rb +4 -8
  70. data/lib/uniword/drawingml/theme.rb +12 -6
  71. data/lib/uniword/errors.rb +8 -1
  72. data/lib/uniword/footer.rb +4 -0
  73. data/lib/uniword/header.rb +4 -0
  74. data/lib/uniword/hyperlink.rb +1 -1
  75. data/lib/uniword/image.rb +6 -6
  76. data/lib/uniword/images/image_manager.rb +13 -13
  77. data/lib/uniword/lazy_loader.rb +1 -2
  78. data/lib/uniword/model_attribute_access.rb +20 -4
  79. data/lib/uniword/ooxml/dotx_package.rb +42 -2
  80. data/lib/uniword/ooxml/element_order.rb +94 -0
  81. data/lib/uniword/ooxml/part_definition.rb +329 -0
  82. data/lib/uniword/ooxml/part_registry.rb +504 -0
  83. data/lib/uniword/ooxml/relationships/image_relationship.rb +6 -3
  84. data/lib/uniword/ooxml/relationships/package_relationships.rb +20 -22
  85. data/lib/uniword/ooxml/schema/element_serializer.rb +8 -7
  86. data/lib/uniword/ooxml/types/hex_color_value.rb +38 -0
  87. data/lib/uniword/ooxml/types/ooxml_boolean.rb +27 -14
  88. data/lib/uniword/ooxml/types/ooxml_boolean_optional.rb +21 -16
  89. data/lib/uniword/ooxml/types/theme_color_value.rb +22 -0
  90. data/lib/uniword/ooxml/types/unsigned_decimal_number.rb +30 -0
  91. data/lib/uniword/ooxml/types.rb +10 -0
  92. data/lib/uniword/ooxml.rb +3 -0
  93. data/lib/uniword/properties/alignment.rb +8 -3
  94. data/lib/uniword/properties/border.rb +52 -6
  95. data/lib/uniword/properties/cell_vertical_align.rb +4 -1
  96. data/lib/uniword/properties/color_value.rb +8 -3
  97. data/lib/uniword/properties/highlight.rb +9 -5
  98. data/lib/uniword/properties/shading.rb +4 -3
  99. data/lib/uniword/properties/tab_stop.rb +6 -1
  100. data/lib/uniword/properties/table_justification.rb +4 -1
  101. data/lib/uniword/properties/underline.rb +4 -2
  102. data/lib/uniword/properties/vertical_align.rb +6 -3
  103. data/lib/uniword/review/review_manager.rb +23 -9
  104. data/lib/uniword/revision.rb +9 -9
  105. data/lib/uniword/schema/model_generator.rb +31 -15
  106. data/lib/uniword/shared_types/hex_color.rb +4 -1
  107. data/lib/uniword/shared_types/pixel_measure.rb +4 -1
  108. data/lib/uniword/shared_types/point_measure.rb +4 -1
  109. data/lib/uniword/shared_types/text_alignment.rb +6 -1
  110. data/lib/uniword/shared_types/twips_measure.rb +4 -1
  111. data/lib/uniword/spreadsheetml/phonetic_pr.rb +1 -1
  112. data/lib/uniword/spreadsheetml/shared_string_table.rb +2 -2
  113. data/lib/uniword/spreadsheetml/table_formula.rb +1 -1
  114. data/lib/uniword/template/variable_resolver.rb +40 -7
  115. data/lib/uniword/themes/theme_transformation.rb +55 -41
  116. data/lib/uniword/toc/toc_generator.rb +6 -5
  117. data/lib/uniword/transformation/mhtml_element_renderer.rb +1 -5
  118. data/lib/uniword/transformation/mhtml_metadata_builder.rb +6 -6
  119. data/lib/uniword/validation/engine.rb +35 -0
  120. data/lib/uniword/validation/link_checker.rb +1 -1
  121. data/lib/uniword/validation/opc_validator.rb +16 -2
  122. data/lib/uniword/validation/report/layer_result.rb +0 -1
  123. data/lib/uniword/validation/report/terminal_formatter.rb +0 -1
  124. data/lib/uniword/validation/report/verification_report.rb +0 -2
  125. data/lib/uniword/validation/report.rb +20 -0
  126. data/lib/uniword/validation/rules/base.rb +10 -2
  127. data/lib/uniword/validation/rules/bookmark_pairing_rule.rb +38 -0
  128. data/lib/uniword/validation/rules/bookmark_uniqueness_rule.rb +48 -0
  129. data/lib/uniword/validation/rules/bookmarks_rule.rb +0 -2
  130. data/lib/uniword/validation/rules/content_types_coverage_rule.rb +0 -2
  131. data/lib/uniword/validation/rules/core_properties_namespace_rule.rb +0 -2
  132. data/lib/uniword/validation/rules/document_body_rule.rb +25 -0
  133. data/lib/uniword/validation/rules/document_context.rb +7 -0
  134. data/lib/uniword/validation/rules/empty_paragraphs_rule.rb +27 -0
  135. data/lib/uniword/validation/rules/font_table_signature_rule.rb +0 -2
  136. data/lib/uniword/validation/rules/fonts_rule.rb +0 -2
  137. data/lib/uniword/validation/rules/footnotes_rule.rb +0 -2
  138. data/lib/uniword/validation/rules/headers_footers_rule.rb +0 -2
  139. data/lib/uniword/validation/rules/images_rule.rb +0 -2
  140. data/lib/uniword/validation/rules/mc_ignorable_namespace_rule.rb +0 -2
  141. data/lib/uniword/validation/rules/model_context.rb +32 -0
  142. data/lib/uniword/validation/rules/model_rule.rb +34 -0
  143. data/lib/uniword/validation/rules/numbering_preservation_rule.rb +0 -2
  144. data/lib/uniword/validation/rules/numbering_rule.rb +0 -2
  145. data/lib/uniword/validation/rules/relationship_integrity_rule.rb +0 -1
  146. data/lib/uniword/validation/rules/rsid_rule.rb +0 -2
  147. data/lib/uniword/validation/rules/section_properties_rule.rb +0 -2
  148. data/lib/uniword/validation/rules/settings_rule.rb +0 -2
  149. data/lib/uniword/validation/rules/settings_values_rule.rb +0 -2
  150. data/lib/uniword/validation/rules/style_references_rule.rb +0 -2
  151. data/lib/uniword/validation/rules/table_grid_rule.rb +27 -0
  152. data/lib/uniword/validation/rules/table_properties_rule.rb +27 -0
  153. data/lib/uniword/validation/rules/tables_rule.rb +0 -2
  154. data/lib/uniword/validation/rules/theme_completeness_rule.rb +0 -2
  155. data/lib/uniword/validation/rules/theme_rule.rb +0 -2
  156. data/lib/uniword/validation/rules.rb +61 -23
  157. data/lib/uniword/validation/schema_registry.rb +11 -0
  158. data/lib/uniword/validation/validators/document_semantics_validator.rb +1 -9
  159. data/lib/uniword/validation/validators/xml_schema_validator.rb +46 -2
  160. data/lib/uniword/validation/validators.rb +0 -9
  161. data/lib/uniword/validation/verify_orchestrator.rb +0 -7
  162. data/lib/uniword/validation.rb +7 -2
  163. data/lib/uniword/version.rb +1 -1
  164. data/lib/uniword/vml/imagedata.rb +0 -1
  165. data/lib/uniword/wordprocessingml/attached_template.rb +0 -1
  166. data/lib/uniword/wordprocessingml/deleted_text.rb +1 -1
  167. data/lib/uniword/wordprocessingml/document_root.rb +121 -122
  168. data/lib/uniword/wordprocessingml/document_styling.rb +246 -0
  169. data/lib/uniword/wordprocessingml/endnotes.rb +4 -1
  170. data/lib/uniword/wordprocessingml/font_replacer.rb +147 -0
  171. data/lib/uniword/wordprocessingml/footnotes.rb +4 -1
  172. data/lib/uniword/wordprocessingml/hdr_shape_defaults.rb +0 -1
  173. data/lib/uniword/wordprocessingml/hyperlink.rb +0 -1
  174. data/lib/uniword/wordprocessingml/level.rb +6 -20
  175. data/lib/uniword/wordprocessingml/math_pr.rb +0 -11
  176. data/lib/uniword/wordprocessingml/numbering_definition.rb +16 -8
  177. data/lib/uniword/wordprocessingml/numbering_elements.rb +0 -15
  178. data/lib/uniword/wordprocessingml/page_setup.rb +181 -0
  179. data/lib/uniword/wordprocessingml/paragraph_properties.rb +33 -28
  180. data/lib/uniword/wordprocessingml/rsids.rb +0 -2
  181. data/lib/uniword/wordprocessingml/run.rb +3 -0
  182. data/lib/uniword/wordprocessingml/section_properties.rb +2 -2
  183. data/lib/uniword/wordprocessingml/settings.rb +8 -34
  184. data/lib/uniword/wordprocessingml/shape_defaults.rb +0 -1
  185. data/lib/uniword/wordprocessingml/style.rb +2 -1
  186. data/lib/uniword/wordprocessingml/style_cleanup.rb +198 -0
  187. data/lib/uniword/wordprocessingml/table_cell_properties.rb +6 -6
  188. data/lib/uniword/wordprocessingml/update_fields.rb +27 -0
  189. data/lib/uniword/wordprocessingml/w14_attributes.rb +30 -2
  190. data/lib/uniword/wordprocessingml.rb +5 -2
  191. data/lib/uniword.rb +27 -2
  192. metadata +52 -19
  193. data/config/validation_rules.yml +0 -60
  194. data/config/warning_rules.yml +0 -57
  195. data/lib/uniword/validation/document_validator.rb +0 -272
  196. data/lib/uniword/validation/structural_validator.rb +0 -116
  197. data/lib/uniword/validation/validators/content_type_validator.rb +0 -165
  198. data/lib/uniword/validation/validators/file_structure_validator.rb +0 -104
  199. data/lib/uniword/validation/validators/ooxml_part_validator.rb +0 -128
  200. data/lib/uniword/validation/validators/relationship_validator.rb +0 -147
  201. data/lib/uniword/validation/validators/zip_integrity_validator.rb +0 -110
  202. data/lib/uniword/validators/element_validator.rb +0 -93
  203. data/lib/uniword/validators/paragraph_validator.rb +0 -116
  204. data/lib/uniword/validators/table_validator.rb +0 -134
  205. data/lib/uniword/validators.rb +0 -9
  206. data/lib/uniword/warnings/warning.rb +0 -130
  207. data/lib/uniword/warnings/warning_collector.rb +0 -234
  208. data/lib/uniword/warnings/warning_report.rb +0 -159
  209. data/lib/uniword/warnings.rb +0 -9
@@ -2,34 +2,6 @@
2
2
 
3
3
  require "lutaml/model"
4
4
  require "nokogiri"
5
- require_relative "../properties/relationship_id"
6
- require_relative "zoom"
7
- require_relative "compat"
8
- require_relative "proof_state"
9
- require_relative "style_pane_format_filter"
10
- require_relative "default_tab_stop"
11
- require_relative "character_spacing_control"
12
- require_relative "do_not_display_page_boundaries"
13
- require_relative "rsids"
14
- require_relative "math_pr"
15
- require_relative "theme_font_lang"
16
- require_relative "clr_scheme_mapping"
17
- require_relative "shape_defaults"
18
- require_relative "decimal_symbol"
19
- require_relative "list_separator"
20
- require_relative "attached_template"
21
- require_relative "footnote_pr"
22
- require_relative "endnote_pr"
23
- require_relative "hdr_shape_defaults"
24
- require_relative "even_and_odd_headers"
25
- require_relative "mirror_margins"
26
- require_relative "do_not_include_subdocs_in_stats"
27
- require_relative "hyphenation_zone"
28
- require_relative "style_pane_sort_method"
29
- require_relative "doc_vars"
30
- require_relative "w14_doc_id"
31
- require_relative "w15_chart_tracking_ref_based"
32
- require_relative "w15_doc_id"
33
5
 
34
6
  module Uniword
35
7
  module Wordprocessingml
@@ -57,6 +29,7 @@ module Uniword
57
29
  attribute :footnote_pr, FootnotePr
58
30
  attribute :endnote_pr, EndnotePr
59
31
  attribute :hdr_shape_defaults, HdrShapeDefaults
32
+ attribute :update_fields, UpdateFields
60
33
  attribute :even_and_odd_headers, EvenAndOddHeaders
61
34
  attribute :mirror_margins, MirrorMargins
62
35
  attribute :do_not_include_subdocs_in_stats,
@@ -112,9 +85,9 @@ module Uniword
112
85
  map_element "doNotDisplayPageBoundaries",
113
86
  to: :do_not_display_page_boundaries, render_nil: false
114
87
  map_element "mirrorMargins", to: :mirror_margins, render_nil: false
88
+ map_element "proofState", to: :proof_state, render_nil: false
115
89
  map_element "attachedTemplate", to: :attached_template,
116
90
  render_nil: false
117
- map_element "proofState", to: :proof_state, render_nil: false
118
91
  map_element "stylePaneFormatFilter", to: :style_pane_format_filter,
119
92
  render_nil: false
120
93
  map_element "stylePaneSortMethod", to: :style_pane_sort_method,
@@ -126,6 +99,9 @@ module Uniword
126
99
  render_nil: false
127
100
  map_element "characterSpacingControl", to: :character_spacing_control,
128
101
  render_nil: false
102
+ map_element "updateFields", to: :update_fields, render_nil: false
103
+ map_element "hdrShapeDefaults", to: :hdr_shape_defaults,
104
+ render_nil: false
129
105
  map_element "footnotePr", to: :footnote_pr, render_nil: false
130
106
  map_element "endnotePr", to: :endnote_pr, render_nil: false
131
107
  map_element "compat", to: :compat, render_nil: false
@@ -137,11 +113,6 @@ module Uniword
137
113
  render_nil: false
138
114
  map_element "doNotIncludeSubdocsInStats",
139
115
  to: :do_not_include_subdocs_in_stats, render_nil: false
140
- map_element "hdrShapeDefaults", to: :hdr_shape_defaults,
141
- render_nil: false
142
- map_element "shapeDefaults", to: :shape_defaults, render_nil: false
143
- map_element "decimalSymbol", to: :decimal_symbol, render_nil: false
144
- map_element "listSeparator", to: :list_separator, render_nil: false
145
116
  # Both w14:docId and w15:docId use the same element name 'docId'
146
117
  # Separate map_element entries needed for namespace-aware matching
147
118
  # The namespace_scope ensures w14 and w15 namespaces are declared on root
@@ -150,6 +121,9 @@ module Uniword
150
121
  map_element "docId", to: :w14_doc_id, render_nil: false
151
122
  map_element "docId", to: :w15_doc_id, render_nil: false
152
123
  map_element "schemaLibrary", to: :schema_library, render_nil: false
124
+ map_element "shapeDefaults", to: :shape_defaults, render_nil: false
125
+ map_element "decimalSymbol", to: :decimal_symbol, render_nil: false
126
+ map_element "listSeparator", to: :list_separator, render_nil: false
153
127
  end
154
128
 
155
129
  # Override from_xml to manually deserialize w15:docId which has the same
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "lutaml/model"
4
- require_relative "../vml_office"
5
4
 
6
5
  module Uniword
7
6
  module Wordprocessingml
@@ -49,7 +49,8 @@ module Uniword
49
49
  map "run_properties", to: :rPr
50
50
  end
51
51
 
52
- # YAML transform methods (instance methods - called via send on an instance)
52
+ # YAML transform methods (instance methods called by lutaml-model's
53
+ # `with:` transform mechanism)
53
54
  def yaml_name_from(instance, value)
54
55
  instance.name = StyleName.new(val: value) if value
55
56
  end
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uniword
4
+ module Wordprocessingml
5
+ # Removes styles from a document's style definitions — Word's
6
+ # Styles-pane management as an API.
7
+ #
8
+ # `#remove_unused` deletes styles that no content references,
9
+ # directly (pStyle/rStyle/tblStyle in body, headers, footers, notes,
10
+ # comments) or transitively (basedOn/link/next chains from a used
11
+ # style, numbering level pStyle). Default styles (w:default="1")
12
+ # are never removed.
13
+ #
14
+ # @example Declutter a document
15
+ # cleanup = StyleCleanup.new(document)
16
+ # removed = cleanup.remove_unused # => ["ObsoleteStyle", ...]
17
+ class StyleCleanup
18
+ # @param document [DocumentRoot] Document to clean (mutated)
19
+ def initialize(document)
20
+ @document = document
21
+ end
22
+
23
+ # Remove one style by id (unless protected).
24
+ #
25
+ # @param style_id [String] Style id (w:styleId)
26
+ # @return [Boolean] true when the style was removed
27
+ def remove?(style_id)
28
+ return false if protected_id?(style_id)
29
+
30
+ before = styles.size
31
+ styles.delete_if { |style| style.styleId == style_id }
32
+ styles.size < before
33
+ end
34
+
35
+ # Ids of styles no content references.
36
+ #
37
+ # @return [Array<String>] Unreferenced, unprotected style ids
38
+ def unused_ids
39
+ used = used_ids
40
+ styles.filter_map(&:styleId).uniq - used - protected_ids
41
+ end
42
+
43
+ # Remove every unreferenced style.
44
+ #
45
+ # @return [Array<String>] Ids of removed styles
46
+ def remove_unused
47
+ ids = unused_ids
48
+ ids.each { |id| remove?(id) }
49
+ ids
50
+ end
51
+
52
+ private
53
+
54
+ def styles
55
+ @document.styles_configuration.styles
56
+ end
57
+
58
+ def protected_ids
59
+ styles.select(&:default).map(&:styleId)
60
+ end
61
+
62
+ def protected_id?(style_id)
63
+ protected_ids.include?(style_id)
64
+ end
65
+
66
+ # Ids referenced anywhere: content, notes, headers/footers,
67
+ # comments, numbering levels, and style-to-style chains.
68
+ def used_ids
69
+ used = content_style_ids + chain_seed_ids
70
+ expand_chains(used)
71
+ end
72
+
73
+ def content_style_ids
74
+ ids = []
75
+ walk_all_paragraphs do |paragraph|
76
+ ids.concat(paragraph_style_ids(paragraph))
77
+ ids.concat(run_style_ids(paragraph))
78
+ end
79
+ ids.concat(table_style_ids)
80
+ ids.concat(numbering_style_ids)
81
+ ids
82
+ end
83
+
84
+ # Styles referenced by other styles (basedOn/link/next) — chains
85
+ # are expanded from these too.
86
+ def chain_seed_ids
87
+ styles.flat_map { |style| chain_links(style) }
88
+ end
89
+
90
+ def chain_links(style)
91
+ [style.basedOn&.val, style.link&.val, style.nextStyle&.val].compact
92
+ end
93
+
94
+ def expand_chains(ids)
95
+ expanded = ids.to_set
96
+ loop do
97
+ additions = chain_additions(expanded)
98
+ break if additions.empty?
99
+
100
+ additions.each { |id| expanded << id }
101
+ end
102
+ expanded.to_a
103
+ end
104
+
105
+ def chain_additions(expanded)
106
+ styles.select { |style| expanded.include?(style.styleId) }
107
+ .flat_map { |style| chain_links(style) }
108
+ .reject { |id| expanded.include?(id) }
109
+ end
110
+
111
+ def paragraph_style_ids(paragraph)
112
+ Array(paragraph.properties&.style).filter_map(&:value)
113
+ end
114
+
115
+ def run_style_ids(paragraph)
116
+ paragraph_runs(paragraph)
117
+ .filter_map { |run| run.properties&.style&.value }
118
+ end
119
+
120
+ def paragraph_runs(paragraph)
121
+ (paragraph.runs || []) +
122
+ (paragraph.hyperlinks || []).flat_map(&:runs)
123
+ end
124
+
125
+ def table_style_ids
126
+ ids = []
127
+ walk_tables(@document.body) do |table|
128
+ ids << table.properties&.style&.val
129
+ end
130
+ ids.compact
131
+ end
132
+
133
+ def numbering_style_ids
134
+ numbering_definitions.flat_map do |definition|
135
+ (definition.levels || []).filter_map { |level| level.pStyle&.val }
136
+ end
137
+ end
138
+
139
+ def numbering_definitions
140
+ @document.numbering_configuration&.definitions || []
141
+ end
142
+
143
+ # Paragraphs in body (incl. table cells), headers/footers,
144
+ # notes, comments.
145
+ def walk_all_paragraphs(&block)
146
+ each_container { |container| walk_paragraphs(container, &block) }
147
+ end
148
+
149
+ def walk_paragraphs(container, &block)
150
+ return unless container
151
+
152
+ (container.paragraphs || []).each(&block)
153
+ walk_tables(container) { |table| walk_table_rows(table, &block) }
154
+ end
155
+
156
+ def walk_table_rows(table, &block)
157
+ (table.rows || []).each do |row|
158
+ (row.cells || []).each { |cell| walk_paragraphs(cell, &block) }
159
+ end
160
+ end
161
+
162
+ def walk_tables(container, &block)
163
+ return unless container
164
+
165
+ (container.tables || []).each do |table|
166
+ yield table
167
+ walk_table_cells(table, &block)
168
+ end
169
+ end
170
+
171
+ def walk_table_cells(table, &block)
172
+ (table.rows || []).each do |row|
173
+ (row.cells || []).each { |cell| walk_tables(cell, &block) }
174
+ end
175
+ end
176
+
177
+ def each_container(&block)
178
+ yield @document.body if @document.body
179
+ header_footer_containers.each(&block)
180
+ note_containers.each(&block)
181
+ end
182
+
183
+ def header_footer_containers
184
+ (@document.header_footer_parts || []).filter_map do |part|
185
+ part.content if part.content.is_a?(Lutaml::Model::Serializable)
186
+ end
187
+ end
188
+
189
+ def note_containers
190
+ [
191
+ @document.footnotes&.footnote_entries,
192
+ @document.endnotes&.endnote_entries,
193
+ @document.comments&.comments,
194
+ ].flatten.compact
195
+ end
196
+ end
197
+ end
198
+ end
@@ -28,18 +28,18 @@ module Uniword
28
28
  namespace Uniword::Ooxml::Namespaces::WordProcessingML
29
29
  mixed_content
30
30
 
31
+ map_element "cnfStyle", to: :cnf_style, render_nil: false
31
32
  map_element "tcW", to: :cell_width, render_nil: false
32
- map_element "tcBorders", to: :borders, render_nil: false
33
- map_element "shd", to: :shading, render_nil: false
34
- map_element "vAlign", to: :vertical_align, render_nil: false
35
33
  map_element "gridSpan", to: :grid_span, render_nil: false
36
34
  map_element "vMerge", to: :v_merge, render_nil: false
37
- map_element "tcMar", to: :tc_mar, render_nil: false
38
- map_element "cnfStyle", to: :cnf_style, render_nil: false
35
+ map_element "tcBorders", to: :borders, render_nil: false
36
+ map_element "shd", to: :shading, render_nil: false
39
37
  map_element "noWrap", to: :no_wrap, render_nil: false
40
- map_element "hideMark", to: :hide_mark, render_nil: false
38
+ map_element "tcMar", to: :tc_mar, render_nil: false
41
39
  map_element "textDirection", to: :text_direction,
42
40
  render_nil: false
41
+ map_element "vAlign", to: :vertical_align, render_nil: false
42
+ map_element "hideMark", to: :hide_mark, render_nil: false
43
43
  end
44
44
  end
45
45
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Uniword
6
+ module Wordprocessingml
7
+ # Update fields when the document is opened
8
+ #
9
+ # Element: <w:updateFields>
10
+ #
11
+ # When true, Word updates all fields (TOC, page numbers, cross
12
+ # references) the first time the document is opened — generated
13
+ # documents show correct field values without a manual F9.
14
+ class UpdateFields < Lutaml::Model::Serializable
15
+ attribute :value, Ooxml::Types::OoxmlBoolean,
16
+ default: -> { true }
17
+
18
+ xml do
19
+ element "updateFields"
20
+ namespace Uniword::Ooxml::Namespaces::WordProcessingML
21
+
22
+ map_attribute "val", to: :value, render_nil: false,
23
+ render_default: false
24
+ end
25
+ end
26
+ end
27
+ end
@@ -16,15 +16,43 @@ module Uniword
16
16
  # W16CID namespace for Word 2016+ citation identifiers
17
17
  W16CID_NAMESPACE = Uniword::Ooxml::Namespaces::Word2016Cid
18
18
 
19
- # Typed attribute for w14:paraId
19
+ # ST_LongHexNumber (ECMA-376): exactly 8 hexadecimal digits
20
+ LONG_HEX_NUMBER_PATTERN = /\A[0-9A-Fa-f]{8}\z/
21
+
22
+ # Validate an ST_LongHexNumber value at cast time
23
+ #
24
+ # @param value [Object] the raw value
25
+ # @return [String, nil, Object] the validated value
26
+ # @raise [Lutaml::Model::Type::InvalidValueError] when the value is
27
+ # not 8 hexadecimal digits
28
+ def self.cast_long_hex_number(value)
29
+ casted = Lutaml::Model::Type::String.cast(value)
30
+ return casted if casted.nil? ||
31
+ Lutaml::Model::Utils.uninitialized?(casted)
32
+ return casted if LONG_HEX_NUMBER_PATTERN.match?(casted)
33
+
34
+ raise Lutaml::Model::Type::InvalidValueError.new(
35
+ value, ["ST_LongHexNumber: 8 hexadecimal digits"]
36
+ )
37
+ end
38
+
39
+ # Typed attribute for w14:paraId (ST_LongHexNumber)
20
40
  class W14ParaId < Lutaml::Model::Type::String
41
+ def self.cast(value, _options = {})
42
+ Wordprocessingml.cast_long_hex_number(value)
43
+ end
44
+
21
45
  xml do
22
46
  namespace W14_NAMESPACE
23
47
  end
24
48
  end
25
49
 
26
- # Typed attribute for w14:textId
50
+ # Typed attribute for w14:textId (ST_LongHexNumber)
27
51
  class W14TextId < Lutaml::Model::Type::String
52
+ def self.cast(value, _options = {})
53
+ Wordprocessingml.cast_long_hex_number(value)
54
+ end
55
+
28
56
  xml do
29
57
  namespace W14_NAMESPACE
30
58
  end
@@ -4,6 +4,7 @@ module Uniword
4
4
  module Wordprocessingml
5
5
  # Core document structure
6
6
  autoload :DocumentRoot, "uniword/wordprocessingml/document_root"
7
+ autoload :DocumentStyling, "uniword/wordprocessingml/document_styling"
7
8
  autoload :Body, "uniword/wordprocessingml/body"
8
9
  autoload :Paragraph, "uniword/wordprocessingml/paragraph"
9
10
  autoload :Run, "uniword/wordprocessingml/run"
@@ -64,6 +65,7 @@ module Uniword
64
65
  # Structure and metadata
65
66
  autoload :Level, "uniword/wordprocessingml/level"
66
67
  autoload :Style, "uniword/wordprocessingml/style"
68
+ autoload :StyleCleanup, "uniword/wordprocessingml/style_cleanup"
67
69
  autoload :StructuredDocumentTag,
68
70
  "uniword/wordprocessingml/structured_document_tag"
69
71
  autoload :StructuredDocumentTagProperties,
@@ -72,6 +74,7 @@ module Uniword
72
74
  autoload :PageSize, "uniword/wordprocessingml/page_size"
73
75
  autoload :PageMargins, "uniword/wordprocessingml/page_margins"
74
76
  autoload :PageNumbering, "uniword/wordprocessingml/page_numbering"
77
+ autoload :PageSetup, "uniword/wordprocessingml/page_setup"
75
78
  autoload :Columns, "uniword/wordprocessingml/columns"
76
79
  autoload :HeaderReference, "uniword/wordprocessingml/header_reference"
77
80
  autoload :FooterReference, "uniword/wordprocessingml/footer_reference"
@@ -130,6 +133,7 @@ module Uniword
130
133
 
131
134
  # Fonts
132
135
  autoload :Font, "uniword/wordprocessingml/font"
136
+ autoload :FontReplacer, "uniword/wordprocessingml/font_replacer"
133
137
  autoload :Fonts, "uniword/wordprocessingml/fonts"
134
138
  autoload :FontTable, "uniword/wordprocessingml/font_table"
135
139
 
@@ -162,9 +166,7 @@ module Uniword
162
166
  autoload :NumFmt, "uniword/wordprocessingml/num_fmt"
163
167
  autoload :LvlText, "uniword/wordprocessingml/lvl_text"
164
168
  autoload :LvlJc, "uniword/wordprocessingml/lvl_jc"
165
- autoload :Ind, "uniword/wordprocessingml/numbering_elements"
166
169
  autoload :RFonts, "uniword/wordprocessingml/numbering_elements"
167
- autoload :Tabs, "uniword/wordprocessingml/level"
168
170
  autoload :PStyle, "uniword/wordprocessingml/level"
169
171
  autoload :NumberingConfiguration,
170
172
  "uniword/wordprocessingml/numbering_configuration"
@@ -191,6 +193,7 @@ module Uniword
191
193
  autoload :Next, "uniword/wordprocessingml/next"
192
194
  autoload :Link, "uniword/wordprocessingml/link"
193
195
  autoload :UiPriority, "uniword/wordprocessingml/ui_priority"
196
+ autoload :UpdateFields, "uniword/wordprocessingml/update_fields"
194
197
  autoload :LatentStyles, "uniword/wordprocessingml/latent_styles"
195
198
  autoload :ParagraphStyle, "uniword/wordprocessingml/paragraph_style"
196
199
  autoload :CharacterStyle, "uniword/wordprocessingml/character_style"
data/lib/uniword.rb CHANGED
@@ -101,7 +101,6 @@ module Uniword
101
101
  autoload :Template, "uniword/template"
102
102
  autoload :TemplateManager, "uniword/template_manager"
103
103
  autoload :Visitor, "uniword/visitor"
104
- autoload :Validators, "uniword/validators"
105
104
  autoload :Stylesets, "uniword/stylesets"
106
105
  autoload :Infrastructure, "uniword/infrastructure"
107
106
  autoload :Accessibility, "uniword/accessibility"
@@ -114,7 +113,6 @@ module Uniword
114
113
  autoload :Configuration, "uniword/configuration"
115
114
  autoload :Transformation, "uniword/transformation"
116
115
  autoload :Validation, "uniword/validation"
117
- autoload :Warnings, "uniword/warnings"
118
116
  autoload :Mhtml, "uniword/mhtml"
119
117
  autoload :Themes, "uniword/themes"
120
118
  autoload :Resource, "uniword/resource"
@@ -135,6 +133,9 @@ module Uniword
135
133
  # CLI
136
134
  autoload :CLI, "uniword/cli/main"
137
135
  autoload :GenerateCLI, "uniword/cli/generate_cli"
136
+ autoload :FontsCLI, "uniword/cli/fonts_cli"
137
+ autoload :PageCLI, "uniword/cli/page_cli"
138
+ autoload :StylesCLI, "uniword/cli/styles_cli"
138
139
  autoload :DiffCLI, "uniword/cli/diff_cli"
139
140
  autoload :TemplateCLI, "uniword/cli/template_cli"
140
141
  autoload :CLIHelpers, "uniword/cli/helpers"
@@ -250,6 +251,30 @@ module Uniword
250
251
  Wordprocessingml::DocumentRoot.new
251
252
  end
252
253
 
254
+ # Get the global runtime configuration (memoized)
255
+ #
256
+ # @return [Configuration] The global configuration instance
257
+ #
258
+ # @example
259
+ # Uniword.configuration.validate_on_save # => true
260
+ def configuration
261
+ @configuration ||= Configuration.new
262
+ end
263
+
264
+ # Configure global runtime policy
265
+ #
266
+ # @yieldparam config [Configuration] The global configuration instance
267
+ # @return [Configuration] The global configuration instance
268
+ #
269
+ # @example
270
+ # Uniword.configure do |config|
271
+ # config.xsd_validation = true
272
+ # end
273
+ def configure
274
+ yield(configuration)
275
+ configuration
276
+ end
277
+
253
278
  # Load document from file
254
279
  #
255
280
  # @param path [String] File path