caracal 1.4.1 → 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/ci.yml +28 -0
- data/.github/workflows/push_gem.yml +29 -0
- data/CHANGELOG.md +50 -1
- data/Gemfile +3 -0
- data/LICENSE.txt +1 -1
- data/README.md +138 -15
- data/caracal.gemspec +15 -7
- data/lib/caracal/core/footer.rb +50 -0
- data/lib/caracal/core/header.rb +49 -0
- data/lib/caracal/core/iframes.rb +10 -5
- data/lib/caracal/core/models/border_model.rb +1 -1
- 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 +45 -11
- 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 +18 -2
- data/lib/caracal/core/models/relationship_model.rb +29 -5
- data/lib/caracal/core/models/table_model.rb +44 -7
- data/lib/caracal/core/page_numbers.rb +5 -1
- data/lib/caracal/document.rb +123 -31
- data/lib/caracal/renderers/content_types_renderer.rb +7 -1
- data/lib/caracal/renderers/custom_renderer.rb +3 -3
- data/lib/caracal/renderers/document_renderer.rb +69 -21
- 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/renderers/xml_renderer.rb +8 -0
- data/lib/caracal/utilities.rb +16 -1
- data/lib/caracal/version.rb +1 -1
- data/lib/caracal.rb +17 -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 +29 -1
- data/spec/lib/caracal/core/models/table_model_spec.rb +27 -2
- data/spec/lib/caracal/core/relationships_spec.rb +1 -1
- data/spec/lib/caracal/core/tables_spec.rb +8 -1
- data/spec/lib/caracal/document_output_spec.rb +715 -0
- metadata +57 -26
- data/.travis.yml +0 -20
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,28 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
name: Ruby ${{ matrix.ruby }} / rubyzip ${{ matrix.rubyzip }}
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
strategy:
|
|
16
|
+
fail-fast: false
|
|
17
|
+
matrix:
|
|
18
|
+
ruby: ['3.1', '3.2', '3.3', '3.4', '4.0']
|
|
19
|
+
rubyzip: ['~> 1.3', '~> 3.4']
|
|
20
|
+
env:
|
|
21
|
+
RUBYZIP_VERSION: ${{ matrix.rubyzip }}
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
- uses: ruby/setup-ruby@v1
|
|
25
|
+
with:
|
|
26
|
+
ruby-version: ${{ matrix.ruby }}
|
|
27
|
+
bundler-cache: true
|
|
28
|
+
- run: bundle exec rspec
|
|
@@ -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,10 +1,59 @@
|
|
|
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
|
+
|
|
23
|
+
#### v1.5.0
|
|
24
|
+
|
|
25
|
+
* Enhancements
|
|
26
|
+
* Added the `keep_next` paragraph property, which keeps a paragraph on the same page as the one following it. (@benjaminketron)
|
|
27
|
+
* Added the `header_rows` table method, which repeats the leading rows of a table after each page break. (@benjaminketron)
|
|
28
|
+
* Added the `cant_split` and `cant_split?` table methods, which keep a row from splitting across a page break. (@benjaminketron)
|
|
29
|
+
* Added the `column_widths` table method, which sets the table grid explicitly instead of deriving it from the first row. (@martinsp)
|
|
30
|
+
* Widened the rubyzip dependency to `>= 1.1.6, < 4.0`, allowing rubyzip 3.x. (@acwertman)
|
|
31
|
+
* Added continuous integration across Ruby 3.1-4.0 and both supported rubyzip lines. (@dlauer)
|
|
32
|
+
* Declared `required_ruby_version >= 3.1`. (@dlauer)
|
|
33
|
+
* Documented the trust contract for image and iframe sources in a new Security section. (@dlauer)
|
|
34
|
+
|
|
35
|
+
* Bug Fixes
|
|
36
|
+
* Images and iframes are no longer loaded through `Kernel#open`, which executed a target beginning with `|` as a shell command, stopped fetching URLs entirely on Ruby 3.0 and later, and read in text mode on Windows, corrupting image data. Both are now read in binary, with `URI.open` for http(s) targets and `File.binread` for local paths. (@LItterBoy-GB, @dlauer)
|
|
37
|
+
* Removed control characters that XML does not permit from text, links, table cells, page number labels and custom properties. A single such character made Word reject the entire document. (@dlauer)
|
|
38
|
+
* Image part names and content types are now derived from the image data when it is supplied, rather than from the URL. This fixes presigned URLs, extensionless URLs and images carried in embedded documents. Registered content types for bmp, tiff and svg. (@dlauer)
|
|
39
|
+
* `iframe` now works inside table cells. (@catmando, @dlauer)
|
|
40
|
+
* Raised the rubyzip floor past versions whose `write_buffer` required an argument, which raised `ArgumentError` on save. (@acwertman)
|
|
41
|
+
* Corrected the documented units for table border and rule spacing, which are points rather than twips or eighths of a point. (@dlauer)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
#### v1.4.1
|
|
45
|
+
|
|
46
|
+
* Bug Fixes
|
|
47
|
+
* Corrected rowspan logic to handle all cases. (@martinsp)
|
|
48
|
+
|
|
49
|
+
|
|
1
50
|
#### v1.4.0
|
|
2
51
|
|
|
3
52
|
* Enhancements
|
|
4
53
|
* Changed `file_name` method to accept absolute and relative paths. (@jdugan)
|
|
5
54
|
* Added instance save method so documents can be created via commands instead of blocks, if desired. (@jdugan)
|
|
6
55
|
* Removed extra paragraph tag inserted after tables; added logic to ensure final tag of table cells is always a paragraph. (@jdugan)
|
|
7
|
-
|
|
56
|
+
|
|
8
57
|
|
|
9
58
|
#### v1.3.0
|
|
10
59
|
|
data/Gemfile
CHANGED
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Caracal is not a magical HTML to Word translator. Instead, it is a markup langua
|
|
|
11
11
|
|
|
12
12
|
Or, said differently, if you use [Prawn](https://github.com/prawnpdf/prawn) for PDF generation, you'll probably like Caracal. Only you'll probably like it better. :)
|
|
13
13
|
|
|
14
|
-
Please see the [caracal-example](https://github.com/
|
|
14
|
+
Please see the [caracal-example](https://github.com/urvin-compliance/caracal-example) repository for
|
|
15
15
|
a working demonstration of the library's capabilities.
|
|
16
16
|
|
|
17
17
|
|
|
@@ -207,6 +207,19 @@ All Caracal models perform basic validations on their attributes, but this is, w
|
|
|
207
207
|
In forthcoming versions of Caracal, we'll be looking to expand the `InvalidModelError` class to provide broader error reporting abilities across the entire library.
|
|
208
208
|
|
|
209
209
|
|
|
210
|
+
## Security
|
|
211
|
+
|
|
212
|
+
Caracal reads whatever path or URL you hand it. Both `img` and `iframe` accept a local filesystem path or an http(s) URL, and Caracal reads or fetches it while the document renders.
|
|
213
|
+
|
|
214
|
+
**Treat those values as trusted input.** If a path or URL can be influenced by an end user, validate it before passing it to Caracal:
|
|
215
|
+
|
|
216
|
+
* **Local paths** are read with the permissions of your application's process. Any file that process can read can be embedded in the generated document, so `docx.img '/etc/passwd'` embeds the contents of that file.
|
|
217
|
+
* **URLs** are fetched from your application's network position, and redirects are followed. A user-supplied URL can therefore reach internal services, `localhost`, or cloud metadata endpoints that are not otherwise reachable from outside.
|
|
218
|
+
* **Documents embedded with `iframe`** are parsed, and their markup is copied into your output. Only embed documents you trust. See [IFrames](#iframes).
|
|
219
|
+
|
|
220
|
+
Caracal deliberately does not restrict paths, hosts, or directories, because it cannot know what is legitimate for your application. That policy belongs in your code: an allowlist of permitted directories or hosts, or simply never passing user-supplied values through to Caracal.
|
|
221
|
+
|
|
222
|
+
|
|
210
223
|
## Installation
|
|
211
224
|
|
|
212
225
|
Add this line to your application's Gemfile:
|
|
@@ -308,14 +321,61 @@ Page numbers can be added to the footer via the `page_numbers` method. The meth
|
|
|
308
321
|
docx.page_numbers true do
|
|
309
322
|
align :right # sets the alignment. accepts :left, :center, and :right.
|
|
310
323
|
label 'Page' # sets the text that will go to the left of the page number. Defaults to nil.
|
|
311
|
-
size
|
|
312
|
-
label_size
|
|
313
|
-
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.
|
|
314
327
|
end
|
|
315
328
|
```
|
|
316
329
|
|
|
317
330
|
The `size` option and the `label_size` and `number_size` options are mutually exclusive.
|
|
318
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.
|
|
319
379
|
|
|
320
380
|
### Fonts
|
|
321
381
|
|
|
@@ -415,6 +475,7 @@ docx.p 'Sample text.' do
|
|
|
415
475
|
bgcolor 'cccccc' # sets the background color.
|
|
416
476
|
highlight_color 'yellow' # sets the highlight color. only accepts OOXML enumerations. see http://www.datypic.com/sc/ooxml/t-w_ST_HighlightColor.html.
|
|
417
477
|
vertical_align 'superscript' # sets the vertical alignment.
|
|
478
|
+
keep_next false # sets whether or not to keep this paragraph with the next paragraph when separated by page break.
|
|
418
479
|
end
|
|
419
480
|
```
|
|
420
481
|
|
|
@@ -432,6 +493,16 @@ docx.p do
|
|
|
432
493
|
end
|
|
433
494
|
```
|
|
434
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
|
+
```
|
|
435
506
|
|
|
436
507
|
### Links
|
|
437
508
|
|
|
@@ -505,7 +576,7 @@ docx.hr do
|
|
|
505
576
|
color '333333' # sets the color of the line. defaults to auto.
|
|
506
577
|
line :double # sets the line style (single or double). defaults to single.
|
|
507
578
|
size 8 # sets the thickness of the line. units in 1/8 points. defaults to 4.
|
|
508
|
-
spacing 4 # sets the
|
|
579
|
+
spacing 4 # sets the space between the line and the surrounding text. units in points. defaults to 1.
|
|
509
580
|
end
|
|
510
581
|
```
|
|
511
582
|
|
|
@@ -561,7 +632,7 @@ docx.ul do
|
|
|
561
632
|
end
|
|
562
633
|
```
|
|
563
634
|
|
|
564
|
-
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.
|
|
565
636
|
|
|
566
637
|
```ruby
|
|
567
638
|
docx.ul do
|
|
@@ -610,6 +681,7 @@ Images can be added by using the `img` method. The method accepts several optio
|
|
|
610
681
|
```ruby
|
|
611
682
|
docx.img 'https://www.example.com/logo.png' do
|
|
612
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.
|
|
613
685
|
width 396 # sets the image width. units specified in pixels.
|
|
614
686
|
height 216 # sets the image height. units specified in pixels.
|
|
615
687
|
align :right # controls the justification of the image. default is :left.
|
|
@@ -620,11 +692,23 @@ docx.img 'https://www.example.com/logo.png' do
|
|
|
620
692
|
end
|
|
621
693
|
```
|
|
622
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
|
+
|
|
623
705
|
*Note: If you provide the image data, you should still supply a URL. I know this
|
|
624
706
|
is a bit hacky, but it allows the library to key the image more effectively and
|
|
625
707
|
Caracal needs a file extension to apply to the renamed media file. This seemed
|
|
626
708
|
the simplest solution to both problems.*
|
|
627
709
|
|
|
710
|
+
*If the URL or path can come from user input, read [Security](#security) first. Caracal will read any local file your process can read, and will fetch any URL you give it.*
|
|
711
|
+
|
|
628
712
|
|
|
629
713
|
### Tables
|
|
630
714
|
|
|
@@ -632,7 +716,7 @@ the simplest solution to both problems.*
|
|
|
632
716
|
Release v1.4.0 deprecated the behaviour of automatically adding an empty paragraph tag after every table. If you are upgrading from an older version of the library, you will need to control such spacing in your own code.
|
|
633
717
|
```
|
|
634
718
|
|
|
635
|
-
Tables can be added using the `table` method. The method accepts several optional
|
|
719
|
+
Tables can be added using the `table` method. The method accepts several optional parameters to control the layout and style of the table cells.
|
|
636
720
|
|
|
637
721
|
The `table` command accepts data in the form of a two-dimensional arrays. This corresponds to rows and column cells within those rows. Each array item can be a string, a Hash of options, a Proc (which will be passed as a block), or a `TableCellModel`. The command will normalize all array contents into a two-dimensional array of `TableCellModel` instances.
|
|
638
722
|
|
|
@@ -640,8 +724,20 @@ The `table` command accepts data in the form of a two-dimensional arrays. This c
|
|
|
640
724
|
docx.table [['Header 1','Header 2'],['Cell 1', 'Cell 2']] do
|
|
641
725
|
border_color '666666' # sets the border color. defaults to 'auto'.
|
|
642
726
|
border_line :single # sets the border style. defaults to :single. see OOXML docs for details.
|
|
643
|
-
border_size 4 # sets the border width. defaults to 0. units in
|
|
644
|
-
border_spacing 4 # sets the
|
|
727
|
+
border_size 4 # sets the border width. defaults to 0. units in 1/8 points.
|
|
728
|
+
border_spacing 4 # sets the space between the border and the cell text. defaults to 0. units in points.
|
|
729
|
+
column_widths [2400, 1200]
|
|
730
|
+
# sets an explicit width for each column in the table grid. units in twips.
|
|
731
|
+
# defaults to the widths of the cells in the first row.
|
|
732
|
+
end
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
By default, the table grid is built from the cells in the first row. When that row contains a cell with a `colspan`, the grid can't be derived from it correctly and every column ends up the same width. `column_widths` sets the grid explicitly instead, taking one width per column.
|
|
736
|
+
|
|
737
|
+
```ruby
|
|
738
|
+
docx.table [['Spans two columns'], ['Cell 1', 'Cell 2']] do
|
|
739
|
+
cell_style rows[0][0], colspan: 2
|
|
740
|
+
column_widths [2400, 1200]
|
|
645
741
|
end
|
|
646
742
|
```
|
|
647
743
|
|
|
@@ -683,6 +779,31 @@ end
|
|
|
683
779
|
|
|
684
780
|
*Note: content of cells 21 and 24 will disappear*
|
|
685
781
|
|
|
782
|
+
Table rows can be marked as header rows using the `header_rows` method which takes a number indicating how many rows from the top of the table should be marked as such. This has the effect of repeating these rows after each page break.
|
|
783
|
+
|
|
784
|
+
```ruby
|
|
785
|
+
docx.table [['Header 1', 'Header 2'],['Cell 1', 'Cell 2']] do
|
|
786
|
+
header_rows 1
|
|
787
|
+
end
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
Row can be marked as can't split rows that re-position themselves at the top of the next if they would have otherwise split across a page break.
|
|
791
|
+
|
|
792
|
+
```
|
|
793
|
+
docx.table [['11', '22'], ['14', '25'], ['64', '23']] do
|
|
794
|
+
cant_split rows[1]
|
|
795
|
+
end
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
or
|
|
799
|
+
|
|
800
|
+
```
|
|
801
|
+
docx.table [['11', '22'], ['14', '25'], ['64', '23']] do
|
|
802
|
+
cant_split rows[1...-1]
|
|
803
|
+
end
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
|
|
686
807
|
### Table Cells
|
|
687
808
|
|
|
688
809
|
If your table contains more complex data (multiple paragraphs, images, lists, etc.), you will probably want to instantiate your `TableCellModel` instances directly. With the exception of page breaks, table cells can contain anything the document can contain, including another table.
|
|
@@ -760,17 +881,19 @@ will not operate as expected in the parent Caracal document.
|
|
|
760
881
|
|
|
761
882
|
Again, this feature is considered experimental. Use at your own risk/discretion.
|
|
762
883
|
|
|
884
|
+
**Only embed documents you trust.** Caracal parses the file you supply and copies its markup into your output, so the embedded document controls part of the result. Caracal also places no limit on how much data it will decompress while reading the file, so a small crafted document can expand to an arbitrarily large one in memory. Documents uploaded by untrusted users should not be passed to `iframe` without your own validation. See [Security](#security).
|
|
885
|
+
|
|
763
886
|
|
|
764
887
|
## Template Rendering
|
|
765
888
|
|
|
766
889
|
Caracal includes [Tilt](https://github.com/rtomayko/tilt) integration to facilitate its inclusion in other frameworks.
|
|
767
890
|
|
|
768
|
-
Rails integration can be added via the [Caracal-Rails](https://github.com/
|
|
891
|
+
Rails integration can be added via the [Caracal-Rails](https://github.com/urvin-compliance/caracal-rails) gem.
|
|
769
892
|
|
|
770
893
|
|
|
771
894
|
## Using Variables
|
|
772
895
|
|
|
773
|
-
Lexical scope is a pretty big challenge for Caracal and it often confuses new users. This [closed issue](https://github.com/
|
|
896
|
+
Lexical scope is a pretty big challenge for Caracal and it often confuses new users. This [closed issue](https://github.com/urvin-compliance/caracal/issues/71) covers the discussion both from the user and library persepctive.
|
|
774
897
|
|
|
775
898
|
|
|
776
899
|
## Filing an Issue
|
|
@@ -789,7 +912,7 @@ happy to answer questions and provide what guidance we can. We just won't write
|
|
|
789
912
|
|
|
790
913
|
For those using reasonably current versions of Word, please consider the following:
|
|
791
914
|
|
|
792
|
-
- Before you file an issue, please run the example Caracal project [caracal-example](https://github.com/
|
|
915
|
+
- Before you file an issue, please run the example Caracal project [caracal-example](https://github.com/urvin-compliance/caracal-example) in your development environment and
|
|
793
916
|
check the output. This project implements nearly every feature Caracal supports and renders the expected output
|
|
794
917
|
correctly. It can be thought of as a canonical implementation of the library.
|
|
795
918
|
|
|
@@ -818,7 +941,7 @@ for 2010/2013, a set for 2016, etc.).
|
|
|
818
941
|
|
|
819
942
|
## Contributing
|
|
820
943
|
|
|
821
|
-
1. Fork it ( https://github.com/
|
|
944
|
+
1. Fork it ( https://github.com/urvin-compliance/caracal/fork )
|
|
822
945
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
823
946
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
824
947
|
4. Push to the branch (`git push origin my-new-feature`)
|
|
@@ -837,6 +960,6 @@ A tip of the hat to the wonderful PDF generation library [Prawn](https://github.
|
|
|
837
960
|
|
|
838
961
|
## License
|
|
839
962
|
|
|
840
|
-
Copyright (c) 2014
|
|
963
|
+
Copyright (c) 2014 Urvin LLC
|
|
841
964
|
|
|
842
|
-
[MIT License](https://github.com/
|
|
965
|
+
[MIT License](https://github.com/urvin-compliance/caracal/blob/master/LICENSE.txt)
|
data/caracal.gemspec
CHANGED
|
@@ -6,23 +6,31 @@ require 'caracal/version'
|
|
|
6
6
|
Gem::Specification.new do |spec|
|
|
7
7
|
spec.name = 'caracal'
|
|
8
8
|
spec.version = Caracal::VERSION
|
|
9
|
-
spec.authors = ['
|
|
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
|
-
spec.homepage = 'https://github.com/
|
|
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)/})
|
|
19
25
|
spec.require_paths = ['lib']
|
|
20
26
|
|
|
21
|
-
spec.
|
|
22
|
-
|
|
27
|
+
spec.required_ruby_version = '>= 3.1'
|
|
28
|
+
|
|
29
|
+
spec.add_dependency 'nokogiri', '>= 1.16.2', '< 2.0'
|
|
30
|
+
spec.add_dependency 'rubyzip', '>= 1.1.6', '< 4.0'
|
|
23
31
|
spec.add_dependency 'tilt', '>= 1.4'
|
|
24
32
|
|
|
25
|
-
spec.add_development_dependency 'bundler', '
|
|
26
|
-
spec.add_development_dependency 'rake', '
|
|
33
|
+
spec.add_development_dependency 'bundler', '>= 1.3'
|
|
34
|
+
spec.add_development_dependency 'rake', '>= 10.0'
|
|
27
35
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
28
36
|
end
|
|
@@ -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
|
data/lib/caracal/core/iframes.rb
CHANGED
|
@@ -20,11 +20,16 @@ module Caracal
|
|
|
20
20
|
model = Caracal::Core::Models::IFrameModel.new(options, &block)
|
|
21
21
|
if model.valid?
|
|
22
22
|
model.preprocess!
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
|
|
24
|
+
# table cells can't hold namespaces; the document
|
|
25
|
+
# collects those when it renders.
|
|
26
|
+
if respond_to?(:namespace)
|
|
27
|
+
model.namespaces.each do |(prefix, href)|
|
|
28
|
+
namespace({ prefix: prefix, href: href })
|
|
29
|
+
end
|
|
30
|
+
model.ignorables.each do |prefix|
|
|
31
|
+
ignorable(prefix)
|
|
32
|
+
end
|
|
28
33
|
end
|
|
29
34
|
|
|
30
35
|
contents << model
|
|
@@ -18,7 +18,7 @@ module Caracal
|
|
|
18
18
|
const_set(:DEFAULT_BORDER_COLOR, 'auto')
|
|
19
19
|
const_set(:DEFAULT_BORDER_LINE, :single)
|
|
20
20
|
const_set(:DEFAULT_BORDER_SIZE, 4) # 0.5pt in 1/8 points
|
|
21
|
-
const_set(:DEFAULT_BORDER_SPACING, 1) #
|
|
21
|
+
const_set(:DEFAULT_BORDER_SPACING, 1) # 1pt. w:space is measured in points, not 1/8 points.
|
|
22
22
|
const_set(:DEFAULT_BORDER_TYPE, :top)
|
|
23
23
|
|
|
24
24
|
# accessors
|