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
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uniword
|
|
4
|
+
module Wordprocessingml
|
|
5
|
+
# Applies uniform page setup across every section of a document —
|
|
6
|
+
# Word's Layout dialog as an API.
|
|
7
|
+
#
|
|
8
|
+
# Paper size (named presets), orientation (with dimension swap, like
|
|
9
|
+
# Word), and margins (twips, or strings with in/cm/mm suffix).
|
|
10
|
+
# Header/footer/gutter margins are left untouched.
|
|
11
|
+
#
|
|
12
|
+
# @example A4 landscape with 2 cm margins
|
|
13
|
+
# setup = PageSetup.new(size: "a4", orientation: "landscape",
|
|
14
|
+
# margins: "2cm")
|
|
15
|
+
# setup.apply(document)
|
|
16
|
+
# setup.sections_updated # => 1
|
|
17
|
+
class PageSetup
|
|
18
|
+
# Named paper sizes in twips [width, height] (portrait).
|
|
19
|
+
PAPER_SIZES = {
|
|
20
|
+
"letter" => [12_240, 15_840],
|
|
21
|
+
"legal" => [12_240, 20_160],
|
|
22
|
+
"a4" => [11_906, 16_838],
|
|
23
|
+
"a5" => [8_391, 11_906],
|
|
24
|
+
"executive" => [10_440, 15_120],
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
TWIPS_PER_INCH = 1440
|
|
28
|
+
TWIPS_PER_CM = 567
|
|
29
|
+
TWIPS_PER_MM = 57
|
|
30
|
+
|
|
31
|
+
ORIENTATIONS = %w[portrait landscape].freeze
|
|
32
|
+
|
|
33
|
+
# @return [Integer] number of sections updated
|
|
34
|
+
attr_reader :sections_updated
|
|
35
|
+
|
|
36
|
+
# @param size [String, nil] Paper size name (see PAPER_SIZES)
|
|
37
|
+
# @param orientation [String, nil] "portrait" or "landscape"
|
|
38
|
+
# @param margins [Integer, String, nil] Uniform margin for all four
|
|
39
|
+
# sides (twips, or "1in" / "2.5cm" / "25mm")
|
|
40
|
+
# @param side_margins [Hash] Per-side overrides
|
|
41
|
+
# (:top, :right, :bottom, :left)
|
|
42
|
+
# @raise [ArgumentError] on unknown size, orientation, or margin
|
|
43
|
+
def initialize(size: nil, orientation: nil, margins: nil,
|
|
44
|
+
**side_margins)
|
|
45
|
+
@size = normalize_size(size)
|
|
46
|
+
@orientation = normalize_orientation(orientation)
|
|
47
|
+
@margins = side_margins.to_h do |side, value|
|
|
48
|
+
[side, parse_margin(value)]
|
|
49
|
+
end
|
|
50
|
+
uniform = parse_margin(margins)
|
|
51
|
+
%i[top right bottom left].each do |side|
|
|
52
|
+
@margins[side] = uniform if @margins[side].nil?
|
|
53
|
+
end
|
|
54
|
+
@sections_updated = 0
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Apply the setup to every section in the document.
|
|
58
|
+
#
|
|
59
|
+
# @param document [DocumentRoot] Document to update (mutated)
|
|
60
|
+
# @return [Integer] number of sections updated
|
|
61
|
+
def apply(document)
|
|
62
|
+
each_section_properties(document) do |sect_pr|
|
|
63
|
+
apply_page_size(sect_pr) if @size || @orientation
|
|
64
|
+
apply_margins(sect_pr)
|
|
65
|
+
@sections_updated += 1
|
|
66
|
+
end
|
|
67
|
+
sections_updated
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def normalize_size(size)
|
|
73
|
+
return nil if size.nil?
|
|
74
|
+
|
|
75
|
+
dims = PAPER_SIZES[size.to_s.downcase]
|
|
76
|
+
unless dims
|
|
77
|
+
raise ArgumentError,
|
|
78
|
+
"Unknown paper size '#{size}'. " \
|
|
79
|
+
"Available: #{PAPER_SIZES.keys.join(', ')}"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
dims
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def normalize_orientation(orientation)
|
|
86
|
+
return nil if orientation.nil?
|
|
87
|
+
|
|
88
|
+
value = orientation.to_s.downcase
|
|
89
|
+
return value if ORIENTATIONS.include?(value)
|
|
90
|
+
|
|
91
|
+
raise ArgumentError,
|
|
92
|
+
"Unknown orientation '#{orientation}'. " \
|
|
93
|
+
"Available: #{ORIENTATIONS.join(', ')}"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Parse a margin value: Integer (twips) or String with an
|
|
97
|
+
# in/cm/mm/dxa suffix (bare numbers are inches).
|
|
98
|
+
def parse_margin(value)
|
|
99
|
+
case value
|
|
100
|
+
when nil then nil
|
|
101
|
+
when Numeric then value.round
|
|
102
|
+
when /\A([\d.]+)\s*(in|cm|mm|dxa|twips?)?\z/i
|
|
103
|
+
amount = Regexp.last_match(1).to_f
|
|
104
|
+
unit = Regexp.last_match(2)&.downcase || "in"
|
|
105
|
+
to_twips(amount, unit)
|
|
106
|
+
else
|
|
107
|
+
raise ArgumentError, "Invalid margin '#{value}' " \
|
|
108
|
+
"(use twips, or '1in' / '2.5cm' / '25mm')"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def to_twips(amount, unit)
|
|
113
|
+
factor = case unit
|
|
114
|
+
when "in" then TWIPS_PER_INCH
|
|
115
|
+
when "cm" then TWIPS_PER_CM
|
|
116
|
+
when "mm" then TWIPS_PER_MM
|
|
117
|
+
else 1
|
|
118
|
+
end
|
|
119
|
+
(amount * factor).round
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def each_section_properties(document, &block)
|
|
123
|
+
body = document.body
|
|
124
|
+
return unless body
|
|
125
|
+
|
|
126
|
+
yield body.section_properties if body.section_properties
|
|
127
|
+
each_paragraph_section(body, &block)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def each_paragraph_section(body)
|
|
131
|
+
(body.paragraphs || []).each do |paragraph|
|
|
132
|
+
sect_pr = paragraph.properties&.section_properties
|
|
133
|
+
yield sect_pr if sect_pr
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def apply_page_size(sect_pr)
|
|
138
|
+
width, height = target_dimensions(sect_pr)
|
|
139
|
+
|
|
140
|
+
sect_pr.page_size ||= PageSize.new
|
|
141
|
+
sect_pr.page_size.width = width
|
|
142
|
+
sect_pr.page_size.height = height
|
|
143
|
+
sect_pr.page_size.orientation = @orientation if @orientation
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def target_dimensions(sect_pr)
|
|
147
|
+
width, height = current_dimensions(sect_pr)
|
|
148
|
+
width, height = @size if @size
|
|
149
|
+
width, height = orient(width, height) if @orientation
|
|
150
|
+
[width, height]
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def current_dimensions(sect_pr)
|
|
154
|
+
size = sect_pr.page_size
|
|
155
|
+
[size&.width || PageDefaults.default_page_size.width,
|
|
156
|
+
size&.height || PageDefaults.default_page_size.height]
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Word swaps width/height when flipping orientation.
|
|
160
|
+
def orient(width, height)
|
|
161
|
+
if (@orientation == "landscape" && width < height) ||
|
|
162
|
+
(@orientation == "portrait" && width > height)
|
|
163
|
+
[height, width]
|
|
164
|
+
else
|
|
165
|
+
[width, height]
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def apply_margins(sect_pr)
|
|
170
|
+
return if @margins.values.all?(&:nil?)
|
|
171
|
+
|
|
172
|
+
sect_pr.page_margins ||= PageMargins.new
|
|
173
|
+
@margins.each do |side, value|
|
|
174
|
+
next if value.nil?
|
|
175
|
+
|
|
176
|
+
sect_pr.page_margins.method(:"#{side}=").call(value)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
@@ -116,7 +116,8 @@ module Uniword
|
|
|
116
116
|
with: { from: :yaml_widow_control_from, to: :yaml_widow_control_to }
|
|
117
117
|
end
|
|
118
118
|
|
|
119
|
-
# YAML transform methods (instance methods
|
|
119
|
+
# YAML transform methods (instance methods called by lutaml-model's
|
|
120
|
+
# `with:` transform mechanism)
|
|
120
121
|
def yaml_style_from(instance, value)
|
|
121
122
|
if value
|
|
122
123
|
instance.style = [
|
|
@@ -200,21 +201,6 @@ module Uniword
|
|
|
200
201
|
# Style reference - maps w:pStyle w:val="..." to style attribute
|
|
201
202
|
map_element "pStyle", to: :style, render_nil: false
|
|
202
203
|
|
|
203
|
-
# Alignment - maps w:jc w:val="..." to alignment attribute
|
|
204
|
-
map_element "jc", to: :alignment, render_nil: false
|
|
205
|
-
|
|
206
|
-
# Spacing (complex object - WORKS)
|
|
207
|
-
map_element "spacing", to: :spacing, render_nil: false
|
|
208
|
-
|
|
209
|
-
# Indentation (complex object)
|
|
210
|
-
map_element "ind", to: :indentation, render_nil: false
|
|
211
|
-
|
|
212
|
-
# Outline level (wrapper object)
|
|
213
|
-
map_element "outlineLvl", to: :outline_level, render_nil: false
|
|
214
|
-
|
|
215
|
-
# Numbering properties (wrapped in w:numPr)
|
|
216
|
-
map_element "numPr", to: :numbering_properties, render_nil: false
|
|
217
|
-
|
|
218
204
|
# Keep options (only render if true)
|
|
219
205
|
map_element "keepNext", to: :keep_next_wrapper, render_nil: false,
|
|
220
206
|
render_default: false
|
|
@@ -225,32 +211,51 @@ module Uniword
|
|
|
225
211
|
map_element "widowControl", to: :widow_control_wrapper, render_nil: false,
|
|
226
212
|
render_default: false
|
|
227
213
|
|
|
228
|
-
#
|
|
229
|
-
map_element "
|
|
230
|
-
|
|
214
|
+
# Numbering properties (wrapped in w:numPr)
|
|
215
|
+
map_element "numPr", to: :numbering_properties, render_nil: false
|
|
216
|
+
|
|
217
|
+
# Suppress line numbers (only render if true)
|
|
231
218
|
map_element "suppressLineNumbers", to: :suppress_line_numbers, render_nil: false,
|
|
232
219
|
render_default: false
|
|
233
220
|
|
|
234
|
-
#
|
|
235
|
-
map_element "
|
|
236
|
-
|
|
221
|
+
# Borders (complex object)
|
|
222
|
+
map_element "pBdr", to: :borders, render_nil: false
|
|
223
|
+
|
|
224
|
+
# Shading (complex object)
|
|
225
|
+
map_element "shd", to: :shading, render_nil: false
|
|
226
|
+
|
|
227
|
+
# Tabs (complex object - collection)
|
|
228
|
+
map_element "tabs", to: :tabs, render_nil: false
|
|
237
229
|
|
|
238
230
|
# East Asian typography
|
|
239
231
|
map_element "autoSpaceDE", to: :auto_space_de, render_nil: false,
|
|
240
232
|
render_default: false
|
|
241
233
|
map_element "autoSpaceDN", to: :auto_space_dn, render_nil: false,
|
|
242
234
|
render_default: false
|
|
235
|
+
|
|
236
|
+
# Bidirectional (only render if true)
|
|
237
|
+
map_element "bidi", to: :bidirectional, render_nil: false,
|
|
238
|
+
render_default: false
|
|
239
|
+
|
|
240
|
+
# Right indent adjustment
|
|
243
241
|
map_element "adjustRightInd", to: :adjust_right_ind, render_nil: false,
|
|
244
242
|
render_default: false
|
|
245
243
|
|
|
246
|
-
#
|
|
247
|
-
map_element "
|
|
244
|
+
# Spacing (complex object - WORKS)
|
|
245
|
+
map_element "spacing", to: :spacing, render_nil: false
|
|
248
246
|
|
|
249
|
-
#
|
|
250
|
-
map_element "
|
|
247
|
+
# Indentation (complex object)
|
|
248
|
+
map_element "ind", to: :indentation, render_nil: false
|
|
251
249
|
|
|
252
|
-
#
|
|
253
|
-
map_element "
|
|
250
|
+
# Contextual spacing (only render if true)
|
|
251
|
+
map_element "contextualSpacing", to: :contextual_spacing, render_nil: false,
|
|
252
|
+
render_default: false
|
|
253
|
+
|
|
254
|
+
# Alignment - maps w:jc w:val="..." to alignment attribute
|
|
255
|
+
map_element "jc", to: :alignment, render_nil: false
|
|
256
|
+
|
|
257
|
+
# Outline level (wrapper object)
|
|
258
|
+
map_element "outlineLvl", to: :outline_level, render_nil: false
|
|
254
259
|
|
|
255
260
|
# Run properties (for pPr/rPr - style overrides)
|
|
256
261
|
map_element "rPr", to: :run_properties, render_nil: false
|
|
@@ -18,6 +18,7 @@ module Uniword
|
|
|
18
18
|
attribute :alternate_content, AlternateContent, default: nil
|
|
19
19
|
attribute :footnote_reference, FootnoteReference
|
|
20
20
|
attribute :endnote_reference, EndnoteReference
|
|
21
|
+
attribute :comment_reference, CommentReference
|
|
21
22
|
attribute :field_char, FieldChar
|
|
22
23
|
attribute :instr_text, InstrText
|
|
23
24
|
attribute :position_tab, PositionTab
|
|
@@ -55,6 +56,8 @@ module Uniword
|
|
|
55
56
|
render_nil: false
|
|
56
57
|
map_element "endnoteReference", to: :endnote_reference,
|
|
57
58
|
render_nil: false
|
|
59
|
+
map_element "commentReference", to: :comment_reference,
|
|
60
|
+
render_nil: false
|
|
58
61
|
map_element "fldChar", to: :field_char, render_nil: false
|
|
59
62
|
map_element "instrText", to: :instr_text, render_nil: false
|
|
60
63
|
map_element "ptab", to: :position_tab, render_nil: false
|
|
@@ -50,14 +50,14 @@ module Uniword
|
|
|
50
50
|
|
|
51
51
|
map_element "headerReference", to: :header_references, render_nil: false
|
|
52
52
|
map_element "footerReference", to: :footer_references, render_nil: false
|
|
53
|
+
map_element "footnotePr", to: :footnote_pr, render_nil: false
|
|
53
54
|
map_element "pgSz", to: :page_size, render_nil: false
|
|
54
55
|
map_element "pgMar", to: :page_margins, render_nil: false
|
|
55
56
|
map_attribute "val", to: :type
|
|
56
57
|
map_element "pgNumType", to: :page_numbering, render_nil: false
|
|
57
58
|
map_element "cols", to: :columns, render_nil: false
|
|
58
|
-
map_element "docGrid", to: :doc_grid, render_nil: false
|
|
59
|
-
map_element "footnotePr", to: :footnote_pr, render_nil: false
|
|
60
59
|
map_element "titlePg", to: :title_pg, render_nil: false
|
|
60
|
+
map_element "docGrid", to: :doc_grid, render_nil: false
|
|
61
61
|
map_element "footnoteColumns", to: :footnote_columns, render_nil: false
|
|
62
62
|
end
|
|
63
63
|
end
|
|
@@ -2,34 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require "lutaml/model"
|
|
4
4
|
require "nokogiri"
|
|
5
|
-
require_relative "../properties/relationship_id"
|
|
6
|
-
require_relative "zoom"
|
|
7
|
-
require_relative "compat"
|
|
8
|
-
require_relative "proof_state"
|
|
9
|
-
require_relative "style_pane_format_filter"
|
|
10
|
-
require_relative "default_tab_stop"
|
|
11
|
-
require_relative "character_spacing_control"
|
|
12
|
-
require_relative "do_not_display_page_boundaries"
|
|
13
|
-
require_relative "rsids"
|
|
14
|
-
require_relative "math_pr"
|
|
15
|
-
require_relative "theme_font_lang"
|
|
16
|
-
require_relative "clr_scheme_mapping"
|
|
17
|
-
require_relative "shape_defaults"
|
|
18
|
-
require_relative "decimal_symbol"
|
|
19
|
-
require_relative "list_separator"
|
|
20
|
-
require_relative "attached_template"
|
|
21
|
-
require_relative "footnote_pr"
|
|
22
|
-
require_relative "endnote_pr"
|
|
23
|
-
require_relative "hdr_shape_defaults"
|
|
24
|
-
require_relative "even_and_odd_headers"
|
|
25
|
-
require_relative "mirror_margins"
|
|
26
|
-
require_relative "do_not_include_subdocs_in_stats"
|
|
27
|
-
require_relative "hyphenation_zone"
|
|
28
|
-
require_relative "style_pane_sort_method"
|
|
29
|
-
require_relative "doc_vars"
|
|
30
|
-
require_relative "w14_doc_id"
|
|
31
|
-
require_relative "w15_chart_tracking_ref_based"
|
|
32
|
-
require_relative "w15_doc_id"
|
|
33
5
|
|
|
34
6
|
module Uniword
|
|
35
7
|
module Wordprocessingml
|
|
@@ -57,6 +29,7 @@ module Uniword
|
|
|
57
29
|
attribute :footnote_pr, FootnotePr
|
|
58
30
|
attribute :endnote_pr, EndnotePr
|
|
59
31
|
attribute :hdr_shape_defaults, HdrShapeDefaults
|
|
32
|
+
attribute :update_fields, UpdateFields
|
|
60
33
|
attribute :even_and_odd_headers, EvenAndOddHeaders
|
|
61
34
|
attribute :mirror_margins, MirrorMargins
|
|
62
35
|
attribute :do_not_include_subdocs_in_stats,
|
|
@@ -112,9 +85,9 @@ module Uniword
|
|
|
112
85
|
map_element "doNotDisplayPageBoundaries",
|
|
113
86
|
to: :do_not_display_page_boundaries, render_nil: false
|
|
114
87
|
map_element "mirrorMargins", to: :mirror_margins, render_nil: false
|
|
88
|
+
map_element "proofState", to: :proof_state, render_nil: false
|
|
115
89
|
map_element "attachedTemplate", to: :attached_template,
|
|
116
90
|
render_nil: false
|
|
117
|
-
map_element "proofState", to: :proof_state, render_nil: false
|
|
118
91
|
map_element "stylePaneFormatFilter", to: :style_pane_format_filter,
|
|
119
92
|
render_nil: false
|
|
120
93
|
map_element "stylePaneSortMethod", to: :style_pane_sort_method,
|
|
@@ -126,6 +99,9 @@ module Uniword
|
|
|
126
99
|
render_nil: false
|
|
127
100
|
map_element "characterSpacingControl", to: :character_spacing_control,
|
|
128
101
|
render_nil: false
|
|
102
|
+
map_element "updateFields", to: :update_fields, render_nil: false
|
|
103
|
+
map_element "hdrShapeDefaults", to: :hdr_shape_defaults,
|
|
104
|
+
render_nil: false
|
|
129
105
|
map_element "footnotePr", to: :footnote_pr, render_nil: false
|
|
130
106
|
map_element "endnotePr", to: :endnote_pr, render_nil: false
|
|
131
107
|
map_element "compat", to: :compat, render_nil: false
|
|
@@ -137,11 +113,6 @@ module Uniword
|
|
|
137
113
|
render_nil: false
|
|
138
114
|
map_element "doNotIncludeSubdocsInStats",
|
|
139
115
|
to: :do_not_include_subdocs_in_stats, render_nil: false
|
|
140
|
-
map_element "hdrShapeDefaults", to: :hdr_shape_defaults,
|
|
141
|
-
render_nil: false
|
|
142
|
-
map_element "shapeDefaults", to: :shape_defaults, render_nil: false
|
|
143
|
-
map_element "decimalSymbol", to: :decimal_symbol, render_nil: false
|
|
144
|
-
map_element "listSeparator", to: :list_separator, render_nil: false
|
|
145
116
|
# Both w14:docId and w15:docId use the same element name 'docId'
|
|
146
117
|
# Separate map_element entries needed for namespace-aware matching
|
|
147
118
|
# The namespace_scope ensures w14 and w15 namespaces are declared on root
|
|
@@ -150,6 +121,9 @@ module Uniword
|
|
|
150
121
|
map_element "docId", to: :w14_doc_id, render_nil: false
|
|
151
122
|
map_element "docId", to: :w15_doc_id, render_nil: false
|
|
152
123
|
map_element "schemaLibrary", to: :schema_library, render_nil: false
|
|
124
|
+
map_element "shapeDefaults", to: :shape_defaults, render_nil: false
|
|
125
|
+
map_element "decimalSymbol", to: :decimal_symbol, render_nil: false
|
|
126
|
+
map_element "listSeparator", to: :list_separator, render_nil: false
|
|
153
127
|
end
|
|
154
128
|
|
|
155
129
|
# Override from_xml to manually deserialize w15:docId which has the same
|
|
@@ -49,7 +49,8 @@ module Uniword
|
|
|
49
49
|
map "run_properties", to: :rPr
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
# YAML transform methods (instance methods
|
|
52
|
+
# YAML transform methods (instance methods called by lutaml-model's
|
|
53
|
+
# `with:` transform mechanism)
|
|
53
54
|
def yaml_name_from(instance, value)
|
|
54
55
|
instance.name = StyleName.new(val: value) if value
|
|
55
56
|
end
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uniword
|
|
4
|
+
module Wordprocessingml
|
|
5
|
+
# Removes styles from a document's style definitions — Word's
|
|
6
|
+
# Styles-pane management as an API.
|
|
7
|
+
#
|
|
8
|
+
# `#remove_unused` deletes styles that no content references,
|
|
9
|
+
# directly (pStyle/rStyle/tblStyle in body, headers, footers, notes,
|
|
10
|
+
# comments) or transitively (basedOn/link/next chains from a used
|
|
11
|
+
# style, numbering level pStyle). Default styles (w:default="1")
|
|
12
|
+
# are never removed.
|
|
13
|
+
#
|
|
14
|
+
# @example Declutter a document
|
|
15
|
+
# cleanup = StyleCleanup.new(document)
|
|
16
|
+
# removed = cleanup.remove_unused # => ["ObsoleteStyle", ...]
|
|
17
|
+
class StyleCleanup
|
|
18
|
+
# @param document [DocumentRoot] Document to clean (mutated)
|
|
19
|
+
def initialize(document)
|
|
20
|
+
@document = document
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Remove one style by id (unless protected).
|
|
24
|
+
#
|
|
25
|
+
# @param style_id [String] Style id (w:styleId)
|
|
26
|
+
# @return [Boolean] true when the style was removed
|
|
27
|
+
def remove?(style_id)
|
|
28
|
+
return false if protected_id?(style_id)
|
|
29
|
+
|
|
30
|
+
before = styles.size
|
|
31
|
+
styles.delete_if { |style| style.styleId == style_id }
|
|
32
|
+
styles.size < before
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Ids of styles no content references.
|
|
36
|
+
#
|
|
37
|
+
# @return [Array<String>] Unreferenced, unprotected style ids
|
|
38
|
+
def unused_ids
|
|
39
|
+
used = used_ids
|
|
40
|
+
styles.filter_map(&:styleId).uniq - used - protected_ids
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Remove every unreferenced style.
|
|
44
|
+
#
|
|
45
|
+
# @return [Array<String>] Ids of removed styles
|
|
46
|
+
def remove_unused
|
|
47
|
+
ids = unused_ids
|
|
48
|
+
ids.each { |id| remove?(id) }
|
|
49
|
+
ids
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def styles
|
|
55
|
+
@document.styles_configuration.styles
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def protected_ids
|
|
59
|
+
styles.select(&:default).map(&:styleId)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def protected_id?(style_id)
|
|
63
|
+
protected_ids.include?(style_id)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Ids referenced anywhere: content, notes, headers/footers,
|
|
67
|
+
# comments, numbering levels, and style-to-style chains.
|
|
68
|
+
def used_ids
|
|
69
|
+
used = content_style_ids + chain_seed_ids
|
|
70
|
+
expand_chains(used)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def content_style_ids
|
|
74
|
+
ids = []
|
|
75
|
+
walk_all_paragraphs do |paragraph|
|
|
76
|
+
ids.concat(paragraph_style_ids(paragraph))
|
|
77
|
+
ids.concat(run_style_ids(paragraph))
|
|
78
|
+
end
|
|
79
|
+
ids.concat(table_style_ids)
|
|
80
|
+
ids.concat(numbering_style_ids)
|
|
81
|
+
ids
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Styles referenced by other styles (basedOn/link/next) — chains
|
|
85
|
+
# are expanded from these too.
|
|
86
|
+
def chain_seed_ids
|
|
87
|
+
styles.flat_map { |style| chain_links(style) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def chain_links(style)
|
|
91
|
+
[style.basedOn&.val, style.link&.val, style.nextStyle&.val].compact
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def expand_chains(ids)
|
|
95
|
+
expanded = ids.to_set
|
|
96
|
+
loop do
|
|
97
|
+
additions = chain_additions(expanded)
|
|
98
|
+
break if additions.empty?
|
|
99
|
+
|
|
100
|
+
additions.each { |id| expanded << id }
|
|
101
|
+
end
|
|
102
|
+
expanded.to_a
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def chain_additions(expanded)
|
|
106
|
+
styles.select { |style| expanded.include?(style.styleId) }
|
|
107
|
+
.flat_map { |style| chain_links(style) }
|
|
108
|
+
.reject { |id| expanded.include?(id) }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def paragraph_style_ids(paragraph)
|
|
112
|
+
Array(paragraph.properties&.style).filter_map(&:value)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def run_style_ids(paragraph)
|
|
116
|
+
paragraph_runs(paragraph)
|
|
117
|
+
.filter_map { |run| run.properties&.style&.value }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def paragraph_runs(paragraph)
|
|
121
|
+
(paragraph.runs || []) +
|
|
122
|
+
(paragraph.hyperlinks || []).flat_map(&:runs)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def table_style_ids
|
|
126
|
+
ids = []
|
|
127
|
+
walk_tables(@document.body) do |table|
|
|
128
|
+
ids << table.properties&.style&.val
|
|
129
|
+
end
|
|
130
|
+
ids.compact
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def numbering_style_ids
|
|
134
|
+
numbering_definitions.flat_map do |definition|
|
|
135
|
+
(definition.levels || []).filter_map { |level| level.pStyle&.val }
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def numbering_definitions
|
|
140
|
+
@document.numbering_configuration&.definitions || []
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Paragraphs in body (incl. table cells), headers/footers,
|
|
144
|
+
# notes, comments.
|
|
145
|
+
def walk_all_paragraphs(&block)
|
|
146
|
+
each_container { |container| walk_paragraphs(container, &block) }
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def walk_paragraphs(container, &block)
|
|
150
|
+
return unless container
|
|
151
|
+
|
|
152
|
+
(container.paragraphs || []).each(&block)
|
|
153
|
+
walk_tables(container) { |table| walk_table_rows(table, &block) }
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def walk_table_rows(table, &block)
|
|
157
|
+
(table.rows || []).each do |row|
|
|
158
|
+
(row.cells || []).each { |cell| walk_paragraphs(cell, &block) }
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def walk_tables(container, &block)
|
|
163
|
+
return unless container
|
|
164
|
+
|
|
165
|
+
(container.tables || []).each do |table|
|
|
166
|
+
yield table
|
|
167
|
+
walk_table_cells(table, &block)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def walk_table_cells(table, &block)
|
|
172
|
+
(table.rows || []).each do |row|
|
|
173
|
+
(row.cells || []).each { |cell| walk_tables(cell, &block) }
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def each_container(&block)
|
|
178
|
+
yield @document.body if @document.body
|
|
179
|
+
header_footer_containers.each(&block)
|
|
180
|
+
note_containers.each(&block)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def header_footer_containers
|
|
184
|
+
(@document.header_footer_parts || []).filter_map do |part|
|
|
185
|
+
part.content if part.content.is_a?(Lutaml::Model::Serializable)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def note_containers
|
|
190
|
+
[
|
|
191
|
+
@document.footnotes&.footnote_entries,
|
|
192
|
+
@document.endnotes&.endnote_entries,
|
|
193
|
+
@document.comments&.comments,
|
|
194
|
+
].flatten.compact
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
@@ -28,18 +28,18 @@ module Uniword
|
|
|
28
28
|
namespace Uniword::Ooxml::Namespaces::WordProcessingML
|
|
29
29
|
mixed_content
|
|
30
30
|
|
|
31
|
+
map_element "cnfStyle", to: :cnf_style, render_nil: false
|
|
31
32
|
map_element "tcW", to: :cell_width, render_nil: false
|
|
32
|
-
map_element "tcBorders", to: :borders, render_nil: false
|
|
33
|
-
map_element "shd", to: :shading, render_nil: false
|
|
34
|
-
map_element "vAlign", to: :vertical_align, render_nil: false
|
|
35
33
|
map_element "gridSpan", to: :grid_span, render_nil: false
|
|
36
34
|
map_element "vMerge", to: :v_merge, render_nil: false
|
|
37
|
-
map_element "
|
|
38
|
-
map_element "
|
|
35
|
+
map_element "tcBorders", to: :borders, render_nil: false
|
|
36
|
+
map_element "shd", to: :shading, render_nil: false
|
|
39
37
|
map_element "noWrap", to: :no_wrap, render_nil: false
|
|
40
|
-
map_element "
|
|
38
|
+
map_element "tcMar", to: :tc_mar, render_nil: false
|
|
41
39
|
map_element "textDirection", to: :text_direction,
|
|
42
40
|
render_nil: false
|
|
41
|
+
map_element "vAlign", to: :vertical_align, render_nil: false
|
|
42
|
+
map_element "hideMark", to: :hide_mark, render_nil: false
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
|
|
5
|
+
module Uniword
|
|
6
|
+
module Wordprocessingml
|
|
7
|
+
# Update fields when the document is opened
|
|
8
|
+
#
|
|
9
|
+
# Element: <w:updateFields>
|
|
10
|
+
#
|
|
11
|
+
# When true, Word updates all fields (TOC, page numbers, cross
|
|
12
|
+
# references) the first time the document is opened — generated
|
|
13
|
+
# documents show correct field values without a manual F9.
|
|
14
|
+
class UpdateFields < Lutaml::Model::Serializable
|
|
15
|
+
attribute :value, Ooxml::Types::OoxmlBoolean,
|
|
16
|
+
default: -> { true }
|
|
17
|
+
|
|
18
|
+
xml do
|
|
19
|
+
element "updateFields"
|
|
20
|
+
namespace Uniword::Ooxml::Namespaces::WordProcessingML
|
|
21
|
+
|
|
22
|
+
map_attribute "val", to: :value, render_nil: false,
|
|
23
|
+
render_default: false
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|