uniword 1.2.5 → 1.3.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 (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +223 -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 +19 -9
  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 +6 -5
  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 +9 -1
  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 +83 -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/package.rb +223 -35
  38. data/lib/uniword/docx/package_defaults.rb +58 -78
  39. data/lib/uniword/docx/package_integrity_checker.rb +312 -0
  40. data/lib/uniword/docx/package_serialization.rb +103 -231
  41. data/lib/uniword/docx/part.rb +106 -0
  42. data/lib/uniword/docx/part_collection.rb +123 -0
  43. data/lib/uniword/docx/reconciler/body.rb +59 -68
  44. data/lib/uniword/docx/reconciler/fix.rb +46 -0
  45. data/lib/uniword/docx/reconciler/fix_codes.rb +37 -30
  46. data/lib/uniword/docx/reconciler/helpers.rb +9 -1
  47. data/lib/uniword/docx/reconciler/notes.rb +17 -8
  48. data/lib/uniword/docx/reconciler/package_structure.rb +169 -221
  49. data/lib/uniword/docx/reconciler/parts.rb +36 -13
  50. data/lib/uniword/docx/reconciler/referential_integrity.rb +297 -123
  51. data/lib/uniword/docx/reconciler/tables.rb +78 -16
  52. data/lib/uniword/docx/reconciler/theme.rb +7 -4
  53. data/lib/uniword/docx/reconciler.rb +36 -14
  54. data/lib/uniword/docx.rb +10 -0
  55. data/lib/uniword/drawingml/blip.rb +0 -1
  56. data/lib/uniword/drawingml/color_scheme.rb +5 -6
  57. data/lib/uniword/drawingml/font_scheme.rb +4 -8
  58. data/lib/uniword/drawingml/theme.rb +12 -6
  59. data/lib/uniword/errors.rb +8 -1
  60. data/lib/uniword/footer.rb +4 -0
  61. data/lib/uniword/header.rb +4 -0
  62. data/lib/uniword/hyperlink.rb +1 -1
  63. data/lib/uniword/image.rb +6 -6
  64. data/lib/uniword/lazy_loader.rb +1 -2
  65. data/lib/uniword/model_attribute_access.rb +20 -4
  66. data/lib/uniword/ooxml/dotx_package.rb +42 -2
  67. data/lib/uniword/ooxml/part_definition.rb +171 -0
  68. data/lib/uniword/ooxml/part_registry.rb +311 -0
  69. data/lib/uniword/ooxml/relationships/image_relationship.rb +1 -1
  70. data/lib/uniword/ooxml/relationships/package_relationships.rb +20 -22
  71. data/lib/uniword/ooxml/schema/element_serializer.rb +8 -7
  72. data/lib/uniword/ooxml/types/hex_color_value.rb +38 -0
  73. data/lib/uniword/ooxml/types/ooxml_boolean.rb +27 -14
  74. data/lib/uniword/ooxml/types/ooxml_boolean_optional.rb +21 -16
  75. data/lib/uniword/ooxml/types/theme_color_value.rb +22 -0
  76. data/lib/uniword/ooxml/types/unsigned_decimal_number.rb +30 -0
  77. data/lib/uniword/ooxml/types.rb +10 -0
  78. data/lib/uniword/ooxml.rb +2 -0
  79. data/lib/uniword/properties/alignment.rb +8 -3
  80. data/lib/uniword/properties/border.rb +52 -6
  81. data/lib/uniword/properties/cell_vertical_align.rb +4 -1
  82. data/lib/uniword/properties/color_value.rb +8 -3
  83. data/lib/uniword/properties/highlight.rb +9 -5
  84. data/lib/uniword/properties/shading.rb +4 -3
  85. data/lib/uniword/properties/tab_stop.rb +6 -1
  86. data/lib/uniword/properties/table_justification.rb +4 -1
  87. data/lib/uniword/properties/underline.rb +4 -2
  88. data/lib/uniword/properties/vertical_align.rb +6 -3
  89. data/lib/uniword/review/review_manager.rb +23 -9
  90. data/lib/uniword/revision.rb +9 -9
  91. data/lib/uniword/schema/model_generator.rb +31 -15
  92. data/lib/uniword/shared_types/hex_color.rb +4 -1
  93. data/lib/uniword/shared_types/pixel_measure.rb +4 -1
  94. data/lib/uniword/shared_types/point_measure.rb +4 -1
  95. data/lib/uniword/shared_types/text_alignment.rb +6 -1
  96. data/lib/uniword/shared_types/twips_measure.rb +4 -1
  97. data/lib/uniword/spreadsheetml/phonetic_pr.rb +1 -1
  98. data/lib/uniword/spreadsheetml/shared_string_table.rb +2 -2
  99. data/lib/uniword/spreadsheetml/table_formula.rb +1 -1
  100. data/lib/uniword/template/variable_resolver.rb +40 -7
  101. data/lib/uniword/themes/theme_transformation.rb +55 -41
  102. data/lib/uniword/toc/toc_generator.rb +2 -0
  103. data/lib/uniword/validation/engine.rb +35 -0
  104. data/lib/uniword/validation/link_checker.rb +1 -1
  105. data/lib/uniword/validation/opc_validator.rb +0 -1
  106. data/lib/uniword/validation/report/layer_result.rb +0 -1
  107. data/lib/uniword/validation/report/terminal_formatter.rb +0 -1
  108. data/lib/uniword/validation/report/verification_report.rb +0 -2
  109. data/lib/uniword/validation/report.rb +20 -0
  110. data/lib/uniword/validation/rules/base.rb +10 -2
  111. data/lib/uniword/validation/rules/bookmark_pairing_rule.rb +38 -0
  112. data/lib/uniword/validation/rules/bookmark_uniqueness_rule.rb +48 -0
  113. data/lib/uniword/validation/rules/bookmarks_rule.rb +0 -2
  114. data/lib/uniword/validation/rules/content_types_coverage_rule.rb +0 -2
  115. data/lib/uniword/validation/rules/core_properties_namespace_rule.rb +0 -2
  116. data/lib/uniword/validation/rules/document_body_rule.rb +25 -0
  117. data/lib/uniword/validation/rules/document_context.rb +7 -0
  118. data/lib/uniword/validation/rules/empty_paragraphs_rule.rb +27 -0
  119. data/lib/uniword/validation/rules/font_table_signature_rule.rb +0 -2
  120. data/lib/uniword/validation/rules/fonts_rule.rb +0 -2
  121. data/lib/uniword/validation/rules/footnotes_rule.rb +0 -2
  122. data/lib/uniword/validation/rules/headers_footers_rule.rb +0 -2
  123. data/lib/uniword/validation/rules/images_rule.rb +0 -2
  124. data/lib/uniword/validation/rules/mc_ignorable_namespace_rule.rb +0 -2
  125. data/lib/uniword/validation/rules/model_context.rb +32 -0
  126. data/lib/uniword/validation/rules/model_rule.rb +34 -0
  127. data/lib/uniword/validation/rules/numbering_preservation_rule.rb +0 -2
  128. data/lib/uniword/validation/rules/numbering_rule.rb +0 -2
  129. data/lib/uniword/validation/rules/relationship_integrity_rule.rb +0 -1
  130. data/lib/uniword/validation/rules/rsid_rule.rb +0 -2
  131. data/lib/uniword/validation/rules/section_properties_rule.rb +0 -2
  132. data/lib/uniword/validation/rules/settings_rule.rb +0 -2
  133. data/lib/uniword/validation/rules/settings_values_rule.rb +0 -2
  134. data/lib/uniword/validation/rules/style_references_rule.rb +0 -2
  135. data/lib/uniword/validation/rules/table_grid_rule.rb +27 -0
  136. data/lib/uniword/validation/rules/table_properties_rule.rb +27 -0
  137. data/lib/uniword/validation/rules/tables_rule.rb +0 -2
  138. data/lib/uniword/validation/rules/theme_completeness_rule.rb +0 -2
  139. data/lib/uniword/validation/rules/theme_rule.rb +0 -2
  140. data/lib/uniword/validation/rules.rb +61 -23
  141. data/lib/uniword/validation/schema_registry.rb +11 -0
  142. data/lib/uniword/validation/validators/document_semantics_validator.rb +1 -9
  143. data/lib/uniword/validation/validators/xml_schema_validator.rb +0 -2
  144. data/lib/uniword/validation/validators.rb +0 -9
  145. data/lib/uniword/validation/verify_orchestrator.rb +0 -7
  146. data/lib/uniword/validation.rb +7 -2
  147. data/lib/uniword/version.rb +1 -1
  148. data/lib/uniword/vml/imagedata.rb +0 -1
  149. data/lib/uniword/wordprocessingml/attached_template.rb +0 -1
  150. data/lib/uniword/wordprocessingml/deleted_text.rb +1 -1
  151. data/lib/uniword/wordprocessingml/document_root.rb +211 -21
  152. data/lib/uniword/wordprocessingml/endnotes.rb +4 -0
  153. data/lib/uniword/wordprocessingml/font_replacer.rb +147 -0
  154. data/lib/uniword/wordprocessingml/footnotes.rb +4 -0
  155. data/lib/uniword/wordprocessingml/hdr_shape_defaults.rb +0 -1
  156. data/lib/uniword/wordprocessingml/hyperlink.rb +0 -1
  157. data/lib/uniword/wordprocessingml/level.rb +6 -20
  158. data/lib/uniword/wordprocessingml/math_pr.rb +0 -11
  159. data/lib/uniword/wordprocessingml/numbering_definition.rb +16 -8
  160. data/lib/uniword/wordprocessingml/page_setup.rb +181 -0
  161. data/lib/uniword/wordprocessingml/paragraph_properties.rb +33 -28
  162. data/lib/uniword/wordprocessingml/rsids.rb +0 -2
  163. data/lib/uniword/wordprocessingml/run.rb +3 -0
  164. data/lib/uniword/wordprocessingml/section_properties.rb +2 -2
  165. data/lib/uniword/wordprocessingml/settings.rb +8 -34
  166. data/lib/uniword/wordprocessingml/shape_defaults.rb +0 -1
  167. data/lib/uniword/wordprocessingml/style.rb +2 -1
  168. data/lib/uniword/wordprocessingml/style_cleanup.rb +198 -0
  169. data/lib/uniword/wordprocessingml/table_cell_properties.rb +6 -6
  170. data/lib/uniword/wordprocessingml/update_fields.rb +27 -0
  171. data/lib/uniword/wordprocessingml/w14_attributes.rb +30 -2
  172. data/lib/uniword/wordprocessingml.rb +4 -0
  173. data/lib/uniword.rb +27 -2
  174. metadata +38 -19
  175. data/config/validation_rules.yml +0 -60
  176. data/config/warning_rules.yml +0 -57
  177. data/lib/uniword/validation/document_validator.rb +0 -272
  178. data/lib/uniword/validation/structural_validator.rb +0 -116
  179. data/lib/uniword/validation/validators/content_type_validator.rb +0 -165
  180. data/lib/uniword/validation/validators/file_structure_validator.rb +0 -104
  181. data/lib/uniword/validation/validators/ooxml_part_validator.rb +0 -128
  182. data/lib/uniword/validation/validators/relationship_validator.rb +0 -147
  183. data/lib/uniword/validation/validators/zip_integrity_validator.rb +0 -110
  184. data/lib/uniword/validators/element_validator.rb +0 -93
  185. data/lib/uniword/validators/paragraph_validator.rb +0 -116
  186. data/lib/uniword/validators/table_validator.rb +0 -134
  187. data/lib/uniword/validators.rb +0 -9
  188. data/lib/uniword/warnings/warning.rb +0 -130
  189. data/lib/uniword/warnings/warning_collector.rb +0 -234
  190. data/lib/uniword/warnings/warning_report.rb +0 -159
  191. 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: c28f3b26ea127104f901589ebdb8752163be6f9e963c11eca94a26c86bdbd0df
4
+ data.tar.gz: d8fbe7de4f8f005d78d10acd4f6c1f7459d06385b419c487732e0398470d2c23
5
5
  SHA512:
6
- metadata.gz: c4c71185ce70622298500cb9512e6e0ab5f5b4509783f3273cfe8ac8828ec44159b875d9bf06b3d8f0ac346791f5ce779050e3030858d2320477ee0e89d6c7ec
7
- data.tar.gz: 849da67a5a82968042f85239b1b2a536f7f85056e657f9e8b7c0b3487141e956b8903232efa1478dc476174d51e11f7580557b8ee5bd0d8ac93a88eeb8fe723b
6
+ metadata.gz: 807f859be7a5b6b45512ea946e4a4b1bd12a8e9df975e9dd9e06dd75c0842d916e5f811e9ac10062873426402b0a3526fa5301a0634e15062fb3f65696f3bd03
7
+ data.tar.gz: 3d6d287a816d2c2cc42a59b5da669dc92026774b0a2e27ff02e33a916367a63336e390338e556d1688b6cb7bbf78676dd5f200b47e8fdcd5c1509e0ebd238f90
data/CHANGELOG.md CHANGED
@@ -8,6 +8,67 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [1.3.0] - 2026-07-19
12
+
13
+ ### Added
14
+
15
+ #### Comment Authoring and Round-Trip Preservation
16
+ - `DocumentBuilder#comment` now anchors the comment around the preceding
17
+ body paragraph (`w:commentRangeStart`/`w:commentRangeEnd` plus a
18
+ `w:commentReference` run styled with the `CommentReference` character
19
+ style, auto-registered in styles.xml) in addition to registering it in
20
+ the document's comments collection
21
+ - Saving a document with comments emits `word/comments.xml` with its
22
+ `[Content_Types].xml` override and `document.xml.rels` relationship
23
+ (part metadata registered in `Ooxml::PartRegistry` under `:comments`);
24
+ loading parses the part into the document's comments collection, so
25
+ comments (ids, authors, dates, text) and their body anchors survive
26
+ load → save round-trips
27
+ - `Builder::CommentAnchorer`: anchor placement for fresh and parsed
28
+ paragraphs (nested anchors on the same paragraph match Word's output)
29
+ - `Wordprocessingml::Run` maps `w:commentReference`
30
+ - `CommentsPart` is the single comments collection for both
31
+ `DocumentBuilder` and `Review::ReviewManager`; it gains Array-style
32
+ access (`size`/`[]`/`each`) and collision-safe sequential ID
33
+ assignment (OOXML `ST_DecimalNumber`)
34
+
35
+ #### Validation Engine Consolidation
36
+ - `Validation::Engine`: single validation engine running the rules of
37
+ `Rules::Registry` against a validation context, with two front-ends
38
+ sharing one result model (`Report::ValidationIssue`)
39
+ - `Rules::ModelContext`: in-memory validation context wrapping a
40
+ `Wordprocessingml::DocumentRoot` (pre-save invocation)
41
+ - `Rules::ModelRule`: base class for in-memory model-level rules;
42
+ `Rules::Base#context_type` (`:package`/`:model`) selects the rule set
43
+ - Model-level rules: DOC-200 (document requires body), DOC-201 (bookmark
44
+ pairing), DOC-202 (bookmark name uniqueness), DOC-203 (empty
45
+ paragraphs), DOC-204 (`tbl` requires `tblGrid` per wml.xsd CT_Tbl),
46
+ DOC-205 (`tbl` requires `tblPr` per wml.xsd CT_Tbl)
47
+
48
+ ### Changed
49
+
50
+ - `DocumentRoot#valid?`, `#validation_errors`, `#validation_warnings` are
51
+ now powered by `Validation::Engine` over the model-level rules instead
52
+ of the deleted `Validation::StructuralValidator`
53
+ - `uniword validate` runs the same engine on the in-memory model, prints
54
+ rule-coded issues, and exits 1 on error-severity issues (aligned with
55
+ `uniword verify`); it previously always exited 0
56
+ - `Validators::DocumentSemanticsValidator` (verify layer 3) runs through
57
+ `Validation::Engine` like every other front-end
58
+
59
+ ### Removed
60
+
61
+ - Dead validation code (no production callers):
62
+ `Validation::DocumentValidator` (7-layer pipeline),
63
+ `Validation::StructuralValidator`, the `validation/validators/` layer
64
+ validators unused by `VerifyOrchestrator` (`FileStructureValidator`,
65
+ `ZipIntegrityValidator`, `OoxmlPartValidator`, `RelationshipValidator`,
66
+ `ContentTypeValidator`), the `Uniword::Validators` stub namespace
67
+ (`ElementValidator`/`ParagraphValidator`/`TableValidator`), the
68
+ `Uniword::Warnings` module (`Warning`/`WarningCollector`/
69
+ `WarningReport`), and the orphaned `config/validation_rules.yml` and
70
+ `config/warning_rules.yml`
71
+
11
72
  ### Added
12
73
 
13
74
  #### Open-Source Resource System (April 2025)
@@ -41,8 +102,140 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
41
102
  - Injects missing separator (id=-1) and continuation (id=0) entries
42
103
  - Runs during `Docx::Package#to_zip_content` before serialization
43
104
 
105
+ #### Write-Time Validation and Reconciler Transparency (July 2026)
106
+ - `Docx::PackageIntegrityChecker`: write-time OPC integrity gate invoked on
107
+ every save after reconciliation, before packaging; refuses invalid output
108
+ (missing content types, unresolvable relationship targets, dangling
109
+ `r:id`/`r:embed`/`r:link` references, duplicate rIds, malformed XML)
110
+ - Saves raise `Uniword::ValidationError` with structured `issues` (code,
111
+ part, message) when the gate rejects the package
112
+ - Escape hatch: `save(path, validate: false)` threaded through
113
+ `DocumentRoot#save`/`#to_file`, `DocumentWriter`, `Docx::Package.to_file`
114
+ and `Ooxml::DotxPackage.to_file`; default from
115
+ `Uniword.configuration.validate_on_save` (default `true`)
116
+ - `Uniword.configuration.log_save_fixes` (default `true`): log each repair
117
+ the Reconciler applies during save
118
+ - `Docx::Package#applied_fixes`: reconciliation report (Fix value objects
119
+ with code, message, part) exposed after save
120
+ - Reconciler now repairs dangling `r:embed` image references by removing
121
+ the drawing, and strips relationships whose target part the package does
122
+ not carry (e.g. unmodelled parts dropped on load) — recorded as fixes
123
+ - Allocator (builder) and legacy (template) save paths now apply identical
124
+ referential repairs (previously the builder path only logged warnings)
125
+ - `SchemaRegistry` now maps `word/webSettings.xml` and the docProps
126
+ core/app/custom parts to their XSDs; `docProps/core.xml` validates
127
+ offline (Dublin Core XSD imports bundled with relative schemaLocations)
128
+ - Integration spec now XSD-validates real library output end-to-end
129
+ (Builder corpus + fixture round-trips) via `uniword verify --xsd`
130
+ semantics
131
+ - `uniword theme fonts INPUT OUTPUT --name X` and `uniword theme colors
132
+ INPUT OUTPUT --name Y`: apply a bundled font scheme (25) or color
133
+ scheme (23) to an existing document — Word's Design → Fonts / Colors
134
+ galleries as CLI commands, replacing only the targeted theme element
135
+ (`--list` shows available schemes); also available as
136
+ `DocumentRoot#apply_font_scheme` / `#apply_color_scheme`
137
+ - `uniword fonts replace INPUT OUTPUT --from X --to Y`: Word's
138
+ Replace Fonts dialog as a one-shot CLI command — rewrites rFonts
139
+ references across styles and defaults, body content, headers/footers,
140
+ notes, comments, and numbering (also `DocumentRoot#replace_font`)
141
+ - `w:updateFields` support: `uniword toc insert` / `toc update` now set
142
+ it by default (`--no-update-fields` to opt out), so Word refreshes
143
+ the TOC and all fields when the document is opened — no manual F9
144
+ - Comments can now target an arbitrary paragraph:
145
+ `doc.comment(author:, text:, on: paragraph_or_index)` — anchor around
146
+ any existing paragraph object or body index, including paragraphs
147
+ inside table cells (default remains the last paragraph)
148
+ - `uniword page setup INPUT OUTPUT`: Word's Layout dialog as a CLI
149
+ command — paper size presets (letter/legal/a4/a5/executive),
150
+ orientation with Word-style dimension swap, and margins in
151
+ in/cm/mm/twips units, applied to every section (also
152
+ `DocumentRoot#apply_page_setup`)
153
+ - `uniword styles list FILE [--type T] [--verbose]`: Word's Styles
154
+ pane as a terminal listing — style id, type, base style, and key
155
+ formatting details
156
+ - `uniword styles remove FILE OUTPUT [--id X | --unused] [--dry-run]`:
157
+ Styles-pane management as a one-shot command — delete a style by id,
158
+ or declutter every style that no content references (directly or via
159
+ basedOn/link/next chains); default styles are always kept (also
160
+ `DocumentRoot#remove_style` / `#remove_unused_styles`)
161
+ - `uniword styles rename FILE OUTPUT --id X --name Y`: Word's style
162
+ rename — updates the display name while the styleId (and every
163
+ reference to it) stays intact (also `DocumentRoot#rename_style`)
164
+ - `uniword repair INPUT OUTPUT`: load a document, run the save-time
165
+ Reconciler over it, and write a repaired copy — dangling references
166
+ stripped, missing parts rebuilt — with every repair reported by code
167
+ (substantive repairs vs routine normalization); exits non-zero when
168
+ the write-time gate rejects what the Reconciler cannot fix
169
+
170
+ ### Fixed
171
+
172
+ - `w:lvl` no longer emits `w:ind`/`w:tabs` as direct children (invalid per
173
+ ECMA-376 CT_Lvl); numbering level indentation is now written via the
174
+ level's `w:pPr` — previously every Builder-generated list produced
175
+ schema-invalid `numbering.xml`
176
+ - Dangling image drawings and their relationships are now removed in a
177
+ single reconcile pass: a drawing whose `r:embed` rel exists but points
178
+ at a part the package does not carry is treated as dangling (previously
179
+ the first pass kept the drawing and stripped the rel, leaving a new
180
+ dangling reference for the next save)
181
+ - `word/_rels/settings.xml.rels`, `word/_rels/footnotes.xml.rels` and
182
+ `word/_rels/endnotes.xml.rels` now survive load → save round-trips on
183
+ both save paths (previously dropped, leaving dangling `r:id`
184
+ references in the saved package)
185
+ - Saving no longer crashes with FrozenError when the Reconciler injects
186
+ missing footnote/endnote separator entries into a parsed document
187
+ - Theme application no longer produces invalid `theme1.xml`:
188
+ `Drawingml::Theme#dup` silently dropped the format scheme (fill/line/
189
+ effect lists), so every `theme apply` emitted a `fillStyleLst` Word
190
+ rejects as "unreadable content"; the save-time fill repair also used
191
+ a wrong attribute kwarg (`scheme_color:` for `scheme_clr:`), creating
192
+ color-less fills. Both fixed — applied themes now carry the complete
193
+ format scheme. Same family fixed in `FontScheme#dup` (per-script
194
+ font entries were dropped) and `ColorScheme#dup` (system-color
195
+ entries like windowText were lost)
196
+ - Tables whose `gridCol` columns lack widths now get even shares of the
197
+ section content width (page width minus margins) at save time —
198
+ matching Word's fallback — instead of emitting width-less `gridCol`
199
+ elements and warning; explicit widths are preserved and the remainder
200
+ is shared
201
+ - `uniword verify`-style integration spec hang: `soffice --view` example
202
+ no longer blocks the LibreOffice integration spec indefinitely
203
+ - Header/footer dual path: adding a header/footer via the Builder to a
204
+ loaded document no longer drops the new part, duplicates
205
+ relationships or content-type overrides, or points two sectPr
206
+ references at the same rId. Round-trips that previously re-added a
207
+ stale relationship with the original rId (duplicate target rels, e.g.
208
+ on the APA paper template) now emit exactly one rel per part
209
+ - `uniword headers list` now reports headers/footers of loaded
210
+ documents, and `headers add-header` / watermark managers no longer
211
+ crash when saving (both used to bypass the round-trip storage)
212
+ - rId stability through round-trip: `Docx::IdAllocator` is now the single
213
+ rId authority for all relationships parts. Loaded documents round-trip
214
+ with their original rIds preserved verbatim (previously the reconciler
215
+ renumbered every rId on save, e.g. `r:embed="rId8"` becoming
216
+ `"rId9"`); only genuinely new relationships (builder-added images,
217
+ hyperlinks, charts, headers/footers) receive fresh allocations.
218
+ The 3 long-skipped `docx_roundtrip_spec.rb` round-trip examples
219
+ (APA template, two ISO fixtures) now pass unskipped
220
+ - Loaded image parts are now keyed by their actual document relationship
221
+ rId, so `r:embed` references resolve correctly in MHTML conversion and
222
+ the image manager; theme-only media no longer gains a spurious
223
+ document-level relationship on save
224
+ - Adding the same image file twice now stores one part with one
225
+ relationship shared by both drawings (previously produced a dangling
226
+ duplicate rId reference)
227
+
44
228
  ### Changed
45
229
 
230
+ - `Uniword::Ooxml::Relationships::PackageRelationships.next_available_rid`
231
+ removed: all rId assignment flows through `Docx::IdAllocator` (scoped
232
+ per rels part — `:document`/`:package` — with `seed_from_rels`
233
+ preserving loaded rIds verbatim). The reconciler's legacy renumbering
234
+ path, the serializer-side `next_rid`, and `ChartBuilder`'s literal
235
+ `rIdChartN` ids are gone; chart relationship ids are now allocator
236
+ numerics. The reconciler's light-touch vs full normalization choice is
237
+ now explicit (`builder_managed`) instead of inferred from allocator
238
+ presence
46
239
  - `Uniword::Ooxml::DocxPackage` renamed to `Uniword::Docx::Package`
47
240
  - All references in lib/ and spec/ updated (~20 files)
48
241
  - `lib/uniword/ooxml/docx_package.rb` moved to `lib/uniword/docx/package.rb`
@@ -52,6 +245,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
52
245
  - All 12 stylesets reauthored with OFL-licensed fonts (Calibri references fixed)
53
246
  - `uniword.gemspec` updated to include `data/**/*.yml` and `data/**/*.xsd` in gem
54
247
  - Added `rainbow ~> 3.1` gem dependency
248
+ - Single `Uniword::Ooxml::PartRegistry` now owns the part↔content-type↔
249
+ relationship-type mapping for every DOCX part kind the library writes
250
+ - New `Ooxml::PartDefinition` value class (key, path/pattern, content
251
+ type, relationship type, extension, required, default-vs-override)
252
+ - `ContentTypes.generate`, `Docx::PackageDefaults.minimal_*`,
253
+ `Reconciler::PackageStructure`, and the `PackageSerialization`
254
+ `inject_*` methods derive from the registry instead of holding
255
+ string literals; `IdAllocator` and builder rel-type constants also
256
+ derive from it
257
+ - Open/closed: register a `PartDefinition` to add a part kind —
258
+ consumers need no changes
259
+ - No behavior change: saved packages are byte-identical
260
+ - Package-held parts are now model objects instead of raw hashes
261
+ - New `Uniword::Docx::Part` value object (definition, rId, target,
262
+ content, verbatim rel-type/content-type overrides) with `ChartPart`
263
+ and `HeaderFooterPart` subclasses and `CustomXmlItem`;
264
+ `Docx::PartCollection` backs `chart_parts` (keyed by rId) and
265
+ `embeddings` (keyed by target)
266
+ - Single header/footer storage path: `DocumentRoot#header_footer_parts`
267
+ is a `Docx::HeaderFooterPartCollection` fed identically by the loader
268
+ (original rIds/targets, sectPr-derived types) and the Builder;
269
+ `document.headers`/`document.footers` are delegating
270
+ `Docx::HeaderFooterView`s over that store (Hash-style by sectPr type,
271
+ Array-style over parts) — public API preserved
272
+ - Single wiring implementation: the Reconciler wires fresh
273
+ header/footer parts into `document.xml.rels` and sectPr
274
+ (`IdAllocator` on the builder path, `find_or_create_rel` on the
275
+ legacy path); the serializer's duplicate rel/sectPr wiring and its
276
+ divergent rId strategy are deleted — it now only emits one part
277
+ file and one content-type override per store entry
55
278
 
56
279
  #### StylesetPackage Implementation (December 4, 2024)
57
280
  - **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>