uniword 1.3.0 → 1.4.1
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 +90 -0
- data/lib/uniword/builder/chart_builder.rb +3 -5
- data/lib/uniword/builder/image_builder.rb +5 -5
- data/lib/uniword/document_factory.rb +10 -24
- data/lib/uniword/docx/custom_xml_item.rb +8 -0
- data/lib/uniword/docx/image_part.rb +83 -0
- data/lib/uniword/docx/package.rb +22 -402
- data/lib/uniword/docx/package_defaults.rb +36 -32
- data/lib/uniword/docx/package_serialization.rb +46 -7
- data/lib/uniword/docx/part.rb +19 -0
- data/lib/uniword/docx/part_collection.rb +15 -7
- 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/helpers.rb +9 -30
- data/lib/uniword/docx/reconciler/package_structure.rb +4 -5
- data/lib/uniword/docx/reconciler/referential_integrity.rb +4 -79
- data/lib/uniword/docx.rb +3 -0
- data/lib/uniword/images/image_manager.rb +13 -13
- data/lib/uniword/ooxml/element_order.rb +94 -0
- data/lib/uniword/ooxml/part_definition.rb +164 -6
- data/lib/uniword/ooxml/part_registry.rb +238 -45
- data/lib/uniword/ooxml/relationships/image_relationship.rb +5 -2
- data/lib/uniword/ooxml.rb +1 -0
- data/lib/uniword/toc/toc_generator.rb +6 -7
- data/lib/uniword/transformation/mhtml_element_renderer.rb +1 -5
- data/lib/uniword/transformation/mhtml_metadata_builder.rb +6 -6
- data/lib/uniword/validation/opc_validator.rb +16 -1
- data/lib/uniword/validation/validators/xml_schema_validator.rb +46 -0
- data/lib/uniword/version.rb +1 -1
- data/lib/uniword/wordprocessingml/document_root.rb +29 -220
- data/lib/uniword/wordprocessingml/document_styling.rb +246 -0
- data/lib/uniword/wordprocessingml/endnotes.rb +2 -3
- data/lib/uniword/wordprocessingml/footnotes.rb +2 -3
- data/lib/uniword/wordprocessingml/numbering_elements.rb +0 -15
- data/lib/uniword/wordprocessingml.rb +1 -2
- metadata +16 -2
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Uniword
|
|
4
|
+
module Wordprocessingml
|
|
5
|
+
# Theme and style application for DocumentRoot — Word's
|
|
6
|
+
# Design/Layout/Styles surfaces as document-level API.
|
|
7
|
+
#
|
|
8
|
+
# Extracted from DocumentRoot to keep the document model focused:
|
|
9
|
+
# bundled and file-based theme application, stylesets, font and
|
|
10
|
+
# color schemes, font replacement, page setup, and style
|
|
11
|
+
# management (rename/remove).
|
|
12
|
+
module DocumentStyling
|
|
13
|
+
# Applies a Uniword theme by name, updating doc defaults and
|
|
14
|
+
# built-in heading/hyperlink styles to reference the theme.
|
|
15
|
+
#
|
|
16
|
+
# @param name [String, Symbol] Theme slug (e.g., 'meridian',
|
|
17
|
+
# 'corporate')
|
|
18
|
+
# @param options [Hash] optional overrides
|
|
19
|
+
# @option options [Hash] :colors override specific color keys
|
|
20
|
+
# @option options [String] :major_font override major font
|
|
21
|
+
# @option options [String] :minor_font override minor font
|
|
22
|
+
# @return [self] For method chaining
|
|
23
|
+
def apply_theme(name, **options)
|
|
24
|
+
friendly = Themes::Theme.load(name.to_s)
|
|
25
|
+
|
|
26
|
+
options[:colors]&.each do |key, value|
|
|
27
|
+
friendly.color_scheme[key.to_s] = value
|
|
28
|
+
end
|
|
29
|
+
if options[:major_font]
|
|
30
|
+
friendly.font_scheme.major_font = options[:major_font]
|
|
31
|
+
end
|
|
32
|
+
if options[:minor_font]
|
|
33
|
+
friendly.font_scheme.minor_font = options[:minor_font]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
word_theme = Themes::ThemeTransformation.new.to_word(friendly)
|
|
37
|
+
Themes::ThemeApplicator.new.apply(word_theme, self)
|
|
38
|
+
self
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Apply theme from .thmx file
|
|
42
|
+
#
|
|
43
|
+
# @param path [String] Path to .thmx file
|
|
44
|
+
# @param variant [String, Integer, nil] Optional variant
|
|
45
|
+
# @return [self] For method chaining
|
|
46
|
+
def apply_theme_file(path, variant: nil)
|
|
47
|
+
loader = Themes::ThemeLoader.new
|
|
48
|
+
self.theme = if variant
|
|
49
|
+
loader.load_with_variant(path, variant)
|
|
50
|
+
else
|
|
51
|
+
loader.load(path)
|
|
52
|
+
end
|
|
53
|
+
self
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Apply StyleSet to document
|
|
57
|
+
#
|
|
58
|
+
# @param name [String, Symbol] StyleSet slug (e.g., 'signature',
|
|
59
|
+
# 'heritage')
|
|
60
|
+
# @param strategy [Symbol] Application strategy (:keep_existing,
|
|
61
|
+
# :replace, :rename)
|
|
62
|
+
# @return [self] For method chaining
|
|
63
|
+
def apply_styleset(name, strategy: :keep_existing)
|
|
64
|
+
styleset = Uniword::Stylesets::YamlStyleSetLoader
|
|
65
|
+
.load_bundled(name.to_s)
|
|
66
|
+
styleset.apply_to(self, strategy: strategy)
|
|
67
|
+
self
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Apply a bundled font scheme to the document's theme
|
|
71
|
+
#
|
|
72
|
+
# Mirrors Word's Design → Fonts gallery: replaces only the theme's
|
|
73
|
+
# fontScheme — colors and formats are untouched. Creates the theme
|
|
74
|
+
# part from the bundled Office theme when the document has none.
|
|
75
|
+
#
|
|
76
|
+
# @param name [String, Symbol] Font scheme slug (data/font_schemes/)
|
|
77
|
+
# @return [self] For method chaining
|
|
78
|
+
# @raise [ArgumentError] if the scheme is unknown
|
|
79
|
+
def apply_font_scheme(name)
|
|
80
|
+
fonts = Resource::FontSchemeLoader.load(name.to_s)
|
|
81
|
+
transformation = Themes::ThemeTransformation.new
|
|
82
|
+
ensure_theme!(transformation)
|
|
83
|
+
theme.theme_elements.font_scheme =
|
|
84
|
+
transformation.build_font_scheme(fonts)
|
|
85
|
+
self
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Apply a bundled color scheme to the document's theme
|
|
89
|
+
#
|
|
90
|
+
# Mirrors Word's Design → Colors gallery: replaces only the theme's
|
|
91
|
+
# clrScheme — fonts and formats are untouched. Creates the theme
|
|
92
|
+
# part from the bundled Office theme when the document has none.
|
|
93
|
+
#
|
|
94
|
+
# @param name [String, Symbol] Color scheme slug (data/color_schemes/)
|
|
95
|
+
# @return [self] For method chaining
|
|
96
|
+
# @raise [ArgumentError] if the scheme is unknown
|
|
97
|
+
def apply_color_scheme(name)
|
|
98
|
+
colors = Resource::ColorSchemeLoader.load(name.to_s)
|
|
99
|
+
transformation = Themes::ThemeTransformation.new
|
|
100
|
+
ensure_theme!(transformation)
|
|
101
|
+
theme.theme_elements.clr_scheme =
|
|
102
|
+
transformation.build_color_scheme(colors)
|
|
103
|
+
self
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Replace one font family with another across the document
|
|
107
|
+
#
|
|
108
|
+
# Mirrors Word's Home → Replace → Replace Fonts: rewrites rFonts
|
|
109
|
+
# references in styles and defaults, body content, headers,
|
|
110
|
+
# footers, notes, comments, and numbering. Theme font references
|
|
111
|
+
# (asciiTheme etc.) are untouched — use #apply_font_scheme for
|
|
112
|
+
# those.
|
|
113
|
+
#
|
|
114
|
+
# @param from [String] Font family to replace (exact match)
|
|
115
|
+
# @param to [String] Replacement font family
|
|
116
|
+
# @return [Integer] Number of rFonts attribute values rewritten
|
|
117
|
+
def replace_font(from:, to:)
|
|
118
|
+
replacer = FontReplacer.new(from: from, to: to)
|
|
119
|
+
replacer.replace(self)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Apply uniform page setup to every section of the document
|
|
123
|
+
#
|
|
124
|
+
# Mirrors Word's Layout dialog: named paper sizes, orientation
|
|
125
|
+
# (with Word-style dimension swap), and margins.
|
|
126
|
+
#
|
|
127
|
+
# @param size [String, nil] Paper size: letter, legal, a4, a5,
|
|
128
|
+
# executive
|
|
129
|
+
# @param orientation [String, nil] "portrait" or "landscape"
|
|
130
|
+
# @param margins [Integer, String, nil] Uniform margin for all
|
|
131
|
+
# sides (twips, or "1in" / "2.5cm" / "25mm")
|
|
132
|
+
# @param top, right, bottom, left [Integer, String, nil] Per-side
|
|
133
|
+
# margin overrides
|
|
134
|
+
# @return [Integer] Number of sections updated
|
|
135
|
+
def apply_page_setup(size: nil, orientation: nil, margins: nil,
|
|
136
|
+
top: nil, right: nil, bottom: nil, left: nil)
|
|
137
|
+
setup = PageSetup.new(size: size, orientation: orientation,
|
|
138
|
+
margins: margins, top: top, right: right,
|
|
139
|
+
bottom: bottom, left: left)
|
|
140
|
+
setup.apply(self)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Remove one style from the document's style definitions
|
|
144
|
+
#
|
|
145
|
+
# Default styles (w:default="1") are never removed.
|
|
146
|
+
#
|
|
147
|
+
# @param style_id [String] Style id (w:styleId)
|
|
148
|
+
# @return [Style, nil] The removed style, or nil when not
|
|
149
|
+
# removed (absent or protected)
|
|
150
|
+
def remove_style(style_id)
|
|
151
|
+
style = styles_configuration.style(style_id)
|
|
152
|
+
return unless StyleCleanup.new(self).remove?(style_id)
|
|
153
|
+
|
|
154
|
+
style
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Rename a style's display name (w:name) — Word's style rename.
|
|
158
|
+
# References (pStyle/rStyle/tblStyle) target the styleId, so they
|
|
159
|
+
# keep working unchanged.
|
|
160
|
+
#
|
|
161
|
+
# @param identifier [String] Style id (w:styleId) or current
|
|
162
|
+
# display name (w:name)
|
|
163
|
+
# @param new_name [String] New display name
|
|
164
|
+
# @return [Style, nil] The renamed style, or nil when not found
|
|
165
|
+
def rename_style(identifier, new_name)
|
|
166
|
+
style = styles_configuration.style(identifier)
|
|
167
|
+
return unless style
|
|
168
|
+
|
|
169
|
+
style.name = StyleName.new(val: new_name)
|
|
170
|
+
style
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Remove every style that no content references — Word's
|
|
174
|
+
# Styles-pane decluttering as one call. Default styles are kept.
|
|
175
|
+
#
|
|
176
|
+
# @return [Array<String>] Ids of removed styles
|
|
177
|
+
def remove_unused_styles
|
|
178
|
+
StyleCleanup.new(self).remove_unused
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Auto-transition from MS theme to Uniword equivalent
|
|
182
|
+
#
|
|
183
|
+
# Detects the MS theme in the document's embedded theme and replaces
|
|
184
|
+
# it with the corresponding Uniword theme (font-substituted, renamed).
|
|
185
|
+
#
|
|
186
|
+
# @return [Hash, nil] { uniword_slug:, ms_name: } or nil if no match
|
|
187
|
+
#
|
|
188
|
+
# @example
|
|
189
|
+
# result = doc.auto_transition_theme
|
|
190
|
+
# puts "Transitioned from " \
|
|
191
|
+
# "#{result[:ms_name]} to #{result[:uniword_slug]}"
|
|
192
|
+
def auto_transition_theme
|
|
193
|
+
Resource::ThemeTransition.auto_transition!(self)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Apply theme from another document
|
|
197
|
+
#
|
|
198
|
+
# @param source_path [String] Path to source .docx file
|
|
199
|
+
# @return [self] For method chaining
|
|
200
|
+
def apply_theme_from(source_path)
|
|
201
|
+
source_doc = Uniword.load(source_path)
|
|
202
|
+
self.theme = source_doc.theme.dup if source_doc.theme
|
|
203
|
+
self
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Apply styles from another document
|
|
207
|
+
#
|
|
208
|
+
# @param source_path [String] Path to source .docx file
|
|
209
|
+
# @param strategy [Symbol] Conflict resolution strategy
|
|
210
|
+
# @return [self] For method chaining
|
|
211
|
+
def apply_styles_from(source_path, strategy: :keep_existing)
|
|
212
|
+
source_doc = Uniword.load(source_path)
|
|
213
|
+
styles_configuration.merge(source_doc.styles_configuration,
|
|
214
|
+
conflict_resolution: strategy)
|
|
215
|
+
self
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Apply both theme and styles from a template document
|
|
219
|
+
#
|
|
220
|
+
# @param template_path [String] Path to template .docx file
|
|
221
|
+
# @param strategy [Symbol] Conflict resolution strategy for styles
|
|
222
|
+
# @return [self] For method chaining
|
|
223
|
+
def apply_template(template_path, strategy: :keep_existing)
|
|
224
|
+
template_doc = Uniword.load(template_path)
|
|
225
|
+
self.theme = template_doc.theme.dup if template_doc.theme
|
|
226
|
+
styles_configuration.merge(template_doc.styles_configuration,
|
|
227
|
+
conflict_resolution: strategy)
|
|
228
|
+
self
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
private
|
|
232
|
+
|
|
233
|
+
# Ensure the document carries a theme part, creating it from a
|
|
234
|
+
# fresh parse of the bundled Office theme when absent (every
|
|
235
|
+
# document gets its own copy — no shared state).
|
|
236
|
+
#
|
|
237
|
+
# @param transformation [Themes::ThemeTransformation] Converter
|
|
238
|
+
# @return [void]
|
|
239
|
+
def ensure_theme!(transformation)
|
|
240
|
+
return if theme&.theme_elements
|
|
241
|
+
|
|
242
|
+
self.theme = transformation.default_office_theme
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
@@ -34,10 +34,9 @@ module Uniword
|
|
|
34
34
|
missing = endnote_entries.size - counts["endnote"]
|
|
35
35
|
return unless missing.positive?
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
self.element_order = element_order.dup
|
|
37
|
+
order = Ooxml::ElementOrder.mutable_order(self)
|
|
39
38
|
missing.times do
|
|
40
|
-
|
|
39
|
+
order << Lutaml::Xml::Element.new("Element", "endnote")
|
|
41
40
|
end
|
|
42
41
|
end
|
|
43
42
|
|
|
@@ -34,10 +34,9 @@ module Uniword
|
|
|
34
34
|
missing = footnote_entries.size - counts["footnote"]
|
|
35
35
|
return unless missing.positive?
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
self.element_order = element_order.dup
|
|
37
|
+
order = Ooxml::ElementOrder.mutable_order(self)
|
|
39
38
|
missing.times do
|
|
40
|
-
|
|
39
|
+
order << Lutaml::Xml::Element.new("Element", "footnote")
|
|
41
40
|
end
|
|
42
41
|
end
|
|
43
42
|
|
|
@@ -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>
|
|
@@ -4,6 +4,7 @@ module Uniword
|
|
|
4
4
|
module Wordprocessingml
|
|
5
5
|
# Core document structure
|
|
6
6
|
autoload :DocumentRoot, "uniword/wordprocessingml/document_root"
|
|
7
|
+
autoload :DocumentStyling, "uniword/wordprocessingml/document_styling"
|
|
7
8
|
autoload :Body, "uniword/wordprocessingml/body"
|
|
8
9
|
autoload :Paragraph, "uniword/wordprocessingml/paragraph"
|
|
9
10
|
autoload :Run, "uniword/wordprocessingml/run"
|
|
@@ -165,9 +166,7 @@ module Uniword
|
|
|
165
166
|
autoload :NumFmt, "uniword/wordprocessingml/num_fmt"
|
|
166
167
|
autoload :LvlText, "uniword/wordprocessingml/lvl_text"
|
|
167
168
|
autoload :LvlJc, "uniword/wordprocessingml/lvl_jc"
|
|
168
|
-
autoload :Ind, "uniword/wordprocessingml/numbering_elements"
|
|
169
169
|
autoload :RFonts, "uniword/wordprocessingml/numbering_elements"
|
|
170
|
-
autoload :Tabs, "uniword/wordprocessingml/level"
|
|
171
170
|
autoload :PStyle, "uniword/wordprocessingml/level"
|
|
172
171
|
autoload :NumberingConfiguration,
|
|
173
172
|
"uniword/wordprocessingml/numbering_configuration"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: uniword
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: logger
|
|
@@ -822,13 +822,25 @@ files:
|
|
|
822
822
|
- lib/uniword/docx/header_footer_part_collection.rb
|
|
823
823
|
- lib/uniword/docx/header_footer_view.rb
|
|
824
824
|
- lib/uniword/docx/id_allocator.rb
|
|
825
|
+
- lib/uniword/docx/image_part.rb
|
|
825
826
|
- lib/uniword/docx/package.rb
|
|
826
827
|
- lib/uniword/docx/package_defaults.rb
|
|
827
828
|
- lib/uniword/docx/package_integrity_checker.rb
|
|
828
829
|
- lib/uniword/docx/package_serialization.rb
|
|
829
830
|
- lib/uniword/docx/part.rb
|
|
830
831
|
- lib/uniword/docx/part_collection.rb
|
|
832
|
+
- lib/uniword/docx/part_loader.rb
|
|
833
|
+
- lib/uniword/docx/part_loader/chart_loader.rb
|
|
834
|
+
- lib/uniword/docx/part_loader/custom_xml_loader.rb
|
|
835
|
+
- lib/uniword/docx/part_loader/embedding_loader.rb
|
|
836
|
+
- lib/uniword/docx/part_loader/header_footer_loader.rb
|
|
837
|
+
- lib/uniword/docx/part_loader/image_loader.rb
|
|
838
|
+
- lib/uniword/docx/part_loader/load_context.rb
|
|
839
|
+
- lib/uniword/docx/part_loader/raw_part_loader.rb
|
|
840
|
+
- lib/uniword/docx/part_loader/theme_media_loader.rb
|
|
841
|
+
- lib/uniword/docx/part_loader/xml_model_loader.rb
|
|
831
842
|
- lib/uniword/docx/profile.rb
|
|
843
|
+
- lib/uniword/docx/raw_part.rb
|
|
832
844
|
- lib/uniword/docx/reconciler.rb
|
|
833
845
|
- lib/uniword/docx/reconciler/body.rb
|
|
834
846
|
- lib/uniword/docx/reconciler/fix.rb
|
|
@@ -1109,6 +1121,7 @@ files:
|
|
|
1109
1121
|
- lib/uniword/ooxml/core_properties.rb
|
|
1110
1122
|
- lib/uniword/ooxml/custom_properties.rb
|
|
1111
1123
|
- lib/uniword/ooxml/dotx_package.rb
|
|
1124
|
+
- lib/uniword/ooxml/element_order.rb
|
|
1112
1125
|
- lib/uniword/ooxml/namespaces.rb
|
|
1113
1126
|
- lib/uniword/ooxml/package_file.rb
|
|
1114
1127
|
- lib/uniword/ooxml/part_definition.rb
|
|
@@ -1669,6 +1682,7 @@ files:
|
|
|
1669
1682
|
- lib/uniword/wordprocessingml/doc_vars.rb
|
|
1670
1683
|
- lib/uniword/wordprocessingml/document_root.rb
|
|
1671
1684
|
- lib/uniword/wordprocessingml/document_root/feature_facade.rb
|
|
1685
|
+
- lib/uniword/wordprocessingml/document_styling.rb
|
|
1672
1686
|
- lib/uniword/wordprocessingml/drawing.rb
|
|
1673
1687
|
- lib/uniword/wordprocessingml/emboss.rb
|
|
1674
1688
|
- lib/uniword/wordprocessingml/endnote.rb
|