docxtor 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|