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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d8cdb6f45c08ae845ce242ca0a0140e7784413d9fe1b552b56eefd8040f9b03c
4
- data.tar.gz: 6603afb7881e218ed635e77349cff132817086ec1399bd2a5157ce487907165f
3
+ metadata.gz: ee9e7988ad5a17537188aa93af92867049b6c71fc99e00b24b09cde8f81eda15
4
+ data.tar.gz: 57908a45bf89d01a4b0630736678a5a2ff8ea891fe25650229f83b7b94a88b69
5
5
  SHA512:
6
- metadata.gz: c4c71185ce70622298500cb9512e6e0ab5f5b4509783f3273cfe8ac8828ec44159b875d9bf06b3d8f0ac346791f5ce779050e3030858d2320477ee0e89d6c7ec
7
- data.tar.gz: 849da67a5a82968042f85239b1b2a536f7f85056e657f9e8b7c0b3487141e956b8903232efa1478dc476174d51e11f7580557b8ee5bd0d8ac93a88eeb8fe723b
6
+ metadata.gz: e7b6660cbc36ccff3170559c4c8eebaeed56a2face7fba85ae5ec1c42ad09ba22270fe9d0b5cabc82be78a5cfc29dfa67cb8caf32afc976bd0c7d8530dd358db
7
+ data.tar.gz: b1d88957cf0b8c3b647246afb81d3e61e3a9e6c327115994b69f0a0c7070754380dafbd3e9a7ef7cf0379821d741de190f0fce58a04db8f86b1dcecd3095286e
data/CHANGELOG.md CHANGED
@@ -8,6 +8,157 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [1.4.0] - 2026-07-20
12
+
13
+ ### Added
14
+
15
+ - DOCX parts the library does not model (e.g. `docProps/meta.xml`,
16
+ glossary documents, VBA projects, customXml outside the standard
17
+ layout, header/comment `.rels` sidecars) now round-trip
18
+ byte-for-byte instead of being dropped on load. Unclaimed ZIP
19
+ entries are preserved as raw parts (`Docx::RawPart`,
20
+ `Package#raw_parts`) with their source content types, re-emitted
21
+ verbatim on save; relationships targeting them are kept by the
22
+ reconciler's dangling-relationship sweep, and the write-time
23
+ package integrity gate treats them as first-class parts.
24
+
25
+ ### Changed
26
+
27
+ - Internal refactor (no user-visible behavior change): `Ooxml::PartRegistry`
28
+ is now bidirectional and loader-aware. `Ooxml::PartDefinition` describes
29
+ how each part loads (loader strategy key, parsing model, load priority,
30
+ dynamic path resolution) and where it lives on
31
+ `Wordprocessingml::DocumentRoot` and `Docx::Package`. Registry
32
+ registrations now cover every part the library reads (content types,
33
+ package/document/settings/theme/footnote/endnote rels, theme media).
34
+ `Docx::Package.from_zip_content` loads parts by iterating registry
35
+ entries through the new `Docx::PartLoader` strategy registry
36
+ (`XmlModelLoader`, `CustomXmlLoader`, `HeaderFooterLoader`,
37
+ `ChartLoader`, `ImageLoader`, `EmbeddingLoader`, `ThemeMediaLoader`)
38
+ instead of a hand-written per-part sequence, and the two
39
+ document↔package copy lists
40
+ (`DocumentFactory.copy_package_parts_to_document`,
41
+ `Docx::PackageDefaults.copy_document_parts_to_package`) are single
42
+ registry-driven loops — a part can no longer exist in only one
43
+ direction.
44
+ - Internal refactor (no user-visible behavior change): the remaining
45
+ hash-based part families are now model objects. New
46
+ `Docx::ImagePart` (binary data, target, content type, relationship
47
+ id, source path) backs `DocumentRoot#image_parts`, which is now an
48
+ rId-keyed `Docx::PartCollection` that normalizes legacy
49
+ `{ data:, target:, content_type: }` hash assignments and keeps
50
+ hash-style reads working (`[]`, `each`, `each_value`, `keys`,
51
+ `size`, `empty?`, `[]=`, `delete`, `key?`/`has_key?`).
52
+ `Docx::PartCollection` wraps hash entries through the new
53
+ polymorphic `Docx::Part.from_hash` hook. The write-only
54
+ `Package#chart_parts` and `Package#bibliography_sources` copies are
55
+ gone — `DocumentRoot#chart_parts` and
56
+ `DocumentRoot#bibliography_sources` are the single homes, and the
57
+ registry's `:chart`/`:bibliography` definitions no longer carry
58
+ document/package copy attributes. Builders, part loaders,
59
+ serializers and reconcilers read and write the model objects
60
+ directly.
61
+ - Internal refactor (no user-visible behavior change): "will this part
62
+ be emitted?" is now answered by `Ooxml::PartRegistry.emitted_paths`
63
+ instead of a hand-written mirror in the reconciler. Part objects
64
+ (`Docx::Part`, `Docx::CustomXmlItem`) report their own emitted
65
+ package paths (`package_paths`), and family definitions
66
+ (`:header`/`:footer`/`:chart`/`:image`/`:bibliography`) gained the
67
+ collection attributes needed to enumerate them.
68
+ - Internal refactor (no user-visible behavior change):
69
+ `Uniword::Ooxml::ElementOrder` is now the single safe
70
+ `element_order` mutation point (`mutable_order`, `append`,
71
+ `insert_at`, `insert_once`), replacing per-model workarounds for
72
+ lutaml-model's frozen parse arrays in footnotes, endnotes, the TOC
73
+ generator, and the reconciler helpers
74
+
75
+ ### Fixed
76
+
77
+ - `uniword verify` no longer reports false OPC-006 "relationship
78
+ target not found" errors for relationship targets containing dot
79
+ segments (e.g. `../docProps/meta.xml`); target resolution now
80
+ normalizes `.`/`..` per OPC Part 2, matching the write-time gate.
81
+ - `uniword verify --xsd` no longer drowns in false errors for MCE
82
+ extension content every real document carries: the XSD layer
83
+ preprocesses each part per its own `mc:Ignorable` declaration,
84
+ stripping ignorable-marked attributes (w14:paraId/textId) and the
85
+ `mc:Ignorable` attribute itself before validation. Extension elements
86
+ are deliberately kept (removing them exposed baseline Word-vs-schema
87
+ quirks as false errors); their findings remain classified as
88
+ MCE-attributable
89
+
90
+ ### Changed
91
+
92
+ - `DocumentRoot#remove_style` now returns the removed `Style` object or
93
+ nil (previously a Boolean), consistent with `#rename_style`; the
94
+ theme/style/scheme application methods on `DocumentRoot` moved into
95
+ `Wordprocessingml::DocumentStyling` (internal extraction, signatures
96
+ unchanged)
97
+ - `Ooxml::Relationships::ImageRelationship.new` now requires an
98
+ explicit `id:` (the unreachable random rId default is gone;
99
+ `Docx::IdAllocator` remains the single rId authority)
100
+
101
+ ## [1.3.0] - 2026-07-19
102
+
103
+ ### Added
104
+
105
+ #### Comment Authoring and Round-Trip Preservation
106
+ - `DocumentBuilder#comment` now anchors the comment around the preceding
107
+ body paragraph (`w:commentRangeStart`/`w:commentRangeEnd` plus a
108
+ `w:commentReference` run styled with the `CommentReference` character
109
+ style, auto-registered in styles.xml) in addition to registering it in
110
+ the document's comments collection
111
+ - Saving a document with comments emits `word/comments.xml` with its
112
+ `[Content_Types].xml` override and `document.xml.rels` relationship
113
+ (part metadata registered in `Ooxml::PartRegistry` under `:comments`);
114
+ loading parses the part into the document's comments collection, so
115
+ comments (ids, authors, dates, text) and their body anchors survive
116
+ load → save round-trips
117
+ - `Builder::CommentAnchorer`: anchor placement for fresh and parsed
118
+ paragraphs (nested anchors on the same paragraph match Word's output)
119
+ - `Wordprocessingml::Run` maps `w:commentReference`
120
+ - `CommentsPart` is the single comments collection for both
121
+ `DocumentBuilder` and `Review::ReviewManager`; it gains Array-style
122
+ access (`size`/`[]`/`each`) and collision-safe sequential ID
123
+ assignment (OOXML `ST_DecimalNumber`)
124
+
125
+ #### Validation Engine Consolidation
126
+ - `Validation::Engine`: single validation engine running the rules of
127
+ `Rules::Registry` against a validation context, with two front-ends
128
+ sharing one result model (`Report::ValidationIssue`)
129
+ - `Rules::ModelContext`: in-memory validation context wrapping a
130
+ `Wordprocessingml::DocumentRoot` (pre-save invocation)
131
+ - `Rules::ModelRule`: base class for in-memory model-level rules;
132
+ `Rules::Base#context_type` (`:package`/`:model`) selects the rule set
133
+ - Model-level rules: DOC-200 (document requires body), DOC-201 (bookmark
134
+ pairing), DOC-202 (bookmark name uniqueness), DOC-203 (empty
135
+ paragraphs), DOC-204 (`tbl` requires `tblGrid` per wml.xsd CT_Tbl),
136
+ DOC-205 (`tbl` requires `tblPr` per wml.xsd CT_Tbl)
137
+
138
+ ### Changed
139
+
140
+ - `DocumentRoot#valid?`, `#validation_errors`, `#validation_warnings` are
141
+ now powered by `Validation::Engine` over the model-level rules instead
142
+ of the deleted `Validation::StructuralValidator`
143
+ - `uniword validate` runs the same engine on the in-memory model, prints
144
+ rule-coded issues, and exits 1 on error-severity issues (aligned with
145
+ `uniword verify`); it previously always exited 0
146
+ - `Validators::DocumentSemanticsValidator` (verify layer 3) runs through
147
+ `Validation::Engine` like every other front-end
148
+
149
+ ### Removed
150
+
151
+ - Dead validation code (no production callers):
152
+ `Validation::DocumentValidator` (7-layer pipeline),
153
+ `Validation::StructuralValidator`, the `validation/validators/` layer
154
+ validators unused by `VerifyOrchestrator` (`FileStructureValidator`,
155
+ `ZipIntegrityValidator`, `OoxmlPartValidator`, `RelationshipValidator`,
156
+ `ContentTypeValidator`), the `Uniword::Validators` stub namespace
157
+ (`ElementValidator`/`ParagraphValidator`/`TableValidator`), the
158
+ `Uniword::Warnings` module (`Warning`/`WarningCollector`/
159
+ `WarningReport`), and the orphaned `config/validation_rules.yml` and
160
+ `config/warning_rules.yml`
161
+
11
162
  ### Added
12
163
 
13
164
  #### Open-Source Resource System (April 2025)
@@ -41,8 +192,140 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
41
192
  - Injects missing separator (id=-1) and continuation (id=0) entries
42
193
  - Runs during `Docx::Package#to_zip_content` before serialization
43
194
 
195
+ #### Write-Time Validation and Reconciler Transparency (July 2026)
196
+ - `Docx::PackageIntegrityChecker`: write-time OPC integrity gate invoked on
197
+ every save after reconciliation, before packaging; refuses invalid output
198
+ (missing content types, unresolvable relationship targets, dangling
199
+ `r:id`/`r:embed`/`r:link` references, duplicate rIds, malformed XML)
200
+ - Saves raise `Uniword::ValidationError` with structured `issues` (code,
201
+ part, message) when the gate rejects the package
202
+ - Escape hatch: `save(path, validate: false)` threaded through
203
+ `DocumentRoot#save`/`#to_file`, `DocumentWriter`, `Docx::Package.to_file`
204
+ and `Ooxml::DotxPackage.to_file`; default from
205
+ `Uniword.configuration.validate_on_save` (default `true`)
206
+ - `Uniword.configuration.log_save_fixes` (default `true`): log each repair
207
+ the Reconciler applies during save
208
+ - `Docx::Package#applied_fixes`: reconciliation report (Fix value objects
209
+ with code, message, part) exposed after save
210
+ - Reconciler now repairs dangling `r:embed` image references by removing
211
+ the drawing, and strips relationships whose target part the package does
212
+ not carry (e.g. unmodelled parts dropped on load) — recorded as fixes
213
+ - Allocator (builder) and legacy (template) save paths now apply identical
214
+ referential repairs (previously the builder path only logged warnings)
215
+ - `SchemaRegistry` now maps `word/webSettings.xml` and the docProps
216
+ core/app/custom parts to their XSDs; `docProps/core.xml` validates
217
+ offline (Dublin Core XSD imports bundled with relative schemaLocations)
218
+ - Integration spec now XSD-validates real library output end-to-end
219
+ (Builder corpus + fixture round-trips) via `uniword verify --xsd`
220
+ semantics
221
+ - `uniword theme fonts INPUT OUTPUT --name X` and `uniword theme colors
222
+ INPUT OUTPUT --name Y`: apply a bundled font scheme (25) or color
223
+ scheme (23) to an existing document — Word's Design → Fonts / Colors
224
+ galleries as CLI commands, replacing only the targeted theme element
225
+ (`--list` shows available schemes); also available as
226
+ `DocumentRoot#apply_font_scheme` / `#apply_color_scheme`
227
+ - `uniword fonts replace INPUT OUTPUT --from X --to Y`: Word's
228
+ Replace Fonts dialog as a one-shot CLI command — rewrites rFonts
229
+ references across styles and defaults, body content, headers/footers,
230
+ notes, comments, and numbering (also `DocumentRoot#replace_font`)
231
+ - `w:updateFields` support: `uniword toc insert` / `toc update` now set
232
+ it by default (`--no-update-fields` to opt out), so Word refreshes
233
+ the TOC and all fields when the document is opened — no manual F9
234
+ - Comments can now target an arbitrary paragraph:
235
+ `doc.comment(author:, text:, on: paragraph_or_index)` — anchor around
236
+ any existing paragraph object or body index, including paragraphs
237
+ inside table cells (default remains the last paragraph)
238
+ - `uniword page setup INPUT OUTPUT`: Word's Layout dialog as a CLI
239
+ command — paper size presets (letter/legal/a4/a5/executive),
240
+ orientation with Word-style dimension swap, and margins in
241
+ in/cm/mm/twips units, applied to every section (also
242
+ `DocumentRoot#apply_page_setup`)
243
+ - `uniword styles list FILE [--type T] [--verbose]`: Word's Styles
244
+ pane as a terminal listing — style id, type, base style, and key
245
+ formatting details
246
+ - `uniword styles remove FILE OUTPUT [--id X | --unused] [--dry-run]`:
247
+ Styles-pane management as a one-shot command — delete a style by id,
248
+ or declutter every style that no content references (directly or via
249
+ basedOn/link/next chains); default styles are always kept (also
250
+ `DocumentRoot#remove_style` / `#remove_unused_styles`)
251
+ - `uniword styles rename FILE OUTPUT --id X --name Y`: Word's style
252
+ rename — updates the display name while the styleId (and every
253
+ reference to it) stays intact (also `DocumentRoot#rename_style`)
254
+ - `uniword repair INPUT OUTPUT`: load a document, run the save-time
255
+ Reconciler over it, and write a repaired copy — dangling references
256
+ stripped, missing parts rebuilt — with every repair reported by code
257
+ (substantive repairs vs routine normalization); exits non-zero when
258
+ the write-time gate rejects what the Reconciler cannot fix
259
+
260
+ ### Fixed
261
+
262
+ - `w:lvl` no longer emits `w:ind`/`w:tabs` as direct children (invalid per
263
+ ECMA-376 CT_Lvl); numbering level indentation is now written via the
264
+ level's `w:pPr` — previously every Builder-generated list produced
265
+ schema-invalid `numbering.xml`
266
+ - Dangling image drawings and their relationships are now removed in a
267
+ single reconcile pass: a drawing whose `r:embed` rel exists but points
268
+ at a part the package does not carry is treated as dangling (previously
269
+ the first pass kept the drawing and stripped the rel, leaving a new
270
+ dangling reference for the next save)
271
+ - `word/_rels/settings.xml.rels`, `word/_rels/footnotes.xml.rels` and
272
+ `word/_rels/endnotes.xml.rels` now survive load → save round-trips on
273
+ both save paths (previously dropped, leaving dangling `r:id`
274
+ references in the saved package)
275
+ - Saving no longer crashes with FrozenError when the Reconciler injects
276
+ missing footnote/endnote separator entries into a parsed document
277
+ - Theme application no longer produces invalid `theme1.xml`:
278
+ `Drawingml::Theme#dup` silently dropped the format scheme (fill/line/
279
+ effect lists), so every `theme apply` emitted a `fillStyleLst` Word
280
+ rejects as "unreadable content"; the save-time fill repair also used
281
+ a wrong attribute kwarg (`scheme_color:` for `scheme_clr:`), creating
282
+ color-less fills. Both fixed — applied themes now carry the complete
283
+ format scheme. Same family fixed in `FontScheme#dup` (per-script
284
+ font entries were dropped) and `ColorScheme#dup` (system-color
285
+ entries like windowText were lost)
286
+ - Tables whose `gridCol` columns lack widths now get even shares of the
287
+ section content width (page width minus margins) at save time —
288
+ matching Word's fallback — instead of emitting width-less `gridCol`
289
+ elements and warning; explicit widths are preserved and the remainder
290
+ is shared
291
+ - `uniword verify`-style integration spec hang: `soffice --view` example
292
+ no longer blocks the LibreOffice integration spec indefinitely
293
+ - Header/footer dual path: adding a header/footer via the Builder to a
294
+ loaded document no longer drops the new part, duplicates
295
+ relationships or content-type overrides, or points two sectPr
296
+ references at the same rId. Round-trips that previously re-added a
297
+ stale relationship with the original rId (duplicate target rels, e.g.
298
+ on the APA paper template) now emit exactly one rel per part
299
+ - `uniword headers list` now reports headers/footers of loaded
300
+ documents, and `headers add-header` / watermark managers no longer
301
+ crash when saving (both used to bypass the round-trip storage)
302
+ - rId stability through round-trip: `Docx::IdAllocator` is now the single
303
+ rId authority for all relationships parts. Loaded documents round-trip
304
+ with their original rIds preserved verbatim (previously the reconciler
305
+ renumbered every rId on save, e.g. `r:embed="rId8"` becoming
306
+ `"rId9"`); only genuinely new relationships (builder-added images,
307
+ hyperlinks, charts, headers/footers) receive fresh allocations.
308
+ The 3 long-skipped `docx_roundtrip_spec.rb` round-trip examples
309
+ (APA template, two ISO fixtures) now pass unskipped
310
+ - Loaded image parts are now keyed by their actual document relationship
311
+ rId, so `r:embed` references resolve correctly in MHTML conversion and
312
+ the image manager; theme-only media no longer gains a spurious
313
+ document-level relationship on save
314
+ - Adding the same image file twice now stores one part with one
315
+ relationship shared by both drawings (previously produced a dangling
316
+ duplicate rId reference)
317
+
44
318
  ### Changed
45
319
 
320
+ - `Uniword::Ooxml::Relationships::PackageRelationships.next_available_rid`
321
+ removed: all rId assignment flows through `Docx::IdAllocator` (scoped
322
+ per rels part — `:document`/`:package` — with `seed_from_rels`
323
+ preserving loaded rIds verbatim). The reconciler's legacy renumbering
324
+ path, the serializer-side `next_rid`, and `ChartBuilder`'s literal
325
+ `rIdChartN` ids are gone; chart relationship ids are now allocator
326
+ numerics. The reconciler's light-touch vs full normalization choice is
327
+ now explicit (`builder_managed`) instead of inferred from allocator
328
+ presence
46
329
  - `Uniword::Ooxml::DocxPackage` renamed to `Uniword::Docx::Package`
47
330
  - All references in lib/ and spec/ updated (~20 files)
48
331
  - `lib/uniword/ooxml/docx_package.rb` moved to `lib/uniword/docx/package.rb`
@@ -52,6 +335,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
52
335
  - All 12 stylesets reauthored with OFL-licensed fonts (Calibri references fixed)
53
336
  - `uniword.gemspec` updated to include `data/**/*.yml` and `data/**/*.xsd` in gem
54
337
  - Added `rainbow ~> 3.1` gem dependency
338
+ - Single `Uniword::Ooxml::PartRegistry` now owns the part↔content-type↔
339
+ relationship-type mapping for every DOCX part kind the library writes
340
+ - New `Ooxml::PartDefinition` value class (key, path/pattern, content
341
+ type, relationship type, extension, required, default-vs-override)
342
+ - `ContentTypes.generate`, `Docx::PackageDefaults.minimal_*`,
343
+ `Reconciler::PackageStructure`, and the `PackageSerialization`
344
+ `inject_*` methods derive from the registry instead of holding
345
+ string literals; `IdAllocator` and builder rel-type constants also
346
+ derive from it
347
+ - Open/closed: register a `PartDefinition` to add a part kind —
348
+ consumers need no changes
349
+ - No behavior change: saved packages are byte-identical
350
+ - Package-held parts are now model objects instead of raw hashes
351
+ - New `Uniword::Docx::Part` value object (definition, rId, target,
352
+ content, verbatim rel-type/content-type overrides) with `ChartPart`
353
+ and `HeaderFooterPart` subclasses and `CustomXmlItem`;
354
+ `Docx::PartCollection` backs `chart_parts` (keyed by rId) and
355
+ `embeddings` (keyed by target)
356
+ - Single header/footer storage path: `DocumentRoot#header_footer_parts`
357
+ is a `Docx::HeaderFooterPartCollection` fed identically by the loader
358
+ (original rIds/targets, sectPr-derived types) and the Builder;
359
+ `document.headers`/`document.footers` are delegating
360
+ `Docx::HeaderFooterView`s over that store (Hash-style by sectPr type,
361
+ Array-style over parts) — public API preserved
362
+ - Single wiring implementation: the Reconciler wires fresh
363
+ header/footer parts into `document.xml.rels` and sectPr
364
+ (`IdAllocator` on the builder path, `find_or_create_rel` on the
365
+ legacy path); the serializer's duplicate rel/sectPr wiring and its
366
+ divergent rId strategy are deleted — it now only emits one part
367
+ file and one content-type override per store entry
55
368
 
56
369
  #### StylesetPackage Implementation (December 4, 2024)
57
370
  - **StylesetPackage**: Proper MODEL-DRIVEN package for .dotx files
data/CONTRIBUTING.md CHANGED
@@ -226,7 +226,7 @@ After adding any class (autoload or require_relative):
226
226
  **Good (autoload)**:
227
227
  ```ruby
228
228
  # New utility class with no dependencies
229
- autoload :DocumentValidator, 'uniword/document_validator'
229
+ autoload :DocumentFactory, 'uniword/document_factory'
230
230
  ```
231
231
 
232
232
  **Good (require_relative with documentation)**:
@@ -46,7 +46,7 @@ elements:
46
46
  description: 'Hexadecimal color value (RGB)'
47
47
  attributes:
48
48
  - name: val
49
- type: :string
49
+ type: Uniword::Ooxml::Types::HexColorValue
50
50
  xml_name: val
51
51
  xml_attribute: true
52
52
  required: true
@@ -57,7 +57,7 @@ elements:
57
57
  description: 'Measurement in twips (1/20th of a point)'
58
58
  attributes:
59
59
  - name: val
60
- type: :integer
60
+ type: Uniword::Ooxml::Types::UnsignedDecimalNumber
61
61
  xml_name: val
62
62
  xml_attribute: true
63
63
  required: true
@@ -79,7 +79,7 @@ elements:
79
79
  description: 'Measurement in points'
80
80
  attributes:
81
81
  - name: val
82
- type: :integer
82
+ type: Uniword::Ooxml::Types::UnsignedDecimalNumber
83
83
  xml_name: val
84
84
  xml_attribute: true
85
85
  required: true
@@ -90,7 +90,7 @@ elements:
90
90
  description: 'Measurement in pixels'
91
91
  attributes:
92
92
  - name: val
93
- type: :integer
93
+ type: Uniword::Ooxml::Types::UnsignedDecimalNumber
94
94
  xml_name: val
95
95
  xml_attribute: true
96
96
  required: true
@@ -135,6 +135,7 @@ elements:
135
135
  attributes:
136
136
  - name: val
137
137
  type: :string
138
+ values: [top, center, baseline, bottom, auto]
138
139
  xml_name: val
139
140
  xml_attribute: true
140
141
  required: true
@@ -0,0 +1,118 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
3
+ xmlns="http://purl.org/dc/elements/1.1/"
4
+ targetNamespace="http://purl.org/dc/elements/1.1/"
5
+ elementFormDefault="qualified"
6
+ attributeFormDefault="unqualified">
7
+
8
+ <xs:annotation>
9
+ <xs:documentation xml:lang="en">
10
+ DCMES 1.1 XML Schema
11
+ XML Schema for http://purl.org/dc/elements/1.1/ namespace
12
+
13
+ Created 2003-04-02
14
+
15
+ Created by
16
+
17
+ Tim Cole (t-cole3@uiuc.edu)
18
+ Tom Habing (thabing@uiuc.edu)
19
+ Jane Hunter (jane@dstc.edu.au)
20
+ Pete Johnston (p.johnston@ukoln.ac.uk),
21
+ Carl Lagoze (lagoze@cs.cornell.edu)
22
+
23
+ This schema declares XML elements for the 15 DC elements from the
24
+ http://purl.org/dc/elements/1.1/ namespace.
25
+
26
+ It defines a complexType SimpleLiteral which permits mixed content
27
+ and makes the xml:lang attribute available. It disallows child elements by
28
+ use of minOcccurs/maxOccurs.
29
+
30
+ However, this complexType does permit the derivation of other complexTypes
31
+ which would permit child elements.
32
+
33
+ All elements are declared as substitutable for the abstract element any,
34
+ which means that the default type for all elements is dc:SimpleLiteral.
35
+
36
+ </xs:documentation>
37
+
38
+ </xs:annotation>
39
+
40
+
41
+ <xs:import namespace="http://www.w3.org/XML/1998/namespace"
42
+ schemaLocation="xml.xsd">
43
+ </xs:import>
44
+
45
+ <xs:complexType name="SimpleLiteral">
46
+ <xs:annotation>
47
+ <xs:documentation xml:lang="en">
48
+ This is the default type for all of the DC elements.
49
+ It permits text content only with optional
50
+ xml:lang attribute.
51
+ Text is allowed because mixed="true", but sub-elements
52
+ are disallowed because minOccurs="0" and maxOccurs="0"
53
+ are on the xs:any tag.
54
+
55
+ This complexType allows for restriction or extension permitting
56
+ child elements.
57
+ </xs:documentation>
58
+ </xs:annotation>
59
+
60
+ <xs:complexContent mixed="true">
61
+ <xs:restriction base="xs:anyType">
62
+ <xs:sequence>
63
+ <xs:any processContents="lax" minOccurs="0" maxOccurs="0"/>
64
+ </xs:sequence>
65
+ <xs:attribute ref="xml:lang" use="optional"/>
66
+ </xs:restriction>
67
+ </xs:complexContent>
68
+ </xs:complexType>
69
+
70
+ <xs:element name="any" type="SimpleLiteral" abstract="true"/>
71
+
72
+ <xs:element name="title" substitutionGroup="any"/>
73
+ <xs:element name="creator" substitutionGroup="any"/>
74
+ <xs:element name="subject" substitutionGroup="any"/>
75
+ <xs:element name="description" substitutionGroup="any"/>
76
+ <xs:element name="publisher" substitutionGroup="any"/>
77
+ <xs:element name="contributor" substitutionGroup="any"/>
78
+ <xs:element name="date" substitutionGroup="any"/>
79
+ <xs:element name="type" substitutionGroup="any"/>
80
+ <xs:element name="format" substitutionGroup="any"/>
81
+ <xs:element name="identifier" substitutionGroup="any"/>
82
+ <xs:element name="source" substitutionGroup="any"/>
83
+ <xs:element name="language" substitutionGroup="any"/>
84
+ <xs:element name="relation" substitutionGroup="any"/>
85
+ <xs:element name="coverage" substitutionGroup="any"/>
86
+ <xs:element name="rights" substitutionGroup="any"/>
87
+
88
+ <xs:group name="elementsGroup">
89
+ <xs:annotation>
90
+ <xs:documentation xml:lang="en">
91
+ This group is included as a convenience for schema authors
92
+ who need to refer to all the elements in the
93
+ http://purl.org/dc/elements/1.1/ namespace.
94
+ </xs:documentation>
95
+ </xs:annotation>
96
+
97
+ <xs:sequence>
98
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
99
+ <xs:element ref="any"/>
100
+ </xs:choice>
101
+ </xs:sequence>
102
+ </xs:group>
103
+
104
+ <xs:complexType name="elementContainer">
105
+ <xs:annotation>
106
+ <xs:documentation xml:lang="en">
107
+ This complexType is included as a convenience for schema authors who need to define a root
108
+ or container element for all of the DC elements.
109
+ </xs:documentation>
110
+ </xs:annotation>
111
+
112
+ <xs:choice>
113
+ <xs:group ref="elementsGroup"/>
114
+ </xs:choice>
115
+ </xs:complexType>
116
+
117
+
118
+ </xs:schema>
@@ -0,0 +1,50 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
3
+ xmlns="http://purl.org/dc/dcmitype/"
4
+ targetNamespace="http://purl.org/dc/dcmitype/"
5
+ elementFormDefault="qualified"
6
+ attributeFormDefault="unqualified">
7
+
8
+ <xs:annotation>
9
+ <xs:documentation xml:lang="en">
10
+ DCMI Type Vocabulary XML Schema
11
+ XML Schema for http://purl.org/dc/dcmitype/ namespace
12
+
13
+ Created 2003-04-02
14
+
15
+ Created by
16
+
17
+ Tim Cole (t-cole3@uiuc.edu)
18
+ Tom Habing (thabing@uiuc.edu)
19
+ Jane Hunter (jane@dstc.edu.au)
20
+ Pete Johnston (p.johnston@ukoln.ac.uk),
21
+ Carl Lagoze (lagoze@cs.cornell.edu)
22
+
23
+ This schema defines a simpleType which enumerates
24
+ the allowable values for the DCMI Type Vocabulary.
25
+ </xs:documentation>
26
+
27
+
28
+ </xs:annotation>
29
+
30
+
31
+ <xs:simpleType name="DCMIType">
32
+ <xs:union>
33
+ <xs:simpleType>
34
+ <xs:restriction base="xs:Name">
35
+ <xs:enumeration value="Collection"/>
36
+ <xs:enumeration value="Dataset"/>
37
+ <xs:enumeration value="Event"/>
38
+ <xs:enumeration value="Image"/>
39
+ <xs:enumeration value="InteractiveResource"/>
40
+ <xs:enumeration value="Service"/>
41
+ <xs:enumeration value="Software"/>
42
+ <xs:enumeration value="Sound"/>
43
+ <xs:enumeration value="Text"/>
44
+ <xs:enumeration value="PhysicalObject"/>
45
+ </xs:restriction>
46
+ </xs:simpleType>
47
+ </xs:union>
48
+ </xs:simpleType>
49
+
50
+ </xs:schema>