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
|
@@ -7,34 +7,35 @@ module Uniword
|
|
|
7
7
|
#
|
|
8
8
|
# Rebuilds content types and relationships for standard parts,
|
|
9
9
|
# preserving non-standard entries from the source document.
|
|
10
|
+
# All part metadata (paths, content types, rel types) comes from
|
|
11
|
+
# Ooxml::PartRegistry; this module only declares which parts go
|
|
12
|
+
# where, in which order.
|
|
10
13
|
module PackageStructure
|
|
11
14
|
# Relationship types for parts we don't model or serialize.
|
|
15
|
+
# Exception: stylesWithEffects is a Microsoft extension never
|
|
16
|
+
# written by uniword, so it is not in Ooxml::PartRegistry.
|
|
12
17
|
UNSUPPORTED_REL_TYPES = Set[
|
|
13
18
|
"http://schemas.microsoft.com/office/2007/relationships/stylesWithEffects",
|
|
14
19
|
].freeze
|
|
15
20
|
|
|
16
21
|
# Rel types that belong in package-level _rels/.rels, not document.xml.rels.
|
|
17
|
-
PACKAGE_LEVEL_REL_TYPES =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
].freeze
|
|
22
|
+
PACKAGE_LEVEL_REL_TYPES =
|
|
23
|
+
Ooxml::PartRegistry.package_rel_types.to_set.freeze
|
|
24
|
+
|
|
25
|
+
# Standard package-level parts, in _rels/.rels emission order.
|
|
26
|
+
PACKAGE_REL_PARTS = %i[document core_properties app_properties].freeze
|
|
23
27
|
|
|
24
28
|
def reconcile_content_types
|
|
25
29
|
ct = package.content_types
|
|
26
30
|
return unless ct
|
|
27
31
|
|
|
28
|
-
ct.defaults = [
|
|
32
|
+
ct.defaults = %i[rels xml].map do |key|
|
|
33
|
+
defn = Ooxml::PartRegistry.find_by_key(key)
|
|
29
34
|
Uniword::ContentTypes::Default.new(
|
|
30
|
-
extension:
|
|
31
|
-
content_type:
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
extension: "xml",
|
|
35
|
-
content_type: "application/xml",
|
|
36
|
-
),
|
|
37
|
-
]
|
|
35
|
+
extension: defn.extension,
|
|
36
|
+
content_type: defn.content_type,
|
|
37
|
+
)
|
|
38
|
+
end
|
|
38
39
|
|
|
39
40
|
standard = content_type_overrides_for_present_parts
|
|
40
41
|
standard_parts = standard.to_set(&:part_name)
|
|
@@ -43,202 +44,170 @@ module Uniword
|
|
|
43
44
|
end
|
|
44
45
|
|
|
45
46
|
ct.overrides = standard + non_standard
|
|
46
|
-
record_fix(FixCodes::CONTENT_TYPES_ASSEMBLED,
|
|
47
|
+
record_fix(FixCodes::CONTENT_TYPES_ASSEMBLED,
|
|
48
|
+
"Rebuilt content types for standard parts",
|
|
49
|
+
part: "[Content_Types].xml")
|
|
47
50
|
end
|
|
48
51
|
|
|
49
52
|
def reconcile_package_rels
|
|
50
53
|
rels = package.package_rels
|
|
51
54
|
return unless rels
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
standard_targets = standard_defs.to_set { |_, _, t| t }
|
|
67
|
-
standard_rids = standard_defs.to_set { |rid, _, _| rid }
|
|
68
|
-
non_standard = rels.relationships.reject do |r|
|
|
69
|
-
standard_targets.include?(r.target) || standard_rids.include?(r.id)
|
|
56
|
+
# Preserve-first: existing rels keep their position and rId;
|
|
57
|
+
# only genuinely missing standard parts are appended, with
|
|
58
|
+
# rIds allocated by the allocator (seeded from these rels).
|
|
59
|
+
# A later rel whose rId duplicates an already-kept one is
|
|
60
|
+
# dropped — duplicates are impossible by construction.
|
|
61
|
+
kept = []
|
|
62
|
+
seen_ids = Set.new
|
|
63
|
+
rels.relationships.each do |rel|
|
|
64
|
+
next if rel.id && seen_ids.include?(rel.id)
|
|
65
|
+
|
|
66
|
+
kept << rel
|
|
67
|
+
seen_ids << rel.id if rel.id
|
|
70
68
|
end
|
|
71
69
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
PACKAGE_REL_PARTS.each do |key|
|
|
71
|
+
defn = Ooxml::PartRegistry.find_by_key(key)
|
|
72
|
+
next if kept.any? { |r| r.target == defn.target }
|
|
73
|
+
|
|
74
|
+
kept << build_rel(
|
|
75
|
+
allocator.alloc_rid(target: defn.target, type: defn.rel_type,
|
|
76
|
+
scope: :package),
|
|
77
|
+
defn.rel_type, defn.target,
|
|
78
|
+
)
|
|
76
79
|
end
|
|
77
80
|
|
|
78
|
-
rels.relationships =
|
|
79
|
-
record_fix(FixCodes::RELATIONSHIPS_ASSEMBLED,
|
|
81
|
+
rels.relationships = kept
|
|
82
|
+
record_fix(FixCodes::RELATIONSHIPS_ASSEMBLED,
|
|
83
|
+
"Assembled package relationships (preserve-first)",
|
|
84
|
+
part: "_rels/.rels")
|
|
80
85
|
end
|
|
81
86
|
|
|
82
87
|
def reconcile_document_rels
|
|
83
88
|
rels = package.document_rels
|
|
84
89
|
return unless rels
|
|
85
90
|
|
|
86
|
-
|
|
87
|
-
defs = [
|
|
88
|
-
["styles", "styles.xml", package.styles],
|
|
89
|
-
["settings", "settings.xml", package.settings],
|
|
90
|
-
["webSettings", "webSettings.xml", package.web_settings],
|
|
91
|
-
["fontTable", "fontTable.xml", package.font_table],
|
|
92
|
-
["theme", "theme/theme1.xml", package.theme],
|
|
93
|
-
["numbering", "numbering.xml", package.numbering],
|
|
94
|
-
["footnotes", "footnotes.xml", package.footnotes],
|
|
95
|
-
["endnotes", "endnotes.xml", package.endnotes],
|
|
96
|
-
]
|
|
97
|
-
|
|
98
|
-
standard_targets = defs.filter_map { |_, target, obj| target if obj }.to_set
|
|
99
|
-
|
|
100
|
-
# If allocator is present, use it to build rels — preserves existing rIds
|
|
101
|
-
alloc = allocator
|
|
102
|
-
if alloc
|
|
103
|
-
reconcile_document_rels_from_allocator(rels, base, defs, standard_targets, alloc)
|
|
104
|
-
else
|
|
105
|
-
reconcile_document_rels_legacy(rels, base, defs, standard_targets)
|
|
106
|
-
end
|
|
91
|
+
assemble_document_rels(rels, document_rel_defs)
|
|
107
92
|
end
|
|
108
93
|
|
|
109
94
|
private
|
|
110
95
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
defs
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
# Add allocator-managed rels (images, headers, footers, hyperlinks)
|
|
128
|
-
alloc.all_rels.each do |entry|
|
|
129
|
-
next if standard_targets.include?(entry[:target])
|
|
130
|
-
next if all_rels.any? { |r| r.target == entry[:target] }
|
|
131
|
-
next if unsupported_rel_type?(entry[:type])
|
|
132
|
-
next if package_level_rel?(entry[:type])
|
|
133
|
-
next unless header_footer_target_present?(entry[:target])
|
|
134
|
-
|
|
135
|
-
all_rels << build_rel(
|
|
136
|
-
entry[:id], entry[:type], entry[:target],
|
|
137
|
-
target_mode: entry[:target_mode],
|
|
138
|
-
)
|
|
139
|
-
end
|
|
96
|
+
# [PartDefinition, package part] pairs for document-level parts
|
|
97
|
+
# that must carry a relationship when present, in emission order.
|
|
98
|
+
def document_rel_defs
|
|
99
|
+
defs = [
|
|
100
|
+
[Ooxml::PartRegistry.find_by_key(:styles), package.styles],
|
|
101
|
+
[Ooxml::PartRegistry.find_by_key(:settings), package.settings],
|
|
102
|
+
[Ooxml::PartRegistry.find_by_key(:web_settings),
|
|
103
|
+
package.web_settings],
|
|
104
|
+
[Ooxml::PartRegistry.find_by_key(:font_table),
|
|
105
|
+
package.font_table],
|
|
106
|
+
[Ooxml::PartRegistry.find_by_key(:theme), package.theme],
|
|
107
|
+
[Ooxml::PartRegistry.find_by_key(:numbering), package.numbering],
|
|
108
|
+
[Ooxml::PartRegistry.find_by_key(:footnotes), package.footnotes],
|
|
109
|
+
[Ooxml::PartRegistry.find_by_key(:endnotes), package.endnotes],
|
|
110
|
+
[Ooxml::PartRegistry.find_by_key(:comments), package.comments],
|
|
111
|
+
]
|
|
140
112
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
standard_targets.include?(r.target) ||
|
|
146
|
-
unsupported_rel_type?(r.type) ||
|
|
147
|
-
package_level_rel?(r.type) ||
|
|
148
|
-
!header_footer_target_present?(r.target)
|
|
113
|
+
sources = package.bibliography_sources ||
|
|
114
|
+
package.document&.bibliography_sources
|
|
115
|
+
if sources
|
|
116
|
+
defs << [Ooxml::PartRegistry.find_by_key(:bibliography), sources]
|
|
149
117
|
end
|
|
150
118
|
|
|
151
|
-
|
|
152
|
-
record_fix(FixCodes::RELATIONSHIPS_ASSEMBLED, "Assembled document relationships from allocator")
|
|
119
|
+
defs
|
|
153
120
|
end
|
|
154
121
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
defs.each do |suffix, target, obj|
|
|
167
|
-
next unless obj
|
|
168
|
-
rid = "rId#{all_rels.size + 1}"
|
|
169
|
-
all_rels << build_rel(rid, "#{base}/#{suffix}", target)
|
|
170
|
-
end
|
|
122
|
+
# Preserve-first assembly: existing relationships keep their
|
|
123
|
+
# position and rId (the allocator was seeded from them), new
|
|
124
|
+
# standard parts and new allocator-registered entries (images,
|
|
125
|
+
# hyperlinks, headers/footers added at build time) are appended
|
|
126
|
+
# with freshly allocated ids. No renumbering, ever.
|
|
127
|
+
def assemble_document_rels(rels, defs)
|
|
128
|
+
alloc = allocator
|
|
129
|
+
register_auxiliary_part_rels(alloc)
|
|
130
|
+
kept = kept_existing_relationships(rels, alloc)
|
|
131
|
+
kept_targets = kept.to_set(&:target)
|
|
171
132
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
new_rid = "rId#{all_rels.size + 1}"
|
|
175
|
-
rid_mapping[old_rid] = new_rid if old_rid != new_rid
|
|
176
|
-
all_rels << build_rel(new_rid, rel.type, rel.target,
|
|
177
|
-
target_mode: rel.target_mode)
|
|
178
|
-
end
|
|
133
|
+
append_missing_standard_rels(kept, kept_targets, defs, alloc)
|
|
134
|
+
append_allocator_rels(kept, kept_targets, alloc)
|
|
179
135
|
|
|
180
|
-
rels.relationships =
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
record_fix(FixCodes::RELATIONSHIPS_ASSEMBLED, "Rebuilt document relationships with sequential rIds")
|
|
136
|
+
rels.relationships = kept
|
|
137
|
+
record_fix(FixCodes::RELATIONSHIPS_ASSEMBLED,
|
|
138
|
+
"Assembled document relationships (preserve-first)",
|
|
139
|
+
part: "word/_rels/document.xml.rels")
|
|
185
140
|
end
|
|
186
141
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
142
|
+
# Existing rels that survive the assembly, in original order:
|
|
143
|
+
# drops unsupported and package-level types and stale
|
|
144
|
+
# header/footer targets; everything else keeps its rId verbatim
|
|
145
|
+
# (or the allocator's reallocated id when seeding resolved a
|
|
146
|
+
# collision for this target+type).
|
|
147
|
+
def kept_existing_relationships(rels, alloc)
|
|
148
|
+
used_ids = Set.new
|
|
149
|
+
rels.relationships.filter_map do |rel|
|
|
150
|
+
next if unsupported_rel_type?(rel.type)
|
|
151
|
+
next if package_level_rel?(rel.type)
|
|
152
|
+
next unless header_footer_target_present?(rel.target)
|
|
153
|
+
|
|
154
|
+
rid = alloc.rid_for(target: rel.target, type: rel.type)
|
|
155
|
+
rid = rel.id if rid.nil? || used_ids.include?(rid)
|
|
156
|
+
next if rid && used_ids.include?(rid)
|
|
157
|
+
|
|
158
|
+
used_ids << rid if rid
|
|
159
|
+
build_rel(rid, rel.type, rel.target,
|
|
160
|
+
target_mode: rel.target_mode)
|
|
198
161
|
end
|
|
199
162
|
end
|
|
200
163
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
164
|
+
# Register rels for package-carried parts outside the standard
|
|
165
|
+
# defs: OLE/embedded binaries and chart parts. Loaded packages
|
|
166
|
+
# seeded these already (no-op); programmatically added parts
|
|
167
|
+
# get freshly allocated ids from the allocator.
|
|
168
|
+
def register_auxiliary_part_rels(alloc)
|
|
169
|
+
ole = Ooxml::PartRegistry.find_by_key(:ole_object)
|
|
170
|
+
(package.embeddings&.keys || []).each do |target|
|
|
171
|
+
alloc.alloc_rid(target: target, type: ole.rel_type)
|
|
172
|
+
end
|
|
207
173
|
|
|
208
|
-
|
|
209
|
-
|
|
174
|
+
chart = Ooxml::PartRegistry.find_by_key(:chart)
|
|
175
|
+
(package.document&.chart_parts&.values || []).each do |data|
|
|
176
|
+
next unless data[:target]
|
|
210
177
|
|
|
211
|
-
|
|
212
|
-
update_drawing_blip(drawing, mapping)
|
|
213
|
-
end
|
|
214
|
-
end
|
|
178
|
+
alloc.alloc_rid(target: data[:target], type: chart.rel_type)
|
|
215
179
|
end
|
|
216
180
|
end
|
|
217
181
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
blip = picture.blip_fill&.blip
|
|
226
|
-
return unless blip&.embed
|
|
182
|
+
# Append rels for present standard parts that have no
|
|
183
|
+
# relationship yet (fresh or newly added parts).
|
|
184
|
+
def append_missing_standard_rels(kept, kept_targets, defs, alloc)
|
|
185
|
+
defs.each do |defn, obj|
|
|
186
|
+
next unless obj
|
|
187
|
+
next if kept_targets.include?(defn.target)
|
|
227
188
|
|
|
228
|
-
|
|
229
|
-
|
|
189
|
+
kept << build_rel(
|
|
190
|
+
alloc.alloc_rid(target: defn.target, type: defn.rel_type),
|
|
191
|
+
defn.rel_type, defn.target,
|
|
192
|
+
)
|
|
193
|
+
kept_targets << defn.target
|
|
194
|
+
end
|
|
230
195
|
end
|
|
231
196
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
197
|
+
# Append allocator-registered rels not already present —
|
|
198
|
+
# builder-added images, hyperlinks, charts, headers/footers.
|
|
199
|
+
def append_allocator_rels(kept, kept_targets, alloc)
|
|
200
|
+
alloc.all_rels(scope: :document).each do |entry|
|
|
201
|
+
next if kept_targets.include?(entry[:target])
|
|
202
|
+
next if unsupported_rel_type?(entry[:type])
|
|
203
|
+
next if package_level_rel?(entry[:type])
|
|
204
|
+
next unless header_footer_target_present?(entry[:target])
|
|
235
205
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
end
|
|
206
|
+
kept << build_rel(
|
|
207
|
+
entry[:id], entry[:type], entry[:target],
|
|
208
|
+
target_mode: entry[:target_mode],
|
|
209
|
+
)
|
|
210
|
+
kept_targets << entry[:target]
|
|
242
211
|
end
|
|
243
212
|
end
|
|
244
213
|
|
|
@@ -250,69 +219,48 @@ module Uniword
|
|
|
250
219
|
PACKAGE_LEVEL_REL_TYPES.include?(type.to_s)
|
|
251
220
|
end
|
|
252
221
|
|
|
253
|
-
# Check if a header/footer rel target corresponds to a
|
|
254
|
-
#
|
|
255
|
-
#
|
|
256
|
-
#
|
|
257
|
-
#
|
|
258
|
-
#
|
|
222
|
+
# Check if a header/footer rel target corresponds to a part the
|
|
223
|
+
# package will emit. All header/footer parts live in the unified
|
|
224
|
+
# store (document.header_footer_parts), whether loaded from the
|
|
225
|
+
# source package or added programmatically; a rel whose target
|
|
226
|
+
# is not in the store points at a part that will not be
|
|
227
|
+
# serialized and must be treated as stale.
|
|
259
228
|
def header_footer_target_present?(target)
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
return
|
|
266
|
-
end
|
|
267
|
-
if target.start_with?("footer") && target.end_with?(".xml")
|
|
268
|
-
num = target[/footer(\d+)\.xml/, 1]&.to_i
|
|
269
|
-
hash_count = package.document&.footers&.size || 0
|
|
270
|
-
parts_count = count_parts_matching("footer")
|
|
271
|
-
count = [hash_count, parts_count].max
|
|
272
|
-
return num && num <= count
|
|
229
|
+
header_like = target.start_with?("header") || target.start_with?("footer")
|
|
230
|
+
if header_like && target.end_with?(".xml")
|
|
231
|
+
parts = package.document&.header_footer_parts
|
|
232
|
+
return false unless parts
|
|
233
|
+
|
|
234
|
+
return parts.targets.include?(target)
|
|
273
235
|
end
|
|
274
236
|
true
|
|
275
237
|
end
|
|
276
238
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
return 0 unless parts
|
|
280
|
-
|
|
281
|
-
parts.count { |p| p[:target].to_s.start_with?(prefix) }
|
|
282
|
-
end
|
|
283
|
-
|
|
239
|
+
# [registry key, package part] pairs in content-types emission
|
|
240
|
+
# order; overrides are derived from Ooxml::PartRegistry.
|
|
284
241
|
def content_type_overrides_for_present_parts
|
|
285
242
|
checks = [
|
|
286
|
-
[
|
|
287
|
-
|
|
288
|
-
[
|
|
289
|
-
|
|
290
|
-
[
|
|
291
|
-
|
|
292
|
-
[
|
|
293
|
-
|
|
294
|
-
[
|
|
295
|
-
|
|
296
|
-
[
|
|
297
|
-
|
|
298
|
-
[package.core_properties, "/docProps/core.xml",
|
|
299
|
-
"application/vnd.openxmlformats-package.core-properties+xml"],
|
|
300
|
-
[package.app_properties, "/docProps/app.xml",
|
|
301
|
-
"application/vnd.openxmlformats-officedocument.extended-properties+xml"],
|
|
302
|
-
[package.footnotes, "/word/footnotes.xml",
|
|
303
|
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml"],
|
|
304
|
-
[package.endnotes, "/word/endnotes.xml",
|
|
305
|
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml"],
|
|
306
|
-
[package.numbering, "/word/numbering.xml",
|
|
307
|
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"],
|
|
243
|
+
[:document, package.document],
|
|
244
|
+
[:styles, package.styles],
|
|
245
|
+
[:settings, package.settings],
|
|
246
|
+
[:font_table, package.font_table],
|
|
247
|
+
[:web_settings, package.web_settings],
|
|
248
|
+
[:theme, package.theme],
|
|
249
|
+
[:core_properties, package.core_properties],
|
|
250
|
+
[:app_properties, package.app_properties],
|
|
251
|
+
[:footnotes, package.footnotes],
|
|
252
|
+
[:endnotes, package.endnotes],
|
|
253
|
+
[:comments, package.comments],
|
|
254
|
+
[:numbering, package.numbering],
|
|
308
255
|
]
|
|
309
256
|
|
|
310
|
-
checks.filter_map do |
|
|
257
|
+
checks.filter_map do |key, obj|
|
|
311
258
|
next unless obj
|
|
312
259
|
|
|
260
|
+
defn = Ooxml::PartRegistry.find_by_key(key)
|
|
313
261
|
Uniword::ContentTypes::Override.new(
|
|
314
|
-
part_name: part_name,
|
|
315
|
-
content_type: content_type,
|
|
262
|
+
part_name: defn.part_name,
|
|
263
|
+
content_type: defn.content_type,
|
|
316
264
|
)
|
|
317
265
|
end
|
|
318
266
|
end
|
|
@@ -50,11 +50,19 @@ module Uniword
|
|
|
50
50
|
val: profile.list_separator,
|
|
51
51
|
)
|
|
52
52
|
|
|
53
|
+
# w:updateFields set by the user/API: ensure it serializes —
|
|
54
|
+
# parsed settings carry a stored element_order that lacks it.
|
|
55
|
+
if settings.update_fields
|
|
56
|
+
ensure_element_in_order(settings, "updateFields",
|
|
57
|
+
after: "characterSpacingControl")
|
|
58
|
+
end
|
|
59
|
+
|
|
53
60
|
unless settings.w14_doc_id
|
|
54
61
|
settings.w14_doc_id = Wordprocessingml::W14DocId.new(
|
|
55
62
|
val: hex_derive("w14_doc_id", 4),
|
|
56
63
|
)
|
|
57
|
-
record_fix(FixCodes::DOC_ID_GENERATED, "Generated w14:docId"
|
|
64
|
+
record_fix(FixCodes::DOC_ID_GENERATED, "Generated w14:docId",
|
|
65
|
+
part: "word/settings.xml")
|
|
58
66
|
end
|
|
59
67
|
unless settings.w15_doc_id
|
|
60
68
|
raw = hex_derive("w15_doc_id", 16)
|
|
@@ -63,7 +71,9 @@ module Uniword
|
|
|
63
71
|
settings.w15_doc_id = Wordprocessingml::W15DocId.new(
|
|
64
72
|
val: "{#{formatted.upcase}}",
|
|
65
73
|
)
|
|
66
|
-
record_fix(FixCodes::DOC_ID_GENERATED,
|
|
74
|
+
record_fix(FixCodes::DOC_ID_GENERATED,
|
|
75
|
+
"Generated w15:docId in GUID format",
|
|
76
|
+
part: "word/settings.xml")
|
|
67
77
|
end
|
|
68
78
|
|
|
69
79
|
set_mc_ignorable(settings)
|
|
@@ -75,7 +85,8 @@ module Uniword
|
|
|
75
85
|
font_table = package.font_table
|
|
76
86
|
font_table ||= begin
|
|
77
87
|
package.font_table = Wordprocessingml::FontTable.new
|
|
78
|
-
record_fix(FixCodes::FONT_TABLE_CREATED, "Created font table"
|
|
88
|
+
record_fix(FixCodes::FONT_TABLE_CREATED, "Created font table",
|
|
89
|
+
part: "word/fontTable.xml")
|
|
79
90
|
package.font_table
|
|
80
91
|
end
|
|
81
92
|
|
|
@@ -114,7 +125,8 @@ module Uniword
|
|
|
114
125
|
|
|
115
126
|
set_mc_ignorable(font_table)
|
|
116
127
|
record_fix(FixCodes::FONT_TABLE_CREATED,
|
|
117
|
-
"Populated font table with profile fonts and signatures"
|
|
128
|
+
"Populated font table with profile fonts and signatures",
|
|
129
|
+
part: "word/fontTable.xml")
|
|
118
130
|
end
|
|
119
131
|
|
|
120
132
|
def reconcile_styles
|
|
@@ -133,7 +145,8 @@ module Uniword
|
|
|
133
145
|
|
|
134
146
|
set_mc_ignorable(styles)
|
|
135
147
|
record_fix(FixCodes::STYLE_DEFAULTS_ADDED,
|
|
136
|
-
"Ensured styles have docDefaults, latentStyles, and default styles"
|
|
148
|
+
"Ensured styles have docDefaults, latentStyles, and default styles",
|
|
149
|
+
part: "word/styles.xml")
|
|
137
150
|
end
|
|
138
151
|
|
|
139
152
|
def reconcile_numbering
|
|
@@ -149,7 +162,8 @@ module Uniword
|
|
|
149
162
|
raw = raw - 0x100000000 if raw >= 0x80000000
|
|
150
163
|
inst.durable_id = raw.to_s
|
|
151
164
|
record_fix(FixCodes::NUMBERING_REFERENCED,
|
|
152
|
-
"Generated w16cid:durableId for numId=#{inst.num_id}"
|
|
165
|
+
"Generated w16cid:durableId for numId=#{inst.num_id}",
|
|
166
|
+
part: "word/numbering.xml")
|
|
153
167
|
end
|
|
154
168
|
|
|
155
169
|
package.numbering.instances.each do |inst|
|
|
@@ -165,8 +179,10 @@ module Uniword
|
|
|
165
179
|
end
|
|
166
180
|
next if defn
|
|
167
181
|
|
|
168
|
-
record_fix(FixCodes::NUMBERING_REFERENCED,
|
|
169
|
-
|
|
182
|
+
record_fix(FixCodes::NUMBERING_REFERENCED,
|
|
183
|
+
"Numbering instance numId=#{inst.num_id} references " \
|
|
184
|
+
"missing abstractNumId=#{abs_id}",
|
|
185
|
+
part: "word/numbering.xml")
|
|
170
186
|
end
|
|
171
187
|
end
|
|
172
188
|
|
|
@@ -180,7 +196,8 @@ module Uniword
|
|
|
180
196
|
end
|
|
181
197
|
|
|
182
198
|
set_mc_ignorable(ws)
|
|
183
|
-
record_fix(FixCodes::MC_IGNORABLE, "Cleared mc:Ignorable on webSettings"
|
|
199
|
+
record_fix(FixCodes::MC_IGNORABLE, "Cleared mc:Ignorable on webSettings",
|
|
200
|
+
part: "word/webSettings.xml")
|
|
184
201
|
end
|
|
185
202
|
|
|
186
203
|
def reconcile_app_properties
|
|
@@ -219,7 +236,9 @@ module Uniword
|
|
|
219
236
|
app.heading_pairs = nil
|
|
220
237
|
app.titles_of_parts = nil
|
|
221
238
|
|
|
222
|
-
record_fix(FixCodes::APP_PROPERTIES_ENSURED,
|
|
239
|
+
record_fix(FixCodes::APP_PROPERTIES_ENSURED,
|
|
240
|
+
"Ensured app properties with statistics",
|
|
241
|
+
part: "docProps/app.xml")
|
|
223
242
|
end
|
|
224
243
|
|
|
225
244
|
def reconcile_core_properties
|
|
@@ -259,7 +278,9 @@ module Uniword
|
|
|
259
278
|
)
|
|
260
279
|
|
|
261
280
|
cp.revision = "1" unless cp.revision
|
|
262
|
-
record_fix(FixCodes::CORE_PROPERTIES_REBUILT,
|
|
281
|
+
record_fix(FixCodes::CORE_PROPERTIES_REBUILT,
|
|
282
|
+
"Rebuilt core properties with namespace declarations",
|
|
283
|
+
part: "docProps/core.xml")
|
|
263
284
|
end
|
|
264
285
|
|
|
265
286
|
private
|
|
@@ -421,10 +442,12 @@ module Uniword
|
|
|
421
442
|
end
|
|
422
443
|
|
|
423
444
|
dpf = styles.styles.find { |s| s.id == "DefaultParagraphFont" }
|
|
424
|
-
if dpf && !dpf.semiHidden && !
|
|
445
|
+
if dpf && !dpf.semiHidden && !builder_managed?
|
|
425
446
|
dpf.semiHidden = Wordprocessingml::SemiHidden.new
|
|
426
447
|
ensure_element_in_order(dpf, "semiHidden", after: "uiPriority")
|
|
427
|
-
record_fix(FixCodes::SEMI_HIDDEN_ADDED,
|
|
448
|
+
record_fix(FixCodes::SEMI_HIDDEN_ADDED,
|
|
449
|
+
"Added semiHidden to DefaultParagraphFont style",
|
|
450
|
+
part: "word/styles.xml")
|
|
428
451
|
end
|
|
429
452
|
|
|
430
453
|
unless style_ids.include?("TableNormal")
|