docxtor 0.1.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.
- data/.coveralls.yml +1 -0
- data/.gitignore +21 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -0
- data/.wrong +1 -0
- data/Gemfile +18 -0
- data/LICENSE.txt +22 -0
- data/README.md +117 -0
- data/Rakefile +7 -0
- data/TODO.md +5 -0
- data/docxtor.gemspec +32 -0
- data/lib/docxtor.rb +30 -0
- data/lib/docxtor/document/builder.rb +32 -0
- data/lib/docxtor/document/element.rb +96 -0
- data/lib/docxtor/document/heading.rb +13 -0
- data/lib/docxtor/document/page_break.rb +14 -0
- data/lib/docxtor/document/paragraph.rb +125 -0
- data/lib/docxtor/document/root.rb +17 -0
- data/lib/docxtor/document/table_of_contents.rb +95 -0
- data/lib/docxtor/generator.rb +13 -0
- data/lib/docxtor/package/builder.rb +35 -0
- data/lib/docxtor/package/part.rb +12 -0
- data/lib/docxtor/template_parser.rb +38 -0
- data/lib/docxtor/version.rb +3 -0
- data/spec/docxtor/document/builder_spec.rb +16 -0
- data/spec/docxtor/document/heading_spec.rb +9 -0
- data/spec/docxtor/document/paragraph_spec.rb +56 -0
- data/spec/docxtor/document/root_spec.rb +20 -0
- data/spec/docxtor/document/table_of_contents_spec.rb +18 -0
- data/spec/docxtor/docxtor_spec.rb +13 -0
- data/spec/docxtor/package/builder_spec.rb +37 -0
- data/spec/docxtor/package/part_spec.rb +16 -0
- data/spec/docxtor/prerequisites_spec.rb +9 -0
- data/spec/docxtor/support/contexts/integration_context.rb +6 -0
- data/spec/docxtor/support/contexts/xml_builder_context.rb +11 -0
- data/spec/docxtor/support/examples/.gitkeep +0 -0
- data/spec/docxtor/support/matchers/wordprocessingml_matchers.rb +42 -0
- data/spec/docxtor/support/matchers/xpath_matchers.rb +46 -0
- data/spec/docxtor/template_parser_spec.rb +18 -0
- data/spec/spec_helper.rb +35 -0
- data/templates/.gitkeep +0 -0
- data/templates/default/[Content_Types].xml +16 -0
- data/templates/default/_rels/.rels +4 -0
- data/templates/default/word/_rels/document.xml.rels +13 -0
- data/templates/default/word/endnotes.xml +17 -0
- data/templates/default/word/fontTable.xml +31 -0
- data/templates/default/word/footer1.xml +35 -0
- data/templates/default/word/footer2.xml +48 -0
- data/templates/default/word/footnotes.xml +17 -0
- data/templates/default/word/numbering.xml +212 -0
- data/templates/default/word/settings.xml +107 -0
- data/templates/default/word/styles.xml +368 -0
- data/templates/default/word/theme/theme1.xml +2 -0
- data/templates/default/word/websettings.xml +4 -0
- metadata +265 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
module Docxtor
|
2
|
+
module Document
|
3
|
+
class Root < Element
|
4
|
+
map({ :table_of_contents => Document::TableOfContents,
|
5
|
+
:p => Document::Paragraph,
|
6
|
+
:h => Document::Heading,
|
7
|
+
:page_break => Document::PageBreak
|
8
|
+
})
|
9
|
+
|
10
|
+
def render(xml)
|
11
|
+
super
|
12
|
+
write_elements
|
13
|
+
xml.target!
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Docxtor
|
2
|
+
module Document
|
3
|
+
class TableOfContents < Element
|
4
|
+
STYLE = 'defprnRUSSelStyle'
|
5
|
+
PARAGRAPH_STYLE = '10'
|
6
|
+
|
7
|
+
def after_initialize(text)
|
8
|
+
create_params({:style => STYLE})
|
9
|
+
@text = text
|
10
|
+
end
|
11
|
+
|
12
|
+
# TODO: Add support for extended styling & properties
|
13
|
+
|
14
|
+
def render(xml)
|
15
|
+
super
|
16
|
+
|
17
|
+
xml.w :sdt, "xmlns:w" => "http://schemas.openxmlformats.org/wordprocessingml/2006/main" do
|
18
|
+
|
19
|
+
write_properties
|
20
|
+
|
21
|
+
xml.w :sdtContent do
|
22
|
+
|
23
|
+
write_heading
|
24
|
+
|
25
|
+
xml.w :p do
|
26
|
+
xml.w :pPr do
|
27
|
+
xml.w :pStyle, "w:val" => PARAGRAPH_STYLE
|
28
|
+
xml.w :tabs do
|
29
|
+
xml.w :tab, "w:val" => "right", "w:leader" => "dot", "w:pos" => 9350
|
30
|
+
end
|
31
|
+
xml.w :rPr do
|
32
|
+
xml.w :noProof
|
33
|
+
end
|
34
|
+
end
|
35
|
+
xml.w :r do
|
36
|
+
xml.w :fldChar, "w:fldCharType" => "begin", "w:dirty" => true
|
37
|
+
end
|
38
|
+
|
39
|
+
write_instruct
|
40
|
+
|
41
|
+
xml.w :r do
|
42
|
+
xml.w :fldChar, "w:fldCharType" => "separate"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
xml.w :p do
|
47
|
+
xml.w :r do
|
48
|
+
# xml.w :rPr do
|
49
|
+
# xml.w :b
|
50
|
+
# xml.w :bCs
|
51
|
+
# xml.w :noProof
|
52
|
+
# end
|
53
|
+
xml.w :fldChar, "w:fldCharType" => "end"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def write_properties
|
64
|
+
xml.w :sdtPr do
|
65
|
+
xml.w :docPartObj do
|
66
|
+
xml.w :docPartGallery, "w:val" => "Table of Contents"
|
67
|
+
xml.w :docPartUnique
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def write_heading
|
73
|
+
xml.w :p do
|
74
|
+
xml.w :pPr do
|
75
|
+
xml.w :pStyle, "w:val" => @params[:style]
|
76
|
+
end
|
77
|
+
xml.w :r do
|
78
|
+
xml.w :t do
|
79
|
+
xml.text! @text
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def write_instruct
|
86
|
+
xml.w :r do
|
87
|
+
xml.w :instrText, "xml:space" => "preserve" do
|
88
|
+
xml.text! " TOC \\o '1-3' \\h \\z \\u "
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Docxtor
|
2
|
+
class Generator
|
3
|
+
class << self
|
4
|
+
def generate(template, &block)
|
5
|
+
template_parser = TemplateParser.new(template)
|
6
|
+
parts = template_parser.parts
|
7
|
+
|
8
|
+
document = Document::Builder.new(&block)
|
9
|
+
Package::Builder.new(parts, document)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Docxtor
|
2
|
+
module Package
|
3
|
+
class Builder
|
4
|
+
attr_reader :parts
|
5
|
+
|
6
|
+
def initialize(parts, document)
|
7
|
+
@parts = parts
|
8
|
+
@parts['document'] = Part.new("word/document.xml", document.content)
|
9
|
+
end
|
10
|
+
|
11
|
+
def save(filepath)
|
12
|
+
Zip::ZipOutputStream.open(filepath) do |ostream|
|
13
|
+
write_parts(ostream)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_stream
|
18
|
+
ostream = Zip::ZipOutputStream.new("streamed", true)
|
19
|
+
write_parts(ostream)
|
20
|
+
string_io = ostream.close_buffer
|
21
|
+
string_io.rewind
|
22
|
+
string_io
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def write_parts(ostream)
|
28
|
+
@parts.each do |name, part|
|
29
|
+
ostream.put_next_entry(part.filename)
|
30
|
+
ostream.puts part.content
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Docxtor
|
2
|
+
class TemplateParser
|
3
|
+
attr_accessor :template
|
4
|
+
|
5
|
+
FILES_PATTERN = File.join('**', '{*,.}{xml,rels}')
|
6
|
+
|
7
|
+
def initialize(template)
|
8
|
+
@template = template || File.join(File.dirname(__FILE__), "..", "..", "templates", "default")
|
9
|
+
end
|
10
|
+
|
11
|
+
def parts
|
12
|
+
@parts ||= parse
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def parse
|
18
|
+
Dir.chdir(template) do
|
19
|
+
Hash[create_parts]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_parts
|
24
|
+
Dir[FILES_PATTERN].
|
25
|
+
delete_if { |file| File.directory?(file) }.
|
26
|
+
map { |file| create_part(file) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_part(file)
|
30
|
+
content = File.read(file)
|
31
|
+
part = Package::Part.new(file, content)
|
32
|
+
key = File.basename(file, '.xml')
|
33
|
+
|
34
|
+
[key, part]
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Docxtor
|
4
|
+
describe Document::Builder do
|
5
|
+
include WordprocessingMLMatchers
|
6
|
+
|
7
|
+
subject { Document::Builder.new do
|
8
|
+
p "Hi"
|
9
|
+
end
|
10
|
+
}
|
11
|
+
|
12
|
+
it 'contains body' do
|
13
|
+
subject.content.should contain_body
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Docxtor
|
4
|
+
describe Document::Paragraph do
|
5
|
+
include_context 'xmlbuilder' do
|
6
|
+
|
7
|
+
subject {
|
8
|
+
render(Document::Paragraph, "text1", :b => true) do
|
9
|
+
style 123
|
10
|
+
spacing :before => 80, :after => 240
|
11
|
+
font_size 32
|
12
|
+
font_size_complex 30
|
13
|
+
indent :start => 720
|
14
|
+
i; u
|
15
|
+
|
16
|
+
w "text2"; br; w "text3"
|
17
|
+
end
|
18
|
+
}
|
19
|
+
|
20
|
+
it 'contains reference to style' do
|
21
|
+
subject.should contain_element_style(:p)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'contains spacing property' do
|
25
|
+
subject.should contain_element_property(:p, :spacing)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'contains font size property' do
|
29
|
+
subject.should contain_element_property(:p, :sz)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'contains complex font size property' do
|
33
|
+
subject.should contain_element_property(:p, :szCs)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'contains indentiation property' do
|
37
|
+
subject.should contain_element_property(:p, :ind)
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'nested run' do
|
41
|
+
it 'is bold' do
|
42
|
+
subject.should contain_element_property(:r, :b)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'is italic' do
|
46
|
+
subject.should contain_element_property(:r, :i)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'is underlined' do
|
50
|
+
subject.should contain_element_property(:r, :u)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Docxtor
|
4
|
+
describe Document::Root do
|
5
|
+
include_context "xmlbuilder" do
|
6
|
+
|
7
|
+
subject {
|
8
|
+
Document::Root.new(:style => 1) do
|
9
|
+
p do
|
10
|
+
w "Hi"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
}
|
14
|
+
|
15
|
+
it 'appends element to elements' do
|
16
|
+
subject.elements.first.should be_instance_of Docxtor::Document::Paragraph
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Docxtor
|
4
|
+
describe Document::TableOfContents do
|
5
|
+
include_context 'xmlbuilder' do
|
6
|
+
|
7
|
+
subject { render(Document::TableOfContents, 'Contents') }
|
8
|
+
|
9
|
+
it 'contains gallery of document parts' do
|
10
|
+
subject.should contain_gallery_of_document_parts
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'contains heading text' do
|
14
|
+
subject.should contain_sdt_content_heading
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Docxtor, "#generate" do
|
4
|
+
context "when supplied all the same parameters that Generator takes" do
|
5
|
+
it 'does not throw errors' do
|
6
|
+
deny { rescuing {
|
7
|
+
Docxtor.generate do
|
8
|
+
p "Paragraph"
|
9
|
+
end
|
10
|
+
} }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Docxtor
|
4
|
+
describe Package::Builder do
|
5
|
+
subject { Package::Builder.new({}, Package::Part.new("word/document.xml", 'content')) }
|
6
|
+
|
7
|
+
include_context 'integration' do
|
8
|
+
context "given filepath and source document" do
|
9
|
+
it 'saves to file' do
|
10
|
+
subject.save(docx)
|
11
|
+
|
12
|
+
expect { File.exists?(docx) }
|
13
|
+
deny { rescuing { File.delete(docx) } }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'after serialization' do
|
18
|
+
it 'contains data' do
|
19
|
+
string_io = subject.to_stream
|
20
|
+
expect { string_io.size > 0 }
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'contains document part' do
|
24
|
+
string_io = subject.to_stream
|
25
|
+
istream = Zip::ZipInputStream.open_buffer(string_io)
|
26
|
+
|
27
|
+
deny { rescuing {
|
28
|
+
document_entry = istream.get_next_entry
|
29
|
+
|
30
|
+
expect { document_entry != nil }
|
31
|
+
expect { document_entry.name == "word/document.xml" }
|
32
|
+
} }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Docxtor
|
4
|
+
describe Package::Part do
|
5
|
+
context "given filename and content" do
|
6
|
+
subject { Package::Part.new("filename", "content") }
|
7
|
+
|
8
|
+
it 'returns filename' do
|
9
|
+
expect { subject.filename == "filename" }
|
10
|
+
end
|
11
|
+
it 'returns content' do
|
12
|
+
expect { subject.content == "content" }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|