open_xml 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ece1bea9f9256c7d83cdb1394600d707c9d90231
4
- data.tar.gz: d4114902905037b7bb784e628edfcd4fc09e01f6
3
+ metadata.gz: b75c6c22557762c26b167295be1afa1e6598abc1
4
+ data.tar.gz: e149a88a547038884e322ddb66f5dea018591027
5
5
  SHA512:
6
- metadata.gz: 482b6c85019fd035aab63bc487281fdc0b750b076abe758fa4a9cd4f34aa90d7de1b8d8e88c0a2b453ea84cc433c65c5a271a4a897e4ea34a7f1efee76724109
7
- data.tar.gz: b3ac90b31711f61e0a83ed819a3d9fceab806a914a6481bfaf938acf4326daefd24cf1f4d91517d1b5bd779a0fad0bb47e9120fdb03c3e82c52245fa3eb8fcdd
6
+ metadata.gz: 071829499a303aeeb9d536e1ee07d8f1f9537b207fd17352a5a350cae341a8c97d715a8d5b88e39542f2cb04f65386804f7803c91f1e0696ee2257a181a86ed6
7
+ data.tar.gz: f76afcba14892b117bbcf1ff0d2d03da1ff029e046bd3a1952c690432aa4a423c774719ae92fa3e5df2256cffcb7ef7e72095e948175bec3e4d733c7257873cf
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ .DS_Store
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
- ## v0.0.1
1
+ ## v0.1.0
2
+ * API Change!!! the template document is now created once and the data
3
+ is passed in through the process call. This allows you to loop over
4
+ lots of data and generate documents without having to instatiate a new
5
+ template document object.
2
6
 
7
+ ## v0.0.1
3
8
  * initial release
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  OpenXml
2
2
  ========
3
3
 
4
- Library for reading and writing to open xml documents (*but at the moment you can generate word docs from a template*)
4
+ A ruby library for generating word documents that can handle basic html and images too.
5
5
 
6
6
  ## Installation
7
7
 
@@ -17,27 +17,41 @@ Or install it yourself as:
17
17
 
18
18
  $ gem install open_xml
19
19
 
20
-
20
+
21
21
  ## Usage
22
22
  Provide a path to a docx with the text **[SUPERPOWER]** placed anywhere.
23
23
 
24
24
  ```ruby
25
25
  require 'open_xml'
26
26
 
27
- doc = OpenXml::TemplateDocument.new(path: "[path to template]", data: {"[SUPERPOWER]" => "Bug Fixing!!!!"})
28
- doc.process
29
-
27
+ doc = OpenXml::TemplateDocument.new(path: "[path to template]")
28
+ doc.process({"[SUPERPOWER]" => {text: "Bug Fixing!!!!"}})
29
+
30
30
  IO.write "./powers.docx", doc.to_zip_buffer.string
31
31
  ```
32
32
 
33
+ HTML content
34
+
35
+ ```ruby
36
+ doc = OpenXml::TemplateDocument.new(path: "[path to template]")
37
+ doc.process({"[SUPERPOWER]" => {text: "<h1>Bug Fixing!!!!</h1>", html: true}})
38
+ ```
39
+
40
+ HTML with images
41
+
42
+ ```ruby
43
+ doc = OpenXml::TemplateDocument.new(path: "[path to template]")
44
+ doc.process({"[SUPERPOWER]" => {text: "<img src='/powers.png' />", html: true, images: {'/powers.png' => "[Base64 encoded image]"}}})
45
+ ```
46
+
33
47
  ## Todo
34
48
  * ~~Implement reading and writing the word zip files~~
35
49
  * ~~Create a template word document with formatted key words (bold, 14pt).~~
36
50
  * ~~Replace the key words with the supplied plain text content but maintain all the formatting.~~
37
51
  * ~~Handle replacing a key with multiple content~~
38
52
  * ~~Extract these features into a gem~~
39
- * Format html content for wordprocessingML e.x. bold, italic,
40
- underline
53
+ * ~~Format html content for wordprocessingML e.x. bold, italic,
54
+ underline and handle images~~
41
55
 
42
56
  ## Contributing
43
57
 
@@ -2,49 +2,149 @@ require 'zip'
2
2
  require 'nokogiri'
