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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +223 -0
- data/CONTRIBUTING.md +1 -1
- data/config/ooxml/schemas/shared_types.yml +5 -4
- data/data/schemas/ecma/dc.xsd +118 -0
- data/data/schemas/ecma/dcmitype.xsd +50 -0
- data/data/schemas/ecma/dcterms.xsd +331 -0
- data/data/schemas/ecma/opc-coreProperties.xsd +2 -2
- data/data/schemas/ecma/xml.xsd +117 -0
- data/lib/uniword/builder/bibliography_builder.rb +1 -1
- data/lib/uniword/builder/chart_builder.rb +19 -9
- data/lib/uniword/builder/comment_anchorer.rb +221 -0
- data/lib/uniword/builder/document_builder.rb +97 -6
- data/lib/uniword/builder/image_builder.rb +6 -5
- data/lib/uniword/builder/run_utils.rb +2 -0
- data/lib/uniword/builder.rb +1 -0
- data/lib/uniword/cli/fonts_cli.rb +52 -0
- data/lib/uniword/cli/main.rb +110 -11
- data/lib/uniword/cli/page_cli.rb +85 -0
- data/lib/uniword/cli/styles_cli.rb +204 -0
- data/lib/uniword/cli/theme_cli.rb +88 -0
- data/lib/uniword/cli/toc_cli.rb +22 -2
- data/lib/uniword/comment.rb +20 -15
- data/lib/uniword/comment_range.rb +3 -3
- data/lib/uniword/comments_part.rb +51 -19
- data/lib/uniword/configuration/configuration_loader.rb +5 -3
- data/lib/uniword/configuration.rb +93 -1
- data/lib/uniword/content_types.rb +19 -39
- data/lib/uniword/document_factory.rb +9 -1
- data/lib/uniword/document_writer.rb +16 -5
- data/lib/uniword/docx/chart_part.rb +34 -0
- data/lib/uniword/docx/custom_xml_item.rb +83 -0
- data/lib/uniword/docx/header_footer_part.rb +140 -0
- data/lib/uniword/docx/header_footer_part_collection.rb +135 -0
- data/lib/uniword/docx/header_footer_view.rb +225 -0
- data/lib/uniword/docx/id_allocator.rb +130 -36
- data/lib/uniword/docx/package.rb +223 -35
- data/lib/uniword/docx/package_defaults.rb +58 -78
- data/lib/uniword/docx/package_integrity_checker.rb +312 -0
- data/lib/uniword/docx/package_serialization.rb +103 -231
- data/lib/uniword/docx/part.rb +106 -0
- data/lib/uniword/docx/part_collection.rb +123 -0
- data/lib/uniword/docx/reconciler/body.rb +59 -68
- data/lib/uniword/docx/reconciler/fix.rb +46 -0
- data/lib/uniword/docx/reconciler/fix_codes.rb +37 -30
- data/lib/uniword/docx/reconciler/helpers.rb +9 -1
- data/lib/uniword/docx/reconciler/notes.rb +17 -8
- data/lib/uniword/docx/reconciler/package_structure.rb +169 -221
- data/lib/uniword/docx/reconciler/parts.rb +36 -13
- data/lib/uniword/docx/reconciler/referential_integrity.rb +297 -123
- data/lib/uniword/docx/reconciler/tables.rb +78 -16
- data/lib/uniword/docx/reconciler/theme.rb +7 -4
- data/lib/uniword/docx/reconciler.rb +36 -14
- data/lib/uniword/docx.rb +10 -0
- data/lib/uniword/drawingml/blip.rb +0 -1
- data/lib/uniword/drawingml/color_scheme.rb +5 -6
- data/lib/uniword/drawingml/font_scheme.rb +4 -8
- data/lib/uniword/drawingml/theme.rb +12 -6
- data/lib/uniword/errors.rb +8 -1
- data/lib/uniword/footer.rb +4 -0
- data/lib/uniword/header.rb +4 -0
- data/lib/uniword/hyperlink.rb +1 -1
- data/lib/uniword/image.rb +6 -6
- data/lib/uniword/lazy_loader.rb +1 -2
- data/lib/uniword/model_attribute_access.rb +20 -4
- data/lib/uniword/ooxml/dotx_package.rb +42 -2
- data/lib/uniword/ooxml/part_definition.rb +171 -0
- data/lib/uniword/ooxml/part_registry.rb +311 -0
- data/lib/uniword/ooxml/relationships/image_relationship.rb +1 -1
- data/lib/uniword/ooxml/relationships/package_relationships.rb +20 -22
- data/lib/uniword/ooxml/schema/element_serializer.rb +8 -7
- data/lib/uniword/ooxml/types/hex_color_value.rb +38 -0
- data/lib/uniword/ooxml/types/ooxml_boolean.rb +27 -14
- data/lib/uniword/ooxml/types/ooxml_boolean_optional.rb +21 -16
- data/lib/uniword/ooxml/types/theme_color_value.rb +22 -0
- data/lib/uniword/ooxml/types/unsigned_decimal_number.rb +30 -0
- data/lib/uniword/ooxml/types.rb +10 -0
- data/lib/uniword/ooxml.rb +2 -0
- data/lib/uniword/properties/alignment.rb +8 -3
- data/lib/uniword/properties/border.rb +52 -6
- data/lib/uniword/properties/cell_vertical_align.rb +4 -1
- data/lib/uniword/properties/color_value.rb +8 -3
- data/lib/uniword/properties/highlight.rb +9 -5
- data/lib/uniword/properties/shading.rb +4 -3
- data/lib/uniword/properties/tab_stop.rb +6 -1
- data/lib/uniword/properties/table_justification.rb +4 -1
- data/lib/uniword/properties/underline.rb +4 -2
- data/lib/uniword/properties/vertical_align.rb +6 -3
- data/lib/uniword/review/review_manager.rb +23 -9
- data/lib/uniword/revision.rb +9 -9
- data/lib/uniword/schema/model_generator.rb +31 -15
- data/lib/uniword/shared_types/hex_color.rb +4 -1
- data/lib/uniword/shared_types/pixel_measure.rb +4 -1
- data/lib/uniword/shared_types/point_measure.rb +4 -1
- data/lib/uniword/shared_types/text_alignment.rb +6 -1
- data/lib/uniword/shared_types/twips_measure.rb +4 -1
- data/lib/uniword/spreadsheetml/phonetic_pr.rb +1 -1
- data/lib/uniword/spreadsheetml/shared_string_table.rb +2 -2
- data/lib/uniword/spreadsheetml/table_formula.rb +1 -1
- data/lib/uniword/template/variable_resolver.rb +40 -7
- data/lib/uniword/themes/theme_transformation.rb +55 -41
- data/lib/uniword/toc/toc_generator.rb +2 -0
- data/lib/uniword/validation/engine.rb +35 -0
- data/lib/uniword/validation/link_checker.rb +1 -1
- data/lib/uniword/validation/opc_validator.rb +0 -1
- data/lib/uniword/validation/report/layer_result.rb +0 -1
- data/lib/uniword/validation/report/terminal_formatter.rb +0 -1
- data/lib/uniword/validation/report/verification_report.rb +0 -2
- data/lib/uniword/validation/report.rb +20 -0
- data/lib/uniword/validation/rules/base.rb +10 -2
- data/lib/uniword/validation/rules/bookmark_pairing_rule.rb +38 -0
- data/lib/uniword/validation/rules/bookmark_uniqueness_rule.rb +48 -0
- data/lib/uniword/validation/rules/bookmarks_rule.rb +0 -2
- data/lib/uniword/validation/rules/content_types_coverage_rule.rb +0 -2
- data/lib/uniword/validation/rules/core_properties_namespace_rule.rb +0 -2
- data/lib/uniword/validation/rules/document_body_rule.rb +25 -0
- data/lib/uniword/validation/rules/document_context.rb +7 -0
- data/lib/uniword/validation/rules/empty_paragraphs_rule.rb +27 -0
- data/lib/uniword/validation/rules/font_table_signature_rule.rb +0 -2
- data/lib/uniword/validation/rules/fonts_rule.rb +0 -2
- data/lib/uniword/validation/rules/footnotes_rule.rb +0 -2
- data/lib/uniword/validation/rules/headers_footers_rule.rb +0 -2
- data/lib/uniword/validation/rules/images_rule.rb +0 -2
- data/lib/uniword/validation/rules/mc_ignorable_namespace_rule.rb +0 -2
- data/lib/uniword/validation/rules/model_context.rb +32 -0
- data/lib/uniword/validation/rules/model_rule.rb +34 -0
- data/lib/uniword/validation/rules/numbering_preservation_rule.rb +0 -2
- data/lib/uniword/validation/rules/numbering_rule.rb +0 -2
- data/lib/uniword/validation/rules/relationship_integrity_rule.rb +0 -1
- data/lib/uniword/validation/rules/rsid_rule.rb +0 -2
- data/lib/uniword/validation/rules/section_properties_rule.rb +0 -2
- data/lib/uniword/validation/rules/settings_rule.rb +0 -2
- data/lib/uniword/validation/rules/settings_values_rule.rb +0 -2
- data/lib/uniword/validation/rules/style_references_rule.rb +0 -2
- data/lib/uniword/validation/rules/table_grid_rule.rb +27 -0
- data/lib/uniword/validation/rules/table_properties_rule.rb +27 -0
- data/lib/uniword/validation/rules/tables_rule.rb +0 -2
- data/lib/uniword/validation/rules/theme_completeness_rule.rb +0 -2
- data/lib/uniword/validation/rules/theme_rule.rb +0 -2
- data/lib/uniword/validation/rules.rb +61 -23
- data/lib/uniword/validation/schema_registry.rb +11 -0
- data/lib/uniword/validation/validators/document_semantics_validator.rb +1 -9
- data/lib/uniword/validation/validators/xml_schema_validator.rb +0 -2
- data/lib/uniword/validation/validators.rb +0 -9
- data/lib/uniword/validation/verify_orchestrator.rb +0 -7
- data/lib/uniword/validation.rb +7 -2
- data/lib/uniword/version.rb +1 -1
- data/lib/uniword/vml/imagedata.rb +0 -1
- data/lib/uniword/wordprocessingml/attached_template.rb +0 -1
- data/lib/uniword/wordprocessingml/deleted_text.rb +1 -1
- data/lib/uniword/wordprocessingml/document_root.rb +211 -21
- data/lib/uniword/wordprocessingml/endnotes.rb +4 -0
- data/lib/uniword/wordprocessingml/font_replacer.rb +147 -0
- data/lib/uniword/wordprocessingml/footnotes.rb +4 -0
- data/lib/uniword/wordprocessingml/hdr_shape_defaults.rb +0 -1
- data/lib/uniword/wordprocessingml/hyperlink.rb +0 -1
- data/lib/uniword/wordprocessingml/level.rb +6 -20
- data/lib/uniword/wordprocessingml/math_pr.rb +0 -11
- data/lib/uniword/wordprocessingml/numbering_definition.rb +16 -8
- data/lib/uniword/wordprocessingml/page_setup.rb +181 -0
- data/lib/uniword/wordprocessingml/paragraph_properties.rb +33 -28
- data/lib/uniword/wordprocessingml/rsids.rb +0 -2
- data/lib/uniword/wordprocessingml/run.rb +3 -0
- data/lib/uniword/wordprocessingml/section_properties.rb +2 -2
- data/lib/uniword/wordprocessingml/settings.rb +8 -34
- data/lib/uniword/wordprocessingml/shape_defaults.rb +0 -1
- data/lib/uniword/wordprocessingml/style.rb +2 -1
- data/lib/uniword/wordprocessingml/style_cleanup.rb +198 -0
- data/lib/uniword/wordprocessingml/table_cell_properties.rb +6 -6
- data/lib/uniword/wordprocessingml/update_fields.rb +27 -0
- data/lib/uniword/wordprocessingml/w14_attributes.rb +30 -2
- data/lib/uniword/wordprocessingml.rb +4 -0
- data/lib/uniword.rb +27 -2
- metadata +38 -19
- data/config/validation_rules.yml +0 -60
- data/config/warning_rules.yml +0 -57
- data/lib/uniword/validation/document_validator.rb +0 -272
- data/lib/uniword/validation/structural_validator.rb +0 -116
- data/lib/uniword/validation/validators/content_type_validator.rb +0 -165
- data/lib/uniword/validation/validators/file_structure_validator.rb +0 -104
- data/lib/uniword/validation/validators/ooxml_part_validator.rb +0 -128
- data/lib/uniword/validation/validators/relationship_validator.rb +0 -147
- data/lib/uniword/validation/validators/zip_integrity_validator.rb +0 -110
- data/lib/uniword/validators/element_validator.rb +0 -93
- data/lib/uniword/validators/paragraph_validator.rb +0 -116
- data/lib/uniword/validators/table_validator.rb +0 -134
- data/lib/uniword/validators.rb +0 -9
- data/lib/uniword/warnings/warning.rb +0 -130
- data/lib/uniword/warnings/warning_collector.rb +0 -234
- data/lib/uniword/warnings/warning_report.rb +0 -159
- data/lib/uniword/warnings.rb +0 -9
|
@@ -44,10 +44,9 @@ document_rels)
|
|
|
44
44
|
inject_bibliography(content_types, document_rels)
|
|
45
45
|
inject_custom_properties(content_types, package_rels)
|
|
46
46
|
inject_custom_xml(content_types)
|
|
47
|
-
|
|
48
|
-
inject_footers(content_types, document_rels)
|
|
49
|
-
inject_header_footer_parts(content_types, document_rels)
|
|
47
|
+
inject_header_footer_content_types(content_types)
|
|
50
48
|
inject_notes(content_types, document_rels)
|
|
49
|
+
inject_comments(content_types, document_rels)
|
|
51
50
|
inject_theme(content_types, document_rels)
|
|
52
51
|
inject_numbering(content_types, document_rels)
|
|
53
52
|
inject_embeddings(content_types, document_rels)
|
|
@@ -137,13 +136,12 @@ document_rels)
|
|
|
137
136
|
end
|
|
138
137
|
|
|
139
138
|
# Notes
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
serialize_part(endnotes)
|
|
139
|
+
serialize_notes(content)
|
|
140
|
+
|
|
141
|
+
# Comments
|
|
142
|
+
if comments
|
|
143
|
+
content["word/comments.xml"] =
|
|
144
|
+
serialize_part(comments)
|
|
147
145
|
end
|
|
148
146
|
|
|
149
147
|
# Bibliography sources
|
|
@@ -152,9 +150,7 @@ document_rels)
|
|
|
152
150
|
serialize_infrastructure(document.bibliography_sources)
|
|
153
151
|
end
|
|
154
152
|
|
|
155
|
-
# Headers and footers
|
|
156
|
-
serialize_headers(content)
|
|
157
|
-
serialize_footers(content)
|
|
153
|
+
# Headers and footers (unified store)
|
|
158
154
|
serialize_header_footer_parts(content)
|
|
159
155
|
|
|
160
156
|
# OLE/embedded object binaries
|
|
@@ -183,36 +179,23 @@ document_rels)
|
|
|
183
179
|
|
|
184
180
|
private
|
|
185
181
|
|
|
186
|
-
#
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
# Shared pattern: ensure a part has both a content type override and
|
|
194
|
-
# a document relationship. Idempotent — skips if already present.
|
|
195
|
-
# When allocator is present, only adds content type (rels handled by allocator).
|
|
196
|
-
def ensure_content_type(content_types, rels, part_name:,
|
|
197
|
-
content_type:, rel_type:, target:)
|
|
198
|
-
unless content_types.overrides.any? { |o| o.part_name == part_name }
|
|
199
|
-
content_types.overrides << Uniword::ContentTypes::Override.new(
|
|
200
|
-
part_name: part_name, content_type: content_type,
|
|
201
|
-
)
|
|
202
|
-
end
|
|
182
|
+
# Shared pattern: ensure a part has a content type override.
|
|
183
|
+
# Idempotent — skips if already present. Relationships are owned
|
|
184
|
+
# by the Reconciler (assembled from the IdAllocator), never by
|
|
185
|
+
# the serializer. Part metadata comes from Ooxml::PartRegistry.
|
|
186
|
+
def ensure_content_type(content_types, definition)
|
|
187
|
+
part_name = definition.part_name
|
|
188
|
+
return if content_types.overrides.any? { |o| o.part_name == part_name }
|
|
203
189
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
rels.relationships << Ooxml::Relationships::Relationship.new(
|
|
208
|
-
id: next_rid(rels), type: rel_type, target: target,
|
|
190
|
+
content_types.overrides << Uniword::ContentTypes::Override.new(
|
|
191
|
+
part_name: part_name, content_type: definition.content_type,
|
|
209
192
|
)
|
|
210
193
|
end
|
|
211
194
|
|
|
212
195
|
# Legacy alias — calls ensure_content_type (same behavior)
|
|
213
196
|
alias ensure_part_registered ensure_content_type
|
|
214
197
|
|
|
215
|
-
def inject_image_parts(content, content_types,
|
|
198
|
+
def inject_image_parts(content, content_types, _document_rels)
|
|
216
199
|
return unless document&.image_parts && !document.image_parts.empty?
|
|
217
200
|
|
|
218
201
|
document.image_parts.each_value do |image_data|
|
|
@@ -227,296 +210,185 @@ document_rels)
|
|
|
227
210
|
document.image_parts.each_value do |image_data|
|
|
228
211
|
content["word/#{image_data[:target]}"] = image_data[:data]
|
|
229
212
|
end
|
|
230
|
-
|
|
231
|
-
return if allocator
|
|
232
|
-
|
|
233
|
-
document.image_parts.each do |r_id, image_data|
|
|
234
|
-
next if document_rels.relationships.any? { |r| r.target == image_data[:target] }
|
|
235
|
-
|
|
236
|
-
document_rels.relationships << Ooxml::Relationships::Relationship.new(
|
|
237
|
-
id: r_id,
|
|
238
|
-
type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
|
239
|
-
target: image_data[:target],
|
|
240
|
-
)
|
|
241
|
-
end
|
|
242
213
|
end
|
|
243
214
|
|
|
244
|
-
def inject_chart_parts(content, content_types,
|
|
215
|
+
def inject_chart_parts(content, content_types, _document_rels)
|
|
245
216
|
return unless document&.chart_parts && !document.chart_parts.empty?
|
|
246
217
|
|
|
218
|
+
chart = Ooxml::PartRegistry.find_by_key(:chart)
|
|
247
219
|
unless content_types.overrides.any? do |o|
|
|
248
220
|
o.part_name&.start_with?("/word/charts/")
|
|
249
221
|
end
|
|
250
222
|
content_types.overrides << Uniword::ContentTypes::Override.new(
|
|
251
|
-
part_name:
|
|
252
|
-
content_type:
|
|
223
|
+
part_name: chart.part_name_for(n: 1),
|
|
224
|
+
content_type: chart.content_type,
|
|
253
225
|
)
|
|
254
226
|
end
|
|
255
227
|
|
|
256
|
-
document.chart_parts.
|
|
228
|
+
document.chart_parts.each_value do |chart_data|
|
|
257
229
|
content["word/#{chart_data[:target]}"] = chart_data[:xml]
|
|
258
|
-
document_rels.relationships << Ooxml::Relationships::Relationship.new(
|
|
259
|
-
id: r_id,
|
|
260
|
-
type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart",
|
|
261
|
-
target: chart_data[:target],
|
|
262
|
-
)
|
|
263
230
|
end
|
|
264
231
|
end
|
|
265
232
|
|
|
266
|
-
def inject_bibliography(content_types,
|
|
233
|
+
def inject_bibliography(content_types, _document_rels)
|
|
267
234
|
return unless document&.bibliography_sources
|
|
268
235
|
|
|
269
|
-
ensure_content_type(content_types,
|
|
270
|
-
|
|
271
|
-
content_type: "application/vnd.openxmlformats-officedocument.bibliography+xml",
|
|
272
|
-
rel_type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/bibliography",
|
|
273
|
-
target: "sources.xml")
|
|
236
|
+
ensure_content_type(content_types,
|
|
237
|
+
Ooxml::PartRegistry.find_by_key(:bibliography))
|
|
274
238
|
end
|
|
275
239
|
|
|
276
240
|
def inject_custom_properties(content_types, package_rels)
|
|
277
241
|
return unless custom_properties && !custom_properties.properties.empty?
|
|
278
242
|
|
|
243
|
+
definition = Ooxml::PartRegistry.find_by_key(:custom_properties)
|
|
279
244
|
unless content_types.overrides.any? do |o|
|
|
280
|
-
o.part_name ==
|
|
245
|
+
o.part_name == definition.part_name
|
|
281
246
|
end
|
|
282
247
|
content_types.overrides << Uniword::ContentTypes::Override.new(
|
|
283
|
-
part_name:
|
|
284
|
-
content_type:
|
|
248
|
+
part_name: definition.part_name,
|
|
249
|
+
content_type: definition.content_type,
|
|
285
250
|
)
|
|
286
251
|
end
|
|
287
252
|
|
|
288
253
|
return if package_rels.relationships.any? do |r|
|
|
289
|
-
r.type.to_s.include?(
|
|
254
|
+
r.type.to_s.include?(definition.rel_type)
|
|
290
255
|
end
|
|
291
256
|
|
|
257
|
+
# The reconciler runs before injection; register the rId with
|
|
258
|
+
# the allocator (single authority) and append the rel here.
|
|
292
259
|
package_rels.relationships << Ooxml::Relationships::Relationship.new(
|
|
293
|
-
id:
|
|
294
|
-
|
|
295
|
-
|
|
260
|
+
id: allocator.alloc_rid(target: definition.target,
|
|
261
|
+
type: definition.rel_type,
|
|
262
|
+
scope: :package),
|
|
263
|
+
type: definition.rel_type,
|
|
264
|
+
target: definition.target,
|
|
296
265
|
)
|
|
297
266
|
end
|
|
298
267
|
|
|
299
268
|
def inject_custom_xml(content_types)
|
|
300
269
|
return unless custom_xml_items && !custom_xml_items.empty?
|
|
301
270
|
|
|
271
|
+
props = Ooxml::PartRegistry.find_by_key(:custom_xml_item_props)
|
|
302
272
|
custom_xml_items.each do |item|
|
|
303
273
|
idx = item[:index]
|
|
274
|
+
part_name = props.part_name_for(index: idx)
|
|
304
275
|
next if content_types.overrides.any? do |o|
|
|
305
|
-
o.part_name ==
|
|
276
|
+
o.part_name == part_name
|
|
306
277
|
end
|
|
307
278
|
|
|
308
279
|
content_types.overrides << Uniword::ContentTypes::Override.new(
|
|
309
|
-
part_name:
|
|
310
|
-
content_type:
|
|
311
|
-
)
|
|
312
|
-
end
|
|
313
|
-
end
|
|
314
|
-
|
|
315
|
-
def inject_headers(content_types, document_rels)
|
|
316
|
-
return unless document&.headers && !document.headers.empty?
|
|
317
|
-
|
|
318
|
-
counter = 0
|
|
319
|
-
document.headers.each_key do |type|
|
|
320
|
-
counter += 1
|
|
321
|
-
target = "header#{counter}.xml"
|
|
322
|
-
|
|
323
|
-
unless content_types.overrides.any? { |o| o.part_name == "/word/#{target}" }
|
|
324
|
-
content_types.overrides << Uniword::ContentTypes::Override.new(
|
|
325
|
-
part_name: "/word/#{target}",
|
|
326
|
-
content_type: "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml",
|
|
327
|
-
)
|
|
328
|
-
end
|
|
329
|
-
|
|
330
|
-
next if allocator
|
|
331
|
-
next if document_rels.relationships.any? { |r| r.target == target }
|
|
332
|
-
|
|
333
|
-
r_id = next_rid(document_rels)
|
|
334
|
-
document_rels.relationships << Ooxml::Relationships::Relationship.new(
|
|
335
|
-
id: r_id,
|
|
336
|
-
type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header",
|
|
337
|
-
target: target,
|
|
338
|
-
)
|
|
339
|
-
|
|
340
|
-
wire_header_reference(type, r_id)
|
|
341
|
-
end
|
|
342
|
-
end
|
|
343
|
-
|
|
344
|
-
def inject_footers(content_types, document_rels)
|
|
345
|
-
return unless document&.footers && !document.footers.empty?
|
|
346
|
-
|
|
347
|
-
counter = 0
|
|
348
|
-
document.footers.each_key do |type|
|
|
349
|
-
counter += 1
|
|
350
|
-
target = "footer#{counter}.xml"
|
|
351
|
-
|
|
352
|
-
unless content_types.overrides.any? { |o| o.part_name == "/word/#{target}" }
|
|
353
|
-
content_types.overrides << Uniword::ContentTypes::Override.new(
|
|
354
|
-
part_name: "/word/#{target}",
|
|
355
|
-
content_type: "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml",
|
|
356
|
-
)
|
|
357
|
-
end
|
|
358
|
-
|
|
359
|
-
next if allocator
|
|
360
|
-
next if document_rels.relationships.any? { |r| r.target == target }
|
|
361
|
-
|
|
362
|
-
r_id = next_rid(document_rels)
|
|
363
|
-
document_rels.relationships << Ooxml::Relationships::Relationship.new(
|
|
364
|
-
id: r_id,
|
|
365
|
-
type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer",
|
|
366
|
-
target: target,
|
|
280
|
+
part_name: part_name,
|
|
281
|
+
content_type: props.content_type,
|
|
367
282
|
)
|
|
368
|
-
|
|
369
|
-
wire_footer_reference(type, r_id)
|
|
370
283
|
end
|
|
371
284
|
end
|
|
372
285
|
|
|
373
|
-
|
|
374
|
-
|
|
286
|
+
# Register content-type overrides for every header/footer part in
|
|
287
|
+
# the unified store. Relationships and sectPr references are owned
|
|
288
|
+
# by the Reconciler (single wiring implementation); the serializer
|
|
289
|
+
# only ensures each emitted part has exactly one override.
|
|
290
|
+
def inject_header_footer_content_types(content_types)
|
|
291
|
+
parts = document&.header_footer_parts
|
|
292
|
+
return unless parts && !parts.empty?
|
|
375
293
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
unless content_types.overrides.any? { |o| o.part_name == part_name }
|
|
379
|
-
content_types.overrides << Uniword::ContentTypes::Override.new(
|
|
380
|
-
part_name: part_name,
|
|
381
|
-
content_type: part[:content_type],
|
|
382
|
-
)
|
|
383
|
-
end
|
|
294
|
+
parts.each do |part|
|
|
295
|
+
next unless part.target
|
|
384
296
|
|
|
385
|
-
|
|
386
|
-
next if
|
|
297
|
+
part_name = "/word/#{part.target}"
|
|
298
|
+
next if content_types.overrides.any? { |o| o.part_name == part_name }
|
|
387
299
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
type: part[:rel_type],
|
|
391
|
-
target: part[:target],
|
|
300
|
+
content_types.overrides << Uniword::ContentTypes::Override.new(
|
|
301
|
+
part_name: part_name, content_type: part.content_type,
|
|
392
302
|
)
|
|
393
303
|
end
|
|
394
304
|
end
|
|
395
305
|
|
|
396
|
-
def inject_notes(content_types,
|
|
306
|
+
def inject_notes(content_types, _document_rels)
|
|
397
307
|
if footnotes
|
|
398
|
-
ensure_content_type(content_types,
|
|
399
|
-
|
|
400
|
-
content_type: "application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml",
|
|
401
|
-
rel_type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes",
|
|
402
|
-
target: "footnotes.xml")
|
|
308
|
+
ensure_content_type(content_types,
|
|
309
|
+
Ooxml::PartRegistry.find_by_key(:footnotes))
|
|
403
310
|
end
|
|
404
311
|
|
|
405
312
|
return unless endnotes
|
|
406
313
|
|
|
407
|
-
ensure_content_type(content_types,
|
|
408
|
-
|
|
409
|
-
content_type: "application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml",
|
|
410
|
-
rel_type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes",
|
|
411
|
-
target: "endnotes.xml")
|
|
314
|
+
ensure_content_type(content_types,
|
|
315
|
+
Ooxml::PartRegistry.find_by_key(:endnotes))
|
|
412
316
|
end
|
|
413
317
|
|
|
414
|
-
def inject_theme(content_types,
|
|
318
|
+
def inject_theme(content_types, _document_rels)
|
|
415
319
|
return unless theme
|
|
416
320
|
|
|
417
|
-
ensure_content_type(content_types,
|
|
418
|
-
|
|
419
|
-
content_type: "application/vnd.openxmlformats-officedocument.theme+xml",
|
|
420
|
-
rel_type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme",
|
|
421
|
-
target: "theme/theme1.xml")
|
|
321
|
+
ensure_content_type(content_types,
|
|
322
|
+
Ooxml::PartRegistry.find_by_key(:theme))
|
|
422
323
|
end
|
|
423
324
|
|
|
424
|
-
def
|
|
325
|
+
def inject_comments(content_types, _document_rels)
|
|
326
|
+
return unless comments
|
|
327
|
+
|
|
328
|
+
ensure_content_type(content_types,
|
|
329
|
+
Ooxml::PartRegistry.find_by_key(:comments))
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def inject_numbering(content_types, _document_rels)
|
|
425
333
|
return unless numbering
|
|
426
334
|
|
|
427
|
-
ensure_content_type(content_types,
|
|
428
|
-
|
|
429
|
-
content_type: "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml",
|
|
430
|
-
rel_type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering",
|
|
431
|
-
target: "numbering.xml")
|
|
335
|
+
ensure_content_type(content_types,
|
|
336
|
+
Ooxml::PartRegistry.find_by_key(:numbering))
|
|
432
337
|
end
|
|
433
338
|
|
|
434
|
-
def inject_embeddings(content_types,
|
|
339
|
+
def inject_embeddings(content_types, _document_rels)
|
|
435
340
|
return unless embeddings && !embeddings.empty?
|
|
436
341
|
|
|
437
|
-
|
|
342
|
+
ole = Ooxml::PartRegistry.find_by_key(:ole_object)
|
|
343
|
+
embeddings.each_key do |target|
|
|
438
344
|
part_name = "/word/#{target}"
|
|
439
345
|
next if content_types.overrides.any? { |o| o.part_name == part_name }
|
|
440
346
|
|
|
441
347
|
content_types.overrides << Uniword::ContentTypes::Override.new(
|
|
442
348
|
part_name: part_name,
|
|
443
|
-
content_type:
|
|
444
|
-
)
|
|
445
|
-
|
|
446
|
-
next if document_rels.relationships.any? { |r| r.target == target }
|
|
447
|
-
|
|
448
|
-
document_rels.relationships << Ooxml::Relationships::Relationship.new(
|
|
449
|
-
id: "rIdEmbedding#{idx + 1}",
|
|
450
|
-
type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject",
|
|
451
|
-
target: target,
|
|
349
|
+
content_type: ole.content_type,
|
|
452
350
|
)
|
|
453
351
|
end
|
|
454
352
|
end
|
|
455
353
|
|
|
456
|
-
def
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
existing = sect_pr.header_references&.find { |r| r.type == type }
|
|
461
|
-
if existing
|
|
462
|
-
existing.r_id = r_id
|
|
463
|
-
else
|
|
464
|
-
sect_pr.header_references << Wordprocessingml::HeaderReference.new(
|
|
465
|
-
type: type, r_id: r_id,
|
|
466
|
-
)
|
|
354
|
+
def serialize_notes(content)
|
|
355
|
+
if footnotes
|
|
356
|
+
content["word/footnotes.xml"] =
|
|
357
|
+
serialize_part(footnotes)
|
|
467
358
|
end
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
return unless document&.body
|
|
472
|
-
|
|
473
|
-
sect_pr = document.body.section_properties ||= Wordprocessingml::SectionProperties.new
|
|
474
|
-
existing = sect_pr.footer_references&.find { |r| r.type == type }
|
|
475
|
-
if existing
|
|
476
|
-
existing.r_id = r_id
|
|
477
|
-
else
|
|
478
|
-
sect_pr.footer_references << Wordprocessingml::FooterReference.new(
|
|
479
|
-
type: type, r_id: r_id,
|
|
480
|
-
)
|
|
359
|
+
if endnotes
|
|
360
|
+
content["word/endnotes.xml"] =
|
|
361
|
+
serialize_part(endnotes)
|
|
481
362
|
end
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
return unless document&.headers && !document.headers.empty?
|
|
486
|
-
|
|
487
|
-
idx = 0
|
|
488
|
-
document.headers.each_value do |header_obj|
|
|
489
|
-
idx += 1
|
|
490
|
-
content["word/header#{idx}.xml"] =
|
|
491
|
-
serialize_part(header_obj)
|
|
363
|
+
if footnotes_rels
|
|
364
|
+
content["word/_rels/footnotes.xml.rels"] =
|
|
365
|
+
serialize_infrastructure(footnotes_rels)
|
|
492
366
|
end
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
return unless document&.footers && !document.footers.empty?
|
|
497
|
-
|
|
498
|
-
idx = 0
|
|
499
|
-
document.footers.each_value do |footer_obj|
|
|
500
|
-
idx += 1
|
|
501
|
-
content["word/footer#{idx}.xml"] =
|
|
502
|
-
serialize_part(footer_obj)
|
|
367
|
+
if endnotes_rels
|
|
368
|
+
content["word/_rels/endnotes.xml.rels"] =
|
|
369
|
+
serialize_infrastructure(endnotes_rels)
|
|
503
370
|
end
|
|
504
371
|
end
|
|
505
372
|
|
|
373
|
+
# Emit every header/footer part from the unified store: one part
|
|
374
|
+
# file per part, headers before footers (historic emission order).
|
|
506
375
|
def serialize_header_footer_parts(content)
|
|
507
|
-
|
|
376
|
+
parts = document&.header_footer_parts
|
|
377
|
+
return unless parts && !parts.empty?
|
|
378
|
+
|
|
379
|
+
(parts.of_kind(:header) + parts.of_kind(:footer)).each do |part|
|
|
380
|
+
next unless part.target && part.content
|
|
508
381
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
serialize_part(part[:content])
|
|
382
|
+
content["word/#{part.target}"] =
|
|
383
|
+
serialize_part(part.serializable_content)
|
|
512
384
|
end
|
|
513
385
|
end
|
|
514
386
|
|
|
515
387
|
def serialize_embeddings(content)
|
|
516
388
|
return unless embeddings && !embeddings.empty?
|
|
517
389
|
|
|
518
|
-
embeddings.each do |target,
|
|
519
|
-
content["word/#{target}"] =
|
|
390
|
+
embeddings.each do |target, part|
|
|
391
|
+
content["word/#{target}"] = part.content
|
|
520
392
|
end
|
|
521
393
|
end
|
|
522
394
|
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uniword
|
|
4
|
+
module Docx
|
|
5
|
+
# Value object for one package-held part (chart, embedding,
|
|
6
|
+
# header, footer, ...).
|
|
7
|
+
#
|
|
8
|
+
# A Part couples the part's content (a lutaml-model object or a
|
|
9
|
+
# raw String) with the packaging metadata the OPC layer needs to
|
|
10
|
+
# emit it: relationship target, relationship id, content type and
|
|
11
|
+
# relationship type. Defaults for the metadata come from the
|
|
12
|
+
# part's Ooxml::PartDefinition (the single source of truth);
|
|
13
|
+
# loaded documents may carry verbatim overrides (e.g. strict-OOXML
|
|
14
|
+
# relationship URIs).
|
|
15
|
+
#
|
|
16
|
+
# Replaces the raw Hash entries previously stored in
|
|
17
|
+
# +chart_parts+, +embeddings+ and +header_footer_parts+. Hash-style
|
|
18
|
+
# read access (+part[:target]+) is kept for backward compatibility.
|
|
19
|
+
#
|
|
20
|
+
# @example
|
|
21
|
+
# part = Part.new(
|
|
22
|
+
# definition: Ooxml::PartRegistry.find_by_key(:ole_object),
|
|
23
|
+
# target: "embeddings/file.xlsx",
|
|
24
|
+
# content: binary_data,
|
|
25
|
+
# )
|
|
26
|
+
# part.path # => "word/embeddings/file.xlsx"
|
|
27
|
+
# part.content_type # => definition's content type
|
|
28
|
+
class Part
|
|
29
|
+
# @return [Ooxml::PartDefinition, nil] registry definition for
|
|
30
|
+
# this part kind
|
|
31
|
+
attr_reader :definition
|
|
32
|
+
|
|
33
|
+
# @return [String, nil] relationship id (nil until wired)
|
|
34
|
+
attr_accessor :r_id
|
|
35
|
+
|
|
36
|
+
# @return [String, nil] relationship target relative to word/
|
|
37
|
+
attr_accessor :target
|
|
38
|
+
|
|
39
|
+
# @return [Object] part content (model object or raw String)
|
|
40
|
+
attr_accessor :content
|
|
41
|
+
|
|
42
|
+
# @param definition [Ooxml::PartDefinition, nil] registry entry
|
|
43
|
+
# @param r_id [String, nil] relationship id
|
|
44
|
+
# @param target [String, nil] relationship target (relative to
|
|
45
|
+
# the owning part, e.g. "charts/chart1.xml")
|
|
46
|
+
# @param content [Object, nil] part content
|
|
47
|
+
# @param rel_type [String, nil] explicit relationship type
|
|
48
|
+
# override (verbatim value from a loaded package)
|
|
49
|
+
# @param content_type [String, nil] explicit content type
|
|
50
|
+
# override (verbatim value from a loaded package)
|
|
51
|
+
# rubocop:disable Metrics/ParameterLists
|
|
52
|
+
def initialize(definition: nil, r_id: nil, target: nil, content: nil,
|
|
53
|
+
rel_type: nil, content_type: nil)
|
|
54
|
+
@definition = definition
|
|
55
|
+
@r_id = r_id
|
|
56
|
+
@target = target
|
|
57
|
+
@content = content
|
|
58
|
+
@rel_type = rel_type
|
|
59
|
+
@content_type = content_type
|
|
60
|
+
end
|
|
61
|
+
# rubocop:enable Metrics/ParameterLists
|
|
62
|
+
|
|
63
|
+
# @return [String, nil] relationship type URI
|
|
64
|
+
def rel_type
|
|
65
|
+
@rel_type || definition&.rel_type
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @return [String, nil] MIME content type
|
|
69
|
+
def content_type
|
|
70
|
+
@content_type || definition&.content_type
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Package-relative path of the emitted part. Document-scoped
|
|
74
|
+
# parts live under word/; their target is already relative to
|
|
75
|
+
# word/.
|
|
76
|
+
#
|
|
77
|
+
# @return [String, nil] e.g. "word/charts/chart1.xml"
|
|
78
|
+
def path
|
|
79
|
+
target && "word/#{target}"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Verbatim overrides carried from a loaded package.
|
|
83
|
+
attr_writer :rel_type, :content_type
|
|
84
|
+
|
|
85
|
+
# Key → reader lambdas backing hash-style access (kept as data
|
|
86
|
+
# so the dispatch stays declarative).
|
|
87
|
+
HASH_LOOKUP = {
|
|
88
|
+
r_id: lambda(&:r_id),
|
|
89
|
+
target: lambda(&:target),
|
|
90
|
+
rel_type: lambda(&:rel_type),
|
|
91
|
+
content_type: lambda(&:content_type),
|
|
92
|
+
content: lambda(&:content),
|
|
93
|
+
path: lambda(&:path),
|
|
94
|
+
}.freeze
|
|
95
|
+
|
|
96
|
+
# Hash-style read compatibility for former hash entries.
|
|
97
|
+
#
|
|
98
|
+
# @param key [Symbol, String] one of :r_id, :target, :rel_type,
|
|
99
|
+
# :content_type, :content, :path
|
|
100
|
+
# @return [Object, nil]
|
|
101
|
+
def [](key)
|
|
102
|
+
HASH_LOOKUP[key.to_sym]&.call(self)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|