docxify 0.0.2 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +309 -0
- data/CHANGELOG.md +33 -0
- data/LICENSE.md +9 -0
- data/README.md +26 -16
- data/Rakefile +3 -3
- data/lib/docxify/container.rb +10 -5
- data/lib/docxify/document.rb +21 -19
- data/lib/docxify/element/base.rb +70 -0
- data/lib/docxify/element/divider.rb +18 -0
- data/lib/docxify/element/file.rb +50 -0
- data/lib/docxify/element/image.rb +88 -0
- data/lib/docxify/element/page_break.rb +16 -0
- data/lib/docxify/element/paragraph.rb +86 -11
- data/lib/docxify/element/table.rb +107 -0
- data/lib/docxify/element/table_cell.rb +66 -0
- data/lib/docxify/element/web_address.rb +20 -0
- data/lib/docxify/template.rb +2 -0
- data/lib/docxify/version.rb +1 -1
- data/lib/docxify.rb +18 -7
- metadata +8 -4
- data/.standard.yml +0 -3
- data/lib/docxify/element/numbered_list_item.rb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb6c9076520d0776ed132e2abbc42166a6ff0106fce181d5d178ecf3abff1a96
|
4
|
+
data.tar.gz: 042d1e27e5572cd0b4868eb9af065d1b3831e25d80569eafd2086bda130ab7b1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 83ee99a55280b56efd7f571c00c8cb09eb026345b821173c389f40f3bffb907bc2f4b57ce08d6d587a0bc653cdbc4efc630a1400dd48e3dee78f005bf574469e
|
7
|
+
data.tar.gz: 62a9965149dde87c65db3877642fff5e6cf0cc8f02f373ab59422b78ca9a645522c2df45125268140f8b1e36b29a105b500ea06489f0fc5fee6056bff740bd6a
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,309 @@
|
|
1
|
+
AllCops:
|
2
|
+
SuggestExtensions: false
|
3
|
+
NewCops: enable
|
4
|
+
Exclude:
|
5
|
+
- 'Gemfile'
|
6
|
+
- 'Rakefile'
|
7
|
+
- 'spec/**/*.rb'
|
8
|
+
- 'vendor/**/*'
|
9
|
+
Style/FormatStringToken:
|
10
|
+
EnforcedStyle: unannotated
|
11
|
+
Style/FetchEnvVar:
|
12
|
+
Enabled: false
|
13
|
+
Style/StringChars:
|
14
|
+
Enabled: false
|
15
|
+
Naming/BlockForwarding:
|
16
|
+
EnforcedStyle: explicit
|
17
|
+
Metrics/ClassLength:
|
18
|
+
Enabled: false
|
19
|
+
Naming/MethodParameterName:
|
20
|
+
Enabled: false
|
21
|
+
Lint/SuppressedException:
|
22
|
+
Enabled: false
|
23
|
+
Metrics/ParameterLists:
|
24
|
+
Enabled: false
|
25
|
+
Metrics/BlockNesting:
|
26
|
+
Enabled: false
|
27
|
+
Metrics/BlockLength:
|
28
|
+
Enabled: false
|
29
|
+
Metrics/ModuleLength:
|
30
|
+
Enabled: false
|
31
|
+
Metrics/CyclomaticComplexity:
|
32
|
+
Enabled: false
|
33
|
+
Metrics/PerceivedComplexity:
|
34
|
+
Enabled: false
|
35
|
+
Metrics/MethodLength:
|
36
|
+
Enabled: false
|
37
|
+
Layout/HashAlignment:
|
38
|
+
Enabled: false
|
39
|
+
Layout/ArgumentAlignment:
|
40
|
+
Enabled: false
|
41
|
+
Layout/LineLength:
|
42
|
+
Enabled: false
|
43
|
+
Layout/SpaceAroundMethodCallOperator:
|
44
|
+
Enabled: true
|
45
|
+
Lint/RaiseException:
|
46
|
+
Enabled: true
|
47
|
+
Lint/StructNewOverride:
|
48
|
+
Enabled: true
|
49
|
+
Style/ExponentialNotation:
|
50
|
+
Enabled: true
|
51
|
+
Style/HashEachMethods:
|
52
|
+
Enabled: true
|
53
|
+
Style/Documentation:
|
54
|
+
Enabled: false
|
55
|
+
Bundler/OrderedGems:
|
56
|
+
Enabled: false
|
57
|
+
Style/FrozenStringLiteralComment:
|
58
|
+
Enabled: false
|
59
|
+
Style/TrailingCommaInHashLiteral:
|
60
|
+
Enabled: false
|
61
|
+
Layout/SpaceBeforeSemicolon:
|
62
|
+
Enabled: false
|
63
|
+
Style/ClassAndModuleChildren:
|
64
|
+
Enabled: false
|
65
|
+
Metrics/AbcSize:
|
66
|
+
Enabled: false
|
67
|
+
Lint/RescueException:
|
68
|
+
Enabled: false
|
69
|
+
Layout/MultilineMethodCallIndentation:
|
70
|
+
Enabled: false
|
71
|
+
|
72
|
+
# Prefer &&/|| over and/or.
|
73
|
+
Style/AndOr:
|
74
|
+
Enabled: true
|
75
|
+
|
76
|
+
Style/FormatString:
|
77
|
+
EnforcedStyle: percent
|
78
|
+
|
79
|
+
# Align `when` with `case`.
|
80
|
+
Layout/CaseIndentation:
|
81
|
+
Enabled: false
|
82
|
+
|
83
|
+
Layout/ClosingHeredocIndentation:
|
84
|
+
Enabled: true
|
85
|
+
|
86
|
+
# Align comments with method definitions.
|
87
|
+
Layout/CommentIndentation:
|
88
|
+
Enabled: true
|
89
|
+
|
90
|
+
Layout/ElseAlignment:
|
91
|
+
Enabled: true
|
92
|
+
|
93
|
+
Style/RedundantRegexpEscape:
|
94
|
+
Enabled: false
|
95
|
+
|
96
|
+
Style/RaiseArgs:
|
97
|
+
EnforcedStyle: compact
|
98
|
+
|
99
|
+
# Align `end` with the matching keyword or starting expression except for
|
100
|
+
# assignments, where it should be aligned with the LHS.
|
101
|
+
Layout/EndAlignment:
|
102
|
+
Enabled: true
|
103
|
+
EnforcedStyleAlignWith: variable
|
104
|
+
AutoCorrect: true
|
105
|
+
|
106
|
+
Layout/EmptyLineAfterMagicComment:
|
107
|
+
Enabled: true
|
108
|
+
|
109
|
+
Layout/EmptyLinesAroundAccessModifier:
|
110
|
+
Enabled: false
|
111
|
+
|
112
|
+
Layout/EmptyLinesAroundBlockBody:
|
113
|
+
Enabled: true
|
114
|
+
|
115
|
+
# In a regular class definition, no empty lines around the body.
|
116
|
+
Layout/EmptyLinesAroundClassBody:
|
117
|
+
Enabled: true
|
118
|
+
|
119
|
+
# In a regular method definition, no empty lines around the body.
|
120
|
+
Layout/EmptyLinesAroundMethodBody:
|
121
|
+
Enabled: true
|
122
|
+
|
123
|
+
# In a regular module definition, no empty lines around the body.
|
124
|
+
Layout/EmptyLinesAroundModuleBody:
|
125
|
+
Enabled: true
|
126
|
+
|
127
|
+
# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }.
|
128
|
+
Style/HashSyntax:
|
129
|
+
Enabled: true
|
130
|
+
EnforcedShorthandSyntax: never
|
131
|
+
|
132
|
+
Layout/FirstArgumentIndentation:
|
133
|
+
Enabled: true
|
134
|
+
|
135
|
+
Style/RescueModifier:
|
136
|
+
Enabled: false
|
137
|
+
|
138
|
+
# Method definitions after `private` or `protected` isolated calls need one
|
139
|
+
# extra level of indentation.
|
140
|
+
Layout/IndentationConsistency:
|
141
|
+
Enabled: false
|
142
|
+
|
143
|
+
# Two spaces, no tabs (for indentation).
|
144
|
+
Layout/IndentationWidth:
|
145
|
+
Enabled: true
|
146
|
+
|
147
|
+
Layout/LeadingCommentSpace:
|
148
|
+
Enabled: true
|
149
|
+
|
150
|
+
Layout/SpaceAfterColon:
|
151
|
+
Enabled: true
|
152
|
+
|
153
|
+
Layout/SpaceAfterComma:
|
154
|
+
Enabled: true
|
155
|
+
|
156
|
+
Layout/SpaceAfterSemicolon:
|
157
|
+
Enabled: true
|
158
|
+
|
159
|
+
Layout/SpaceAroundEqualsInParameterDefault:
|
160
|
+
Enabled: true
|
161
|
+
|
162
|
+
Layout/SpaceAroundKeyword:
|
163
|
+
Enabled: true
|
164
|
+
|
165
|
+
Layout/SpaceBeforeComma:
|
166
|
+
Enabled: true
|
167
|
+
|
168
|
+
Layout/SpaceBeforeComment:
|
169
|
+
Enabled: true
|
170
|
+
|
171
|
+
Layout/SpaceBeforeFirstArg:
|
172
|
+
Enabled: true
|
173
|
+
|
174
|
+
Style/DefWithParentheses:
|
175
|
+
Enabled: true
|
176
|
+
|
177
|
+
Style/MethodDefParentheses:
|
178
|
+
Enabled: true
|
179
|
+
|
180
|
+
Style/RedundantFreeze:
|
181
|
+
Enabled: true
|
182
|
+
|
183
|
+
# Use `foo {}` not `foo{}`.
|
184
|
+
Layout/SpaceBeforeBlockBraces:
|
185
|
+
Enabled: true
|
186
|
+
|
187
|
+
# Use `foo { bar }` not `foo {bar}`.
|
188
|
+
Layout/SpaceInsideBlockBraces:
|
189
|
+
Enabled: true
|
190
|
+
EnforcedStyleForEmptyBraces: space
|
191
|
+
|
192
|
+
# Use `{ a: 1 }` not `{a:1}`.
|
193
|
+
Layout/SpaceInsideHashLiteralBraces:
|
194
|
+
Enabled: true
|
195
|
+
|
196
|
+
Layout/SpaceInsideParens:
|
197
|
+
Enabled: true
|
198
|
+
|
199
|
+
# Check quotes usage according to lint rule below.
|
200
|
+
Style/StringLiterals:
|
201
|
+
Enabled: true
|
202
|
+
EnforcedStyle: double_quotes
|
203
|
+
SupportedStyles:
|
204
|
+
- single_quotes
|
205
|
+
- double_quotes
|
206
|
+
|
207
|
+
Style/StringLiteralsInInterpolation:
|
208
|
+
Enabled: false
|
209
|
+
|
210
|
+
Style/HashLikeCase:
|
211
|
+
Enabled: false
|
212
|
+
|
213
|
+
Style/IfUnlessModifier:
|
214
|
+
Enabled: false
|
215
|
+
|
216
|
+
# Detect hard tabs, no hard tabs.
|
217
|
+
Layout/IndentationStyle:
|
218
|
+
Enabled: true
|
219
|
+
|
220
|
+
# Empty lines should not have any spaces.
|
221
|
+
Layout/TrailingEmptyLines:
|
222
|
+
Enabled: true
|
223
|
+
|
224
|
+
# No trailing whitespace.
|
225
|
+
Layout/TrailingWhitespace:
|
226
|
+
Enabled: true
|
227
|
+
|
228
|
+
# Use quotes for string literals when they are enough.
|
229
|
+
Style/RedundantPercentQ:
|
230
|
+
Enabled: true
|
231
|
+
|
232
|
+
Lint/AmbiguousOperator:
|
233
|
+
Enabled: true
|
234
|
+
|
235
|
+
Lint/AmbiguousRegexpLiteral:
|
236
|
+
Enabled: true
|
237
|
+
|
238
|
+
Lint/ErbNewArguments:
|
239
|
+
Enabled: true
|
240
|
+
|
241
|
+
Lint/ParenthesesAsGroupedExpression:
|
242
|
+
Enabled: false
|
243
|
+
|
244
|
+
# Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg.
|
245
|
+
Lint/RequireParentheses:
|
246
|
+
Enabled: true
|
247
|
+
|
248
|
+
Lint/ShadowingOuterLocalVariable:
|
249
|
+
Enabled: true
|
250
|
+
|
251
|
+
Lint/RedundantStringCoercion:
|
252
|
+
Enabled: true
|
253
|
+
|
254
|
+
Lint/UriEscapeUnescape:
|
255
|
+
Enabled: true
|
256
|
+
|
257
|
+
Lint/UselessAssignment:
|
258
|
+
Enabled: true
|
259
|
+
|
260
|
+
Lint/DeprecatedClassMethods:
|
261
|
+
Enabled: true
|
262
|
+
|
263
|
+
Style/GuardClause:
|
264
|
+
Enabled: false
|
265
|
+
|
266
|
+
Style/ParenthesesAroundCondition:
|
267
|
+
Enabled: true
|
268
|
+
|
269
|
+
Style/HashTransformKeys:
|
270
|
+
Enabled: true
|
271
|
+
|
272
|
+
Style/HashTransformValues:
|
273
|
+
Enabled: true
|
274
|
+
|
275
|
+
Style/RedundantBegin:
|
276
|
+
Enabled: true
|
277
|
+
|
278
|
+
Style/RedundantReturn:
|
279
|
+
Enabled: true
|
280
|
+
AllowMultipleReturnValues: true
|
281
|
+
|
282
|
+
Style/Semicolon:
|
283
|
+
Enabled: true
|
284
|
+
AllowAsExpressionSeparator: true
|
285
|
+
|
286
|
+
# Prefer Foo.method over Foo::method
|
287
|
+
Style/ColonMethodCall:
|
288
|
+
Enabled: true
|
289
|
+
|
290
|
+
Style/TrivialAccessors:
|
291
|
+
Enabled: true
|
292
|
+
|
293
|
+
Style/BarePercentLiterals:
|
294
|
+
Enabled: false
|
295
|
+
|
296
|
+
Style/IfInsideElse:
|
297
|
+
Enabled: false
|
298
|
+
|
299
|
+
Style/RegexpLiteral:
|
300
|
+
Enabled: false
|
301
|
+
|
302
|
+
Style/ParallelAssignment:
|
303
|
+
Enabled: false
|
304
|
+
|
305
|
+
Style/RedundantParentheses:
|
306
|
+
Enabled: false
|
307
|
+
|
308
|
+
Layout/FirstHashElementIndentation:
|
309
|
+
Enabled: false
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,38 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 0.0.7
|
4
|
+
|
5
|
+
Features:
|
6
|
+
|
7
|
+
- Table support
|
8
|
+
|
9
|
+
## 0.0.6
|
10
|
+
|
11
|
+
Features:
|
12
|
+
|
13
|
+
- Tab stops and hanging indents done
|
14
|
+
- Replacing of {CHECKBOX_EMPTY} and {CHECKBOX_CHECKED} with correct UTF-8 characters done
|
15
|
+
|
16
|
+
## 0.0.5
|
17
|
+
|
18
|
+
Features:
|
19
|
+
|
20
|
+
- Paragraph font name, size, colour and yellow highlight done
|
21
|
+
- Document level default font name, size and colour done
|
22
|
+
|
23
|
+
## 0.0.4
|
24
|
+
|
25
|
+
Features:
|
26
|
+
|
27
|
+
- Image insertion is working
|
28
|
+
|
29
|
+
## 0.0.3
|
30
|
+
|
31
|
+
Features:
|
32
|
+
|
33
|
+
- Dividers and page breaks implemented
|
34
|
+
- Simple HTML parsing (for paragraphs) implemented
|
35
|
+
|
3
36
|
## 0.0.2
|
4
37
|
|
5
38
|
Features:
|
data/LICENSE.md
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# MIT License
|
2
|
+
|
3
|
+
Copyright ©️2024 FounderCatalyst IP Ltd
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
6
|
+
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
8
|
+
|
9
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -25,32 +25,42 @@ gem install docxify
|
|
25
25
|
|
26
26
|
@docx.add_paragraph "Title", font: "Something", size: 18, color: "#00000"
|
27
27
|
@docx.add_paragraph "Body copy"
|
28
|
-
@docx.add_paragraph "This is <b>bold</b>, <i>Italic</i> and <u>Underlined</u>.
|
28
|
+
@docx.add_paragraph "This is <b>bold</b>, <i>Italic</i> and <u>Underlined</u>."
|
29
|
+
@docx.add_paragraph "Text can also contain <a href='foo'>Links</a>."
|
29
30
|
@docx.add_paragraph "Centred text", align: :center
|
30
31
|
@docx.add_paragraph "Highlighted text", background: "#FFFF99"
|
31
|
-
@docx.add_paragraph "This
|
32
|
+
@docx.add_paragraph "This won't show as <b>bold</b>", inline_styling: false
|
32
33
|
|
33
|
-
@docx.add_paragraph "
|
34
|
+
@docx.add_paragraph "\t1.1.1\tBody copy", tab_stops_cm: [1, 2]
|
34
35
|
@docx.add_paragraph "{CHECKBOX_EMPTY}\tEmpty checkbox", tab_stops_cm: [2]
|
35
36
|
@docx.add_paragraph "{CHECKBOX_CHECKED}\tChecked checkbox", tab_stops_cm: [2]
|
36
37
|
|
37
|
-
@docx.add_numbered_list_item "This is a list item", level: 0
|
38
|
-
|
39
|
-
@docx.add_page_break
|
40
38
|
@docx.add_divider
|
41
39
|
|
42
40
|
@docx.add_image "file_path or data", align: :right, height_cm: 2, width_cm: 4
|
43
41
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
]
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
42
|
+
@docx.add_page_break
|
43
|
+
|
44
|
+
@docx.container page_width: DocXify::A4_PORTRAIT_HEIGHT, page_height: DocXify::A4_PORTRAIT_WIDTH do |container|
|
45
|
+
rows = []
|
46
|
+
rows << [
|
47
|
+
DocXify::Element::TableCell.new("<b>Header 1</b>"),
|
48
|
+
DocXify::Element::TableCell.new("<b>Header 2</b>")
|
49
|
+
DocXify::Element::TableCell.new("<b>Header 3</b>")
|
50
|
+
DocXify::Element::TableCell.new("<b>Header 4</b>", width_cm: 10)
|
51
|
+
]
|
52
|
+
rows << [
|
53
|
+
DocXify::Element::TableCell.new("Content 1", valign: :center, align: :left, nowrap: true, colspan: 3),
|
54
|
+
DocXify::Element::TableCell.new("Rowspan <b>here</b>", rowspan: true) # merges until a "nil" cell
|
55
|
+
]
|
56
|
+
rows << [
|
57
|
+
DocXify::Element::TableCell.new("Secondary 1")
|
58
|
+
DocXify::Element::TableCell.new("Secondary 2")
|
59
|
+
DocXify::Element::TableCell.new("Secondary 3")
|
60
|
+
DocXify::Element::TableCell.new(nil) # Word requires that there's an empty cell merged with the previous rowspan
|
61
|
+
]
|
62
|
+
container.add_table rows
|
63
|
+
end
|
54
64
|
|
55
65
|
docx_binary_data = @docx.render
|
56
66
|
# or
|
data/Rakefile
CHANGED
data/lib/docxify/container.rb
CHANGED
@@ -36,9 +36,14 @@ module DocXify
|
|
36
36
|
zip.put_next_entry "word/document.xml"
|
37
37
|
zip.write document.build_xml(self)
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
zip.put_next_entry "word/_rels/document.xml.rels"
|
40
|
+
zip.write document_xml_rels
|
41
|
+
|
42
|
+
@document.relationships.each do |relation|
|
43
|
+
if relation.is_a?(DocXify::Element::File)
|
44
|
+
zip.put_next_entry "word/media/#{relation.filename}"
|
45
|
+
zip.write relation.data
|
46
|
+
end
|
42
47
|
end
|
43
48
|
end
|
44
49
|
|
@@ -61,8 +66,8 @@ module DocXify
|
|
61
66
|
<Relationship Id="rId4" Target="fontTable.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable"/>
|
62
67
|
XML
|
63
68
|
|
64
|
-
@document.
|
65
|
-
xml <<
|
69
|
+
@document.relationships.each do |relation|
|
70
|
+
xml << relation.to_s
|
66
71
|
end
|
67
72
|
|
68
73
|
xml << "</Relationships>"
|
data/lib/docxify/document.rb
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
module DocXify
|
2
2
|
class Document
|
3
3
|
attr_accessor :font, :size, :color, :background
|
4
|
-
attr_reader :content, :
|
4
|
+
attr_reader :content, :relationships, :width
|
5
5
|
|
6
6
|
def initialize(options = {})
|
7
7
|
@content = []
|
8
|
-
@
|
8
|
+
@relationships = []
|
9
9
|
@width = options[:width] || A4_PORTRAIT_WIDTH
|
10
10
|
@height = options[:height] || A4_PORTRAIT_HEIGHT
|
11
11
|
@font = options[:font] || "Times New Roman"
|
12
12
|
@size = options[:size] || 12
|
13
13
|
@color = options[:color] || 12
|
14
14
|
@background = options[:background] if options[:background]
|
15
|
-
@margins = {top: 2, bottom: 2, left: 2, right: 2}
|
15
|
+
@margins = { top: 2, bottom: 2, left: 2, right: 2 }
|
16
16
|
end
|
17
17
|
|
18
18
|
def default_styling(options = {})
|
@@ -37,7 +37,7 @@ module DocXify
|
|
37
37
|
def build_xml(container)
|
38
38
|
xml = <<~XML
|
39
39
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
40
|
-
<w:document mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh wp14" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape">
|
40
|
+
<w:document mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh wp14" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape">
|
41
41
|
<w:body>
|
42
42
|
XML
|
43
43
|
|
@@ -64,34 +64,36 @@ module DocXify
|
|
64
64
|
end
|
65
65
|
|
66
66
|
def add_image(file_path_or_data, options = {})
|
67
|
-
file = DocXify::Element::File
|
68
|
-
|
69
|
-
|
67
|
+
file = if file_path_or_data.is_a?(DocXify::Element::File)
|
68
|
+
file_path_or_data
|
69
|
+
else
|
70
|
+
add_file(file_path_or_data)
|
71
|
+
end
|
72
|
+
add DocXify::Element::Image.new(file, options)
|
70
73
|
end
|
71
74
|
|
72
|
-
def
|
73
|
-
|
75
|
+
def add_file(file_path_or_data)
|
76
|
+
file = DocXify::Element::File.new(file_path_or_data)
|
77
|
+
@relationships << file
|
78
|
+
file
|
74
79
|
end
|
75
80
|
|
76
81
|
def add_page_break
|
77
|
-
add DocXify::Element::PageBreak.new
|
82
|
+
add DocXify::Element::PageBreak.new
|
78
83
|
end
|
79
84
|
|
80
85
|
def add_divider
|
81
|
-
add DocXify::Element::Divider.new
|
86
|
+
add DocXify::Element::Divider.new
|
82
87
|
end
|
83
88
|
|
84
89
|
def add_paragraph(text, options = {})
|
85
|
-
|
90
|
+
options[:document] = self
|
91
|
+
add DocXify::Element::Paragraph.new(text, options)
|
86
92
|
end
|
87
93
|
|
88
|
-
def add_table(
|
89
|
-
|
94
|
+
def add_table(rows, options = {})
|
95
|
+
options[:document] = self
|
96
|
+
add DocXify::Element::Table.new(rows, options)
|
90
97
|
end
|
91
|
-
|
92
|
-
# MARK: Private
|
93
|
-
|
94
|
-
private
|
95
|
-
|
96
98
|
end
|
97
99
|
end
|
data/lib/docxify/element/base.rb
CHANGED
@@ -4,6 +4,76 @@ module DocXify
|
|
4
4
|
def to_s(container = nil)
|
5
5
|
raise NotImplementedError
|
6
6
|
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def parse_simple_html(html)
|
11
|
+
state = :text
|
12
|
+
tag = ""
|
13
|
+
content = ""
|
14
|
+
result = []
|
15
|
+
text = ""
|
16
|
+
last_char = ""
|
17
|
+
|
18
|
+
html.each_char do |char|
|
19
|
+
# puts "State: #{state}, Char: #{char}, Tag: #{tag}, Content: #{content}, Text: #{text}"
|
20
|
+
case state
|
21
|
+
when :text # This is a text node, not part of an HTML tag
|
22
|
+
if char == "<"
|
23
|
+
state = :tag
|
24
|
+
tag = ""
|
25
|
+
result << text unless text.empty?
|
26
|
+
text = ""
|
27
|
+
else
|
28
|
+
text << char
|
29
|
+
end
|
30
|
+
when :tag # Within an HTML tag itself
|
31
|
+
if char == ">"
|
32
|
+
state = :content
|
33
|
+
content = ""
|
34
|
+
tag_name, *attributes = tag.split
|
35
|
+
tag_name.gsub!(/\s*\//, "")
|
36
|
+
attributes.delete_if { |attr| attr == "/" }
|
37
|
+
if attributes.any?
|
38
|
+
attrs = {}
|
39
|
+
attributes.each do |attribute|
|
40
|
+
name, value = attribute.split("=")
|
41
|
+
value = value.to_s[1..-2] if value
|
42
|
+
attrs[name.to_sym] = value
|
43
|
+
end
|
44
|
+
result << { tag: tag_name, attributes: attrs, content: "" }
|
45
|
+
else
|
46
|
+
result << { tag: tag_name, content: "" }
|
47
|
+
end
|
48
|
+
|
49
|
+
if last_char == "/"
|
50
|
+
state = :text
|
51
|
+
end
|
52
|
+
elsif char == "/" && last_char == "<"
|
53
|
+
state = :closing_tag
|
54
|
+
else
|
55
|
+
tag << char
|
56
|
+
end
|
57
|
+
when :closing_tag
|
58
|
+
if char == ">"
|
59
|
+
state = :text
|
60
|
+
end
|
61
|
+
when :content # With the content of an HTML tag
|
62
|
+
if char == "<"
|
63
|
+
state = :tag
|
64
|
+
result.last[:content] = content
|
65
|
+
content = ""
|
66
|
+
else
|
67
|
+
content << char
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
last_char = char
|
72
|
+
end
|
73
|
+
|
74
|
+
result << text unless text.empty?
|
75
|
+
result
|
76
|
+
end
|
7
77
|
end
|
8
78
|
end
|
9
79
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module DocXify
|
2
|
+
module Element
|
3
|
+
class Divider < Base
|
4
|
+
def to_s(_container = nil)
|
5
|
+
<<~XML
|
6
|
+
<w:p>
|
7
|
+
<w:pPr>
|
8
|
+
<w:pBdr>
|
9
|
+
<w:bottom w:color="auto" w:space="1" w:sz="6" w:val="single"/>
|
10
|
+
</w:pBdr>
|
11
|
+
</w:pPr>
|
12
|
+
</w:p>
|
13
|
+
<w:p/>
|
14
|
+
XML
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "digest/sha1"
|
2
|
+
|
3
|
+
module DocXify
|
4
|
+
module Element
|
5
|
+
class File < Base
|
6
|
+
PNG_SIGNATURE = "\x89PNG\r\n\u001A\n".b.freeze
|
7
|
+
JPEG_START = "\xFF\xD8".b.freeze
|
8
|
+
JPEG_END = "\xFF\xD9".b.freeze
|
9
|
+
|
10
|
+
attr_accessor :data, :filename
|
11
|
+
|
12
|
+
def initialize(file_path_or_data)
|
13
|
+
super()
|
14
|
+
load_file_data(file_path_or_data)
|
15
|
+
end
|
16
|
+
|
17
|
+
def load_file_data(file_path_or_data)
|
18
|
+
if contains_png_image?(file_path_or_data)
|
19
|
+
@data = file_path_or_data
|
20
|
+
@filename = "#{Digest::SHA1.hexdigest(@data)}.png"
|
21
|
+
elsif contains_jpeg_image?(file_path_or_data)
|
22
|
+
@data = file_path_or_data
|
23
|
+
@filename = "#{Digest::SHA1.hexdigest(@data)}.jpg"
|
24
|
+
elsif ::File.exist?(file_path_or_data)
|
25
|
+
@filename = ::File.basename(file_path_or_data)
|
26
|
+
@data = ::File.read(file_path_or_data, mode: "rb")
|
27
|
+
raise ArgumentError.new("Unsupported file type - images must be PNG or JPEG") unless contains_png_image?(@data) || contains_jpeg_image?(@data)
|
28
|
+
else
|
29
|
+
raise ArgumentError.new("Unsupported file type - images must be PNG or JPEG")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def reference
|
34
|
+
"image-#{Digest::SHA1.hexdigest(@data)[0, 8]}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
"<Relationship Id=\"#{reference}\" Target=\"media/#{filename}\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image\"/>"
|
39
|
+
end
|
40
|
+
|
41
|
+
def contains_png_image?(data)
|
42
|
+
data[0, 8] == PNG_SIGNATURE
|
43
|
+
end
|
44
|
+
|
45
|
+
def contains_jpeg_image?(data)
|
46
|
+
data[0..1] == JPEG_START && data[-2..] == JPEG_END
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module DocXify
|
2
|
+
module Element
|
3
|
+
class Image < Base
|
4
|
+
attr_accessor :file, :align, :height_cm, :width_cm
|
5
|
+
|
6
|
+
def initialize(file, options = {})
|
7
|
+
super()
|
8
|
+
@file = file
|
9
|
+
|
10
|
+
@align = options[:align] || :left
|
11
|
+
@height_cm = options[:height_cm] || 5
|
12
|
+
@width_cm = options[:width_cm] || 5
|
13
|
+
end
|
14
|
+
|
15
|
+
def id
|
16
|
+
rand(1_000_000_000)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s(_container = nil)
|
20
|
+
xml = "<w:p>"
|
21
|
+
|
22
|
+
if @align == :right
|
23
|
+
xml << <<~XML
|
24
|
+
<w:pPr>
|
25
|
+
<w:jc w:val="right"/>
|
26
|
+
</w:pPr>
|
27
|
+
XML
|
28
|
+
elsif @align == :center
|
29
|
+
xml << <<~XML
|
30
|
+
<w:pPr>
|
31
|
+
<w:jc w:val="center"/>
|
32
|
+
</w:pPr>
|
33
|
+
XML
|
34
|
+
end
|
35
|
+
|
36
|
+
xml << <<~XML
|
37
|
+
<w:r>
|
38
|
+
<w:rPr>
|
39
|
+
<w:noProof/>
|
40
|
+
</w:rPr>
|
41
|
+
<w:drawing>
|
42
|
+
<wp:inline distB="0" distL="0" distR="0" distT="0">
|
43
|
+
<wp:extent cx="#{DocXify.cm2emu(@width_cm)}" cy="#{DocXify.cm2emu(@height_cm)}"/>
|
44
|
+
<wp:effectExtent b="0" l="0" r="0" t="0"/>
|
45
|
+
<wp:docPr id="#{id}" name="Picture 1"/>
|
46
|
+
<wp:cNvGraphicFramePr>
|
47
|
+
<a:graphicFrameLocks noChangeAspect="1" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"/>
|
48
|
+
</wp:cNvGraphicFramePr>
|
49
|
+
<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
|
50
|
+
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
|
51
|
+
<pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
|
52
|
+
<pic:nvPicPr>
|
53
|
+
<pic:cNvPr id="#{id}" name="Picture #{id}"/>
|
54
|
+
<pic:cNvPicPr/>
|
55
|
+
</pic:nvPicPr>
|
56
|
+
<pic:blipFill>
|
57
|
+
<a:blip cstate="print" r:embed="#{@file.reference}">
|
58
|
+
<a:extLst>
|
59
|
+
<a:ext uri="{28A0092B-C50C-407E-A947-70E740481C1C}">
|
60
|
+
<a14:useLocalDpi val="0" xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main"/>
|
61
|
+
</a:ext>
|
62
|
+
</a:extLst>
|
63
|
+
</a:blip>
|
64
|
+
<a:stretch>
|
65
|
+
<a:fillRect/>
|
66
|
+
</a:stretch>
|
67
|
+
</pic:blipFill>
|
68
|
+
<pic:spPr>
|
69
|
+
<a:xfrm>
|
70
|
+
<a:off x="0" y="0"/>
|
71
|
+
<a:ext cx="#{DocXify.cm2emu(@width_cm)}" cy="#{DocXify.cm2emu(@height_cm)}"/>
|
72
|
+
</a:xfrm>
|
73
|
+
<a:prstGeom prst="rect">
|
74
|
+
<a:avLst/>
|
75
|
+
</a:prstGeom>
|
76
|
+
</pic:spPr>
|
77
|
+
</pic:pic>
|
78
|
+
</a:graphicData>
|
79
|
+
</a:graphic>
|
80
|
+
</wp:inline>
|
81
|
+
</w:drawing>
|
82
|
+
</w:r>
|
83
|
+
</w:p>
|
84
|
+
XML
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -1,24 +1,99 @@
|
|
1
1
|
module DocXify
|
2
2
|
module Element
|
3
3
|
class Paragraph < Base
|
4
|
-
attr_accessor :text, :font, :size, :color, :background, :align, :inline_styling, :tab_stops_cm
|
4
|
+
attr_accessor :text, :font, :size, :color, :background, :align, :inline_styling, :tab_stops_cm, :hanging_cm
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
|
6
|
+
def initialize(text, options = {})
|
7
|
+
super()
|
8
|
+
@document = options[:document]
|
8
9
|
@text = text
|
9
|
-
@font = options[:font] || document
|
10
|
-
@size = options[:size] || document
|
11
|
-
@color = options[:color] || document
|
10
|
+
@font = options[:font] || @document&.font || "Times New Roman"
|
11
|
+
@size = options[:size] || @document&.size || 14
|
12
|
+
@color = options[:color] || @document&.color || "#000000"
|
13
|
+
@highlight = options[:highlight] || false
|
12
14
|
@background = options[:background] if options[:background]
|
13
|
-
@background ||= document
|
15
|
+
@background ||= @document&.background if @document&.background
|
14
16
|
@align = options[:align] || :left
|
15
|
-
@inline_styling = options[:inline_styling]
|
17
|
+
@inline_styling = options.key?(:inline_styling) ? options[:inline_styling] : true
|
16
18
|
@tab_stops_cm = options[:tab_stops_cm] || []
|
19
|
+
@hanging_cm = options[:hanging_cm] || 0
|
17
20
|
end
|
18
21
|
|
19
|
-
def to_s(
|
20
|
-
|
21
|
-
|
22
|
+
def to_s(_container = nil)
|
23
|
+
nodes = if @inline_styling
|
24
|
+
parse_simple_html(@text)
|
25
|
+
else
|
26
|
+
[@text.gsub("<", "<").gsub(">", ">")]
|
27
|
+
end
|
28
|
+
|
29
|
+
xml = "<w:p>"
|
30
|
+
|
31
|
+
xml << "<w:pPr>"
|
32
|
+
xml << "<w:jc w:val=\"#{@align}\"/>" if @align != :left
|
33
|
+
|
34
|
+
if tab_stops_cm.any?
|
35
|
+
xml << "<w:tabs>"
|
36
|
+
tab_stops_cm.each do |stop|
|
37
|
+
xml << "<w:tab w:pos=\"#{DocXify.cm2dxa(stop)}\" w:val=\"left\"/>"
|
38
|
+
end
|
39
|
+
xml << "</w:tabs>"
|
40
|
+
end
|
41
|
+
|
42
|
+
if @hanging_cm&.positive?
|
43
|
+
xml << "<w:ind w:hanging=\"#{DocXify.cm2dxa(@hanging_cm)}\" w:left=\"#{DocXify.cm2dxa(@hanging_cm)}\"/>"
|
44
|
+
end
|
45
|
+
|
46
|
+
xml << "</w:pPr>"
|
47
|
+
|
48
|
+
nodes.each do |node|
|
49
|
+
if node.is_a?(Hash) && node[:tag] == "a"
|
50
|
+
xml << "<w:hyperlink r:id=\"#{ref_for_href(node[:attributes][:href])}\">"
|
51
|
+
end
|
52
|
+
|
53
|
+
xml << "<w:r>"
|
54
|
+
xml << <<~XML
|
55
|
+
<w:rPr>
|
56
|
+
<w:rFonts w:ascii="#{@font}" w:cs="#{@font}" w:hAnsi="#{@font}"/>
|
57
|
+
<w:color w:val="#{@color.gsub("#", "")}"/>
|
58
|
+
<w:sz w:val="#{DocXify.pt2halfpt(@size)}"/>
|
59
|
+
<w:szCs w:val="#{DocXify.pt2halfpt(@size)}"/>
|
60
|
+
#{"<w:highlight w:val=\"yellow\"/>" if @highlight}
|
61
|
+
#{"<w:b/><w:bCs/>" if node.is_a?(Hash) && node[:tag] == "b"}
|
62
|
+
#{"<w:i/><w:iCs/>" if node.is_a?(Hash) && node[:tag] == "i"}
|
63
|
+
#{"<w:u w:val=\"single\"/>" if node.is_a?(Hash) && (node[:tag] == "u" || node[:tag] == "a")}
|
64
|
+
#{"<w:rStyle w:val=\"Hyperlink\"/>" if node.is_a?(Hash) && node[:tag] == "a"}
|
65
|
+
</w:rPr>
|
66
|
+
XML
|
67
|
+
|
68
|
+
content = node.is_a?(Hash) ? node[:content] : node
|
69
|
+
content = content.gsub("{CHECKBOX_EMPTY}", "☐").gsub("{CHECKBOX_CHECKED}", "☒")
|
70
|
+
xml << "<w:t xml:space=\"preserve\">#{content}</w:t>"
|
71
|
+
xml << "</w:r>"
|
72
|
+
|
73
|
+
if node.is_a?(Hash) && node[:tag] == "a"
|
74
|
+
xml << "</w:hyperlink>"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
xml << "</w:p>"
|
79
|
+
end
|
80
|
+
|
81
|
+
def ref_for_href(href)
|
82
|
+
relation = nil
|
83
|
+
|
84
|
+
@document.relationships.select { |r| r.is_a?(DocXify::Element::WebAddress) }.each do |r|
|
85
|
+
if r.target == href
|
86
|
+
relation = r
|
87
|
+
break
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
if relation.nil?
|
92
|
+
relation = DocXify::Element::WebAddress.new(href)
|
93
|
+
@document.relationships << relation
|
94
|
+
end
|
95
|
+
|
96
|
+
relation.reference
|
22
97
|
end
|
23
98
|
end
|
24
99
|
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# Structure of a table element looks something like this
|
2
|
+
# <w:tbl> - Table element
|
3
|
+
# <w:tblPr> - Table properties
|
4
|
+
# <w:tblGrid> - Table grid (column widths)
|
5
|
+
# <w:tr> - Table row
|
6
|
+
# <w:tc> - Table cell
|
7
|
+
# <w:tcPr> - Table cell properties
|
8
|
+
# <w:tcW w:type="dxa" w:w="1502"/> - Table cell width
|
9
|
+
# <w:vMerge w:val="restart"/> - Something about merging TODO - figure out
|
10
|
+
# <w:tcBorders> - Overriding table cell borders
|
11
|
+
# <w:top w:val="nil"/>
|
12
|
+
# <w:bottom w:color="auto" w:space="0" w:sz="4" w:val="single"/>
|
13
|
+
# <w:right w:val="nil"/>
|
14
|
+
# </w:tcBorders>
|
15
|
+
# <w:vAlign w:val="bottom"/>
|
16
|
+
# </w:tcPr>
|
17
|
+
# <w:p> - Paragraph element
|
18
|
+
|
19
|
+
module DocXify
|
20
|
+
module Element
|
21
|
+
class Table < Base
|
22
|
+
attr_accessor :rows, :column_widths
|
23
|
+
|
24
|
+
def initialize(rows, options = {})
|
25
|
+
super()
|
26
|
+
@document = options[:document]
|
27
|
+
@rows = rows
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s(_container = nil)
|
31
|
+
xml = ""
|
32
|
+
xml << table_element_start
|
33
|
+
xml << table_properties
|
34
|
+
xml << table_grid
|
35
|
+
@rows.each do |row|
|
36
|
+
xml << table_row(row)
|
37
|
+
end
|
38
|
+
xml << table_element_end
|
39
|
+
xml
|
40
|
+
end
|
41
|
+
|
42
|
+
def table_element_start
|
43
|
+
"<w:tbl>"
|
44
|
+
end
|
45
|
+
|
46
|
+
def table_properties
|
47
|
+
xml = ""
|
48
|
+
xml << "<w:tblPr>"
|
49
|
+
xml << '<w:tblStyle w:val="TableGrid"/>'
|
50
|
+
xml << '<w:tblW w:type="auto" w:w="0"/>'
|
51
|
+
xml << "<w:tblBorders>"
|
52
|
+
xml << '<w:top w:color="auto" w:space="0" w:sz="0" w:val="none"/>'
|
53
|
+
xml << '<w:left w:color="auto" w:space="0" w:sz="0" w:val="none"/>'
|
54
|
+
xml << '<w:bottom w:color="auto" w:space="0" w:sz="0" w:val="none"/>'
|
55
|
+
xml << '<w:right w:color="auto" w:space="0" w:sz="0" w:val="none"/>'
|
56
|
+
xml << "</w:tblBorders>"
|
57
|
+
xml << '<w:tblLook w:firstColumn="1" w:firstRow="1" w:lastColumn="0" w:lastRow="0" w:noHBand="0" w:noVBand="1" w:val="04A0"/>'
|
58
|
+
xml << "</w:tblPr>"
|
59
|
+
xml
|
60
|
+
end
|
61
|
+
|
62
|
+
def table_grid
|
63
|
+
default_equal_width = (@document.width / @rows.first.size).to_i
|
64
|
+
|
65
|
+
@column_widths = []
|
66
|
+
xml = "<w:tblGrid>"
|
67
|
+
@rows.first.each do |cell|
|
68
|
+
width = if cell.width_cm
|
69
|
+
DocXify.cm2dxa(cell.width_cm)
|
70
|
+
else
|
71
|
+
default_equal_width
|
72
|
+
end
|
73
|
+
xml << "<w:gridCol w:w=\"#{width}\"/>"
|
74
|
+
@column_widths << width
|
75
|
+
end
|
76
|
+
|
77
|
+
xml << "</w:tblGrid>"
|
78
|
+
xml
|
79
|
+
end
|
80
|
+
|
81
|
+
def table_row(row)
|
82
|
+
xml = "<w:tr>"
|
83
|
+
widths = @column_widths.dup
|
84
|
+
row.each do |cell|
|
85
|
+
if cell.nil?
|
86
|
+
cell = DocXify::Element::TableCell.new(nil)
|
87
|
+
end
|
88
|
+
|
89
|
+
if cell.width_cm.nil?
|
90
|
+
cell.width_cm = 0
|
91
|
+
(cell.colspan || 1).times do
|
92
|
+
cell.width_cm += DocXify.dxa2cm(widths.shift)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
xml << cell.to_s
|
97
|
+
end
|
98
|
+
xml << "</w:tr>"
|
99
|
+
xml
|
100
|
+
end
|
101
|
+
|
102
|
+
def table_element_end
|
103
|
+
"</w:tbl>"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module DocXify
|
2
|
+
module Element
|
3
|
+
class TableCell < Base
|
4
|
+
attr_accessor :content, :valign, :align, :nowrap, :colspan, :width_cm, :borders, :font, :size, :colour
|
5
|
+
|
6
|
+
def initialize(content, options = {})
|
7
|
+
super()
|
8
|
+
@content = content
|
9
|
+
@valign = options[:valign] || :top
|
10
|
+
@align = options[:align] || :left
|
11
|
+
@nowrap = options[:nowrap]
|
12
|
+
@colspan = options[:colspan]
|
13
|
+
@rowspan = options[:rowspan]
|
14
|
+
@width_cm = options[:width_cm]
|
15
|
+
@font = options[:font]
|
16
|
+
@size = options[:size]
|
17
|
+
@color = options[:color]
|
18
|
+
@borders = options[:borders]&.map(&:to_sym) || []
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
xml = "<w:tc>"
|
23
|
+
xml << "<w:tcPr>"
|
24
|
+
xml << %Q(<w:tcW w:type="dxa" w:w="#{DocXify.cm2dxa(@width_cm)}"/>)
|
25
|
+
if !@colspan.nil? && @colspan.to_i > 1
|
26
|
+
xml << %Q(<w:gridSpan w:val="#{@colspan}"/>')
|
27
|
+
end
|
28
|
+
|
29
|
+
if borders.any?
|
30
|
+
xml << "<w:tcBorders>"
|
31
|
+
xml << (borders.include?(:top) ? '<w:top w:color="auto" w:space="0" w:sz="4" w:val="single"/>' : '<w:top w:val="nil"/>')
|
32
|
+
xml << (borders.include?(:bottom) ? '<w:bottom w:color="auto" w:space="0" w:sz="4" w:val="single"/>' : '<w:bottom w:val="nil"/>')
|
33
|
+
xml << (borders.include?(:left) ? '<w:left w:color="auto" w:space="0" w:sz="4" w:val="single"/>' : '<w:left w:val="nil"/>')
|
34
|
+
xml << (borders.include?(:right) ? '<w:right w:color="auto" w:space="0" w:sz="4" w:val="single"/>' : '<w:right w:val="nil"/>')
|
35
|
+
xml << "</w:tcBorders>"
|
36
|
+
end
|
37
|
+
|
38
|
+
if @valign != :top
|
39
|
+
xml << %Q(<w:vAlign w:val="#{@valign}"/>)
|
40
|
+
end
|
41
|
+
|
42
|
+
if @nowrap
|
43
|
+
xml << "<w:noWrap/>"
|
44
|
+
end
|
45
|
+
|
46
|
+
if @content.nil?
|
47
|
+
@content = "" # Nil is useful for rowspan, but we need to output something or Word breaks
|
48
|
+
end
|
49
|
+
|
50
|
+
if @rowspan
|
51
|
+
xml << '<w:vMerge w:val="restart"/>'
|
52
|
+
elsif @content == ""
|
53
|
+
xml << "<w:vMerge/>"
|
54
|
+
end
|
55
|
+
|
56
|
+
xml << "</w:tcPr>"
|
57
|
+
|
58
|
+
xml << DocXify::Element::Paragraph.new(@content, document: @document, font: @font, size: @size, color: @color, align: @align).to_s
|
59
|
+
|
60
|
+
xml << "</w:tc>"
|
61
|
+
|
62
|
+
xml
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module DocXify
|
2
|
+
module Element
|
3
|
+
class WebAddress < Base
|
4
|
+
attr_accessor :target
|
5
|
+
|
6
|
+
def initialize(target)
|
7
|
+
super()
|
8
|
+
@target = target
|
9
|
+
end
|
10
|
+
|
11
|
+
def reference
|
12
|
+
"url-#{Digest::SHA1.hexdigest(target)[0, 8]}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
"<Relationship Id=\"#{reference}\" Target=\"#{target}/\" TargetMode=\"External\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink\"/>"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/docxify/template.rb
CHANGED
@@ -15,6 +15,8 @@ module DocXify
|
|
15
15
|
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
16
16
|
<Default ContentType="application/vnd.openxmlformats-package.relationships+xml" Extension="rels"/>
|
17
17
|
<Default ContentType="application/xml" Extension="xml"/>
|
18
|
+
<Default ContentType="image/png" Extension="png"/>
|
19
|
+
<Default ContentType="image/jpeg" Extension="jpg"/>
|
18
20
|
<Override ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" PartName="/word/document.xml"/>
|
19
21
|
<Override ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml" PartName="/word/styles.xml"/>
|
20
22
|
<Override ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml" PartName="/word/settings.xml"/>
|
data/lib/docxify/version.rb
CHANGED
data/lib/docxify.rb
CHANGED
@@ -2,13 +2,16 @@ require_relative "docxify/version"
|
|
2
2
|
require_relative "docxify/document"
|
3
3
|
require_relative "docxify/container"
|
4
4
|
require_relative "docxify/template"
|
5
|
+
|
5
6
|
require_relative "docxify/element/base"
|
6
7
|
require_relative "docxify/element/divider"
|
8
|
+
require_relative "docxify/element/file"
|
7
9
|
require_relative "docxify/element/image"
|
8
|
-
require_relative "docxify/element/numbered_list_item"
|
9
10
|
require_relative "docxify/element/page_break"
|
10
11
|
require_relative "docxify/element/paragraph"
|
11
12
|
require_relative "docxify/element/table"
|
13
|
+
require_relative "docxify/element/table_cell"
|
14
|
+
require_relative "docxify/element/web_address"
|
12
15
|
|
13
16
|
module DocXify
|
14
17
|
class Error < StandardError; end
|
@@ -22,24 +25,32 @@ module DocXify
|
|
22
25
|
# Used for most sizes
|
23
26
|
def self.cm2dxa(value)
|
24
27
|
value = value.to_f
|
25
|
-
raise ArgumentError
|
28
|
+
raise ArgumentError.new("Value must be greater than or equal to 0") if value.negative?
|
29
|
+
|
30
|
+
(value * UNITS_PER_CM).to_i
|
31
|
+
end
|
32
|
+
|
33
|
+
# Inverse of cm2dxa
|
34
|
+
def self.dxa2cm(value)
|
35
|
+
value = value.to_f
|
36
|
+
raise ArgumentError.new("Value must be greater than or equal to 0") if value.negative?
|
26
37
|
|
27
|
-
value
|
38
|
+
(value / UNITS_PER_CM).to_i
|
28
39
|
end
|
29
40
|
|
30
41
|
# Used for font sizes
|
31
42
|
def self.pt2halfpt(value)
|
32
43
|
value = value.to_f
|
33
|
-
raise ArgumentError
|
44
|
+
raise ArgumentError.new("Value must be greater than or equal to 0") if value.negative?
|
34
45
|
|
35
|
-
value * 2
|
46
|
+
(value * 2).to_i
|
36
47
|
end
|
37
48
|
|
38
49
|
# Used for image sizes
|
39
50
|
def self.cm2emu(value)
|
40
51
|
value = value.to_f
|
41
|
-
raise ArgumentError
|
52
|
+
raise ArgumentError.new("Value must be greater than or equal to 0") if value.negative?
|
42
53
|
|
43
|
-
value *
|
54
|
+
(value * 360_000).to_i
|
44
55
|
end
|
45
56
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: docxify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Jeffries
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-04-
|
11
|
+
date: 2024-04-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubyzip
|
@@ -33,8 +33,9 @@ extensions: []
|
|
33
33
|
extra_rdoc_files: []
|
34
34
|
files:
|
35
35
|
- ".rspec"
|
36
|
-
- ".
|
36
|
+
- ".rubocop.yml"
|
37
37
|
- CHANGELOG.md
|
38
|
+
- LICENSE.md
|
38
39
|
- README.md
|
39
40
|
- Rakefile
|
40
41
|
- lib/docxify.rb
|
@@ -42,11 +43,13 @@ files:
|
|
42
43
|
- lib/docxify/document.rb
|
43
44
|
- lib/docxify/element/base.rb
|
44
45
|
- lib/docxify/element/divider.rb
|
46
|
+
- lib/docxify/element/file.rb
|
45
47
|
- lib/docxify/element/image.rb
|
46
|
-
- lib/docxify/element/numbered_list_item.rb
|
47
48
|
- lib/docxify/element/page_break.rb
|
48
49
|
- lib/docxify/element/paragraph.rb
|
49
50
|
- lib/docxify/element/table.rb
|
51
|
+
- lib/docxify/element/table_cell.rb
|
52
|
+
- lib/docxify/element/web_address.rb
|
50
53
|
- lib/docxify/template.rb
|
51
54
|
- lib/docxify/version.rb
|
52
55
|
homepage: https://github.com/foundercatalyst/docxify
|
@@ -55,6 +58,7 @@ metadata:
|
|
55
58
|
homepage_uri: https://github.com/foundercatalyst/docxify
|
56
59
|
source_code_uri: https://github.com/foundercatalyst/docxify
|
57
60
|
changelog_uri: https://github.com/foundercatalyst/docxify/CHANGELOG.md
|
61
|
+
rubygems_mfa_required: 'true'
|
58
62
|
post_install_message:
|
59
63
|
rdoc_options: []
|
60
64
|
require_paths:
|
data/.standard.yml
DELETED
File without changes
|