caracal 1.5.0 → 1.6.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/.github/workflows/push_gem.yml +29 -0
- data/CHANGELOG.md +22 -0
- data/README.md +72 -4
- data/caracal.gemspec +9 -3
- data/lib/caracal/core/footer.rb +50 -0
- data/lib/caracal/core/header.rb +49 -0
- data/lib/caracal/core/models/field_model.rb +131 -0
- data/lib/caracal/core/models/footer_model.rb +43 -0
- data/lib/caracal/core/models/header_model.rb +43 -0
- data/lib/caracal/core/models/iframe_model.rb +39 -4
- data/lib/caracal/core/models/image_model.rb +1 -1
- data/lib/caracal/core/models/list_item_model.rb +57 -3
- data/lib/caracal/core/models/list_model.rb +1 -7
- data/lib/caracal/core/models/list_style_model.rb +1 -1
- data/lib/caracal/core/models/page_number_model.rb +17 -2
- data/lib/caracal/core/models/paragraph_model.rb +16 -1
- data/lib/caracal/core/models/relationship_model.rb +1 -0
- data/lib/caracal/core/models/table_model.rb +1 -1
- data/lib/caracal/core/page_numbers.rb +5 -1
- data/lib/caracal/document.rb +98 -29
- data/lib/caracal/renderers/content_types_renderer.rb +3 -1
- data/lib/caracal/renderers/document_renderer.rb +47 -10
- data/lib/caracal/renderers/footer_renderer.rb +57 -50
- data/lib/caracal/renderers/header_footer_renderer.rb +90 -0
- data/lib/caracal/renderers/header_renderer.rb +28 -0
- data/lib/caracal/renderers/relationships_renderer.rb +22 -4
- data/lib/caracal/version.rb +1 -1
- data/lib/caracal.rb +16 -0
- data/spec/lib/caracal/core/footer_spec.rb +37 -0
- data/spec/lib/caracal/core/header_spec.rb +48 -0
- data/spec/lib/caracal/core/images_spec.rb +7 -0
- data/spec/lib/caracal/core/models/footer_model_spec.rb +41 -0
- data/spec/lib/caracal/core/models/header_model_spec.rb +41 -0
- data/spec/lib/caracal/core/models/image_model_spec.rb +1 -1
- data/spec/lib/caracal/core/models/list_item_model_spec.rb +18 -0
- data/spec/lib/caracal/core/models/list_style_model_spec.rb +1 -1
- data/spec/lib/caracal/core/models/option_keys_spec.rb +47 -0
- data/spec/lib/caracal/core/models/page_number_model_spec.rb +16 -1
- data/spec/lib/caracal/core/models/paragraph_model_spec.rb +22 -1
- data/spec/lib/caracal/core/models/table_model_spec.rb +1 -1
- data/spec/lib/caracal/core/relationships_spec.rb +1 -1
- data/spec/lib/caracal/document_output_spec.rb +503 -0
- metadata +35 -13
|
@@ -17,7 +17,7 @@ module Caracal
|
|
|
17
17
|
#-------------------------------------------------------------
|
|
18
18
|
|
|
19
19
|
# accessors
|
|
20
|
-
|
|
20
|
+
attr_writer :continuation
|
|
21
21
|
|
|
22
22
|
# readers (create aliases for superclass methods to conform
|
|
23
23
|
# to expected naming convention.)
|
|
@@ -54,6 +54,51 @@ module Caracal
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
|
|
57
|
+
#=============== NESTED LISTS =========================
|
|
58
|
+
|
|
59
|
+
# This method returns the nested lists in the order they were
|
|
60
|
+
# added, each with the number of runs that came before it, so text
|
|
61
|
+
# after a nested list can be rendered after it.
|
|
62
|
+
#
|
|
63
|
+
def nested_lists
|
|
64
|
+
@nested_lists ||= []
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def nested_list
|
|
68
|
+
(pair = nested_lists.last) && pair.last
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def nested_list=(model)
|
|
72
|
+
nested_lists << [runs.size, model]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# This method returns whether this is the text that follows a
|
|
76
|
+
# nested list, which renders as an unnumbered paragraph indented
|
|
77
|
+
# with the item.
|
|
78
|
+
#
|
|
79
|
+
def continuation?
|
|
80
|
+
!!@continuation
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# This method returns this item and everything nested in it in
|
|
84
|
+
# document order: the item's text up to its first nested list, the
|
|
85
|
+
# nested list's items, then any text that follows, and so on.
|
|
86
|
+
#
|
|
87
|
+
def recursive_items
|
|
88
|
+
return [self] if nested_lists.empty?
|
|
89
|
+
|
|
90
|
+
items = []
|
|
91
|
+
start = 0
|
|
92
|
+
nested_lists.each do |(index, list)|
|
|
93
|
+
items << segment(start...index, items.any?) if items.empty? || index > start
|
|
94
|
+
items.concat(list.recursive_items)
|
|
95
|
+
start = index
|
|
96
|
+
end
|
|
97
|
+
items << segment(start...runs.size, true) if runs.size > start
|
|
98
|
+
items
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
|
|
57
102
|
#=============== SUB-METHODS ===========================
|
|
58
103
|
|
|
59
104
|
# .ol
|
|
@@ -62,7 +107,7 @@ module Caracal
|
|
|
62
107
|
|
|
63
108
|
model = Caracal::Core::Models::ListModel.new(options, &block)
|
|
64
109
|
if model.valid?
|
|
65
|
-
|
|
110
|
+
self.nested_list = model
|
|
66
111
|
else
|
|
67
112
|
raise Caracal::Errors::InvalidModelError, 'Ordered lists require at least one list item.'
|
|
68
113
|
end
|
|
@@ -75,7 +120,7 @@ module Caracal
|
|
|
75
120
|
|
|
76
121
|
model = Caracal::Core::Models::ListModel.new(options, &block)
|
|
77
122
|
if model.valid?
|
|
78
|
-
|
|
123
|
+
self.nested_list = model
|
|
79
124
|
else
|
|
80
125
|
raise Caracal::Errors::InvalidModelError, 'Unordered lists require at least one list item.'
|
|
81
126
|
end
|
|
@@ -97,6 +142,15 @@ module Caracal
|
|
|
97
142
|
#-------------------------------------------------------------
|
|
98
143
|
private
|
|
99
144
|
|
|
145
|
+
# This method returns a copy of the item holding only the given runs.
|
|
146
|
+
def segment(range, continuation)
|
|
147
|
+
copy = dup
|
|
148
|
+
copy.instance_variable_set(:@runs, runs[range])
|
|
149
|
+
copy.instance_variable_set(:@nested_lists, [])
|
|
150
|
+
copy.continuation = continuation
|
|
151
|
+
copy
|
|
152
|
+
end
|
|
153
|
+
|
|
100
154
|
def option_keys
|
|
101
155
|
[:type, :level, :content, :style, :color, :size, :bold, :italic, :underline, :bgcolor]
|
|
102
156
|
end
|
|
@@ -61,13 +61,7 @@ module Caracal
|
|
|
61
61
|
# item within this list's tree.
|
|
62
62
|
#
|
|
63
63
|
def recursive_items
|
|
64
|
-
items.
|
|
65
|
-
if model.nested_list.nil?
|
|
66
|
-
model
|
|
67
|
-
else
|
|
68
|
-
[model, model.nested_list.recursive_items]
|
|
69
|
-
end
|
|
70
|
-
end.flatten
|
|
64
|
+
items.flat_map(&:recursive_items)
|
|
71
65
|
end
|
|
72
66
|
|
|
73
67
|
|
|
@@ -24,6 +24,8 @@ module Caracal
|
|
|
24
24
|
attr_reader :page_number_label_size
|
|
25
25
|
attr_reader :page_number_number_size
|
|
26
26
|
attr_reader :page_number_show
|
|
27
|
+
attr_reader :page_number_first
|
|
28
|
+
attr_reader :page_number_start
|
|
27
29
|
|
|
28
30
|
# initialization
|
|
29
31
|
def initialize(options={}, &block)
|
|
@@ -32,6 +34,8 @@ module Caracal
|
|
|
32
34
|
@page_number_label_size = nil
|
|
33
35
|
@page_number_number_size = nil
|
|
34
36
|
@page_number_show = DEFAULT_PAGE_NUMBER_SHOW
|
|
37
|
+
@page_number_first = true
|
|
38
|
+
@page_number_start = nil
|
|
35
39
|
|
|
36
40
|
super options, &block
|
|
37
41
|
end
|
|
@@ -65,6 +69,15 @@ module Caracal
|
|
|
65
69
|
@page_number_show = !!value
|
|
66
70
|
end
|
|
67
71
|
|
|
72
|
+
# false leaves the page number off the first page
|
|
73
|
+
def first_page(value)
|
|
74
|
+
@page_number_first = !!value
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def start(value)
|
|
78
|
+
@page_number_start = value.nil? ? nil : value.to_i
|
|
79
|
+
end
|
|
80
|
+
|
|
68
81
|
def size(value)
|
|
69
82
|
v = value.to_i
|
|
70
83
|
@page_number_label_size = (v == 0) ? nil : v
|
|
@@ -75,7 +88,9 @@ module Caracal
|
|
|
75
88
|
#=============== VALIDATION ===========================
|
|
76
89
|
|
|
77
90
|
def valid?
|
|
78
|
-
(!page_number_show || [:left, :center, :right].include?(page_number_align))
|
|
91
|
+
valid_align = (!page_number_show || [:left, :center, :right].include?(page_number_align))
|
|
92
|
+
valid_start = (page_number_start.nil? || page_number_start >= 0)
|
|
93
|
+
valid_align && valid_start
|
|
79
94
|
end
|
|
80
95
|
|
|
81
96
|
|
|
@@ -85,7 +100,7 @@ module Caracal
|
|
|
85
100
|
private
|
|
86
101
|
|
|
87
102
|
def option_keys
|
|
88
|
-
[:align, :label, :label_size, :number_size, :show]
|
|
103
|
+
[:align, :label, :label_size, :number_size, :show, :size, :start, :first_page]
|
|
89
104
|
end
|
|
90
105
|
|
|
91
106
|
end
|
|
@@ -2,6 +2,7 @@ require 'caracal/core/models/base_model'
|
|
|
2
2
|
require 'caracal/core/models/bookmark_model'
|
|
3
3
|
require 'caracal/core/models/link_model'
|
|
4
4
|
require 'caracal/core/models/text_model'
|
|
5
|
+
require 'caracal/core/models/field_model'
|
|
5
6
|
require 'caracal/errors'
|
|
6
7
|
|
|
7
8
|
|
|
@@ -127,6 +128,20 @@ module Caracal
|
|
|
127
128
|
model
|
|
128
129
|
end
|
|
129
130
|
|
|
131
|
+
# .field
|
|
132
|
+
def field(*args, &block)
|
|
133
|
+
options = Caracal::Utilities.extract_options!(args)
|
|
134
|
+
options = options.merge({ type: args[0] })
|
|
135
|
+
|
|
136
|
+
model = Caracal::Core::Models::FieldModel.new(options, &block)
|
|
137
|
+
if model.valid?
|
|
138
|
+
runs << model
|
|
139
|
+
else
|
|
140
|
+
raise Caracal::Errors::InvalidModelError, ':field method must receive :page or :numpages.'
|
|
141
|
+
end
|
|
142
|
+
model
|
|
143
|
+
end
|
|
144
|
+
|
|
130
145
|
# .link
|
|
131
146
|
def link(*args, &block)
|
|
132
147
|
options = Caracal::Utilities.extract_options!(args)
|
|
@@ -177,7 +192,7 @@ module Caracal
|
|
|
177
192
|
private
|
|
178
193
|
|
|
179
194
|
def option_keys
|
|
180
|
-
[:content, :style, :align, :color, :size, :bold, :italic, :underline, :bgcolor]
|
|
195
|
+
[:content, :style, :align, :color, :size, :bold, :italic, :underline, :bgcolor, :keep_next]
|
|
181
196
|
end
|
|
182
197
|
|
|
183
198
|
end
|
|
@@ -18,6 +18,7 @@ module Caracal
|
|
|
18
18
|
TYPE_MAP = {
|
|
19
19
|
font: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable',
|
|
20
20
|
footer: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer',
|
|
21
|
+
header: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/header',
|
|
21
22
|
image: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image',
|
|
22
23
|
link: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink',
|
|
23
24
|
numbering: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering',
|
|
@@ -232,7 +232,7 @@ module Caracal
|
|
|
232
232
|
k << [:data, :align, :width]
|
|
233
233
|
k << [:border_color, :border_line, :border_size, :border_spacing]
|
|
234
234
|
k << [:border_bottom, :border_left, :border_right, :border_top, :border_horizontal, :border_vertical]
|
|
235
|
-
k << [:column_widths]
|
|
235
|
+
k << [:column_widths, :header_rows]
|
|
236
236
|
k.flatten
|
|
237
237
|
end
|
|
238
238
|
|
|
@@ -25,6 +25,8 @@ module Caracal
|
|
|
25
25
|
attr_reader :page_number_label_size
|
|
26
26
|
attr_reader :page_number_number_size
|
|
27
27
|
attr_reader :page_number_show
|
|
28
|
+
attr_reader :page_number_first
|
|
29
|
+
attr_reader :page_number_start
|
|
28
30
|
|
|
29
31
|
|
|
30
32
|
#-------------------------------------------------------------
|
|
@@ -45,8 +47,10 @@ module Caracal
|
|
|
45
47
|
@page_number_label_size = model.page_number_label_size
|
|
46
48
|
@page_number_number_size = model.page_number_number_size
|
|
47
49
|
@page_number_show = model.page_number_show
|
|
50
|
+
@page_number_first = model.page_number_first
|
|
51
|
+
@page_number_start = model.page_number_start
|
|
48
52
|
else
|
|
49
|
-
raise Caracal::Errors::InvalidModelError, 'page_numbers :align
|
|
53
|
+
raise Caracal::Errors::InvalidModelError, 'page_numbers :align must be :left, :center, or :right, and :start must not be negative.'
|
|
50
54
|
end
|
|
51
55
|
end
|
|
52
56
|
|
data/lib/caracal/document.rb
CHANGED
|
@@ -4,6 +4,8 @@ require 'caracal/core/bookmarks'
|
|
|
4
4
|
require 'caracal/core/custom_properties'
|
|
5
5
|
require 'caracal/core/file_name'
|
|
6
6
|
require 'caracal/core/fonts'
|
|
7
|
+
require 'caracal/core/footer'
|
|
8
|
+
require 'caracal/core/header'
|
|
7
9
|
require 'caracal/core/iframes'
|
|
8
10
|
require 'caracal/core/ignorables'
|
|
9
11
|
require 'caracal/core/images'
|
|
@@ -26,6 +28,7 @@ require 'caracal/renderers/custom_renderer'
|
|
|
26
28
|
require 'caracal/renderers/document_renderer'
|
|
27
29
|
require 'caracal/renderers/fonts_renderer'
|
|
28
30
|
require 'caracal/renderers/footer_renderer'
|
|
31
|
+
require 'caracal/renderers/header_renderer'
|
|
29
32
|
require 'caracal/renderers/numbering_renderer'
|
|
30
33
|
require 'caracal/renderers/package_relationships_renderer'
|
|
31
34
|
require 'caracal/renderers/relationships_renderer'
|
|
@@ -62,6 +65,9 @@ module Caracal
|
|
|
62
65
|
include Caracal::Core::Tables
|
|
63
66
|
include Caracal::Core::Text
|
|
64
67
|
|
|
68
|
+
include Caracal::Core::Footer
|
|
69
|
+
include Caracal::Core::Header
|
|
70
|
+
|
|
65
71
|
|
|
66
72
|
#------------------------------------------------------
|
|
67
73
|
# Public Class Methods
|
|
@@ -128,6 +134,49 @@ module Caracal
|
|
|
128
134
|
@contents ||= []
|
|
129
135
|
end
|
|
130
136
|
|
|
137
|
+
# A header or footer part to write: which kind it is, which pages it
|
|
138
|
+
# covers ('default' or 'first'), the content to render, whether to
|
|
139
|
+
# add the page number, and whether the section refers to it.
|
|
140
|
+
#
|
|
141
|
+
HeaderFooterPart = Struct.new(:kind, :type, :target, :model, :page_number, :referenced)
|
|
142
|
+
|
|
143
|
+
# This method returns the header and footer parts the document needs.
|
|
144
|
+
# footer1.xml is always written, as it always has been, but is only
|
|
145
|
+
# referenced when it has something to show.
|
|
146
|
+
#
|
|
147
|
+
def header_footer_parts
|
|
148
|
+
parts = []
|
|
149
|
+
if header_content
|
|
150
|
+
parts << HeaderFooterPart.new(:header, 'default', 'header1.xml', header_content, false, true)
|
|
151
|
+
end
|
|
152
|
+
shown = page_number_show || !footer_content.nil?
|
|
153
|
+
parts << HeaderFooterPart.new(:footer, 'default', 'footer1.xml', footer_content, page_number_show || footer_content.nil?, shown)
|
|
154
|
+
|
|
155
|
+
if title_page?
|
|
156
|
+
# The first page uses only first-page parts, so fall back to the
|
|
157
|
+
# default content for whichever one wasn't customized.
|
|
158
|
+
first_header = first_header_content || header_content
|
|
159
|
+
if first_header && first_header.valid?
|
|
160
|
+
parts << HeaderFooterPart.new(:header, 'first', 'header2.xml', first_header, false, true)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
first_footer = first_footer_content || footer_content
|
|
164
|
+
first_footer = nil unless first_footer && first_footer.valid?
|
|
165
|
+
first_number = page_number_show && page_number_first
|
|
166
|
+
if first_footer || first_number
|
|
167
|
+
parts << HeaderFooterPart.new(:footer, 'first', 'footer2.xml', first_footer, first_number, true)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
parts
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# This method returns whether the first page has its own header and
|
|
174
|
+
# footer (Word's "Different First Page").
|
|
175
|
+
#
|
|
176
|
+
def title_page?
|
|
177
|
+
!first_header_content.nil? || !first_footer_content.nil? || (page_number_show && !page_number_first)
|
|
178
|
+
end
|
|
179
|
+
|
|
131
180
|
|
|
132
181
|
#============ RENDERING ===============================
|
|
133
182
|
|
|
@@ -136,6 +185,9 @@ module Caracal
|
|
|
136
185
|
#
|
|
137
186
|
def render
|
|
138
187
|
register_nested_iframes(contents)
|
|
188
|
+
header_footer_parts.each do |part|
|
|
189
|
+
relationship({ target: part.target, type: part.kind })
|
|
190
|
+
end
|
|
139
191
|
|
|
140
192
|
buffer = ::Zip::OutputStream.write_buffer do |zip|
|
|
141
193
|
render_package_relationships(zip)
|
|
@@ -144,7 +196,7 @@ module Caracal
|
|
|
144
196
|
render_core(zip)
|
|
145
197
|
render_custom(zip)
|
|
146
198
|
render_fonts(zip)
|
|
147
|
-
|
|
199
|
+
render_headers_and_footers(zip)
|
|
148
200
|
render_settings(zip)
|
|
149
201
|
render_styles(zip)
|
|
150
202
|
render_document(zip)
|
|
@@ -193,53 +245,65 @@ module Caracal
|
|
|
193
245
|
|
|
194
246
|
#============ RENDERERS ===============================
|
|
195
247
|
|
|
248
|
+
# This method adds a part to the package. Giving rubyzip the size up
|
|
249
|
+
# front stops rubyzip 3 from adding a zip64 extra field "just in case"
|
|
250
|
+
# to every entry, which LibreOffice 7.4 and older can't read.
|
|
251
|
+
#
|
|
252
|
+
def write_entry(zip, name, content)
|
|
253
|
+
content = content.to_s
|
|
254
|
+
entry = ::Zip::Entry.new('', name)
|
|
255
|
+
entry.size = content.bytesize
|
|
256
|
+
|
|
257
|
+
zip.put_next_entry(entry)
|
|
258
|
+
zip.write(content)
|
|
259
|
+
end
|
|
260
|
+
|
|
196
261
|
def render_app(zip)
|
|
197
262
|
content = ::Caracal::Renderers::AppRenderer.render(self)
|
|
198
263
|
|
|
199
|
-
zip
|
|
200
|
-
zip.write(content)
|
|
264
|
+
write_entry(zip, 'docProps/app.xml', content)
|
|
201
265
|
end
|
|
202
266
|
|
|
203
267
|
def render_content_types(zip)
|
|
204
268
|
content = ::Caracal::Renderers::ContentTypesRenderer.render(self)
|
|
205
269
|
|
|
206
|
-
zip
|
|
207
|
-
zip.write(content)
|
|
270
|
+
write_entry(zip, '[Content_Types].xml', content)
|
|
208
271
|
end
|
|
209
272
|
|
|
210
273
|
def render_core(zip)
|
|
211
274
|
content = ::Caracal::Renderers::CoreRenderer.render(self)
|
|
212
275
|
|
|
213
|
-
zip
|
|
214
|
-
zip.write(content)
|
|
276
|
+
write_entry(zip, 'docProps/core.xml', content)
|
|
215
277
|
end
|
|
216
278
|
|
|
217
279
|
def render_custom(zip)
|
|
218
280
|
content = ::Caracal::Renderers::CustomRenderer.render(self)
|
|
219
281
|
|
|
220
|
-
zip
|
|
221
|
-
zip.write(content)
|
|
282
|
+
write_entry(zip, 'docProps/custom.xml', content)
|
|
222
283
|
end
|
|
223
284
|
|
|
224
285
|
def render_document(zip)
|
|
225
286
|
content = ::Caracal::Renderers::DocumentRenderer.render(self)
|
|
226
287
|
|
|
227
|
-
zip
|
|
228
|
-
zip.write(content)
|
|
288
|
+
write_entry(zip, 'word/document.xml', content)
|
|
229
289
|
end
|
|
230
290
|
|
|
231
291
|
def render_fonts(zip)
|
|
232
292
|
content = ::Caracal::Renderers::FontsRenderer.render(self)
|
|
233
293
|
|
|
234
|
-
zip
|
|
235
|
-
zip.write(content)
|
|
294
|
+
write_entry(zip, 'word/fontTable.xml', content)
|
|
236
295
|
end
|
|
237
296
|
|
|
238
|
-
def
|
|
239
|
-
|
|
297
|
+
def render_headers_and_footers(zip)
|
|
298
|
+
header_footer_parts.each do |part|
|
|
299
|
+
renderer = (part.kind == :header) ? ::Caracal::Renderers::HeaderRenderer : ::Caracal::Renderers::FooterRenderer
|
|
300
|
+
content = renderer.render(self, part.model, part.page_number)
|
|
240
301
|
|
|
241
|
-
|
|
242
|
-
|
|
302
|
+
write_entry(zip, "word/#{ part.target }", content)
|
|
303
|
+
|
|
304
|
+
# rendering collects the relationships, so write them straight away
|
|
305
|
+
render_part_relationships(zip, part.target, part.model)
|
|
306
|
+
end
|
|
243
307
|
end
|
|
244
308
|
|
|
245
309
|
def render_media(zip)
|
|
@@ -251,44 +315,49 @@ module Caracal
|
|
|
251
315
|
content = ::Caracal::Utilities.read_resource(rel.relationship_target)
|
|
252
316
|
end
|
|
253
317
|
|
|
254
|
-
zip
|
|
255
|
-
zip.write(content)
|
|
318
|
+
write_entry(zip, "word/#{ rel.formatted_target }", content)
|
|
256
319
|
end
|
|
257
320
|
end
|
|
258
321
|
|
|
259
322
|
def render_numbering(zip)
|
|
260
323
|
content = ::Caracal::Renderers::NumberingRenderer.render(self)
|
|
261
324
|
|
|
262
|
-
zip
|
|
263
|
-
zip.write(content)
|
|
325
|
+
write_entry(zip, 'word/numbering.xml', content)
|
|
264
326
|
end
|
|
265
327
|
|
|
266
328
|
def render_package_relationships(zip)
|
|
267
329
|
content = ::Caracal::Renderers::PackageRelationshipsRenderer.render(self)
|
|
268
330
|
|
|
269
|
-
zip
|
|
270
|
-
zip.write(content)
|
|
331
|
+
write_entry(zip, '_rels/.rels', content)
|
|
271
332
|
end
|
|
272
333
|
|
|
273
334
|
def render_relationships(zip)
|
|
274
335
|
content = ::Caracal::Renderers::RelationshipsRenderer.render(self)
|
|
275
336
|
|
|
276
|
-
zip
|
|
277
|
-
|
|
337
|
+
write_entry(zip, 'word/_rels/document.xml.rels', content)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Headers and footers resolve their images and links against their
|
|
341
|
+
# own relationships part, not the document's.
|
|
342
|
+
#
|
|
343
|
+
def render_part_relationships(zip, part, model)
|
|
344
|
+
return if model.nil? || model.relationships.empty?
|
|
345
|
+
|
|
346
|
+
content = ::Caracal::Renderers::RelationshipsRenderer.render(self, model.relationships)
|
|
347
|
+
|
|
348
|
+
write_entry(zip, "word/_rels/#{ part }.rels", content)
|
|
278
349
|
end
|
|
279
350
|
|
|
280
351
|
def render_settings(zip)
|
|
281
352
|
content = ::Caracal::Renderers::SettingsRenderer.render(self)
|
|
282
353
|
|
|
283
|
-
zip
|
|
284
|
-
zip.write(content)
|
|
354
|
+
write_entry(zip, 'word/settings.xml', content)
|
|
285
355
|
end
|
|
286
356
|
|
|
287
357
|
def render_styles(zip)
|
|
288
358
|
content = ::Caracal::Renderers::StylesRenderer.render(self)
|
|
289
359
|
|
|
290
|
-
zip
|
|
291
|
-
zip.write(content)
|
|
360
|
+
write_entry(zip, 'word/styles.xml', content)
|
|
292
361
|
end
|
|
293
362
|
|
|
294
363
|
end
|
|
@@ -31,7 +31,9 @@ module Caracal
|
|
|
31
31
|
xml.send 'Override', { 'PartName' => '/docProps/core.xml', 'ContentType' => 'application/vnd.openxmlformats-package.core-properties+xml' }
|
|
32
32
|
xml.send 'Override', { 'PartName' => '/docProps/custom.xml', 'ContentType' => 'application/vnd.openxmlformats-officedocument.custom-properties+xml' }
|
|
33
33
|
xml.send 'Override', { 'PartName' => '/word/document.xml', 'ContentType' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml' }
|
|
34
|
-
|
|
34
|
+
document.header_footer_parts.each do |part|
|
|
35
|
+
xml.send 'Override', { 'PartName' => "/word/#{ part.target }", 'ContentType' => "application/vnd.openxmlformats-officedocument.wordprocessingml.#{ part.kind }+xml" }
|
|
36
|
+
end
|
|
35
37
|
xml.send 'Override', { 'PartName' => '/word/fontTable.xml', 'ContentType' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml' }
|
|
36
38
|
xml.send 'Override', { 'PartName' => '/word/numbering.xml', 'ContentType' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml' }
|
|
37
39
|
xml.send 'Override', { 'PartName' => '/word/settings.xml', 'ContentType' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml' }
|
|
@@ -31,13 +31,17 @@ module Caracal
|
|
|
31
31
|
#============= PAGE SETTINGS ==============================
|
|
32
32
|
|
|
33
33
|
xml['w'].sectPr do
|
|
34
|
-
|
|
35
|
-
if rel = document.find_relationship(
|
|
36
|
-
xml['w'].
|
|
34
|
+
document.header_footer_parts.select(&:referenced).each do |part|
|
|
35
|
+
if rel = document.find_relationship(part.target)
|
|
36
|
+
xml['w'].send("#{ part.kind }Reference", { 'r:id' => rel.formatted_id, 'w:type' => part.type })
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
xml['w'].pgSz page_size_options
|
|
40
40
|
xml['w'].pgMar page_margin_options
|
|
41
|
+
unless document.page_number_start.nil?
|
|
42
|
+
xml['w'].pgNumType({ 'w:start' => document.page_number_start })
|
|
43
|
+
end
|
|
44
|
+
xml['w'].titlePg if document.title_page?
|
|
41
45
|
end
|
|
42
46
|
|
|
43
47
|
end
|
|
@@ -63,6 +67,13 @@ module Caracal
|
|
|
63
67
|
"render_#{ type }"
|
|
64
68
|
end
|
|
65
69
|
|
|
70
|
+
# This method registers a relationship needed by the part being
|
|
71
|
+
# rendered. Images and links in the body belong to the document.
|
|
72
|
+
#
|
|
73
|
+
def relationship(options)
|
|
74
|
+
document.relationship(options)
|
|
75
|
+
end
|
|
76
|
+
|
|
66
77
|
# This method renders a standard node of run properties based on the
|
|
67
78
|
# model's attributes.
|
|
68
79
|
#
|
|
@@ -113,7 +124,7 @@ module Caracal
|
|
|
113
124
|
r_href = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'
|
|
114
125
|
|
|
115
126
|
entry = zip.glob('word/document.xml').first
|
|
116
|
-
content = entry
|
|
127
|
+
content = Caracal::Core::Models::IFrameModel.read_entry(entry)
|
|
117
128
|
doc_xml = Nokogiri::XML(content)
|
|
118
129
|
|
|
119
130
|
fragment = doc_xml.xpath('//w:body').first.children
|
|
@@ -145,7 +156,7 @@ module Caracal
|
|
|
145
156
|
raise Caracal::Errors::NoDefaultStyleError 'Document must declare a default paragraph style.'
|
|
146
157
|
end
|
|
147
158
|
|
|
148
|
-
rel =
|
|
159
|
+
rel = relationship({ type: :image, target: model.image_url, data: model.image_data })
|
|
149
160
|
rel_id = rel.relationship_id
|
|
150
161
|
rel_name = rel.formatted_target
|
|
151
162
|
|
|
@@ -205,7 +216,7 @@ module Caracal
|
|
|
205
216
|
|
|
206
217
|
def render_link(xml, model)
|
|
207
218
|
if model.external?
|
|
208
|
-
rel =
|
|
219
|
+
rel = relationship({ target: model.link_href, type: :link })
|
|
209
220
|
hyperlink_options = { 'r:id' => rel.formatted_id }
|
|
210
221
|
else
|
|
211
222
|
hyperlink_options = { 'w:anchor' => model.link_href }
|
|
@@ -219,6 +230,27 @@ module Caracal
|
|
|
219
230
|
end
|
|
220
231
|
end
|
|
221
232
|
|
|
233
|
+
# A complex field is a sequence of runs: begin, instruction, separate
|
|
234
|
+
# and end. Viewers that don't evaluate fields show the instruction
|
|
235
|
+
# text when the field characters aren't in runs of their own.
|
|
236
|
+
#
|
|
237
|
+
def render_field(xml, model)
|
|
238
|
+
xml['w'].r run_options do
|
|
239
|
+
render_run_attributes(xml, model, false)
|
|
240
|
+
xml['w'].fldChar({ 'w:fldCharType' => 'begin' })
|
|
241
|
+
end
|
|
242
|
+
xml['w'].r run_options do
|
|
243
|
+
render_run_attributes(xml, model, false)
|
|
244
|
+
xml['w'].instrText({ 'xml:space' => 'preserve' }, " #{ model.formatted_type } ")
|
|
245
|
+
end
|
|
246
|
+
%w(separate end).each do |type|
|
|
247
|
+
xml['w'].r run_options do
|
|
248
|
+
render_run_attributes(xml, model, false)
|
|
249
|
+
xml['w'].fldChar({ 'w:fldCharType' => type })
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
222
254
|
def render_list(xml, model)
|
|
223
255
|
if model.list_level == 0
|
|
224
256
|
document.toplevel_lists << model
|
|
@@ -236,11 +268,16 @@ module Caracal
|
|
|
236
268
|
|
|
237
269
|
xml['w'].p paragraph_options do
|
|
238
270
|
xml['w'].pPr do
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
xml['w'].
|
|
271
|
+
if model.continuation?
|
|
272
|
+
# text after a nested list: no number, aligned with the item's text
|
|
273
|
+
xml['w'].ind({ 'w:left' => ls.style_left })
|
|
274
|
+
else
|
|
275
|
+
xml['w'].numPr do
|
|
276
|
+
xml['w'].ilvl({ 'w:val' => model.list_item_level })
|
|
277
|
+
xml['w'].numId({ 'w:val' => list_num })
|
|
278
|
+
end
|
|
279
|
+
xml['w'].ind({ 'w:left' => ls.style_left, 'w:hanging' => hanging })
|
|
242
280
|
end
|
|
243
|
-
xml['w'].ind({ 'w:left' => ls.style_left, 'w:hanging' => hanging })
|
|
244
281
|
xml['w'].contextualSpacing({ 'w:val' => '1' })
|
|
245
282
|
xml['w'].rPr do
|
|
246
283
|
xml['w'].u({ 'w:val' => 'none' })
|