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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c06f9a2d1fafe9fc3cbc91db5bab3d08a374ef0b688ff188a200e175f0c72073
|
|
4
|
+
data.tar.gz: 7dd9194ecb1a0d84e9e5953a334f0fc5754fbd696305e8fa21a8563f95c2a3fa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 56a868f230d4ae1ad7ab96f1e001aca3efac537026cf3e23f66936e18f8779e692fccc52c1118a55b25a5750e8aeda51e7e347c16b37264c598682cc9aba0748
|
|
7
|
+
data.tar.gz: '02099d9534ed1c0d950028f7dc937e47394082d2f05a4909f554a68a052f1d58f8ed84be30a55f2e91a15e240dc805b65b2e42ac1104b6989965b797ad573f98'
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Push gem
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
push:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
id-token: write
|
|
15
|
+
|
|
16
|
+
environment: release
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v5
|
|
20
|
+
with:
|
|
21
|
+
persist-credentials: false
|
|
22
|
+
|
|
23
|
+
- name: Set up Ruby
|
|
24
|
+
uses: ruby/setup-ruby@v1
|
|
25
|
+
with:
|
|
26
|
+
bundler-cache: true
|
|
27
|
+
ruby-version: ruby
|
|
28
|
+
|
|
29
|
+
- uses: rubygems/release-gem@v1
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
#### v1.6.0
|
|
2
|
+
|
|
3
|
+
* Enhancements
|
|
4
|
+
* Added `header` and `footer`, which accept the same content as the document body: paragraphs, images, tables and links. Images and links resolve through each part's own relationships. (@benjaminketron, @jamesridgway, @dlauer)
|
|
5
|
+
* Added the `field` paragraph method, which inserts a `:page` or `:numpages` field, so a footer can read "Page 2 of 7". (@benjaminketron, @jamesridgway, @dlauer)
|
|
6
|
+
* Added a different first page: `header first: true` and `footer first: true` give the first page its own header or footer, or leave it blank when called without a block, and `page_numbers first_page: false` leaves the number off the first page. (@dlauer)
|
|
7
|
+
* Added the `start` option to `page_numbers`, which sets the number of the first page. (@dlauer)
|
|
8
|
+
* `img` now accepts `data:` and `ppi:` as options, not only inside its block. Previously the options were silently dropped and the image was read from the path instead. (@victorpolko, @dlauer)
|
|
9
|
+
* `keep_next`, `header_rows`, the list style `restart` and the page number `size` are likewise accepted as options. (@dlauer)
|
|
10
|
+
* Entries read from documents embedded with `iframe` are capped by `IFrameModel.max_entry_size` (50MB by default), so a small upload cannot expand into an arbitrarily large allocation. (@dlauer)
|
|
11
|
+
* Raised the nokogiri floor to 1.16.2 and added source, changelog and issue tracker links to the gem metadata. (@dlauer)
|
|
12
|
+
|
|
13
|
+
* Changes
|
|
14
|
+
* `footer` and `page_numbers` now combine, with the page number rendered below the footer content. Previously `page_numbers` replaced the footer.
|
|
15
|
+
* `header` and `footer` raise `InvalidModelError` when given an empty block.
|
|
16
|
+
|
|
17
|
+
* Bug Fixes
|
|
18
|
+
* Page numbers are rendered as a complete complex field, so viewers that do not evaluate fields, such as WPS Office, show the number rather than "PAGE". (@dlauer)
|
|
19
|
+
* Text added to a list item after a nested list now appears after that list, aligned with the item's text, rather than before it. An item can also hold more than one nested list. (@dlauer)
|
|
20
|
+
* Documents written with rubyzip 3 no longer carry zip64 records, which LibreOffice 7.4 and earlier refuse to open. (@dlauer)
|
|
21
|
+
|
|
22
|
+
|
|
1
23
|
#### v1.5.0
|
|
2
24
|
|
|
3
25
|
* Enhancements
|
data/README.md
CHANGED
|
@@ -321,14 +321,61 @@ Page numbers can be added to the footer via the `page_numbers` method. The meth
|
|
|
321
321
|
docx.page_numbers true do
|
|
322
322
|
align :right # sets the alignment. accepts :left, :center, and :right.
|
|
323
323
|
label 'Page' # sets the text that will go to the left of the page number. Defaults to nil.
|
|
324
|
-
size
|
|
325
|
-
label_size
|
|
326
|
-
number_size
|
|
324
|
+
size 24 # sets the label and number size simultaneously. units in half points.
|
|
325
|
+
label_size 24 # sets the label size only. units in half points.
|
|
326
|
+
number_size 20 # sets the number size only. units in half points.
|
|
327
327
|
end
|
|
328
328
|
```
|
|
329
329
|
|
|
330
330
|
The `size` option and the `label_size` and `number_size` options are mutually exclusive.
|
|
331
331
|
|
|
332
|
+
The `start` option sets the number of the first page, which is useful when the document continues another one, or has a cover page you don't want counted. It also applies to `field :page` in a custom footer, so it can be set without showing the built-in page number:
|
|
333
|
+
|
|
334
|
+
```ruby
|
|
335
|
+
docx.page_numbers true, start: 0 # the cover page is page 0, so the next page is page 1
|
|
336
|
+
docx.page_numbers start: 12 # number from 12 without the built-in page number
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Headers and Footers
|
|
340
|
+
|
|
341
|
+
A header or footer that appears on every page can be added with the `header` and `footer` methods. Their blocks accept the same paragraph, list, table, rule and image methods as the document body.
|
|
342
|
+
|
|
343
|
+
```ruby
|
|
344
|
+
docx.header do
|
|
345
|
+
img 'logo.png', width: 120, height: 32, align: :right
|
|
346
|
+
p 'Quarterly Report', align: :center
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
docx.footer do
|
|
350
|
+
p align: :center do
|
|
351
|
+
text 'Page '
|
|
352
|
+
field :page
|
|
353
|
+
text ' of '
|
|
354
|
+
field :numpages
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
If you also turn on `page_numbers`, the page number is rendered below the footer content.
|
|
360
|
+
|
|
361
|
+
*Headers and footers must contain content. Calling `header` or `footer` with an empty block raises an `InvalidModelError`.*
|
|
362
|
+
|
|
363
|
+
#### Different First Page
|
|
364
|
+
|
|
365
|
+
Pass `first: true` to give the first page its own header or footer, such as a cover page. Without a block, it leaves the first page's header or footer blank. Use `first_page: false` to leave the page number off the first page.
|
|
366
|
+
|
|
367
|
+
```ruby
|
|
368
|
+
docx.header first: true do # replaces the header on the first page only
|
|
369
|
+
p 'Annual Report 2026', align: :center, size: 36
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
docx.header first: true # no header on the first page
|
|
373
|
+
docx.footer first: true # no footer content on the first page
|
|
374
|
+
|
|
375
|
+
docx.page_numbers true, first_page: false # no page number on the first page
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
When the first page has its own header or footer, any part you don't set for it falls back to the default. So `first_page: false` on its own hides only the page number, and the default header and footer content still appear.
|
|
332
379
|
|
|
333
380
|
### Fonts
|
|
334
381
|
|
|
@@ -446,6 +493,16 @@ docx.p do
|
|
|
446
493
|
end
|
|
447
494
|
```
|
|
448
495
|
|
|
496
|
+
Fields that Word fills in can be added with the `field` method. It accepts `:page` (the current page number) and `:numpages` (the total number of pages), along with the same formatting options as `text`.
|
|
497
|
+
|
|
498
|
+
```ruby
|
|
499
|
+
docx.p do
|
|
500
|
+
text 'Page '
|
|
501
|
+
field :page, bold: true
|
|
502
|
+
text ' of '
|
|
503
|
+
field :numpages
|
|
504
|
+
end
|
|
505
|
+
```
|
|
449
506
|
|
|
450
507
|
### Links
|
|
451
508
|
|
|
@@ -575,7 +632,7 @@ docx.ul do
|
|
|
575
632
|
end
|
|
576
633
|
```
|
|
577
634
|
|
|
578
|
-
Lists can nested as many levels deep as you wish and mixed in any combination.
|
|
635
|
+
Lists can be nested as many levels deep as you wish and mixed in any combination. An item can contain more than one nested list. Text that follows a nested list renders after it, as an unnumbered paragraph aligned with the item's text.
|
|
579
636
|
|
|
580
637
|
```ruby
|
|
581
638
|
docx.ul do
|
|
@@ -624,6 +681,7 @@ Images can be added by using the `img` method. The method accepts several optio
|
|
|
624
681
|
```ruby
|
|
625
682
|
docx.img 'https://www.example.com/logo.png' do
|
|
626
683
|
data raw_data # sets the file data directly instead of opening the url
|
|
684
|
+
ppi 72 # sets the pixels per inch used to convert width and height. defaults to 72.
|
|
627
685
|
width 396 # sets the image width. units specified in pixels.
|
|
628
686
|
height 216 # sets the image height. units specified in pixels.
|
|
629
687
|
align :right # controls the justification of the image. default is :left.
|
|
@@ -634,6 +692,16 @@ docx.img 'https://www.example.com/logo.png' do
|
|
|
634
692
|
end
|
|
635
693
|
```
|
|
636
694
|
|
|
695
|
+
Every setting can also be passed as an option instead of in a block:
|
|
696
|
+
|
|
697
|
+
```ruby
|
|
698
|
+
docx.img 'logo.png', data: File.binread('path/to/logo.png'), width: 396, height: 216
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
*Note: `data` must be the raw bytes of the image, not Base64. Caracal writes it
|
|
702
|
+
into the document unchanged, so Base64-encoded data produces a corrupt image.
|
|
703
|
+
Read files with `File.binread` so line endings are not translated on Windows.*
|
|
704
|
+
|
|
637
705
|
*Note: If you provide the image data, you should still supply a URL. I know this
|
|
638
706
|
is a bit hacky, but it allows the library to key the image more effectively and
|
|
639
707
|
Caracal needs a file extension to apply to the renamed media file. This seemed
|
data/caracal.gemspec
CHANGED
|
@@ -6,13 +6,19 @@ require 'caracal/version'
|
|
|
6
6
|
Gem::Specification.new do |spec|
|
|
7
7
|
spec.name = 'caracal'
|
|
8
8
|
spec.version = Caracal::VERSION
|
|
9
|
-
spec.authors = ['Urvin LLC', '
|
|
10
|
-
spec.email = ['
|
|
9
|
+
spec.authors = ['Urvin LLC', 'Dave Lauer']
|
|
10
|
+
spec.email = ['dave@plia.com']
|
|
11
11
|
spec.summary = %q{ Fast, professional Microsoft Word (docx) writer for Ruby. }
|
|
12
12
|
spec.description = %q{ Caracal is a pure Ruby Microsoft Word generation library that produces professional quality MSWord documents (docx) using a simple, HTML-style DSL. }
|
|
13
13
|
spec.homepage = 'https://github.com/urvin-compliance/caracal'
|
|
14
14
|
spec.license = 'MIT'
|
|
15
15
|
|
|
16
|
+
spec.metadata = {
|
|
17
|
+
'source_code_uri' => 'https://github.com/urvin-compliance/caracal',
|
|
18
|
+
'changelog_uri' => 'https://github.com/urvin-compliance/caracal/blob/master/CHANGELOG.md',
|
|
19
|
+
'bug_tracker_uri' => 'https://github.com/urvin-compliance/caracal/issues'
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
spec.files = `git ls-files -z`.split("\x0")
|
|
17
23
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
24
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
@@ -20,7 +26,7 @@ Gem::Specification.new do |spec|
|
|
|
20
26
|
|
|
21
27
|
spec.required_ruby_version = '>= 3.1'
|
|
22
28
|
|
|
23
|
-
spec.add_dependency 'nokogiri', '
|
|
29
|
+
spec.add_dependency 'nokogiri', '>= 1.16.2', '< 2.0'
|
|
24
30
|
spec.add_dependency 'rubyzip', '>= 1.1.6', '< 4.0'
|
|
25
31
|
spec.add_dependency 'tilt', '>= 1.4'
|
|
26
32
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'caracal/core/models/footer_model'
|
|
2
|
+
require 'caracal/errors'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
module Caracal
|
|
6
|
+
module Core
|
|
7
|
+
|
|
8
|
+
# This module encapsulates all the functionality related to adding a
|
|
9
|
+
# footer on every page of the document.
|
|
10
|
+
#
|
|
11
|
+
module Footer
|
|
12
|
+
def self.included(base)
|
|
13
|
+
base.class_eval do
|
|
14
|
+
|
|
15
|
+
#-------------------------------------------------------------
|
|
16
|
+
# Public Methods
|
|
17
|
+
#-------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
# With `first: true` the footer replaces the default one on the
|
|
20
|
+
# first page only. Without a block it leaves the first page's
|
|
21
|
+
# footer blank.
|
|
22
|
+
#
|
|
23
|
+
def footer(*args, &block)
|
|
24
|
+
options = Caracal::Utilities.extract_options!(args)
|
|
25
|
+
first = options.delete(:first)
|
|
26
|
+
|
|
27
|
+
model = Caracal::Core::Models::FooterModel.new(options, &block)
|
|
28
|
+
if first
|
|
29
|
+
@first_footer_content = model
|
|
30
|
+
elsif model.valid?
|
|
31
|
+
@footer_content = model
|
|
32
|
+
else
|
|
33
|
+
raise Caracal::Errors::InvalidModelError, 'footer must contain at least one paragraph, list, table, rule or image.'
|
|
34
|
+
end
|
|
35
|
+
model
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def footer_content
|
|
39
|
+
@footer_content
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def first_footer_content
|
|
43
|
+
@first_footer_content
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require 'caracal/core/models/header_model'
|
|
2
|
+
require 'caracal/errors'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
module Caracal
|
|
6
|
+
module Core
|
|
7
|
+
|
|
8
|
+
# This module encapsulates all the functionality related to adding a header
|
|
9
|
+
# to every page of the document.
|
|
10
|
+
#
|
|
11
|
+
module Header
|
|
12
|
+
def self.included(base)
|
|
13
|
+
base.class_eval do
|
|
14
|
+
|
|
15
|
+
#-------------------------------------------------------------
|
|
16
|
+
# Public Methods
|
|
17
|
+
#-------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
# With `first: true` the header replaces the default one on the
|
|
20
|
+
# first page only. Without a block it leaves the first page's
|
|
21
|
+
# header blank.
|
|
22
|
+
#
|
|
23
|
+
def header(*args, &block)
|
|
24
|
+
options = Caracal::Utilities.extract_options!(args)
|
|
25
|
+
first = options.delete(:first)
|
|
26
|
+
|
|
27
|
+
model = Caracal::Core::Models::HeaderModel.new(options, &block)
|
|
28
|
+
if first
|
|
29
|
+
@first_header_content = model
|
|
30
|
+
elsif model.valid?
|
|
31
|
+
@header_content = model
|
|
32
|
+
else
|
|
33
|
+
raise Caracal::Errors::InvalidModelError, 'header must contain at least one paragraph, list, table, rule or image.'
|
|
34
|
+
end
|
|
35
|
+
model
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def header_content
|
|
39
|
+
@header_content
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def first_header_content
|
|
43
|
+
@first_header_content
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
require 'caracal/core/models/base_model'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Caracal
|
|
5
|
+
module Core
|
|
6
|
+
module Models
|
|
7
|
+
|
|
8
|
+
# This class encapsulates the logic needed to store and render a
|
|
9
|
+
# field, such as the current page number, inside a paragraph.
|
|
10
|
+
#
|
|
11
|
+
class FieldModel < BaseModel
|
|
12
|
+
|
|
13
|
+
#--------------------------------------------------
|
|
14
|
+
# Configuration
|
|
15
|
+
#--------------------------------------------------
|
|
16
|
+
|
|
17
|
+
# constants
|
|
18
|
+
const_set(:TYPE_MAP, { page: 'PAGE', numpages: 'NUMPAGES' })
|
|
19
|
+
|
|
20
|
+
# accessors
|
|
21
|
+
attr_reader :field_type
|
|
22
|
+
attr_reader :field_style
|
|
23
|
+
attr_reader :field_font
|
|
24
|
+
attr_reader :field_color
|
|
25
|
+
attr_reader :field_size
|
|
26
|
+
attr_reader :field_bold
|
|
27
|
+
attr_reader :field_italic
|
|
28
|
+
attr_reader :field_underline
|
|
29
|
+
attr_reader :field_bgcolor
|
|
30
|
+
attr_reader :field_highlight_color
|
|
31
|
+
attr_reader :field_vertical_align
|
|
32
|
+
|
|
33
|
+
#-------------------------------------------------------------
|
|
34
|
+
# Public Class Methods
|
|
35
|
+
#-------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
def self.formatted_type(type)
|
|
38
|
+
TYPE_MAP.fetch(type.to_s.to_sym)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
#-------------------------------------------------------------
|
|
43
|
+
# Public Instance Methods
|
|
44
|
+
#-------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
#========== GETTERS ===============================
|
|
47
|
+
|
|
48
|
+
def formatted_type
|
|
49
|
+
self.class.formatted_type(field_type)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# .run_attributes
|
|
53
|
+
def run_attributes
|
|
54
|
+
{
|
|
55
|
+
style: field_style,
|
|
56
|
+
font: field_font,
|
|
57
|
+
color: field_color,
|
|
58
|
+
size: field_size,
|
|
59
|
+
bold: field_bold,
|
|
60
|
+
italic: field_italic,
|
|
61
|
+
underline: field_underline,
|
|
62
|
+
bgcolor: field_bgcolor,
|
|
63
|
+
highlight_color: field_highlight_color,
|
|
64
|
+
vertical_align: field_vertical_align
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
#========== SETTERS ===============================
|
|
70
|
+
|
|
71
|
+
# booleans
|
|
72
|
+
[:bold, :italic, :underline].each do |m|
|
|
73
|
+
define_method "#{ m }" do |value|
|
|
74
|
+
instance_variable_set("@field_#{ m }", !!value)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# integers
|
|
79
|
+
[:size].each do |m|
|
|
80
|
+
define_method "#{ m }" do |value|
|
|
81
|
+
instance_variable_set("@field_#{ m }", value.to_i)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# strings
|
|
86
|
+
[:bgcolor, :color, :font, :highlight_color, :style, :type].each do |m|
|
|
87
|
+
define_method "#{ m }" do |value|
|
|
88
|
+
instance_variable_set("@field_#{ m }", value.to_s)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# symbols
|
|
93
|
+
[:vertical_align].each do |m|
|
|
94
|
+
define_method "#{ m }" do |value|
|
|
95
|
+
instance_variable_set("@field_#{ m }", value.to_s.to_sym)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
#========== VALIDATION ============================
|
|
101
|
+
|
|
102
|
+
def valid?
|
|
103
|
+
TYPE_MAP.key?(field_type.to_s.to_sym)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
#--------------------------------------------------
|
|
108
|
+
# Private Methods
|
|
109
|
+
#--------------------------------------------------
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def option_keys
|
|
113
|
+
[:type, :style, :font, :color, :size, :bold, :italic, :underline, :bgcolor, :highlight_color, :vertical_align]
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def method_missing(method, *args, &block)
|
|
117
|
+
# I'm on the fence with respect to this implementation. We're ignoring
|
|
118
|
+
# :method_missing errors to allow syntax flexibility for paragraph-type
|
|
119
|
+
# models. The issue is the syntax format of those models--the way we pass
|
|
120
|
+
# the content value as a special argument--coupled with the model's
|
|
121
|
+
# ability to accept nested instructions.
|
|
122
|
+
#
|
|
123
|
+
# By ignoring method missing errors here, we can pass the entire paragraph
|
|
124
|
+
# block in the initial, built-in call to :text.
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require 'caracal/core/models/base_model'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Caracal
|
|
5
|
+
module Core
|
|
6
|
+
module Models
|
|
7
|
+
|
|
8
|
+
# This class holds the contents of the footer that appears on
|
|
9
|
+
# every page of the document.
|
|
10
|
+
#
|
|
11
|
+
class FooterModel < BaseModel
|
|
12
|
+
|
|
13
|
+
#-------------------------------------------------------------
|
|
14
|
+
# Public Methods
|
|
15
|
+
#-------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
#=============== DATA ACCESSORS =======================
|
|
18
|
+
|
|
19
|
+
def contents
|
|
20
|
+
@contents ||= []
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Images and links in the footer are registered with the document,
|
|
24
|
+
# which numbers them and writes the media, but Word resolves them
|
|
25
|
+
# against the footer part's own relationships. The renderer collects
|
|
26
|
+
# them here so they can be written to that part.
|
|
27
|
+
#
|
|
28
|
+
def relationships
|
|
29
|
+
@relationships ||= []
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
#=============== VALIDATION ===========================
|
|
34
|
+
|
|
35
|
+
def valid?
|
|
36
|
+
contents.size > 0
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require 'caracal/core/models/base_model'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Caracal
|
|
5
|
+
module Core
|
|
6
|
+
module Models
|
|
7
|
+
|
|
8
|
+
# This class holds the contents of the header that appears on
|
|
9
|
+
# every page of the document.
|
|
10
|
+
#
|
|
11
|
+
class HeaderModel < BaseModel
|
|
12
|
+
|
|
13
|
+
#-------------------------------------------------------------
|
|
14
|
+
# Public Methods
|
|
15
|
+
#-------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
#=============== DATA ACCESSORS =======================
|
|
18
|
+
|
|
19
|
+
def contents
|
|
20
|
+
@contents ||= []
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Images and links in the header are registered with the document,
|
|
24
|
+
# which numbers them and writes the media, but Word resolves them
|
|
25
|
+
# against the header part's own relationships. The renderer collects
|
|
26
|
+
# them here so they can be written to that part.
|
|
27
|
+
#
|
|
28
|
+
def relationships
|
|
29
|
+
@relationships ||= []
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
#=============== VALIDATION ===========================
|
|
34
|
+
|
|
35
|
+
def valid?
|
|
36
|
+
contents.size > 0
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
require 'tempfile'
|
|
2
2
|
require 'caracal/core/models/base_model'
|
|
3
|
+
require 'caracal/errors'
|
|
3
4
|
|
|
4
5
|
module Caracal
|
|
5
6
|
module Core
|
|
6
7
|
module Models
|
|
7
8
|
|
|
8
|
-
# This class handles block options passed to the
|
|
9
|
+
# This class handles block options passed to the iframe method.
|
|
9
10
|
#
|
|
10
11
|
class IFrameModel < BaseModel
|
|
11
12
|
|
|
@@ -13,6 +14,9 @@ module Caracal
|
|
|
13
14
|
# Configuration
|
|
14
15
|
#--------------------------------------------------
|
|
15
16
|
|
|
17
|
+
# constants
|
|
18
|
+
const_set(:DEFAULT_MAX_ENTRY_SIZE, 50 * 1024 * 1024) # 50MB
|
|
19
|
+
|
|
16
20
|
# accessors
|
|
17
21
|
attr_reader :iframe_url
|
|
18
22
|
attr_reader :iframe_data
|
|
@@ -26,6 +30,37 @@ module Caracal
|
|
|
26
30
|
end
|
|
27
31
|
|
|
28
32
|
|
|
33
|
+
#--------------------------------------------------
|
|
34
|
+
# Class Methods
|
|
35
|
+
#--------------------------------------------------
|
|
36
|
+
|
|
37
|
+
# The largest single entry Caracal will read out of an embedded
|
|
38
|
+
# document. Zip entries expand to many times their stored size, so
|
|
39
|
+
# without a limit a small file can exhaust memory. Set to nil to
|
|
40
|
+
# disable the check.
|
|
41
|
+
#
|
|
42
|
+
def self.max_entry_size
|
|
43
|
+
defined?(@max_entry_size) ? @max_entry_size : DEFAULT_MAX_ENTRY_SIZE
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.max_entry_size=(value)
|
|
47
|
+
@max_entry_size = value
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# This method reads a single entry from an embedded document,
|
|
51
|
+
# refusing anything larger than :max_entry_size.
|
|
52
|
+
#
|
|
53
|
+
def self.read_entry(entry)
|
|
54
|
+
limit = max_entry_size
|
|
55
|
+
|
|
56
|
+
if limit && entry.size > limit
|
|
57
|
+
raise Caracal::Errors::InvalidModelError, "iframe entry #{ entry.name } is #{ entry.size } bytes, over the #{ limit } byte limit."
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
entry.get_input_stream.read
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
|
|
29
64
|
#--------------------------------------------------
|
|
30
65
|
# Public Methods
|
|
31
66
|
#--------------------------------------------------
|
|
@@ -36,12 +71,12 @@ module Caracal
|
|
|
36
71
|
::Zip::File.open(file) do |zip|
|
|
37
72
|
# locate relationships xml
|
|
38
73
|
entry = zip.glob('word/_rels/document.xml.rels').first
|
|
39
|
-
content =
|
|
74
|
+
content = self.class.read_entry(entry)
|
|
40
75
|
rel_xml = Nokogiri::XML(content)
|
|
41
76
|
|
|
42
77
|
# locate document xml
|
|
43
78
|
entry = zip.glob('word/document.xml').first
|
|
44
|
-
content =
|
|
79
|
+
content = self.class.read_entry(entry)
|
|
45
80
|
doc_xml = Nokogiri::XML(content)
|
|
46
81
|
|
|
47
82
|
# master nodesets
|
|
@@ -75,7 +110,7 @@ module Caracal
|
|
|
75
110
|
p_node = node.children[0].children[0]
|
|
76
111
|
p_id = p_node.attributes['id'].to_s.to_i
|
|
77
112
|
p_name = p_node.attributes['name'].to_s
|
|
78
|
-
p_data = zip.glob(r_media).first
|
|
113
|
+
p_data = self.class.read_entry(zip.glob(r_media).first)
|
|
79
114
|
|
|
80
115
|
# register relationship
|
|
81
116
|
array << { id: r_id, type: 'image', target: p_name, data: p_data }
|