docx 0.2.03 → 0.6.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.
@@ -13,17 +13,23 @@ module Docx
13
13
  underline: false
14
14
  }
15
15
 
16
- TAG = 'r'
16
+ def self.tag
17
+ 'r'
18
+ end
17
19
 
18
20
  attr_reader :text
19
21
  attr_reader :formatting
20
22
 
21
- def initialize(node)
23
+ def initialize(node, document_properties = {})
22
24
  @node = node
23
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
+
24
28
  @properties_tag = 'rPr'
25
29
  @text = parse_text || ''
26
30
  @formatting = parse_formatting || DEFAULT_FORMATTING
31
+ @document_properties = document_properties
32
+ @font_size = @document_properties[:font_size]
27
33
  end
28
34
 
29
35
  # Set text of text run
@@ -41,6 +47,13 @@ module Docx
41
47
  @text_nodes.map(&:content).join('')
42
48
  end
43
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
+
44
57
  def parse_formatting
45
58
  {
46
59
  italic: !@node.xpath('.//w:i').empty?,
@@ -52,7 +65,21 @@ module Docx
52
65
  def to_s
53
66
  @text
54
67
  end
55
-
68
+
69
+ # Return text as a HTML fragment with formatting based on properties.
70
+ def to_html
71
+ html = @text
72
+ html = html_tag(:em, content: html) if italicized?
73
+ html = html_tag(:strong, content: html) if bolded?
74
+ styles = {}
75
+ styles['text-decoration'] = 'underline' if underlined?
76
+ # No need to be granular with font size down to the span level if it doesn't vary.
77
+ styles['font-size'] = "#{font_size}pt" if font_size != @font_size
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?
80
+ return html
81
+ end
82
+
56
83
  def italicized?
57
84
  @formatting[:italic]
58
85
  end
@@ -64,6 +91,23 @@ module Docx
64
91
  def underlined?
65
92
  @formatting[:underline]
66
93
  end
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
+
107
+ def font_size
108
+ size_tag = @node.xpath('w:rPr//w:sz').first
109
+ size_tag ? size_tag.attributes['val'].value.to_i / 2 : @font_size
110
+ end
67
111
  end
68
112
  end
69
113
  end
@@ -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
@@ -1,87 +1,202 @@
1
- require 'docx/parser'
2
- require 'zip/zip'
3
-
4
- module Docx
5
- # The Document class wraps around a docx file and provides methods to
6
- # interface with it.
7
- #
8
- # # get a Docx::Document for a docx file in the local directory
9
- # doc = Docx::Document.open("test.docx")
10
- #
11
- # # get the text from the document
12
- # puts doc.text
13
- #
14
- # # do the same thing in a block
15
- # Docx::Document.open("test.docx") do |d|
16
- # puts d.text
17
- # end
18
- class Document
19
- delegate :paragraphs, :bookmarks, :to => :@parser
20
- delegate :doc, :xml, :zip, :to => :@parser
21
- def initialize(path, &block)
22
- @replace = {}
23
- if block_given?
24
- @parser = Parser.new(File.expand_path(path), &block)
25
- else
26
- @parser = Parser.new(File.expand_path(path))
27
- end
28
- end
29
-
30
- # 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.
31
- # call-seq:
32
- # open(filepath) => file
33
- # open(filepath) {|file| block } => obj
34
- def self.open(path, &block)
35
- self.new(path, &block)
36
- end
37
-
38
- ##
39
- # *Deprecated*
40
- #
41
- # Iterates over paragraphs within document
42
- # call-seq:
43
- # each_paragraph => Enumerator
44
- def each_paragraph
45
- paragraphs.each { |p| yield(p) }
46
- end
47
-
48
- # call-seq:
49
- # to_s -> string
50
- def to_s
51
- paragraphs.map(&:to_s).join("\n")
52
- end
53
-
54
- # Save document to provided path
55
- # call-seq:
56
- # save(filepath) => void
57
- def save(path)
58
- update
59
- Zip::ZipOutputStream.open(path) do |out|
60
- zip.each do |entry|
61
- out.put_next_entry(entry.name)
62
-
63
- if @replace[entry.name]
64
- out.write(@replace[entry.name])
65
- else
66
- out.write(zip.read(entry.name))
67
- end
68
- end
69
- end
70
- zip.close
71
- end
72
-
73
- alias_method :text, :to_s
74
-
75
- private
76
-
77
- #--
78
- # TODO: Flesh this out to be compatible with other files
79
- # TODO: Method to set flag on files that have been edited, probably by inserting something at the
80
- # end of methods that make edits?
81
- #++
82
- def update
83
- @replace["word/document.xml"] = doc.serialize :save_with => 0
84
- end
85
-
86
- end
87
- 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.find_entry('word/document.xml')
34
+ document ||= @zip.find_entry('word/document2.xml')
35
+ raise Errno::ENOENT if document.nil?
36
+
37
+ @document_xml = document.get_input_stream.read
38
+ @doc = Nokogiri::XML(@document_xml)
39
+ load_styles
40
+ yield(self) if block_given?
41
+ ensure
42
+ @zip.close
43
+ end
44
+
45
+ # This stores the current global document properties, for now
46
+ def document_properties
47
+ {
48
+ font_size: font_size,
49
+ hyperlinks: hyperlinks
50
+ }
51
+ end
52
+
53
+ # 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.
54
+ # call-seq:
55
+ # open(filepath) => file
56
+ # open(filepath) {|file| block } => obj
57
+ def self.open(path, &block)
58
+ new(path, &block)
59
+ end
60
+
61
+ def paragraphs
62
+ @doc.xpath('//w:document//w:body/w:p').map { |p_node| parse_paragraph_from p_node }
63
+ end
64
+
65
+ def bookmarks
66
+ bkmrks_hsh = {}
67
+ bkmrks_ary = @doc.xpath('//w:bookmarkStart').map { |b_node| parse_bookmark_from b_node }
68
+ # auto-generated by office 2010
69
+ bkmrks_ary.reject! { |b| b.name == '_GoBack' }
70
+ bkmrks_ary.each { |b| bkmrks_hsh[b.name] = b }
71
+ bkmrks_hsh
72
+ end
73
+
74
+ def tables
75
+ @doc.xpath('//w:document//w:body//w:tbl').map { |t_node| parse_table_from t_node }
76
+ end
77
+
78
+ # Some documents have this set, others don't.
79
+ # Values are returned as half-points, so to get points, that's why it's divided by 2.
80
+ def font_size
81
+ return nil unless @styles
82
+
83
+ size_tag = @styles.xpath('//w:docDefaults//w:rPrDefault//w:rPr//w:sz').first
84
+ size_tag ? size_tag.attributes['val'].value.to_i / 2 : nil
85
+ end
86
+
87
+ # Hyperlink targets are extracted from the document.xml.rels file
88
+ def hyperlinks
89
+ hyperlink_relationships.each_with_object({}) do |rel, hash|
90
+ hash[rel.attributes['Id'].value] = rel.attributes['Target'].value
91
+ end
92
+ end
93
+
94
+ def hyperlink_relationships
95
+ @rels.xpath("//xmlns:Relationship[contains(@Type,'hyperlink')]")
96
+ end
97
+
98
+ ##
99
+ # *Deprecated*
100
+ #
101
+ # Iterates over paragraphs within document
102
+ # call-seq:
103
+ # each_paragraph => Enumerator
104
+ def each_paragraph
105
+ paragraphs.each { |p| yield(p) }
106
+ end
107
+
108
+ # call-seq:
109
+ # to_s -> string
110
+ def to_s
111
+ paragraphs.map(&:to_s).join("\n")
112
+ end
113
+
114
+ # Output entire document as a String HTML fragment
115
+ def to_html
116
+ paragraphs.map(&:to_html).join("\n")
117
+ end
118
+
119
+ # Save document to provided path
120
+ # call-seq:
121
+ # save(filepath) => void
122
+ def save(path)
123
+ update
124
+ Zip::OutputStream.open(path) do |out|
125
+ zip.each do |entry|
126
+ next unless entry.file?
127
+
128
+ out.put_next_entry(entry.name)
129
+
130
+ if @replace[entry.name]
131
+ out.write(@replace[entry.name])
132
+ else
133
+ out.write(zip.read(entry.name))
134
+ end
135
+ end
136
+ end
137
+ zip.close
138
+ end
139
+
140
+ # Output entire document as a StringIO object
141
+ def stream
142
+ update
143
+ stream = Zip::OutputStream.write_buffer do |out|
144
+ zip.each do |entry|
145
+ next unless entry.file?
146
+
147
+ out.put_next_entry(entry.name)
148
+
149
+ if @replace[entry.name]
150
+ out.write(@replace[entry.name])
151
+ else
152
+ out.write(zip.read(entry.name))
153
+ end
154
+ end
155
+ end
156
+
157
+ stream.rewind
158
+ stream
159
+ end
160
+
161
+ alias text to_s
162
+
163
+ def replace_entry(entry_path, file_contents)
164
+ @replace[entry_path] = file_contents
165
+ end
166
+
167
+ private
168
+
169
+ def load_styles
170
+ @styles_xml = @zip.read('word/styles.xml')
171
+ @styles = Nokogiri::XML(@styles_xml)
172
+ @rels_xml = @zip.read('word/_rels/document.xml.rels')
173
+ @rels = Nokogiri::XML(@rels_xml)
174
+ rescue Errno::ENOENT => e
175
+ warn e.message
176
+ nil
177
+ end
178
+
179
+ #--
180
+ # TODO: Flesh this out to be compatible with other files
181
+ # TODO: Method to set flag on files that have been edited, probably by inserting something at the
182
+ # end of methods that make edits?
183
+ #++
184
+ def update
185
+ replace_entry 'word/document.xml', doc.serialize(save_with: 0)
186
+ end
187
+
188
+ # generate Elements::Containers::Paragraph from paragraph XML node
189
+ def parse_paragraph_from(p_node)
190
+ Elements::Containers::Paragraph.new(p_node, document_properties)
191
+ end
192
+
193
+ # generate Elements::Bookmark from bookmark XML node
194
+ def parse_bookmark_from(b_node)
195
+ Elements::Bookmark.new(b_node)
196
+ end
197
+
198
+ def parse_table_from(t_node)
199
+ Elements::Containers::Table.new(t_node)
200
+ end
201
+ end
202
+ end