docx 0.2.07 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -23,6 +23,8 @@ module Docx
23
23
  def initialize(node, document_properties = {})
24
24
  @node = node
25
25
  @text_nodes = @node.xpath('w:t').map {|t_node| Elements::Text.new(t_node) }
26
+ @text_nodes = @node.xpath('w:t|w:r/w:t').map {|t_node| Elements::Text.new(t_node) }
27
+
26
28
  @properties_tag = 'rPr'
27
29
  @text = parse_text || ''
28
30
  @formatting = parse_formatting || DEFAULT_FORMATTING
@@ -45,6 +47,13 @@ module Docx
45
47
  @text_nodes.map(&:content).join('')
46
48
  end
47
49
 
50
+ # Substitute text in text @text_nodes
51
+ def substitute(match, replacement)
52
+ @text_nodes.each do |text_node|
53
+ text_node.content = text_node.content.gsub(match, replacement)
54
+ end
55
+ end
56
+
48
57
  def parse_formatting
49
58
  {
50
59
  italic: !@node.xpath('.//w:i').empty?,
@@ -67,6 +76,7 @@ module Docx
67
76
  # No need to be granular with font size down to the span level if it doesn't vary.
68
77
  styles['font-size'] = "#{font_size}pt" if font_size != @font_size
69
78
  html = html_tag(:span, content: html, styles: styles) unless styles.empty?
79
+ html = html_tag(:a, content: html, attributes: {href: href, target: "_blank"}) if hyperlink?
70
80
  return html
71
81
  end
72
82
 
@@ -82,6 +92,18 @@ module Docx
82
92
  @formatting[:underline]
83
93
  end
84
94
 
95
+ def hyperlink?
96
+ @node.name == 'hyperlink'
97
+ end
98
+
99
+ def href
100
+ @document_properties[:hyperlinks][hyperlink_id]
101
+ end
102
+
103
+ def hyperlink_id
104
+ @node.attributes['id'].value
105
+ end
106
+
85
107
  def font_size
86
108
  size_tag = @node.xpath('w:rPr//w:sz').first
87
109
  size_tag ? size_tag.attributes['val'].value.to_i / 2 : @font_size
@@ -1,172 +1,172 @@
1
- unless Object.const_defined?("ActiveSupport")
2
- class Module
3
- # Provides a delegate class method to easily expose contained objects' public methods
4
- # as your own. Pass one or more methods (specified as symbols or strings)
5
- # and the name of the target object via the <tt>:to</tt> option (also a symbol
6
- # or string). At least one method and the <tt>:to</tt> option are required.
7
- #
8
- # Delegation is particularly useful with Active Record associations:
9
- #
10
- # class Greeter < ActiveRecord::Base
11
- # def hello
12
- # 'hello'
13
- # end
14
- #
15
- # def goodbye
16
- # 'goodbye'
17
- # end
18
- # end
19
- #
20
- # class Foo < ActiveRecord::Base
21
- # belongs_to :greeter
22
- # delegate :hello, to: :greeter
23
- # end
24
- #
25
- # Foo.new.hello # => "hello"
26
- # Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
27
- #
28
- # Multiple delegates to the same target are allowed:
29
- #
30
- # class Foo < ActiveRecord::Base
31
- # belongs_to :greeter
32
- # delegate :hello, :goodbye, to: :greeter
33
- # end
34
- #
35
- # Foo.new.goodbye # => "goodbye"
36
- #
37
- # Methods can be delegated to instance variables, class variables, or constants
38
- # by providing them as a symbols:
39
- #
40
- # class Foo
41
- # CONSTANT_ARRAY = [0,1,2,3]
42
- # @@class_array = [4,5,6,7]
43
- #
44
- # def initialize
45
- # @instance_array = [8,9,10,11]
46
- # end
47
- # delegate :sum, to: :CONSTANT_ARRAY
48
- # delegate :min, to: :@@class_array
49
- # delegate :max, to: :@instance_array
50
- # end
51
- #
52
- # Foo.new.sum # => 6
53
- # Foo.new.min # => 4
54
- # Foo.new.max # => 11
55
- #
56
- # It's also possible to delegate a method to the class by using +:class+:
57
- #
58
- # class Foo
59
- # def self.hello
60
- # "world"
61
- # end
62
- #
63
- # delegate :hello, to: :class
64
- # end
65
- #
66
- # Foo.new.hello # => "world"
67
- #
68
- # Delegates can optionally be prefixed using the <tt>:prefix</tt> option. If the value
69
- # is <tt>true</tt>, the delegate methods are prefixed with the name of the object being
70
- # delegated to.
71
- #
72
- # Person = Struct.new(:name, :address)
73
- #
74
- # class Invoice < Struct.new(:client)
75
- # delegate :name, :address, to: :client, prefix: true
76
- # end
77
- #
78
- # john_doe = Person.new('John Doe', 'Vimmersvej 13')
79
- # invoice = Invoice.new(john_doe)
80
- # invoice.client_name # => "John Doe"
81
- # invoice.client_address # => "Vimmersvej 13"
82
- #
83
- # It is also possible to supply a custom prefix.
84
- #
85
- # class Invoice < Struct.new(:client)
86
- # delegate :name, :address, to: :client, prefix: :customer
87
- # end
88
- #
89
- # invoice = Invoice.new(john_doe)
90
- # invoice.customer_name # => 'John Doe'
91
- # invoice.customer_address # => 'Vimmersvej 13'
92
- #
93
- # If the delegate object is +nil+ an exception is raised, and that happens
94
- # no matter whether +nil+ responds to the delegated method. You can get a
95
- # +nil+ instead with the +:allow_nil+ option.
96
- #
97
- # class Foo
98
- # attr_accessor :bar
99
- # def initialize(bar = nil)
100
- # @bar = bar
101
- # end
102
- # delegate :zoo, to: :bar
103
- # end
104
- #
105
- # Foo.new.zoo # raises NoMethodError exception (you called nil.zoo)
106
- #
107
- # class Foo
108
- # attr_accessor :bar
109
- # def initialize(bar = nil)
110
- # @bar = bar
111
- # end
112
- # delegate :zoo, to: :bar, allow_nil: true
113
- # end
114
- #
115
- # Foo.new.zoo # returns nil
116
- def delegate(*methods)
117
- options = methods.pop
118
- unless options.is_a?(Hash) && to = options[:to]
119
- raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).'
120
- end
121
-
122
- prefix, allow_nil = options.values_at(:prefix, :allow_nil)
123
-
124
- if prefix == true && to =~ /^[^a-z_]/
125
- raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.'
126
- end
127
-
128
- method_prefix = \
129
- if prefix
130
- "#{prefix == true ? to : prefix}_"
131
- else
132
- ''
133
- end
134
-
135
- file, line = caller.first.split(':', 2)
136
- line = line.to_i
137
-
138
- to = to.to_s
139
- to = 'self.class' if to == 'class'
140
-
141
- methods.each do |method|
142
- # Attribute writer methods only accept one argument. Makes sure []=
143
- # methods still accept two arguments.
144
- definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block'
145
-
146
- if allow_nil
147
- module_eval(<<-EOS, file, line - 2)
148
- def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
149
- if #{to} || #{to}.respond_to?(:#{method}) # if client || client.respond_to?(:name)
150
- #{to}.#{method}(#{definition}) # client.name(*args, &block)
151
- end # end
152
- end # end
153
- EOS
154
- else
155
- exception = %(raise "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
156
-
157
- module_eval(<<-EOS, file, line - 1)
158
- def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
159
- #{to}.#{method}(#{definition}) # client.name(*args, &block)
160
- rescue NoMethodError # rescue NoMethodError
161
- if #{to}.nil? # if client.nil?
162
- #{exception} # # add helpful message to the exception
163
- else # else
164
- raise # raise
165
- end # end
166
- end # end
167
- EOS
168
- end
169
- end
170
- end
171
- end
1
+ unless Object.const_defined?("ActiveSupport")
2
+ class Module
3
+ # Provides a delegate class method to easily expose contained objects' public methods
4
+ # as your own. Pass one or more methods (specified as symbols or strings)
5
+ # and the name of the target object via the <tt>:to</tt> option (also a symbol
6
+ # or string). At least one method and the <tt>:to</tt> option are required.
7
+ #
8
+ # Delegation is particularly useful with Active Record associations:
9
+ #
10
+ # class Greeter < ActiveRecord::Base
11
+ # def hello
12
+ # 'hello'
13
+ # end
14
+ #
15
+ # def goodbye
16
+ # 'goodbye'
17
+ # end
18
+ # end
19
+ #
20
+ # class Foo < ActiveRecord::Base
21
+ # belongs_to :greeter
22
+ # delegate :hello, to: :greeter
23
+ # end
24
+ #
25
+ # Foo.new.hello # => "hello"
26
+ # Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for #<Foo:0x1af30c>
27
+ #
28
+ # Multiple delegates to the same target are allowed:
29
+ #
30
+ # class Foo < ActiveRecord::Base
31
+ # belongs_to :greeter
32
+ # delegate :hello, :goodbye, to: :greeter
33
+ # end
34
+ #
35
+ # Foo.new.goodbye # => "goodbye"
36
+ #
37
+ # Methods can be delegated to instance variables, class variables, or constants
38
+ # by providing them as a symbols:
39
+ #
40
+ # class Foo
41
+ # CONSTANT_ARRAY = [0,1,2,3]
42
+ # @@class_array = [4,5,6,7]
43
+ #
44
+ # def initialize
45
+ # @instance_array = [8,9,10,11]
46
+ # end
47
+ # delegate :sum, to: :CONSTANT_ARRAY
48
+ # delegate :min, to: :@@class_array
49
+ # delegate :max, to: :@instance_array
50
+ # end
51
+ #
52
+ # Foo.new.sum # => 6
53
+ # Foo.new.min # => 4
54
+ # Foo.new.max # => 11
55
+ #
56
+ # It's also possible to delegate a method to the class by using +:class+:
57
+ #
58
+ # class Foo
59
+ # def self.hello
60
+ # "world"
61
+ # end
62
+ #
63
+ # delegate :hello, to: :class
64
+ # end
65
+ #
66
+ # Foo.new.hello # => "world"
67
+ #
68
+ # Delegates can optionally be prefixed using the <tt>:prefix</tt> option. If the value
69
+ # is <tt>true</tt>, the delegate methods are prefixed with the name of the object being
70
+ # delegated to.
71
+ #
72
+ # Person = Struct.new(:name, :address)
73
+ #
74
+ # class Invoice < Struct.new(:client)
75
+ # delegate :name, :address, to: :client, prefix: true
76
+ # end
77
+ #
78
+ # john_doe = Person.new('John Doe', 'Vimmersvej 13')
79
+ # invoice = Invoice.new(john_doe)
80
+ # invoice.client_name # => "John Doe"
81
+ # invoice.client_address # => "Vimmersvej 13"
82
+ #
83
+ # It is also possible to supply a custom prefix.
84
+ #
85
+ # class Invoice < Struct.new(:client)
86
+ # delegate :name, :address, to: :client, prefix: :customer
87
+ # end
88
+ #
89
+ # invoice = Invoice.new(john_doe)
90
+ # invoice.customer_name # => 'John Doe'
91
+ # invoice.customer_address # => 'Vimmersvej 13'
92
+ #
93
+ # If the delegate object is +nil+ an exception is raised, and that happens
94
+ # no matter whether +nil+ responds to the delegated method. You can get a
95
+ # +nil+ instead with the +:allow_nil+ option.
96
+ #
97
+ # class Foo
98
+ # attr_accessor :bar
99
+ # def initialize(bar = nil)
100
+ # @bar = bar
101
+ # end
102
+ # delegate :zoo, to: :bar
103
+ # end
104
+ #
105
+ # Foo.new.zoo # raises NoMethodError exception (you called nil.zoo)
106
+ #
107
+ # class Foo
108
+ # attr_accessor :bar
109
+ # def initialize(bar = nil)
110
+ # @bar = bar
111
+ # end
112
+ # delegate :zoo, to: :bar, allow_nil: true
113
+ # end
114
+ #
115
+ # Foo.new.zoo # returns nil
116
+ def delegate(*methods)
117
+ options = methods.pop
118
+ unless options.is_a?(Hash) && to = options[:to]
119
+ raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).'
120
+ end
121
+
122
+ prefix, allow_nil = options.values_at(:prefix, :allow_nil)
123
+
124
+ if prefix == true && to =~ /^[^a-z_]/
125
+ raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.'
126
+ end
127
+
128
+ method_prefix = \
129
+ if prefix
130
+ "#{prefix == true ? to : prefix}_"
131
+ else
132
+ ''
133
+ end
134
+
135
+ file, line = caller.first.split(':', 2)
136
+ line = line.to_i
137
+
138
+ to = to.to_s
139
+ to = 'self.class' if to == 'class'
140
+
141
+ methods.each do |method|
142
+ # Attribute writer methods only accept one argument. Makes sure []=
143
+ # methods still accept two arguments.
144
+ definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block'
145
+
146
+ if allow_nil
147
+ module_eval(<<-EOS, file, line - 2)
148
+ def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
149
+ if #{to} || #{to}.respond_to?(:#{method}) # if client || client.respond_to?(:name)
150
+ #{to}.#{method}(#{definition}) # client.name(*args, &block)
151
+ end # end
152
+ end # end
153
+ EOS
154
+ else
155
+ exception = %(raise "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
156
+
157
+ module_eval(<<-EOS, file, line - 1)
158
+ def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
159
+ #{to}.#{method}(#{definition}) # client.name(*args, &block)
160
+ rescue NoMethodError # rescue NoMethodError
161
+ if #{to}.nil? # if client.nil?
162
+ #{exception} # # add helpful message to the exception
163
+ else # else
164
+ raise # raise
165
+ end # end
166
+ end # end
167
+ EOS
168
+ end
169
+ end
170
+ end
171
+ end
172
172
  end
data/lib/docx/document.rb CHANGED
@@ -1,148 +1,201 @@
1
- require 'docx/containers'
2
- require 'docx/elements'
3
- require 'nokogiri'
4
- require 'zip'
5
-
6
- module Docx
7
- # The Document class wraps around a docx file and provides methods to
8
- # interface with it.
9
- #
10
- # # get a Docx::Document for a docx file in the local directory
11
- # doc = Docx::Document.open("test.docx")
12
- #
13
- # # get the text from the document
14
- # puts doc.text
15
- #
16
- # # do the same thing in a block
17
- # Docx::Document.open("test.docx") do |d|
18
- # puts d.text
19
- # end
20
- class Document
21
- attr_reader :xml, :doc, :zip, :styles
22
-
23
- def initialize(path, &block)
24
- @replace = {}
25
- @zip = Zip::File.open(path)
26
- @document_xml = @zip.read('word/document.xml')
27
- @doc = Nokogiri::XML(@document_xml)
28
- @styles_xml = @zip.read('word/styles.xml')
29
- @styles = Nokogiri::XML(@styles_xml)
30
- if block_given?
31
- yield self
32
- @zip.close
33
- end
34
- end
35
-
36
-
37
- # This stores the current global document properties, for now
38
- def document_properties
39
- {
40
- font_size: font_size
41
- }
42
- end
43
-
44
-
45
- # With no associated block, Docx::Document.open is a synonym for Docx::Document.new. If the optional code block is given, it will be passed the opened +docx+ file as an argument and the Docx::Document oject will automatically be closed when the block terminates. The values of the block will be returned from Docx::Document.open.
46
- # call-seq:
47
- # open(filepath) => file
48
- # open(filepath) {|file| block } => obj
49
- def self.open(path, &block)
50
- self.new(path, &block)
51
- end
52
-
53
- def paragraphs
54
- @doc.xpath('//w:document//w:body//w:p').map { |p_node| parse_paragraph_from p_node }
55
- end
56
-
57
- def bookmarks
58
- bkmrks_hsh = Hash.new
59
- bkmrks_ary = @doc.xpath('//w:bookmarkStart').map { |b_node| parse_bookmark_from b_node }
60
- # auto-generated by office 2010
61
- bkmrks_ary.reject! {|b| b.name == "_GoBack" }
62
- bkmrks_ary.each {|b| bkmrks_hsh[b.name] = b }
63
- bkmrks_hsh
64
- end
65
-
66
- def tables
67
- @doc.xpath('//w:document//w:body//w:tbl').map { |t_node| parse_table_from t_node }
68
- end
69
-
70
- # Some documents have this set, others don't.
71
- # Values are returned as half-points, so to get points, that's why it's divided by 2.
72
- def font_size
73
- size_tag = @styles.xpath('//w:docDefaults//w:rPrDefault//w:rPr//w:sz').first
74
- size_tag ? size_tag.attributes['val'].value.to_i / 2 : nil
75
- end
76
-
77
- ##
78
- # *Deprecated*
79
- #
80
- # Iterates over paragraphs within document
81
- # call-seq:
82
- # each_paragraph => Enumerator
83
- def each_paragraph
84
- paragraphs.each { |p| yield(p) }
85
- end
86
-
87
- # call-seq:
88
- # to_s -> string
89
- def to_s
90
- paragraphs.map(&:to_s).join("\n")
91
- end
92
-
93
- # Output entire document as a String HTML fragment
94
- def to_html
95
- paragraphs.map(&:to_html).join('\n')
96
- end
97
-
98
- # Save document to provided path
99
- # call-seq:
100
- # save(filepath) => void
101
- def save(path)
102
- update
103
- Zip::OutputStream.open(path) do |out|
104
- zip.each do |entry|
105
- out.put_next_entry(entry.name)
106
-
107
- if @replace[entry.name]
108
- out.write(@replace[entry.name])
109
- else
110
- out.write(zip.read(entry.name))
111
- end
112
- end
113
- end
114
- zip.close
115
- end
116
-
117
- alias_method :text, :to_s
118
-
119
- def replace_entry(entry_path, file_contents)
120
- @replace[entry_path] = file_contents
121
- end
122
-
123
- private
124
-
125
- #--
126
- # TODO: Flesh this out to be compatible with other files
127
- # TODO: Method to set flag on files that have been edited, probably by inserting something at the
128
- # end of methods that make edits?
129
- #++
130
- def update
131
- replace_entry "word/document.xml", doc.serialize(:save_with => 0)
132
- end
133
-
134
- # generate Elements::Containers::Paragraph from paragraph XML node
135
- def parse_paragraph_from(p_node)
136
- Elements::Containers::Paragraph.new(p_node, document_properties)
137
- end
138
-
139
- # generate Elements::Bookmark from bookmark XML node
140
- def parse_bookmark_from(b_node)
141
- Elements::Bookmark.new(b_node)
142
- end
143
-
144
- def parse_table_from(t_node)
145
- Elements::Containers::Table.new(t_node)
146
- end
147
- end
148
- end
1
+ require 'docx/containers'
2
+ require 'docx/elements'
3
+ require 'nokogiri'
4
+ require 'zip'
5
+
6
+ module Docx
7
+ # The Document class wraps around a docx file and provides methods to
8
+ # interface with it.
9
+ #
10
+ # # get a Docx::Document for a docx file in the local directory
11
+ # doc = Docx::Document.open("test.docx")
12
+ #
13
+ # # get the text from the document
14
+ # puts doc.text
15
+ #
16
+ # # do the same thing in a block
17
+ # Docx::Document.open("test.docx") do |d|
18
+ # puts d.text
19
+ # end
20
+ class Document
21
+ attr_reader :xml, :doc, :zip, :styles
22
+
23
+ def initialize(path_or_io, options = {})
24
+ @replace = {}
25
+
26
+ # if path-or_io is string && does not contain a null byte
27
+ if (path_or_io.instance_of?(String) && !/\u0000/.match?(path_or_io))
28
+ @zip = Zip::File.open(path_or_io)
29
+ else
30
+ @zip = Zip::File.open_buffer(path_or_io)
31
+ end
32
+
33
+ document = @zip.glob('word/document*.xml').first
34
+ raise Errno::ENOENT if document.nil?
35
+
36
+ @document_xml = document.get_input_stream.read
37
+ @doc = Nokogiri::XML(@document_xml)
38
+ load_styles
39
+ yield(self) if block_given?
40
+ ensure
41
+ @zip.close
42
+ end
43
+
44
+ # This stores the current global document properties, for now
45
+ def document_properties
46
+ {
47
+ font_size: font_size,
48
+ hyperlinks: hyperlinks
49
+ }
50
+ end
51
+
52
+ # With no associated block, Docx::Document.open is a synonym for Docx::Document.new. If the optional code block is given, it will be passed the opened +docx+ file as an argument and the Docx::Document oject will automatically be closed when the block terminates. The values of the block will be returned from Docx::Document.open.
53
+ # call-seq:
54
+ # open(filepath) => file
55
+ # open(filepath) {|file| block } => obj
56
+ def self.open(path, &block)
57
+ new(path, &block)
58
+ end
59
+
60
+ def paragraphs
61
+ @doc.xpath('//w:document//w:body/w:p').map { |p_node| parse_paragraph_from p_node }
62
+ end
63
+
64
+ def bookmarks
65
+ bkmrks_hsh = {}
66
+ bkmrks_ary = @doc.xpath('//w:bookmarkStart').map { |b_node| parse_bookmark_from b_node }
67
+ # auto-generated by office 2010
68
+ bkmrks_ary.reject! { |b| b.name == '_GoBack' }
69
+ bkmrks_ary.each { |b| bkmrks_hsh[b.name] = b }
70
+ bkmrks_hsh
71
+ end
72
+
73
+ def tables
74
+ @doc.xpath('//w:document//w:body//w:tbl').map { |t_node| parse_table_from t_node }
75
+ end
76
+
77
+ # Some documents have this set, others don't.
78
+ # Values are returned as half-points, so to get points, that's why it's divided by 2.
79
+ def font_size
80
+ return nil unless @styles
81
+
82
+ size_tag = @styles.xpath('//w:docDefaults//w:rPrDefault//w:rPr//w:sz').first
83
+ size_tag ? size_tag.attributes['val'].value.to_i / 2 : nil
84
+ end
85
+
86
+ # Hyperlink targets are extracted from the document.xml.rels file
87
+ def hyperlinks
88
+ hyperlink_relationships.each_with_object({}) do |rel, hash|
89
+ hash[rel.attributes['Id'].value] = rel.attributes['Target'].value
90
+ end
91
+ end
92
+
93
+ def hyperlink_relationships
94
+ @rels.xpath("//xmlns:Relationship[contains(@Type,'hyperlink')]")
95
+ end
96
+
97
+ ##
98
+ # *Deprecated*
99
+ #
100
+ # Iterates over paragraphs within document
101
+ # call-seq:
102
+ # each_paragraph => Enumerator
103
+ def each_paragraph
104
+ paragraphs.each { |p| yield(p) }
105
+ end
106
+
107
+ # call-seq:
108
+ # to_s -> string
109
+ def to_s
110
+ paragraphs.map(&:to_s).join("\n")
111
+ end
112
+
113
+ # Output entire document as a String HTML fragment
114
+ def to_html
115
+ paragraphs.map(&:to_html).join("\n")
116
+ end
117
+
118
+ # Save document to provided path
119
+ # call-seq:
120
+ # save(filepath) => void
121
+ def save(path)
122
+ update
123
+ Zip::OutputStream.open(path) do |out|
124
+ zip.each do |entry|
125
+ next unless entry.file?
126
+
127
+ out.put_next_entry(entry.name)
128
+
129
+ if @replace[entry.name]
130
+ out.write(@replace[entry.name])
131
+ else
132
+ out.write(zip.read(entry.name))
133
+ end
134
+ end
135
+ end
136
+ zip.close
137
+ end
138
+
139
+ # Output entire document as a StringIO object
140
+ def stream
141
+ update
142
+ stream = Zip::OutputStream.write_buffer do |out|
143
+ zip.each do |entry|
144
+ next unless entry.file?
145
+
146
+ out.put_next_entry(entry.name)
147
+
148
+ if @replace[entry.name]
149
+ out.write(@replace[entry.name])
150
+ else
151
+ out.write(zip.read(entry.name))
152
+ end
153
+ end
154
+ end
155
+
156
+ stream.rewind
157
+ stream
158
+ end
159
+
160
+ alias text to_s
161
+
162
+ def replace_entry(entry_path, file_contents)
163
+ @replace[entry_path] = file_contents
164
+ end
165
+
166
+ private
167
+
168
+ def load_styles
169
+ @styles_xml = @zip.read('word/styles.xml')
170
+ @styles = Nokogiri::XML(@styles_xml)
171
+ @rels_xml = @zip.read('word/_rels/document.xml.rels')
172
+ @rels = Nokogiri::XML(@rels_xml)
173
+ rescue Errno::ENOENT => e
174
+ warn e.message
175
+ nil
176
+ end
177
+
178
+ #--
179
+ # TODO: Flesh this out to be compatible with other files
180
+ # TODO: Method to set flag on files that have been edited, probably by inserting something at the
181
+ # end of methods that make edits?
182
+ #++
183
+ def update
184
+ replace_entry 'word/document.xml', doc.serialize(save_with: 0)
185
+ end
186
+
187
+ # generate Elements::Containers::Paragraph from paragraph XML node
188
+ def parse_paragraph_from(p_node)
189
+ Elements::Containers::Paragraph.new(p_node, document_properties)
190
+ end
191
+
192
+ # generate Elements::Bookmark from bookmark XML node
193
+ def parse_bookmark_from(b_node)
194
+ Elements::Bookmark.new(b_node)
195
+ end
196
+
197
+ def parse_table_from(t_node)
198
+ Elements::Containers::Table.new(t_node)
199
+ end
200
+ end
201
+ end