uniword 1.2.5 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +313 -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 +22 -14
- 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 +11 -10
- 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 +12 -18
- 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 +91 -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/image_part.rb +83 -0
- data/lib/uniword/docx/package.rb +141 -333
- data/lib/uniword/docx/package_defaults.rb +83 -99
- data/lib/uniword/docx/package_integrity_checker.rb +312 -0
- data/lib/uniword/docx/package_serialization.rb +144 -233
- data/lib/uniword/docx/part.rb +125 -0
- data/lib/uniword/docx/part_collection.rb +131 -0
- data/lib/uniword/docx/part_loader/chart_loader.rb +50 -0
- data/lib/uniword/docx/part_loader/custom_xml_loader.rb +53 -0
- data/lib/uniword/docx/part_loader/embedding_loader.rb +26 -0
- data/lib/uniword/docx/part_loader/header_footer_loader.rb +101 -0
- data/lib/uniword/docx/part_loader/image_loader.rb +105 -0
- data/lib/uniword/docx/part_loader/load_context.rb +85 -0
- data/lib/uniword/docx/part_loader/raw_part_loader.rb +183 -0
- data/lib/uniword/docx/part_loader/theme_media_loader.rb +39 -0
- data/lib/uniword/docx/part_loader/xml_model_loader.rb +56 -0
- data/lib/uniword/docx/part_loader.rb +92 -0
- data/lib/uniword/docx/raw_part.rb +75 -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 +18 -31
- data/lib/uniword/docx/reconciler/notes.rb +17 -8
- data/lib/uniword/docx/reconciler/package_structure.rb +168 -221
- data/lib/uniword/docx/reconciler/parts.rb +36 -13
- data/lib/uniword/docx/reconciler/referential_integrity.rb +222 -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 +13 -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/images/image_manager.rb +13 -13
- 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/element_order.rb +94 -0
- data/lib/uniword/ooxml/part_definition.rb +329 -0
- data/lib/uniword/ooxml/part_registry.rb +504 -0
- data/lib/uniword/ooxml/relationships/image_relationship.rb +6 -3
- 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 +3 -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 +6 -5
- data/lib/uniword/transformation/mhtml_element_renderer.rb +1 -5
- data/lib/uniword/transformation/mhtml_metadata_builder.rb +6 -6
- data/lib/uniword/validation/engine.rb +35 -0
- data/lib/uniword/validation/link_checker.rb +1 -1
- data/lib/uniword/validation/opc_validator.rb +16 -2
- 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 +46 -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 +121 -122
- data/lib/uniword/wordprocessingml/document_styling.rb +246 -0
- data/lib/uniword/wordprocessingml/endnotes.rb +4 -1
- data/lib/uniword/wordprocessingml/font_replacer.rb +147 -0
- data/lib/uniword/wordprocessingml/footnotes.rb +4 -1
- 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/numbering_elements.rb +0 -15
- 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 +5 -2
- data/lib/uniword.rb +27 -2
- metadata +52 -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,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uniword
|
|
4
|
+
module Wordprocessingml
|
|
5
|
+
# Replaces one font family with another across a document —
|
|
6
|
+
# Word's Home → Replace → Replace Fonts as an API.
|
|
7
|
+
#
|
|
8
|
+
# Covers style definitions and defaults, body content (paragraphs,
|
|
9
|
+
# tables, hyperlinks), headers and footers, footnotes, endnotes,
|
|
10
|
+
# comments, and numbering levels. Theme font references
|
|
11
|
+
# (asciiTheme/hAnsiTheme/...) are NOT rewritten — those are
|
|
12
|
+
# controlled by the theme's font scheme
|
|
13
|
+
# (see DocumentRoot#apply_font_scheme).
|
|
14
|
+
#
|
|
15
|
+
# @example Replace Calibri with Carlito throughout a document
|
|
16
|
+
# replacer = FontReplacer.new(from: "Calibri", to: "Carlito")
|
|
17
|
+
# replacer.replace(document)
|
|
18
|
+
# puts replacer.count # number of rFonts values rewritten
|
|
19
|
+
class FontReplacer
|
|
20
|
+
# RunFonts attributes rewritten by font replacement.
|
|
21
|
+
FONT_ATTRIBUTES = %i[ascii h_ansi east_asia cs].freeze
|
|
22
|
+
|
|
23
|
+
# Create a replacer.
|
|
24
|
+
#
|
|
25
|
+
# @param from [String] Font family to replace (exact match,
|
|
26
|
+
# Word semantics)
|
|
27
|
+
# @param to [String] Replacement font family
|
|
28
|
+
def initialize(from:, to:)
|
|
29
|
+
@from = from.to_s
|
|
30
|
+
@to = to.to_s
|
|
31
|
+
@count = 0
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Number of rFonts attribute values replaced so far.
|
|
35
|
+
#
|
|
36
|
+
# @return [Integer] Replacement count
|
|
37
|
+
attr_reader :count
|
|
38
|
+
|
|
39
|
+
# Replace @from with @to throughout the document (mutated).
|
|
40
|
+
#
|
|
41
|
+
# @param document [DocumentRoot] Document to rewrite
|
|
42
|
+
# @return [Integer] Replacement count
|
|
43
|
+
def replace(document)
|
|
44
|
+
replace_in_styles(document.styles_configuration)
|
|
45
|
+
replace_in_numbering(document.numbering_configuration)
|
|
46
|
+
replace_in_container(document.body)
|
|
47
|
+
part_containers(document).each do |container|
|
|
48
|
+
replace_in_paragraphs(container)
|
|
49
|
+
end
|
|
50
|
+
count
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
# Styles: docDefaults and every style definition.
|
|
56
|
+
def replace_in_styles(styles)
|
|
57
|
+
return unless styles
|
|
58
|
+
|
|
59
|
+
replace_in_run_properties(styles.doc_defaults&.rPrDefault&.rPr)
|
|
60
|
+
(styles.styles || []).each do |style|
|
|
61
|
+
replace_in_run_properties(style.rPr)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Numbering: every level of every abstract definition.
|
|
66
|
+
def replace_in_numbering(numbering)
|
|
67
|
+
return unless numbering
|
|
68
|
+
|
|
69
|
+
(numbering.definitions || []).each do |definition|
|
|
70
|
+
(definition.levels || []).each do |level|
|
|
71
|
+
replace_in_run_properties(level.rPr)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Containers outside the body: header/footer parts, note
|
|
77
|
+
# entries, and comments — model objects holding paragraphs.
|
|
78
|
+
def part_containers(document)
|
|
79
|
+
header_footer_containers(document) +
|
|
80
|
+
note_containers(document) + comment_containers(document)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def header_footer_containers(document)
|
|
84
|
+
(document.header_footer_parts || []).filter_map do |part|
|
|
85
|
+
part.content if part.content.is_a?(Lutaml::Model::Serializable)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def note_containers(document)
|
|
90
|
+
(document.footnotes&.footnote_entries || []) +
|
|
91
|
+
(document.endnotes&.endnote_entries || [])
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def comment_containers(document)
|
|
95
|
+
document.comments&.comments || []
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Walk a container's paragraphs, recursing into (nested) tables.
|
|
99
|
+
def replace_in_container(container)
|
|
100
|
+
return unless container
|
|
101
|
+
|
|
102
|
+
replace_in_paragraphs(container)
|
|
103
|
+
(container.tables || []).each { |table| replace_in_table(table) }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def replace_in_table(table)
|
|
107
|
+
(table.rows || []).each do |row|
|
|
108
|
+
(row.cells || []).each { |cell| replace_in_container(cell) }
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Walk only paragraphs — for note/comment/header/footer parts,
|
|
113
|
+
# whose models do not expose nested tables.
|
|
114
|
+
def replace_in_paragraphs(container)
|
|
115
|
+
(container.paragraphs || []).each do |paragraph|
|
|
116
|
+
replace_in_paragraph(paragraph)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def replace_in_paragraph(paragraph)
|
|
121
|
+
replace_in_runs(paragraph.runs)
|
|
122
|
+
(paragraph.hyperlinks || []).each do |hyperlink|
|
|
123
|
+
replace_in_runs(hyperlink.runs)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def replace_in_runs(runs)
|
|
128
|
+
(runs || []).each do |run|
|
|
129
|
+
replace_in_run_properties(run.properties)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Rewrite matching RunFonts attribute values in place.
|
|
134
|
+
def replace_in_run_properties(run_properties)
|
|
135
|
+
fonts = run_properties&.fonts
|
|
136
|
+
return unless fonts
|
|
137
|
+
|
|
138
|
+
FONT_ATTRIBUTES.each do |attr|
|
|
139
|
+
next unless fonts.method(attr).call == @from
|
|
140
|
+
|
|
141
|
+
fonts.method(:"#{attr}=").call(@to)
|
|
142
|
+
@count += 1
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -32,8 +32,11 @@ module Uniword
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
missing = footnote_entries.size - counts["footnote"]
|
|
35
|
+
return unless missing.positive?
|
|
36
|
+
|
|
37
|
+
order = Ooxml::ElementOrder.mutable_order(self)
|
|
35
38
|
missing.times do
|
|
36
|
-
|
|
39
|
+
order << Lutaml::Xml::Element.new("Element", "footnote")
|
|
37
40
|
end
|
|
38
41
|
end
|
|
39
42
|
|
|
@@ -17,20 +17,6 @@ module Uniword
|
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
# Tabs container for numbering level
|
|
21
|
-
#
|
|
22
|
-
# Element: <w:tabs>
|
|
23
|
-
class Tabs < Lutaml::Model::Serializable
|
|
24
|
-
attribute :tab, Tab, collection: true
|
|
25
|
-
|
|
26
|
-
xml do
|
|
27
|
-
element "tabs"
|
|
28
|
-
namespace Uniword::Ooxml::Namespaces::WordProcessingML
|
|
29
|
-
mixed_content
|
|
30
|
-
map_element "tab", to: :tab, render_nil: false
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
20
|
# Numbering level suffix type
|
|
35
21
|
#
|
|
36
22
|
# Element: <w:suff>
|
|
@@ -63,6 +49,10 @@ module Uniword
|
|
|
63
49
|
#
|
|
64
50
|
# Generated from OOXML schema: wordprocessingml.yml
|
|
65
51
|
# Element: <w:lvl>
|
|
52
|
+
#
|
|
53
|
+
# Per ECMA-376 CT_Lvl, `w:ind` and `w:tabs` are NOT direct children of
|
|
54
|
+
# `w:lvl` — they belong to the level's `w:pPr`. They are therefore not
|
|
55
|
+
# mapped here; use `pPr.indentation` / `pPr.tabs`.
|
|
66
56
|
class Level < Lutaml::Model::Serializable
|
|
67
57
|
attribute :ilvl, :integer
|
|
68
58
|
attribute :tentative, :string
|
|
@@ -75,8 +65,6 @@ module Uniword
|
|
|
75
65
|
attribute :lvlText, LvlText
|
|
76
66
|
attribute :lvlJc, LvlJc
|
|
77
67
|
attribute :pPr, ParagraphProperties
|
|
78
|
-
attribute :tabs, Tabs
|
|
79
|
-
attribute :ind, Ind
|
|
80
68
|
attribute :rPr, RunProperties
|
|
81
69
|
|
|
82
70
|
xml do
|
|
@@ -95,8 +83,6 @@ module Uniword
|
|
|
95
83
|
map_element "lvlText", to: :lvlText, render_nil: false
|
|
96
84
|
map_element "lvlJc", to: :lvlJc, render_nil: false
|
|
97
85
|
map_element "pPr", to: :pPr, render_nil: false
|
|
98
|
-
map_element "tabs", to: :tabs, render_nil: false
|
|
99
|
-
map_element "ind", to: :ind, render_nil: false
|
|
100
86
|
map_element "rPr", to: :rPr, render_nil: false
|
|
101
87
|
end
|
|
102
88
|
|
|
@@ -122,12 +108,12 @@ module Uniword
|
|
|
122
108
|
|
|
123
109
|
# Get left indent (convenience method - returns integer)
|
|
124
110
|
def left_indent_value
|
|
125
|
-
|
|
111
|
+
pPr&.indentation&.left.to_i
|
|
126
112
|
end
|
|
127
113
|
|
|
128
114
|
# Get hanging indent (convenience method - returns integer)
|
|
129
115
|
def hanging_indent_value
|
|
130
|
-
|
|
116
|
+
pPr&.indentation&.hanging.to_i
|
|
131
117
|
end
|
|
132
118
|
|
|
133
119
|
# Get level text value (convenience method - returns string)
|
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "lutaml/model"
|
|
4
|
-
require_relative "math_font"
|
|
5
|
-
require_relative "brk_bin"
|
|
6
|
-
require_relative "brk_bin_sub"
|
|
7
|
-
require_relative "small_frac"
|
|
8
|
-
require_relative "disp_def"
|
|
9
|
-
require_relative "l_margin"
|
|
10
|
-
require_relative "r_margin"
|
|
11
|
-
require_relative "def_jc"
|
|
12
|
-
require_relative "wrap_indent"
|
|
13
|
-
require_relative "int_lim"
|
|
14
|
-
require_relative "nary_lim"
|
|
15
4
|
|
|
16
5
|
module Uniword
|
|
17
6
|
module Wordprocessingml
|
|
@@ -80,8 +80,10 @@ module Uniword
|
|
|
80
80
|
start: Uniword::Wordprocessingml::Start.new(val: 1),
|
|
81
81
|
numFmt: Uniword::Wordprocessingml::NumFmt.new(val: "decimal"),
|
|
82
82
|
lvlJc: Uniword::Wordprocessingml::LvlJc.new(val: "left"),
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
pPr: Uniword::Wordprocessingml::ParagraphProperties.new(
|
|
84
|
+
indentation: Uniword::Properties::Indentation.new(
|
|
85
|
+
left: 720 * (i + 1), hanging: 360,
|
|
86
|
+
),
|
|
85
87
|
),
|
|
86
88
|
)
|
|
87
89
|
end
|
|
@@ -104,8 +106,10 @@ module Uniword
|
|
|
104
106
|
numFmt: Uniword::Wordprocessingml::NumFmt.new(val: "bullet"),
|
|
105
107
|
lvlText: Uniword::Wordprocessingml::LvlText.new(val: level_char),
|
|
106
108
|
lvlJc: Uniword::Wordprocessingml::LvlJc.new(val: "left"),
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
pPr: Uniword::Wordprocessingml::ParagraphProperties.new(
|
|
110
|
+
indentation: Uniword::Properties::Indentation.new(
|
|
111
|
+
left: 720 * (i + 1), hanging: 360,
|
|
112
|
+
),
|
|
109
113
|
),
|
|
110
114
|
rPr: Uniword::Wordprocessingml::RunProperties.new(
|
|
111
115
|
fonts: Properties::RunFonts.new(ascii: "Symbol",
|
|
@@ -126,8 +130,10 @@ module Uniword
|
|
|
126
130
|
start: Uniword::Wordprocessingml::Start.new(val: 1),
|
|
127
131
|
numFmt: Uniword::Wordprocessingml::NumFmt.new(val: format),
|
|
128
132
|
lvlJc: Uniword::Wordprocessingml::LvlJc.new(val: "left"),
|
|
129
|
-
|
|
130
|
-
|
|
133
|
+
pPr: Uniword::Wordprocessingml::ParagraphProperties.new(
|
|
134
|
+
indentation: Uniword::Properties::Indentation.new(
|
|
135
|
+
left: 720 * (i + 1), hanging: 360,
|
|
136
|
+
),
|
|
131
137
|
),
|
|
132
138
|
)
|
|
133
139
|
end
|
|
@@ -144,8 +150,10 @@ module Uniword
|
|
|
144
150
|
start: Uniword::Wordprocessingml::Start.new(val: 1),
|
|
145
151
|
numFmt: Uniword::Wordprocessingml::NumFmt.new(val: format),
|
|
146
152
|
lvlJc: Uniword::Wordprocessingml::LvlJc.new(val: "left"),
|
|
147
|
-
|
|
148
|
-
|
|
153
|
+
pPr: Uniword::Wordprocessingml::ParagraphProperties.new(
|
|
154
|
+
indentation: Uniword::Properties::Indentation.new(
|
|
155
|
+
left: 720 * (i + 1), hanging: 360,
|
|
156
|
+
),
|
|
149
157
|
),
|
|
150
158
|
)
|
|
151
159
|
end
|
|
@@ -30,21 +30,6 @@ module Uniword
|
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
# Indentation for numbering level
|
|
34
|
-
#
|
|
35
|
-
# Element: <w:ind>
|
|
36
|
-
class Ind < Lutaml::Model::Serializable
|
|
37
|
-
attribute :left, :string
|
|
38
|
-
attribute :hanging, :string
|
|
39
|
-
|
|
40
|
-
xml do
|
|
41
|
-
element "ind"
|
|
42
|
-
namespace Uniword::Ooxml::Namespaces::WordProcessingML
|
|
43
|
-
map_attribute "left", to: :left
|
|
44
|
-
map_attribute "hanging", to: :hanging
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
33
|
# Run fonts for numbering
|
|
49
34
|
#
|
|
50
35
|
# Element: <w:rFonts>
|
|
@@ -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
|