notare 0.0.2
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/LICENSE.txt +21 -0
- data/README.md +323 -0
- data/lib/notare/builder.rb +162 -0
- data/lib/notare/document.rb +85 -0
- data/lib/notare/image_dimensions.rb +55 -0
- data/lib/notare/nodes/base.rb +9 -0
- data/lib/notare/nodes/image.rb +38 -0
- data/lib/notare/nodes/list.rb +20 -0
- data/lib/notare/nodes/list_item.rb +20 -0
- data/lib/notare/nodes/paragraph.rb +19 -0
- data/lib/notare/nodes/run.rb +18 -0
- data/lib/notare/nodes/table.rb +18 -0
- data/lib/notare/nodes/table_cell.rb +18 -0
- data/lib/notare/nodes/table_row.rb +18 -0
- data/lib/notare/package.rb +63 -0
- data/lib/notare/style.rb +65 -0
- data/lib/notare/version.rb +5 -0
- data/lib/notare/xml/content_types.rb +56 -0
- data/lib/notare/xml/document_xml.rb +172 -0
- data/lib/notare/xml/numbering.rb +58 -0
- data/lib/notare/xml/relationships.rb +69 -0
- data/lib/notare/xml/styles_xml.rb +66 -0
- data/lib/notare.rb +28 -0
- metadata +164 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Notare
|
|
4
|
+
module Nodes
|
|
5
|
+
class List < Base
|
|
6
|
+
attr_reader :items, :type, :num_id
|
|
7
|
+
|
|
8
|
+
def initialize(type:, num_id:)
|
|
9
|
+
super()
|
|
10
|
+
@type = type
|
|
11
|
+
@num_id = num_id
|
|
12
|
+
@items = []
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def add_item(item)
|
|
16
|
+
@items << item
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Notare
|
|
4
|
+
module Nodes
|
|
5
|
+
class ListItem < Base
|
|
6
|
+
attr_reader :runs, :list_type, :num_id
|
|
7
|
+
|
|
8
|
+
def initialize(runs = [], list_type:, num_id:)
|
|
9
|
+
super()
|
|
10
|
+
@runs = runs
|
|
11
|
+
@list_type = list_type
|
|
12
|
+
@num_id = num_id
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def add_run(run)
|
|
16
|
+
@runs << run
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Notare
|
|
4
|
+
module Nodes
|
|
5
|
+
class Paragraph < Base
|
|
6
|
+
attr_reader :runs, :style
|
|
7
|
+
|
|
8
|
+
def initialize(runs = [], style: nil)
|
|
9
|
+
super()
|
|
10
|
+
@runs = runs
|
|
11
|
+
@style = style
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def add_run(run)
|
|
15
|
+
@runs << run
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Notare
|
|
4
|
+
module Nodes
|
|
5
|
+
class Run < Base
|
|
6
|
+
attr_reader :text, :bold, :italic, :underline, :style
|
|
7
|
+
|
|
8
|
+
def initialize(text, bold: false, italic: false, underline: false, style: nil)
|
|
9
|
+
super()
|
|
10
|
+
@text = text
|
|
11
|
+
@bold = bold
|
|
12
|
+
@italic = italic
|
|
13
|
+
@underline = underline
|
|
14
|
+
@style = style
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "zip"
|
|
4
|
+
|
|
5
|
+
module Notare
|
|
6
|
+
class Package
|
|
7
|
+
def initialize(document)
|
|
8
|
+
@document = document
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def save(path)
|
|
12
|
+
Zip::File.open(path, Zip::File::CREATE) do |zipfile|
|
|
13
|
+
zipfile.get_output_stream("[Content_Types].xml") { |f| f.write(content_types_xml) }
|
|
14
|
+
zipfile.get_output_stream("_rels/.rels") { |f| f.write(relationships_xml) }
|
|
15
|
+
zipfile.get_output_stream("word/_rels/document.xml.rels") { |f| f.write(document_relationships_xml) }
|
|
16
|
+
zipfile.get_output_stream("word/document.xml") { |f| f.write(document_xml) }
|
|
17
|
+
zipfile.get_output_stream("word/styles.xml") { |f| f.write(styles_xml) }
|
|
18
|
+
|
|
19
|
+
zipfile.get_output_stream("word/numbering.xml") { |f| f.write(numbering_xml) } if lists?
|
|
20
|
+
|
|
21
|
+
images.each do |image|
|
|
22
|
+
zipfile.get_output_stream("word/media/#{image.filename}") do |f|
|
|
23
|
+
f.write(File.binread(image.path))
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def lists?
|
|
32
|
+
@document.lists.any?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def images
|
|
36
|
+
@document.images
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def content_types_xml
|
|
40
|
+
Xml::ContentTypes.new(has_numbering: lists?, images: images, has_styles: true).to_xml
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def relationships_xml
|
|
44
|
+
Xml::Relationships.new.to_xml
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def document_relationships_xml
|
|
48
|
+
Xml::DocumentRelationships.new(has_numbering: lists?, images: images, has_styles: true).to_xml
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def document_xml
|
|
52
|
+
Xml::DocumentXml.new(@document.nodes).to_xml
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def styles_xml
|
|
56
|
+
Xml::StylesXml.new(@document.styles).to_xml
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def numbering_xml
|
|
60
|
+
Xml::Numbering.new(@document.lists).to_xml
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
data/lib/notare/style.rb
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Notare
|
|
4
|
+
class Style
|
|
5
|
+
attr_reader :name, :bold, :italic, :underline, :color, :size, :font,
|
|
6
|
+
:align, :indent, :spacing_before, :spacing_after
|
|
7
|
+
|
|
8
|
+
ALIGNMENTS = %i[left center right justify].freeze
|
|
9
|
+
|
|
10
|
+
def initialize(name, bold: nil, italic: nil, underline: nil, color: nil,
|
|
11
|
+
size: nil, font: nil, align: nil, indent: nil,
|
|
12
|
+
spacing_before: nil, spacing_after: nil)
|
|
13
|
+
@name = name
|
|
14
|
+
@bold = bold
|
|
15
|
+
@italic = italic
|
|
16
|
+
@underline = underline
|
|
17
|
+
@color = normalize_color(color)
|
|
18
|
+
@size = size
|
|
19
|
+
@font = font
|
|
20
|
+
@align = validate_align(align)
|
|
21
|
+
@indent = indent
|
|
22
|
+
@spacing_before = spacing_before
|
|
23
|
+
@spacing_after = spacing_after
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def style_id
|
|
27
|
+
name.to_s.split("_").map(&:capitalize).join
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def display_name
|
|
31
|
+
name.to_s.split("_").map(&:capitalize).join(" ")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def paragraph_properties?
|
|
35
|
+
!!(align || indent || spacing_before || spacing_after)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def text_properties?
|
|
39
|
+
!!(bold || italic || underline || color || size || font)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Size in half-points for OOXML (14pt = 28 half-points)
|
|
43
|
+
def size_half_points
|
|
44
|
+
size ? (size * 2).to_i : nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def normalize_color(color)
|
|
50
|
+
return nil if color.nil?
|
|
51
|
+
|
|
52
|
+
hex = color.to_s.sub(/^#/, "").upcase
|
|
53
|
+
return hex if hex.match?(/\A[0-9A-F]{6}\z/)
|
|
54
|
+
|
|
55
|
+
raise ArgumentError, "Invalid color: #{color}. Use 6-digit hex (e.g., 'FF0000')"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def validate_align(align)
|
|
59
|
+
return nil if align.nil?
|
|
60
|
+
return align if ALIGNMENTS.include?(align)
|
|
61
|
+
|
|
62
|
+
raise ArgumentError, "Invalid alignment: #{align}. Use #{ALIGNMENTS.join(", ")}"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Notare
|
|
4
|
+
module Xml
|
|
5
|
+
class ContentTypes
|
|
6
|
+
NAMESPACE = "http://schemas.openxmlformats.org/package/2006/content-types"
|
|
7
|
+
|
|
8
|
+
def initialize(has_numbering: false, images: [], has_styles: false)
|
|
9
|
+
@has_numbering = has_numbering
|
|
10
|
+
@images = images
|
|
11
|
+
@has_styles = has_styles
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_xml
|
|
15
|
+
builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
|
|
16
|
+
xml.Types(xmlns: NAMESPACE) do
|
|
17
|
+
xml.Default(Extension: "rels", ContentType: "application/vnd.openxmlformats-package.relationships+xml")
|
|
18
|
+
xml.Default(Extension: "xml", ContentType: "application/xml")
|
|
19
|
+
|
|
20
|
+
image_extensions.each do |ext, content_type|
|
|
21
|
+
xml.Default(Extension: ext, ContentType: content_type)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
xml.Override(
|
|
25
|
+
PartName: "/word/document.xml",
|
|
26
|
+
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"
|
|
27
|
+
)
|
|
28
|
+
if @has_styles
|
|
29
|
+
xml.Override(
|
|
30
|
+
PartName: "/word/styles.xml",
|
|
31
|
+
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
if @has_numbering
|
|
35
|
+
xml.Override(
|
|
36
|
+
PartName: "/word/numbering.xml",
|
|
37
|
+
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
builder.to_xml
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def image_extensions
|
|
48
|
+
extensions = {}
|
|
49
|
+
@images.each do |image|
|
|
50
|
+
extensions[image.extension] ||= image.content_type
|
|
51
|
+
end
|
|
52
|
+
extensions
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Notare
|
|
4
|
+
module Xml
|
|
5
|
+
class DocumentXml
|
|
6
|
+
NAMESPACES = {
|
|
7
|
+
"xmlns:w" => "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
8
|
+
"xmlns:r" => "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
|
|
9
|
+
"xmlns:wp" => "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
|
|
10
|
+
"xmlns:a" => "http://schemas.openxmlformats.org/drawingml/2006/main",
|
|
11
|
+
"xmlns:pic" => "http://schemas.openxmlformats.org/drawingml/2006/picture"
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
def initialize(nodes)
|
|
15
|
+
@nodes = nodes
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_xml
|
|
19
|
+
builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
|
|
20
|
+
xml.document(NAMESPACES) do
|
|
21
|
+
xml.parent.namespace = xml.parent.namespace_definitions.find { |ns| ns.prefix == "w" }
|
|
22
|
+
xml["w"].body do
|
|
23
|
+
@nodes.each { |node| render_node(xml, node) }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
builder.to_xml
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def render_node(xml, node)
|
|
33
|
+
case node
|
|
34
|
+
when Nodes::Paragraph
|
|
35
|
+
render_paragraph(xml, node)
|
|
36
|
+
when Nodes::List
|
|
37
|
+
render_list(xml, node)
|
|
38
|
+
when Nodes::Table
|
|
39
|
+
render_table(xml, node)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def render_paragraph(xml, para)
|
|
44
|
+
xml["w"].p do
|
|
45
|
+
if para.style
|
|
46
|
+
xml["w"].pPr do
|
|
47
|
+
xml["w"].pStyle("w:val" => para.style.style_id)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
para.runs.each { |run| render_run(xml, run) }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def render_list(xml, list)
|
|
55
|
+
list.items.each { |item| render_list_item(xml, item) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def render_list_item(xml, item)
|
|
59
|
+
xml["w"].p do
|
|
60
|
+
xml["w"].pPr do
|
|
61
|
+
xml["w"].numPr do
|
|
62
|
+
xml["w"].ilvl("w:val" => "0")
|
|
63
|
+
xml["w"].numId("w:val" => item.num_id.to_s)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
item.runs.each { |run| render_run(xml, run) }
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def render_run(xml, run)
|
|
71
|
+
case run
|
|
72
|
+
when Nodes::Image
|
|
73
|
+
render_image(xml, run)
|
|
74
|
+
when Nodes::Run
|
|
75
|
+
render_text_run(xml, run)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def render_text_run(xml, run)
|
|
80
|
+
xml["w"].r do
|
|
81
|
+
if run.bold || run.italic || run.underline || run.style
|
|
82
|
+
xml["w"].rPr do
|
|
83
|
+
xml["w"].rStyle("w:val" => run.style.style_id) if run.style
|
|
84
|
+
xml["w"].b if run.bold
|
|
85
|
+
xml["w"].i if run.italic
|
|
86
|
+
xml["w"].u("w:val" => "single") if run.underline
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
xml["w"].t(run.text, "xml:space" => "preserve")
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def render_image(xml, image)
|
|
94
|
+
xml["w"].r do
|
|
95
|
+
xml["w"].drawing do
|
|
96
|
+
xml["wp"].inline(distT: "0", distB: "0", distL: "0", distR: "0") do
|
|
97
|
+
xml["wp"].extent(cx: image.width_emu.to_s, cy: image.height_emu.to_s)
|
|
98
|
+
xml["wp"].docPr(id: image.doc_pr_id.to_s, name: image.filename)
|
|
99
|
+
xml["wp"].cNvGraphicFramePr do
|
|
100
|
+
xml["a"].graphicFrameLocks(noChangeAspect: "1")
|
|
101
|
+
end
|
|
102
|
+
xml["a"].graphic do
|
|
103
|
+
xml["a"].graphicData(uri: "http://schemas.openxmlformats.org/drawingml/2006/picture") do
|
|
104
|
+
xml["pic"].pic do
|
|
105
|
+
xml["pic"].nvPicPr do
|
|
106
|
+
xml["pic"].cNvPr(id: "0", name: image.filename)
|
|
107
|
+
xml["pic"].cNvPicPr
|
|
108
|
+
end
|
|
109
|
+
xml["pic"].blipFill do
|
|
110
|
+
xml["a"].blip("r:embed" => image.rid)
|
|
111
|
+
xml["a"].stretch do
|
|
112
|
+
xml["a"].fillRect
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
xml["pic"].spPr do
|
|
116
|
+
xml["a"].xfrm do
|
|
117
|
+
xml["a"].off(x: "0", y: "0")
|
|
118
|
+
xml["a"].ext(cx: image.width_emu.to_s, cy: image.height_emu.to_s)
|
|
119
|
+
end
|
|
120
|
+
xml["a"].prstGeom(prst: "rect") do
|
|
121
|
+
xml["a"].avLst
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def render_table(xml, table)
|
|
133
|
+
column_count = table.rows.first&.cells&.size || 1
|
|
134
|
+
col_width = 5000 / column_count
|
|
135
|
+
|
|
136
|
+
xml["w"].tbl do
|
|
137
|
+
xml["w"].tblPr do
|
|
138
|
+
xml["w"].tblW("w:w" => "5000", "w:type" => "pct")
|
|
139
|
+
xml["w"].tblBorders do
|
|
140
|
+
%w[top left bottom right insideH insideV].each do |border|
|
|
141
|
+
xml["w"].send(border, "w:val" => "single", "w:sz" => "4", "w:space" => "0", "w:color" => "000000")
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
xml["w"].tblGrid do
|
|
146
|
+
column_count.times do
|
|
147
|
+
xml["w"].gridCol("w:w" => col_width.to_s)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
table.rows.each { |row| render_table_row(xml, row, col_width) }
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def render_table_row(xml, row, col_width)
|
|
155
|
+
xml["w"].tr do
|
|
156
|
+
row.cells.each { |cell| render_table_cell(xml, cell, col_width) }
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def render_table_cell(xml, cell, col_width)
|
|
161
|
+
xml["w"].tc do
|
|
162
|
+
xml["w"].tcPr do
|
|
163
|
+
xml["w"].tcW("w:w" => col_width.to_s, "w:type" => "pct")
|
|
164
|
+
end
|
|
165
|
+
xml["w"].p do
|
|
166
|
+
cell.runs.each { |run| render_run(xml, run) }
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Notare
|
|
4
|
+
module Xml
|
|
5
|
+
class Numbering
|
|
6
|
+
NAMESPACE = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
|
7
|
+
|
|
8
|
+
def initialize(lists)
|
|
9
|
+
@lists = lists
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def to_xml
|
|
13
|
+
builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
|
|
14
|
+
xml.numbering("xmlns:w" => NAMESPACE) do
|
|
15
|
+
xml.parent.namespace = xml.parent.namespace_definitions.find { |ns| ns.prefix == "w" }
|
|
16
|
+
|
|
17
|
+
@lists.each do |list|
|
|
18
|
+
render_abstract_num(xml, list)
|
|
19
|
+
|
|
20
|
+
render_num(xml, list)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
builder.to_xml
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def render_abstract_num(xml, list)
|
|
30
|
+
xml["w"].abstractNum("w:abstractNumId" => list.num_id.to_s) do
|
|
31
|
+
xml["w"].lvl("w:ilvl" => "0") do
|
|
32
|
+
xml["w"].start("w:val" => "1")
|
|
33
|
+
xml["w"].numFmt("w:val" => num_format(list.type))
|
|
34
|
+
xml["w"].lvlText("w:val" => lvl_text(list.type))
|
|
35
|
+
xml["w"].lvlJc("w:val" => "left")
|
|
36
|
+
xml["w"].pPr do
|
|
37
|
+
xml["w"].ind("w:left" => "720", "w:hanging" => "360")
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def render_num(xml, list)
|
|
44
|
+
xml["w"].num("w:numId" => list.num_id.to_s) do
|
|
45
|
+
xml["w"].abstractNumId("w:val" => list.num_id.to_s)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def num_format(type)
|
|
50
|
+
type == :bullet ? "bullet" : "decimal"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def lvl_text(type)
|
|
54
|
+
type == :bullet ? "•" : "%1."
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Notare
|
|
4
|
+
module Xml
|
|
5
|
+
class Relationships
|
|
6
|
+
NAMESPACE = "http://schemas.openxmlformats.org/package/2006/relationships"
|
|
7
|
+
|
|
8
|
+
def to_xml
|
|
9
|
+
builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
|
|
10
|
+
xml.Relationships(xmlns: NAMESPACE) do
|
|
11
|
+
xml.Relationship(
|
|
12
|
+
Id: "rId1",
|
|
13
|
+
Type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
|
|
14
|
+
Target: "word/document.xml"
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
builder.to_xml
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class DocumentRelationships
|
|
23
|
+
NAMESPACE = "http://schemas.openxmlformats.org/package/2006/relationships"
|
|
24
|
+
STYLES_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"
|
|
25
|
+
NUMBERING_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering"
|
|
26
|
+
IMAGE_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
|
|
27
|
+
|
|
28
|
+
def initialize(has_numbering: false, images: [], has_styles: false)
|
|
29
|
+
@has_numbering = has_numbering
|
|
30
|
+
@images = images
|
|
31
|
+
@has_styles = has_styles
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def to_xml
|
|
35
|
+
builder = Nokogiri::XML::Builder.new(encoding: "UTF-8") do |xml|
|
|
36
|
+
xml.Relationships(xmlns: NAMESPACE) do
|
|
37
|
+
# rId1 = styles.xml (always first when present)
|
|
38
|
+
if @has_styles
|
|
39
|
+
xml.Relationship(
|
|
40
|
+
Id: "rId1",
|
|
41
|
+
Type: STYLES_TYPE,
|
|
42
|
+
Target: "styles.xml"
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# rId2 = numbering.xml (if lists present)
|
|
47
|
+
if @has_numbering
|
|
48
|
+
xml.Relationship(
|
|
49
|
+
Id: "rId2",
|
|
50
|
+
Type: NUMBERING_TYPE,
|
|
51
|
+
Target: "numbering.xml"
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Images start at rId2 or rId3 depending on numbering
|
|
56
|
+
@images.each do |image|
|
|
57
|
+
xml.Relationship(
|
|
58
|
+
Id: image.rid,
|
|
59
|
+
Type: IMAGE_TYPE,
|
|
60
|
+
Target: "media/#{image.filename}"
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
builder.to_xml
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|