caracal_the_curve 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +22 -0
- data/.github/workflows/publish_gem.yml +40 -0
- data/.gitignore +25 -0
- data/.travis.yml +20 -0
- data/CHANGELOG.md +141 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +842 -0
- data/Rakefile +7 -0
- data/caracal.gemspec +28 -0
- data/lib/caracal/core/bookmarks.rb +52 -0
- data/lib/caracal/core/custom_properties.rb +49 -0
- data/lib/caracal/core/file_name.rb +43 -0
- data/lib/caracal/core/fonts.rb +75 -0
- data/lib/caracal/core/iframes.rb +42 -0
- data/lib/caracal/core/ignorables.rb +47 -0
- data/lib/caracal/core/images.rb +37 -0
- data/lib/caracal/core/list_styles.rb +93 -0
- data/lib/caracal/core/lists.rb +57 -0
- data/lib/caracal/core/models/base_model.rb +51 -0
- data/lib/caracal/core/models/bookmark_model.rb +86 -0
- data/lib/caracal/core/models/border_model.rb +120 -0
- data/lib/caracal/core/models/custom_property_model.rb +60 -0
- data/lib/caracal/core/models/font_model.rb +64 -0
- data/lib/caracal/core/models/iframe_model.rb +148 -0
- data/lib/caracal/core/models/image_model.rb +133 -0
- data/lib/caracal/core/models/line_break_model.rb +15 -0
- data/lib/caracal/core/models/link_model.rb +94 -0
- data/lib/caracal/core/models/list_item_model.rb +108 -0
- data/lib/caracal/core/models/list_model.rb +132 -0
- data/lib/caracal/core/models/list_style_model.rb +118 -0
- data/lib/caracal/core/models/margin_model.rb +76 -0
- data/lib/caracal/core/models/namespace_model.rb +65 -0
- data/lib/caracal/core/models/page_break_model.rb +61 -0
- data/lib/caracal/core/models/page_number_model.rb +95 -0
- data/lib/caracal/core/models/page_size_model.rb +79 -0
- data/lib/caracal/core/models/paragraph_model.rb +186 -0
- data/lib/caracal/core/models/relationship_model.rb +114 -0
- data/lib/caracal/core/models/rule_model.rb +27 -0
- data/lib/caracal/core/models/style_model.rb +165 -0
- data/lib/caracal/core/models/table_cell_model.rb +176 -0
- data/lib/caracal/core/models/table_model.rb +206 -0
- data/lib/caracal/core/models/text_model.rb +118 -0
- data/lib/caracal/core/namespaces.rb +89 -0
- data/lib/caracal/core/page_breaks.rb +29 -0
- data/lib/caracal/core/page_numbers.rb +58 -0
- data/lib/caracal/core/page_settings.rb +74 -0
- data/lib/caracal/core/relationships.rb +90 -0
- data/lib/caracal/core/rules.rb +35 -0
- data/lib/caracal/core/styles.rb +86 -0
- data/lib/caracal/core/tables.rb +42 -0
- data/lib/caracal/core/text.rb +75 -0
- data/lib/caracal/document.rb +272 -0
- data/lib/caracal/errors.rb +23 -0
- data/lib/caracal/renderers/app_renderer.rb +41 -0
- data/lib/caracal/renderers/content_types_renderer.rb +54 -0
- data/lib/caracal/renderers/core_renderer.rb +44 -0
- data/lib/caracal/renderers/custom_renderer.rb +64 -0
- data/lib/caracal/renderers/document_renderer.rb +427 -0
- data/lib/caracal/renderers/fonts_renderer.rb +56 -0
- data/lib/caracal/renderers/footer_renderer.rb +90 -0
- data/lib/caracal/renderers/numbering_renderer.rb +88 -0
- data/lib/caracal/renderers/package_relationships_renderer.rb +51 -0
- data/lib/caracal/renderers/relationships_renderer.rb +48 -0
- data/lib/caracal/renderers/settings_renderer.rb +58 -0
- data/lib/caracal/renderers/styles_renderer.rb +181 -0
- data/lib/caracal/renderers/xml_renderer.rb +83 -0
- data/lib/caracal/utilities.rb +23 -0
- data/lib/caracal/version.rb +3 -0
- data/lib/caracal.rb +40 -0
- data/lib/tilt/caracal.rb +21 -0
- data/spec/lib/caracal/core/bookmarks_spec.rb +35 -0
- data/spec/lib/caracal/core/file_name_spec.rb +54 -0
- data/spec/lib/caracal/core/fonts_spec.rb +119 -0
- data/spec/lib/caracal/core/iframes_spec.rb +29 -0
- data/spec/lib/caracal/core/ignorables_spec.rb +79 -0
- data/spec/lib/caracal/core/images_spec.rb +25 -0
- data/spec/lib/caracal/core/list_styles_spec.rb +121 -0
- data/spec/lib/caracal/core/lists_spec.rb +43 -0
- data/spec/lib/caracal/core/models/base_model_spec.rb +38 -0
- data/spec/lib/caracal/core/models/bookmark_model_spec.rb +135 -0
- data/spec/lib/caracal/core/models/border_model_spec.rb +159 -0
- data/spec/lib/caracal/core/models/font_model_spec.rb +92 -0
- data/spec/lib/caracal/core/models/iframe_model_spec.rb +83 -0
- data/spec/lib/caracal/core/models/image_model_spec.rb +225 -0
- data/spec/lib/caracal/core/models/line_break_model_spec.rb +21 -0
- data/spec/lib/caracal/core/models/link_model_spec.rb +179 -0
- data/spec/lib/caracal/core/models/list_item_model_spec.rb +197 -0
- data/spec/lib/caracal/core/models/list_model_spec.rb +178 -0
- data/spec/lib/caracal/core/models/list_style_model_spec.rb +198 -0
- data/spec/lib/caracal/core/models/margin_model_spec.rb +111 -0
- data/spec/lib/caracal/core/models/namespace_model_spec.rb +107 -0
- data/spec/lib/caracal/core/models/page_break_model_spec.rb +21 -0
- data/spec/lib/caracal/core/models/page_number_model_spec.rb +136 -0
- data/spec/lib/caracal/core/models/page_size_model_spec.rb +101 -0
- data/spec/lib/caracal/core/models/paragraph_model_spec.rb +196 -0
- data/spec/lib/caracal/core/models/relationship_model_spec.rb +193 -0
- data/spec/lib/caracal/core/models/rule_model_spec.rb +108 -0
- data/spec/lib/caracal/core/models/style_model_spec.rb +225 -0
- data/spec/lib/caracal/core/models/table_cell_model_spec.rb +230 -0
- data/spec/lib/caracal/core/models/table_model_spec.rb +222 -0
- data/spec/lib/caracal/core/models/text_model_spec.rb +154 -0
- data/spec/lib/caracal/core/namespaces_spec.rb +116 -0
- data/spec/lib/caracal/core/page_breaks_spec.rb +25 -0
- data/spec/lib/caracal/core/page_numbers_spec.rb +89 -0
- data/spec/lib/caracal/core/page_settings_spec.rb +151 -0
- data/spec/lib/caracal/core/relationships_spec.rb +119 -0
- data/spec/lib/caracal/core/rules_spec.rb +25 -0
- data/spec/lib/caracal/core/styles_spec.rb +129 -0
- data/spec/lib/caracal/core/tables_spec.rb +25 -0
- data/spec/lib/caracal/core/text_spec.rb +52 -0
- data/spec/lib/caracal/errors_spec.rb +10 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/support/_fixtures/snippet.docx +0 -0
- metadata +292 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
require 'caracal/renderers/xml_renderer'
|
4
|
+
|
5
|
+
|
6
|
+
module Caracal
|
7
|
+
module Renderers
|
8
|
+
class AppRenderer < XmlRenderer
|
9
|
+
|
10
|
+
#-------------------------------------------------------------
|
11
|
+
# Public Methods
|
12
|
+
#-------------------------------------------------------------
|
13
|
+
|
14
|
+
# This method produces the xml required for the `docProps/app.xml`
|
15
|
+
# sub-document.
|
16
|
+
#
|
17
|
+
def to_xml
|
18
|
+
builder = ::Nokogiri::XML::Builder.with(declaration_xml) do |xml|
|
19
|
+
xml.send 'Properties', root_options do
|
20
|
+
xml.send 'Application', 'Caracal'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
builder.to_xml(save_options)
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
#-------------------------------------------------------------
|
28
|
+
# Private Methods
|
29
|
+
#-------------------------------------------------------------
|
30
|
+
private
|
31
|
+
|
32
|
+
def root_options
|
33
|
+
{
|
34
|
+
'xmlns' => 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties',
|
35
|
+
'xmlns:vt' => 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes'
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
require 'caracal/renderers/xml_renderer'
|
4
|
+
|
5
|
+
|
6
|
+
module Caracal
|
7
|
+
module Renderers
|
8
|
+
class ContentTypesRenderer < XmlRenderer
|
9
|
+
|
10
|
+
#-------------------------------------------------------------
|
11
|
+
# Public Methods
|
12
|
+
#-------------------------------------------------------------
|
13
|
+
|
14
|
+
# This method produces the xml required for the `_[ContentTypes].xml`
|
15
|
+
# sub-document.
|
16
|
+
#
|
17
|
+
def to_xml
|
18
|
+
builder = ::Nokogiri::XML::Builder.with(declaration_xml) do |xml|
|
19
|
+
xml.send 'Types', root_options do
|
20
|
+
xml.send 'Default', { 'Extension' => 'gif', 'ContentType' => 'image/gif' }
|
21
|
+
xml.send 'Default', { 'Extension' => 'jpeg', 'ContentType' => 'image/jpeg' }
|
22
|
+
xml.send 'Default', { 'Extension' => 'jpg', 'ContentType' => 'image/jpeg' }
|
23
|
+
xml.send 'Default', { 'Extension' => 'png', 'ContentType' => 'image/png' }
|
24
|
+
xml.send 'Default', { 'Extension' => 'rels', 'ContentType' => 'application/vnd.openxmlformats-package.relationships+xml' }
|
25
|
+
xml.send 'Default', { 'Extension' => 'xml', 'ContentType' => 'application/xml' }
|
26
|
+
xml.send 'Override', { 'PartName' => '/docProps/app.xml', 'ContentType' => 'application/vnd.openxmlformats-officedocument.extended-properties+xml' }
|
27
|
+
xml.send 'Override', { 'PartName' => '/docProps/core.xml', 'ContentType' => 'application/vnd.openxmlformats-package.core-properties+xml' }
|
28
|
+
xml.send 'Override', { 'PartName' => '/docProps/custom.xml', 'ContentType' => 'application/vnd.openxmlformats-officedocument.custom-properties+xml' }
|
29
|
+
xml.send 'Override', { 'PartName' => '/word/document.xml', 'ContentType' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml' }
|
30
|
+
xml.send 'Override', { 'PartName' => '/word/footer1.xml', 'ContentType' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml' }
|
31
|
+
xml.send 'Override', { 'PartName' => '/word/fontTable.xml', 'ContentType' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml' }
|
32
|
+
xml.send 'Override', { 'PartName' => '/word/numbering.xml', 'ContentType' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml' }
|
33
|
+
xml.send 'Override', { 'PartName' => '/word/settings.xml', 'ContentType' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml' }
|
34
|
+
xml.send 'Override', { 'PartName' => '/word/styles.xml', 'ContentType' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml' }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
builder.to_xml(save_options)
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
#-------------------------------------------------------------
|
42
|
+
# Private Methods
|
43
|
+
#-------------------------------------------------------------
|
44
|
+
private
|
45
|
+
|
46
|
+
def root_options
|
47
|
+
{
|
48
|
+
'xmlns' => 'http://schemas.openxmlformats.org/package/2006/content-types'
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
require 'caracal/renderers/xml_renderer'
|
4
|
+
|
5
|
+
|
6
|
+
module Caracal
|
7
|
+
module Renderers
|
8
|
+
class CoreRenderer < XmlRenderer
|
9
|
+
|
10
|
+
#-------------------------------------------------------------
|
11
|
+
# Public Methods
|
12
|
+
#-------------------------------------------------------------
|
13
|
+
|
14
|
+
# This method produces the xml required for the `docProps/core.xml`
|
15
|
+
# sub-document.
|
16
|
+
#
|
17
|
+
def to_xml
|
18
|
+
builder = ::Nokogiri::XML::Builder.with(declaration_xml) do |xml|
|
19
|
+
xml['cp'].coreProperties root_options do
|
20
|
+
xml['dc'].title document.name
|
21
|
+
end
|
22
|
+
end
|
23
|
+
builder.to_xml(save_options)
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
#-------------------------------------------------------------
|
28
|
+
# Private Methods
|
29
|
+
#-------------------------------------------------------------
|
30
|
+
private
|
31
|
+
|
32
|
+
def root_options
|
33
|
+
{
|
34
|
+
'xmlns:cp' => 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties',
|
35
|
+
'xmlns:dc' => 'http://purl.org/dc/elements/1.1/',
|
36
|
+
'xmlns:dcterms' => 'http://purl.org/dc/terms/',
|
37
|
+
'xmlns:dcmitype' => 'http://purl.org/dc/dcmitype/',
|
38
|
+
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance'
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
require 'caracal/renderers/xml_renderer'
|
5
|
+
|
6
|
+
|
7
|
+
module Caracal
|
8
|
+
module Renderers
|
9
|
+
class CustomRenderer < XmlRenderer
|
10
|
+
|
11
|
+
#-------------------------------------------------------------
|
12
|
+
# Public Methods
|
13
|
+
#-------------------------------------------------------------
|
14
|
+
|
15
|
+
# This method produces the xml required for the `docProps/custom.xml`
|
16
|
+
# sub-document.
|
17
|
+
#
|
18
|
+
def to_xml
|
19
|
+
builder = ::Nokogiri::XML::Builder.with(declaration_xml) do |xml|
|
20
|
+
xml.send 'Properties', root_options do
|
21
|
+
document.custom_props.each_with_index do |property, index|
|
22
|
+
xml.send 'property',
|
23
|
+
{ fmtid: '{D5CDD505-2E9C-101B-9397-08002B2CF9AE}', pid: index + 2, name: property.custom_property_name } do
|
24
|
+
case property.custom_property_type.downcase
|
25
|
+
when 'text'
|
26
|
+
xml['vt'].lpwstr property.custom_property_value
|
27
|
+
when 'date'
|
28
|
+
xml['vt'].filetime property.custom_property_value.to_date
|
29
|
+
when 'number'
|
30
|
+
xml['vt'].i4 property.custom_property_value.to_f
|
31
|
+
when 'boolean'
|
32
|
+
if property.custom_property_value == 'true' || property.custom_property_value == 'false'
|
33
|
+
xml['vt'].bool property.custom_property_value
|
34
|
+
else
|
35
|
+
# Not a boolean sent, so reverting to string so docx will open
|
36
|
+
xml['vt'].lpwstr property.custom_property_value
|
37
|
+
end
|
38
|
+
else
|
39
|
+
# Fail to string type
|
40
|
+
xml['vt'].lpwstr property.custom_property_value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
builder.to_xml(save_options)
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
#-------------------------------------------------------------
|
51
|
+
# Private Methods
|
52
|
+
#-------------------------------------------------------------
|
53
|
+
private
|
54
|
+
|
55
|
+
def root_options
|
56
|
+
{
|
57
|
+
'xmlns' => "http://schemas.openxmlformats.org/officeDocument/2006/custom-properties",
|
58
|
+
'xmlns:vt' => "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,427 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
require 'caracal/renderers/xml_renderer'
|
4
|
+
require 'caracal/errors'
|
5
|
+
|
6
|
+
|
7
|
+
module Caracal
|
8
|
+
module Renderers
|
9
|
+
class DocumentRenderer < XmlRenderer
|
10
|
+
|
11
|
+
#-------------------------------------------------------------
|
12
|
+
# Public Methods
|
13
|
+
#-------------------------------------------------------------
|
14
|
+
|
15
|
+
# This method produces the xml required for the `word/document.xml`
|
16
|
+
# sub-document.
|
17
|
+
#
|
18
|
+
def to_xml
|
19
|
+
builder = ::Nokogiri::XML::Builder.with(declaration_xml) do |xml|
|
20
|
+
xml['w'].document root_options do
|
21
|
+
xml['w'].background({ 'w:color' => 'FFFFFF' })
|
22
|
+
xml['w'].body do
|
23
|
+
|
24
|
+
#============= CONTENTS ===================================
|
25
|
+
|
26
|
+
document.contents.each do |model|
|
27
|
+
method = render_method_for_model(model)
|
28
|
+
send(method, xml, model)
|
29
|
+
end
|
30
|
+
|
31
|
+
#============= PAGE SETTINGS ==============================
|
32
|
+
|
33
|
+
xml['w'].sectPr do
|
34
|
+
if document.page_number_show
|
35
|
+
if rel = document.find_relationship('footer1.xml')
|
36
|
+
xml['w'].footerReference({ 'r:id' => rel.formatted_id, 'w:type' => 'default' })
|
37
|
+
end
|
38
|
+
end
|
39
|
+
xml['w'].pgSz page_size_options
|
40
|
+
xml['w'].pgMar page_margin_options
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
builder.to_xml(save_options)
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
#-------------------------------------------------------------
|
51
|
+
# Private Methods
|
52
|
+
#-------------------------------------------------------------
|
53
|
+
private
|
54
|
+
|
55
|
+
#============= COMMON RENDERERS ==========================
|
56
|
+
|
57
|
+
# This method converts a model class name to a rendering
|
58
|
+
# function on this class (e.g., Caracal::Core::Models::ParagraphModel
|
59
|
+
# translates to `render_paragraph`).
|
60
|
+
#
|
61
|
+
def render_method_for_model(model)
|
62
|
+
type = model.class.name.split('::').last.downcase.gsub('model', '')
|
63
|
+
"render_#{ type }"
|
64
|
+
end
|
65
|
+
|
66
|
+
# This method renders a standard node of run properties based on the
|
67
|
+
# model's attributes.
|
68
|
+
#
|
69
|
+
def render_run_attributes(xml, model, paragraph_level=false)
|
70
|
+
if model.respond_to? :run_attributes
|
71
|
+
attrs = model.run_attributes.delete_if { |k, v| v.nil? }
|
72
|
+
|
73
|
+
if paragraph_level && attrs.empty?
|
74
|
+
# skip
|
75
|
+
else
|
76
|
+
xml['w'].rPr do
|
77
|
+
unless attrs.empty?
|
78
|
+
xml['w'].rStyle( { 'w:val' => attrs[:style] }) unless attrs[:style].nil?
|
79
|
+
xml['w'].color( { 'w:val' => attrs[:color] }) unless attrs[:color].nil?
|
80
|
+
xml['w'].sz( { 'w:val' => attrs[:size] }) unless attrs[:size].nil?
|
81
|
+
xml['w'].b( { 'w:val' => (attrs[:bold] ? '1' : '0') }) unless attrs[:bold].nil?
|
82
|
+
xml['w'].i( { 'w:val' => (attrs[:italic] ? '1' : '0') }) unless attrs[:italic].nil?
|
83
|
+
xml['w'].u( { 'w:val' => (attrs[:underline] ? 'single' : 'none') }) unless attrs[:underline].nil?
|
84
|
+
xml['w'].shd( { 'w:fill' => attrs[:bgcolor], 'w:val' => 'clear' }) unless attrs[:bgcolor].nil?
|
85
|
+
xml['w'].highlight( { 'w:val' => attrs[:highlight_color] }) unless attrs[:highlight_color].nil?
|
86
|
+
xml['w'].vertAlign( { 'w:val' => attrs[:vertical_align] }) unless attrs[:vertical_align].nil?
|
87
|
+
unless attrs[:font].nil?
|
88
|
+
f = attrs[:font]
|
89
|
+
xml['w'].rFonts( { 'w:ascii' => f, 'w:hAnsi' => f, 'w:eastAsia' => f, 'w:cs' => f })
|
90
|
+
end
|
91
|
+
end
|
92
|
+
xml['w'].rtl({ 'w:val' => '0' })
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
#============= MODEL RENDERERS ===========================
|
100
|
+
|
101
|
+
def render_bookmark(xml, model)
|
102
|
+
if model.start?
|
103
|
+
xml['w'].bookmarkStart({ 'w:id' => model.bookmark_id, 'w:name' => model.bookmark_name })
|
104
|
+
else
|
105
|
+
xml['w'].bookmarkEnd({ 'w:id' => model.bookmark_id })
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def render_iframe(xml, model)
|
110
|
+
::Zip::File.open(model.file) do |zip|
|
111
|
+
a_href = 'http://schemas.openxmlformats.org/drawingml/2006/main'
|
112
|
+
pic_href = 'http://schemas.openxmlformats.org/drawingml/2006/picture'
|
113
|
+
r_href = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'
|
114
|
+
|
115
|
+
entry = zip.glob('word/document.xml').first
|
116
|
+
content = entry.get_input_stream.read
|
117
|
+
doc_xml = Nokogiri::XML(content)
|
118
|
+
|
119
|
+
fragment = doc_xml.xpath('//w:body').first.children
|
120
|
+
fragment.pop
|
121
|
+
|
122
|
+
model.relationships.each do |r_hash|
|
123
|
+
id = r_hash.delete(:id) # we can't update the fragment until
|
124
|
+
model = document.relationship(r_hash) # the parent document assigns the embedded
|
125
|
+
index = model.relationship_id # relationship an id.
|
126
|
+
|
127
|
+
r_node = fragment.at_xpath("//a:blip[@r:embed='#{ id }']", { a: a_href, r: r_href })
|
128
|
+
if r_attr = r_node.attributes['embed']
|
129
|
+
r_attr.value = "rId#{ index }"
|
130
|
+
end
|
131
|
+
|
132
|
+
p_parent = r_node.parent.parent
|
133
|
+
p_node = p_parent.children[0].children[0]
|
134
|
+
if p_attr = p_node.attributes['id']
|
135
|
+
p_attr.value = index.to_s
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
xml << fragment.to_s
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def render_image(xml, model)
|
144
|
+
unless ds = document.default_style
|
145
|
+
raise Caracal::Errors::NoDefaultStyleError 'Document must declare a default paragraph style.'
|
146
|
+
end
|
147
|
+
|
148
|
+
rel = document.relationship({ type: :image, target: model.image_url, data: model.image_data })
|
149
|
+
rel_id = rel.relationship_id
|
150
|
+
rel_name = rel.formatted_target
|
151
|
+
|
152
|
+
xml['w'].p paragraph_options do
|
153
|
+
xml['w'].pPr do
|
154
|
+
xml['w'].spacing({ 'w:lineRule' => 'auto', 'w:line' => ds.style_line })
|
155
|
+
xml['w'].contextualSpacing({ 'w:val' => '0' })
|
156
|
+
xml['w'].jc({ 'w:val' => model.image_align.to_s })
|
157
|
+
xml['w'].rPr
|
158
|
+
end
|
159
|
+
xml['w'].r run_options do
|
160
|
+
xml['w'].drawing do
|
161
|
+
xml['wp'].inline({ distR: model.formatted_right, distT: model.formatted_top, distB: model.formatted_bottom, distL: model.formatted_left }) do
|
162
|
+
xml['wp'].extent({ cx: model.formatted_width, cy: model.formatted_height })
|
163
|
+
xml['wp'].effectExtent({ t: 0, b: 0, r: 0, l: 0 })
|
164
|
+
xml['wp'].docPr({ id: rel_id, name: rel_name })
|
165
|
+
xml['a'].graphic do
|
166
|
+
xml['a'].graphicData({ uri: 'http://schemas.openxmlformats.org/drawingml/2006/picture' }) do
|
167
|
+
xml['pic'].pic do
|
168
|
+
xml['pic'].nvPicPr do
|
169
|
+
xml['pic'].cNvPr({ id: rel_id, name: rel_name })
|
170
|
+
xml['pic'].cNvPicPr
|
171
|
+
end
|
172
|
+
xml['pic'].blipFill do
|
173
|
+
xml['a'].blip({ 'r:embed' => rel.formatted_id })
|
174
|
+
xml['a'].srcRect
|
175
|
+
xml['a'].stretch do
|
176
|
+
xml['a'].fillRect
|
177
|
+
end
|
178
|
+
end
|
179
|
+
xml['pic'].spPr do
|
180
|
+
xml['a'].xfrm do
|
181
|
+
xml['a'].ext({ cx: model.formatted_width, cy: model.formatted_height })
|
182
|
+
end
|
183
|
+
xml['a'].prstGeom({ prst: 'rect' })
|
184
|
+
xml['a'].ln
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
xml['w'].r run_options do
|
193
|
+
xml['w'].rPr do
|
194
|
+
xml['w'].rtl({ 'w:val' => '0' })
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def render_linebreak(xml, model)
|
201
|
+
xml['w'].r do
|
202
|
+
xml['w'].br
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def render_link(xml, model)
|
207
|
+
if model.external?
|
208
|
+
rel = document.relationship({ target: model.link_href, type: :link })
|
209
|
+
hyperlink_options = { 'r:id' => rel.formatted_id }
|
210
|
+
else
|
211
|
+
hyperlink_options = { 'w:anchor' => model.link_href }
|
212
|
+
end
|
213
|
+
|
214
|
+
xml['w'].hyperlink(hyperlink_options) do
|
215
|
+
xml['w'].r run_options do
|
216
|
+
render_run_attributes(xml, model, false)
|
217
|
+
xml['w'].t({ 'xml:space' => 'preserve' }, model.link_content)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def render_list(xml, model)
|
223
|
+
if model.list_level == 0
|
224
|
+
document.toplevel_lists << model
|
225
|
+
list_num = document.toplevel_lists.length # numbering uses 1-based index
|
226
|
+
end
|
227
|
+
|
228
|
+
model.recursive_items.each do |item|
|
229
|
+
render_listitem(xml, item, list_num)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def render_listitem(xml, model, list_num)
|
234
|
+
ls = document.find_list_style(model.list_item_type, model.list_item_level)
|
235
|
+
hanging = ls.style_left.to_i - ls.style_indent.to_i - 1
|
236
|
+
|
237
|
+
xml['w'].p paragraph_options do
|
238
|
+
xml['w'].pPr do
|
239
|
+
xml['w'].numPr do
|
240
|
+
xml['w'].ilvl({ 'w:val' => model.list_item_level })
|
241
|
+
xml['w'].numId({ 'w:val' => list_num })
|
242
|
+
end
|
243
|
+
xml['w'].ind({ 'w:left' => ls.style_left, 'w:hanging' => hanging })
|
244
|
+
xml['w'].contextualSpacing({ 'w:val' => '1' })
|
245
|
+
xml['w'].rPr do
|
246
|
+
xml['w'].u({ 'w:val' => 'none' })
|
247
|
+
end
|
248
|
+
end
|
249
|
+
model.runs.each do |run|
|
250
|
+
method = render_method_for_model(run)
|
251
|
+
send(method, xml, run)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def render_pagebreak(xml, model)
|
257
|
+
if model.page_break_wrap
|
258
|
+
xml['w'].p paragraph_options do
|
259
|
+
xml['w'].r run_options do
|
260
|
+
xml['w'].br({ 'w:type' => 'page' })
|
261
|
+
end
|
262
|
+
end
|
263
|
+
else
|
264
|
+
xml['w'].r run_options do
|
265
|
+
xml['w'].br({ 'w:type' => 'page' })
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def render_paragraph(xml, model)
|
271
|
+
run_props = [:color, :size, :bold, :italic, :underline].map { |m| model.send("paragraph_#{ m }") }.compact
|
272
|
+
|
273
|
+
xml['w'].p paragraph_options do
|
274
|
+
xml['w'].pPr do
|
275
|
+
xml['w'].pStyle({ 'w:val' => model.paragraph_style }) unless model.paragraph_style.nil?
|
276
|
+
xml['w'].contextualSpacing({ 'w:val' => '0' })
|
277
|
+
xml['w'].jc({ 'w:val' => model.paragraph_align }) unless model.paragraph_align.nil?
|
278
|
+
render_run_attributes(xml, model, true)
|
279
|
+
end
|
280
|
+
model.runs.each do |run|
|
281
|
+
method = render_method_for_model(run)
|
282
|
+
send(method, xml, run)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def render_rule(xml, model)
|
288
|
+
options = { 'w:color' => model.rule_color, 'w:sz' => model.rule_size, 'w:val' => model.rule_line, 'w:space' => model.rule_spacing }
|
289
|
+
|
290
|
+
xml['w'].p paragraph_options do
|
291
|
+
xml['w'].pPr do
|
292
|
+
xml['w'].pBdr do
|
293
|
+
xml['w'].top options
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def render_text(xml, model)
|
300
|
+
xml['w'].r run_options do
|
301
|
+
render_run_attributes(xml, model, false)
|
302
|
+
xml['w'].t({ 'xml:space' => 'preserve' }, model.text_content)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def render_table(xml, model)
|
307
|
+
borders = %w(top left bottom right horizontal vertical).select do |m|
|
308
|
+
model.send("table_border_#{ m }_size") > 0
|
309
|
+
end
|
310
|
+
|
311
|
+
xml['w'].tbl do
|
312
|
+
xml['w'].tblPr do
|
313
|
+
xml['w'].tblStyle({ 'w:val' => 'DefaultTable' })
|
314
|
+
xml['w'].bidiVisual({ 'w:val' => '0' })
|
315
|
+
xml['w'].tblW({ 'w:w' => model.table_width.to_f, 'w:type' => 'dxa' })
|
316
|
+
xml['w'].tblInd({ 'w:w' => '0.0', 'w:type' => 'dxa' })
|
317
|
+
xml['w'].jc({ 'w:val' => model.table_align })
|
318
|
+
unless borders.empty?
|
319
|
+
xml['w'].tblBorders do
|
320
|
+
borders.each do |m|
|
321
|
+
options = {
|
322
|
+
'w:color' => model.send("table_border_#{ m }_color"),
|
323
|
+
'w:val' => model.send("table_border_#{ m }_line"),
|
324
|
+
'w:sz' => model.send("table_border_#{ m }_size"),
|
325
|
+
'w:space' => model.send("table_border_#{ m }_spacing")
|
326
|
+
}
|
327
|
+
xml['w'].method_missing "#{ Caracal::Core::Models::BorderModel.formatted_type(m) }", options
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
xml['w'].tblLayout({ 'w:type' => 'fixed' })
|
332
|
+
xml['w'].tblLook({ 'w:val' => '0600' })
|
333
|
+
end
|
334
|
+
xml['w'].tblGrid do
|
335
|
+
model.rows.first.each do |tc|
|
336
|
+
(tc.cell_colspan || 1).times do
|
337
|
+
xml['w'].gridCol({ 'w:w' => tc.cell_width })
|
338
|
+
end
|
339
|
+
end
|
340
|
+
xml['w'].tblGridChange({ 'w:id' => '0' }) do
|
341
|
+
xml['w'].tblGrid do
|
342
|
+
model.rows.first.each do |tc|
|
343
|
+
(tc.cell_colspan || 1).times do
|
344
|
+
xml['w'].gridCol({ 'w:w' => tc.cell_width })
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
rowspan_hash = {}
|
352
|
+
model.rows.each do |row|
|
353
|
+
xml['w'].tr do
|
354
|
+
tc_index = 0
|
355
|
+
row.each do |tc|
|
356
|
+
xml['w'].tc do
|
357
|
+
xml['w'].tcPr do
|
358
|
+
xml['w'].shd({ 'w:fill' => tc.cell_background })
|
359
|
+
xml['w'].vAlign({ 'w:val' => tc.cell_vertical_align })
|
360
|
+
|
361
|
+
# applying rowspan
|
362
|
+
if tc.cell_rowspan && tc.cell_rowspan > 0
|
363
|
+
rowspan_hash[tc_index] = tc.cell_rowspan - 1
|
364
|
+
xml['w'].vMerge({ 'w:val' => 'restart' })
|
365
|
+
elsif rowspan_hash[tc_index] && rowspan_hash[tc_index] > 0
|
366
|
+
xml['w'].vMerge({ 'w:val' => 'continue' })
|
367
|
+
rowspan_hash[tc_index] -= 1
|
368
|
+
end
|
369
|
+
|
370
|
+
# applying colspan
|
371
|
+
xml['w'].gridSpan({ 'w:val' => tc.cell_colspan }) if tc.cell_colspan
|
372
|
+
|
373
|
+
xml['w'].tcMar do
|
374
|
+
%w(top left bottom right).each do |d|
|
375
|
+
xml['w'].method_missing "#{ d }", { 'w:w' => tc.send("cell_margin_#{ d }").to_f, 'w:type' => 'dxa' }
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
tc.contents.each do |m|
|
380
|
+
method = render_method_for_model(m)
|
381
|
+
send(method, xml, m)
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
# adjust tc_index for next cell taking into account current cell's colspan
|
386
|
+
tc_index += (tc.cell_colspan && tc.cell_colspan > 0) ? tc.cell_colspan : 1
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
|
394
|
+
#============= OPTIONS ===================================
|
395
|
+
|
396
|
+
def root_options
|
397
|
+
opts = {}
|
398
|
+
document.namespaces.each do |model|
|
399
|
+
opts[model.namespace_prefix] = model.namespace_href
|
400
|
+
end
|
401
|
+
unless document.ignorables.empty?
|
402
|
+
v = document.ignorables.join(' ')
|
403
|
+
opts['mc:Ignorable'] = v
|
404
|
+
end
|
405
|
+
opts
|
406
|
+
end
|
407
|
+
|
408
|
+
def page_margin_options
|
409
|
+
{
|
410
|
+
'w:top' => document.page_margin_top,
|
411
|
+
'w:bottom' => document.page_margin_bottom,
|
412
|
+
'w:left' => document.page_margin_left,
|
413
|
+
'w:right' => document.page_margin_right
|
414
|
+
}
|
415
|
+
end
|
416
|
+
|
417
|
+
def page_size_options
|
418
|
+
{
|
419
|
+
'w:w' => document.page_width,
|
420
|
+
'w:h' => document.page_height,
|
421
|
+
'w:orient' => document.page_orientation
|
422
|
+
}
|
423
|
+
end
|
424
|
+
|
425
|
+
end
|
426
|
+
end
|
427
|
+
end
|