3
3
 
4
4
  module OpenXml
5
-
6
-
7
5
  class TemplateDocument
8
- attr_reader :template_path, :parts, :data
6
+ attr_reader :template_path, :parts
9
7
 
10
8
  def initialize(options)
11
- @template_path = options[:path]
9
+ @template_path = options.fetch(:path)
12
10
  @parts = {}
13
- @data = options[:data]
14
- split_parts
11
+
12
+ read_files
15
13
  end
16
14
 
17
15
  def to_zip_buffer
18
16
  Zip::OutputStream.write_buffer do |w|
19
- parts.each do |k, v|
20
- w.put_next_entry k
21
- w.write v
17
+ parts.each do |key, value|
18
+ w.put_next_entry key
19
+ w.write value
22
20
  end
23
21
  end
24
22
  end
25
23
 
26
- def process
27
- doc = Nokogiri::XML(parts["word/document.xml"])
28
- doc.xpath("//w:t").each do |node|
29
- data.each do |k, v|
30
- node.content = node.content.gsub(k, Array(v).join("\n")) if node.content[/#{k}/]
24
+ def process(data)
25
+ @parts = @parts_cache.clone
26
+ register_type 'message/rfc822', 'mht'
27
+
28
+ doc = Nokogiri::XML(parts['word/document.xml'])
29
+ doc.xpath('//w:t').each do |node|
30
+ data.each do |key, value|
31
+
32
+ if node.content[/#{key}/]
33
+ process_plain_text(node, key, value, doc) unless value[:html]
34
+ process_html(node, key, value, doc) if value[:html]
35
+ end
36
+
31
37
  end
32
38
  end
33
39
 
34
- parts["word/document.xml"] = doc.to_xml
40
+ parts['word/document.xml'] = flatten_xml doc
35
41
  end
36
42
 
37
43
  private
38
44
 
39
- def split_parts
45
+ def process_plain_text(node, key, value, doc)
46
+ values = Array(value[:text])
47
+
48
+ if values.size > 1
49
+ values.each do |v|
50
+ br = Nokogiri::XML::Node.new 'w:br', doc
51
+ n = Nokogiri::XML::Node.new 'w:t', doc
52
+
53
+ n.content = v.to_s
54
+
55
+ node.parent << n
56
+ node.parent << br
57
+ end
58
+
59
+ node.remove
60
+ else
61
+ node.content = node.content.gsub(key, values.first.to_s) if values.first
62
+ end
63
+ end
64
+
65
+ def process_html(node, key, value, doc)
66
+ new_node = create_chunk_file(key, value, doc)
67
+ node.parent.parent.add_next_sibling new_node
68
+ node.remove
69
+ end
70
+
71
+ def create_chunk_file(key, content, doc)
72
+ id = key
73
+
74
+ parts["word/#{id}.mht"] = build_mht(content)
75
+ add_relation id
76
+
77
+ chunk = Nokogiri::XML::Node.new 'w:altChunk', doc
78
+ chunk['r:id'] = id
79
+ chunk
80
+ end
81
+
82
+ def add_relation(id)
83
+ relationships = Nokogiri::XML(parts['word/_rels/document.xml.rels'])
84
+ rel = Nokogiri::XML::Node.new 'Relationship', relationships
85
+ rel['Id'] = id
86
+ rel['Type'] = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/aFChunk'
87
+ rel['Target'] = "/word/#{id}.mht"
88
+
89
+ relationships.at_xpath('//xmlns:Relationships') << rel
90
+ parts['word/_rels/document.xml.rels'] = flatten_xml relationships
91
+ end
92
+
93
+ def register_type(type, extension)
94
+ content = Nokogiri::XML(parts['[Content_Types].xml'])
95
+ node = Nokogiri::XML::Node.new 'Default', content
96
+ node['ContentType'] = type
97
+ node['Extension'] = extension
98
+
99
+ content.at_xpath('//xmlns:Default').add_next_sibling node
100
+ parts['[Content_Types].xml'] = flatten_xml content
101
+ end
102
+
103
+ def flatten_xml(doc)
104
+ doc.to_xml(indent: 0).gsub("\n","")
105
+ end
106
+
107
+ def read_files
40
108
  Zip::File.new(template_path).each do |f|
41
109
  parts[f.name] = f.get_input_stream.read
42
110
  end
43
111
 
44
- parts
112
+ @parts_cache = parts.clone
45
113
  end
46
114
 
115
+ def build_mht(content)
116
+ message =<<MESSAGE
117
+ MIME-Version: 1.0
118
+ Content-Type: multipart/related; boundary=MY-SEPARATOR
119
+
120
+ --MY-SEPARATOR
121
+ Content-Type: text/html; charset=utf-8
122
+ Content-Transfer-Encoding: 8bit
123
+
124
+ #{content[:text]}
125
+
126
+ MESSAGE
47
127
 
48
- end
49
128
 
129
+ content.fetch(:images){{}}.each do |key, value|
130
+ message << img_template(key, value)
131
+ end
132
+
133
+ message << "\n--MY-SEPARATOR--"
134
+ message
135
+ end
136
+
137
+ def img_template(key, value)
138
+ <<IMG
139
+
140
+ --MY-SEPARATOR
141
+ Content-Location: #{key}
142
+ Content-Transfer-Encoding: Base64
143
+
144
+ #{value}
145
+
146
+ IMG
147
+ end
148
+
149
+ end
50
150
  end
@@ -1,3 +1,3 @@
1
1
  module OpenXml
2
- VERSION = "0.0.2"
2
+ VERSION = '0.1.0'
3
3
  end
data/lib/open_xml.rb CHANGED
@@ -1,2 +1,2 @@
1
- require 'open_xml/version'
2
1
  require_relative 'open_xml/template_document'
2
+ require_relative 'open_xml/version'
data/open_xml.gemspec CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
8
8
  spec.version = OpenXml::VERSION
9
9
  spec.authors = ["Carlos Espejo"]
10
10
  spec.email = ["carlosespejo@gmail.com"]
11
- spec.description = %q{Currently you can only generate word documents from a template word document.}
12
- spec.summary = %q{Library for reading and writing to open xml documents (*but at the moment you can generate word docs from a template*)}
11
+ spec.description = %q{Generate Word documents from a template, also handle html and images too.}
12
+ spec.summary = %q{A ruby library for generating word documents that can handle basic html and images too.}
13
13
  spec.homepage = "https://github.com/CarlosEspejo/open_xml"
14
14
  spec.license = "MIT"
15
15
 
data/spec/.DS_Store ADDED
Binary file
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+ require 'base64'
3
+
4
+ describe "Adding HTML and Images through the alternate chunk feature" do
5
+
6
+ let(:report_path){"#{File.expand_path('samples', __dir__)}/report.docx"}
7
+ let(:encoded_img){Base64.encode64(File.read("#{File.expand_path('samples', __dir__)}/caterpillar.jpg"))}
8
+
9
+ let(:t){TemplateDocument.new(path: report_path)}
10
+
11
+ it "should register MIME html type" do
12
+ t.process 'my_content' => {text: 'empty', html: true}
13
+ doc = Nokogiri::XML(t.parts['[Content_Types].xml'])
14
+ doc.xpath('//xmlns:Default/@ContentType').map(&:value).must_include 'message/rfc822'
15
+ end
16
+
17
+ it "should add the chunk id to the rels file" do
18
+ t.process 'my_content' => {text: 'empty', html: true}
19
+ doc = Nokogiri::XML(t.parts['word/_rels/document.xml.rels'])
20
+ doc.xpath('//xmlns:Relationship/@Id').map(&:value).must_include 'my_content'
21
+ end
22
+
23
+ it "should generate MIME html file" do
24
+ t.process 'my_content' => {text: '<u>This is underlined</u>', html: true}
25
+ t.parts['word/my_content.mht'].must_match(/#{'<u>This is underlined</u>'}/)
26
+ end
27
+
28
+ it "should generate MIME html file with a image" do
29
+ content = '<h1>Look at the image</h1><img src="./image.jpg" />'
30
+
31
+ t.process 'my_content' => {text: content, html: true, images: {"./image.jpg" => encoded_img}}
32
+ t.parts['word/my_content.mht'].must_match(/#{'Content-Location: ./image.jpg'}/)
33
+ end
34
+
35
+ it "should geneate MIME html file with multiple images" do
36
+ content = '<h1>Look at the images</h1>'
37
+ content << '<img src="/image.jpg" /><br/><br/><img src="/image2.jpg" />'
38
+ t.process 'my_content' => {text: content, html: true, images: {"/image.jpg" => encoded_img, '/image2.jpg' => encoded_img}}
39
+ t.parts['word/my_content.mht'].must_match(/#{'Content-Location: /image.jpg'}/)
40
+ t.parts['word/my_content.mht'].must_match(/#{'Content-Location: /image2.jpg'}/)
41
+ end
42
+
43
+ end
Binary file
Binary file
Binary file
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'minitest/autorun'
2
2
  require 'minitest/pride'
3
- require 'open_xml'
3
+ require_relative '../lib/open_xml'
4
4
  require 'pry'
5
5
 
6
6
  include OpenXml
@@ -1,17 +1,20 @@
1
1
  require 'spec_helper'
2
2
  require 'tempfile'
3
3
  require 'nokogiri'
4
+ require 'pry'
4
5
 
5
6
  describe TemplateDocument do
6
7
 
7
- it "should read the template file and split it into parts" do
8
- t = TemplateDocument.new(path: template_path)
9
- t.parts.keys.must_equal template_parts.keys
8
+ let(:template_path){"#{File.expand_path('samples', File.dirname(__FILE__))}/template_sample.docx"}
9
+ let(:report_path){"#{File.expand_path('samples', File.dirname(__FILE__))}/report.docx"}
10
10
 
11
+ it 'should read the template file and split it into parts' do
12
+ t = TemplateDocument.new(path: template_path)
13
+ t.parts.keys.must_include 'word/document.xml'
11
14
  end
12
15
 
13
- it "should create a new file from the template parts" do
14
- t = Tempfile.new(['output','.docx'])
16
+ it 'should create a new file from the template parts' do
17
+ t = Tempfile.new(['output', '.docx'])
15
18
 
16
19
  temp_doc = TemplateDocument.new(path: template_path)
17
20
 
@@ -23,56 +26,48 @@ describe TemplateDocument do
23
26
 
24
27
  end
25
28
 
26
- it "should replace key words in the document xml" do
27
- temp = Tempfile.new(['output','.docx'])
28
-
29
- t = TemplateDocument.new(path: template_path, data: {"[NAME]" => "<b>Carlos</b>", "[AGE]" => 30 })
30
- t.process
31
- IO.write temp.path, t.to_zip_buffer.string
32
-
33
- processed = TemplateDocument.new(path: temp.path)
34
- doc = Nokogiri::XML(processed.parts["word/document.xml"])
29
+ it 'should replace key words in the document xml' do
30
+ t = TemplateDocument.new(path: template_path)
31
+ t.process({ 'person_name' => {text: '<b>Carlos</b>'}, 'person_age' => {text: 30} })
32
+ doc = Nokogiri::XML(t.parts['word/document.xml'])
35
33
 
36
- doc.xpath('//w:t').text[/\[NAME\]/].must_be_nil
37
- doc.xpath('//w:t').text[/\[AGE\]/].must_be_nil
34
+ text = doc.xpath('//w:t').text
35
+ text[/Carlos/].wont_be_nil
36
+ text[/30/].wont_be_nil
38
37
  end
39
38
 
40
- it "should replace one key word with many items" do
39
+ it 'should replace one key word with many items' do
41
40
  data = {
42
- "[LIST]" => [
43
- "list 1",
44
- "list 2",
45
- "list 3",
46
- "list 4",
47
- "list 5"
48
- ]
41
+ 'my_list' => {text: [
42
+ 'list 1',
43
+ 'list 2',
44
+ 'list 3',
45
+ 'list 4',
46
+ 'list 5'
47
+ ]}
49
48
  }
50
49
 
51
- t = TemplateDocument.new(path: template_path, data: data)
52
- t.process
53
- doc = Nokogiri::XML(t.parts["word/document.xml"])
50
+ t = TemplateDocument.new(path: template_path)
51
+ t.process(data)
52
+
53
+ doc = Nokogiri::XML(t.parts['word/document.xml'])
54
54
 
55
55
  text = doc.xpath('//w:t').text
56
56
 
57
- text[/list 1\nlist 2/].wont_be_nil
57
+ text[/list 2/].wont_be_nil
58
58
  end
59
59
 
60
- let(:template_path){"#{File.expand_path('samples', File.dirname(__FILE__))}/template_sample.docx"}
60
+ it "should cache the template document" do
61
+ t = TemplateDocument.new(path: template_path)
62
+ t.process({'person_name' => {text: 'steve'}})
63
+ doc = Nokogiri::XML(t.parts['word/document.xml'])
61
64
 
62
- let(:template_parts) do
63
- {
64
- "_rels/.rels" => '',
65
- "docProps/core.xml" => '',
66
- "docProps/app.xml" => '',
67
- "word/document.xml" => '',
68
- "word/styles.xml" => '',
69
- "word/fontTable.xml" => '',
70
- "word/header.xml" => '',
71
- "word/footer.xml" => '',
72
- "word/settings.xml" => '',
73
- "word/_rels/document.xml.rels" => '',
74
- "[Content_Types].xml" => ''
75
- }
65
+ doc.text[/steve/].wont_be_nil
66
+
67
+ t.process({'person_name' => {text: 'carlos'}})
68
+ doc = Nokogiri::XML(t.parts['word/document.xml'])
69
+
70
+ doc.text[/carlos/].wont_be_nil
76
71
  end
77
72
 
78
73
  end
metadata CHANGED
@@ -1,107 +1,108 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: open_xml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carlos Espejo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-04 00:00:00.000000000 Z
11
+ date: 2014-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.3'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.3'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: minitest
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: pry
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - '>='
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - '>='
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: nokogiri
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ~>
73
+ - - "~>"
74
74
  - !ruby/object:Gem::Version
75
75
  version: '1.6'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ~>
80
+ - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '1.6'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rubyzip
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - ~>
87
+ - - "~>"
88
88
  - !ruby/object:Gem::Version
89
89
  version: '1.1'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ~>
94
+ - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '1.1'
97
- description: Currently you can only generate word documents from a template word document.
97
+ description: Generate Word documents from a template, also handle html and images
98
+ too.
98
99
  email:
99
100
  - carlosespejo@gmail.com
100
101
  executables: []
101
102
  extensions: []
102
103
  extra_rdoc_files: []
103
104
  files:
104
- - .gitignore
105
+ - ".gitignore"
105
106
  - CHANGELOG.md
106
107
  - Gemfile
107
108
  - LICENSE
@@ -111,6 +112,10 @@ files:
111
112
  - lib/open_xml/template_document.rb
112
113
  - lib/open_xml/version.rb
113
114
  - open_xml.gemspec
115
+ - spec/.DS_Store
116
+ - spec/alternate_chunk_spec.rb
117
+ - spec/samples/caterpillar.jpg
118
+ - spec/samples/report.docx
114
119
  - spec/samples/template_sample.docx
115
120
  - spec/spec_helper.rb
116
121
  - spec/template_document_spec.rb
@@ -124,22 +129,26 @@ require_paths:
124
129
  - lib
125
130
  required_ruby_version: !ruby/object:Gem::Requirement
126
131
  requirements:
127
- - - '>='
132
+ - - ">="
128
133
  - !ruby/object:Gem::Version
129
134
  version: '0'
130
135
  required_rubygems_version: !ruby/object:Gem::Requirement
131
136
  requirements:
132
- - - '>='
137
+ - - ">="
133
138
  - !ruby/object:Gem::Version
134
139
  version: '0'
135
140
  requirements: []
136
141
  rubyforge_project:
137
- rubygems_version: 2.1.11
142
+ rubygems_version: 2.2.2
138
143
  signing_key:
139
144
  specification_version: 4
140
- summary: Library for reading and writing to open xml documents (*but at the moment
141
- you can generate word docs from a template*)
145
+ summary: A ruby library for generating word documents that can handle basic html and
146
+ images too.
142
147
  test_files:
148
+ - spec/.DS_Store
149
+ - spec/alternate_chunk_spec.rb
150
+ - spec/samples/caterpillar.jpg
151
+ - spec/samples/report.docx
143
152
  - spec/samples/template_sample.docx
144
153
  - spec/spec_helper.rb
145
154
  - spec/template_document_spec.rb