open_xml 0.0.2 → 0.1.0

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 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