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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +28 -0
  3. data/.github/workflows/push_gem.yml +29 -0
  4. data/CHANGELOG.md +50 -1
  5. data/Gemfile +3 -0
  6. data/LICENSE.txt +1 -1
  7. data/README.md +138 -15
  8. data/caracal.gemspec +15 -7
  9. data/lib/caracal/core/footer.rb +50 -0
  10. data/lib/caracal/core/header.rb +49 -0
  11. data/lib/caracal/core/iframes.rb +10 -5
  12. data/lib/caracal/core/models/border_model.rb +1 -1
  13. data/lib/caracal/core/models/field_model.rb +131 -0
  14. data/lib/caracal/core/models/footer_model.rb +43 -0
  15. data/lib/caracal/core/models/header_model.rb +43 -0
  16. data/lib/caracal/core/models/iframe_model.rb +45 -11
  17. data/lib/caracal/core/models/image_model.rb +1 -1
  18. data/lib/caracal/core/models/list_item_model.rb +57 -3
  19. data/lib/caracal/core/models/list_model.rb +1 -7
  20. data/lib/caracal/core/models/list_style_model.rb +1 -1
  21. data/lib/caracal/core/models/page_number_model.rb +17 -2
  22. data/lib/caracal/core/models/paragraph_model.rb +18 -2
  23. data/lib/caracal/core/models/relationship_model.rb +29 -5
  24. data/lib/caracal/core/models/table_model.rb +44 -7
  25. data/lib/caracal/core/page_numbers.rb +5 -1
  26. data/lib/caracal/document.rb +123 -31
  27. data/lib/caracal/renderers/content_types_renderer.rb +7 -1
  28. data/lib/caracal/renderers/custom_renderer.rb +3 -3
  29. data/lib/caracal/renderers/document_renderer.rb +69 -21
  30. data/lib/caracal/renderers/footer_renderer.rb +57 -50
  31. data/lib/caracal/renderers/header_footer_renderer.rb +90 -0
  32. data/lib/caracal/renderers/header_renderer.rb +28 -0
  33. data/lib/caracal/renderers/relationships_renderer.rb +22 -4
  34. data/lib/caracal/renderers/xml_renderer.rb +8 -0
  35. data/lib/caracal/utilities.rb +16 -1
  36. data/lib/caracal/version.rb +1 -1
  37. data/lib/caracal.rb +17 -0
  38. data/spec/lib/caracal/core/footer_spec.rb +37 -0
  39. data/spec/lib/caracal/core/header_spec.rb +48 -0
  40. data/spec/lib/caracal/core/images_spec.rb +7 -0
  41. data/spec/lib/caracal/core/models/footer_model_spec.rb +41 -0
  42. data/spec/lib/caracal/core/models/header_model_spec.rb +41 -0
  43. data/spec/lib/caracal/core/models/image_model_spec.rb +1 -1
  44. data/spec/lib/caracal/core/models/list_item_model_spec.rb +18 -0
  45. data/spec/lib/caracal/core/models/list_style_model_spec.rb +1 -1
  46. data/spec/lib/caracal/core/models/option_keys_spec.rb +47 -0
  47. data/spec/lib/caracal/core/models/page_number_model_spec.rb +16 -1
  48. data/spec/lib/caracal/core/models/paragraph_model_spec.rb +29 -1
  49. data/spec/lib/caracal/core/models/table_model_spec.rb +27 -2
  50. data/spec/lib/caracal/core/relationships_spec.rb +1 -1
  51. data/spec/lib/caracal/core/tables_spec.rb +8 -1
  52. data/spec/lib/caracal/document_output_spec.rb +715 -0
  53. metadata +57 -26
  54. data/.travis.yml +0 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07032410054f6b6e5b8d640aa391c24382eda9461832b7f3fb0fe2bb9203164f
4
- data.tar.gz: 780ae708802f68185d556d6ec0dc28b5fdb86adefc6043ec18bf32027440278b
3
+ metadata.gz: c06f9a2d1fafe9fc3cbc91db5bab3d08a374ef0b688ff188a200e175f0c72073
4
+ data.tar.gz: 7dd9194ecb1a0d84e9e5953a334f0fc5754fbd696305e8fa21a8563f95c2a3fa
5
5
  SHA512:
6
- metadata.gz: 136540cd7409584e274462d2f43b7b21f4533a4351611ea41f0db03fd6282a366d01c0a5d1f6b4d05d7674d9bdd9f939b536ba569189407abcfe253e8fc524c8
7
- data.tar.gz: d9c61a1f362f43625aaa3160639f903ebd003df993dc8e78c17e456bb1a1205aef94b5e1dc8cdf9b69136ba71786bb2eeee03f8e26ae35c1862ea329d1fe9162
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
@@ -1,3 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
+
5
+ # lets CI test against specific rubyzip versions
6
+ gem 'rubyzip', ENV['RUBYZIP_VERSION'] if ENV['RUBYZIP_VERSION']
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014 Trade Informatics, Inc.
1
+ Copyright (c) 2014 Urvin LLC
2
2
 
3
3
  MIT License
4
4
 
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/trade-informatics/caracal-example) repository for
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: 24 # sets the label and number size simultaneously. units in half points.
312
- label_size: 24 # sets the label size only. units in half points.
313
- number_size: 20 # sets the number size only. units in half points.
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 spacing around the line. units in 1/8 points. defaults to 1.
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 paramters to control the layout and style of the table cells.
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 twips.
644
- border_spacing 4 # sets the spacing around the border. defaults to 0. units in twips.
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/trade-informatics/caracal-rails) gem.
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/trade-informatics/caracal/issues/71) covers the discussion both from the user and library persepctive.
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/trade-informatics/caracal-example) in your development environment and
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/trade-informatics/caracal/fork )
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 Trade Informatics, Inc
963
+ Copyright (c) 2014 Urvin LLC
841
964
 
842
- [MIT License](https://github.com/trade-informatics/caracal/blob/master/LICENSE.txt)
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 = ['Trade Infomatics', 'John Dugan']
10
- spec.email = ['jpdugan@gmail.com']
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/trade-informatics/caracal'
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.add_dependency 'nokogiri', '~> 1.6'
22
- spec.add_dependency 'rubyzip', '~> 1.1'
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', '~> 1.3'
26
- spec.add_development_dependency 'rake', '~> 10.0'
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
@@ -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
- model.namespaces.each do |(prefix, href)|
24
- namespace({ prefix: prefix, href: href })
25
- end
26
- model.ignorables.each do |prefix|
27
- ignorable(prefix)
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) # 0.125pt in 1/8 points
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