docx 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZWNiNjA4ZjliOGU1MTQxZjdhNjhmYmU4OWQxMzkyMDM2MzMxOTI4Ng==
5
+ data.tar.gz: !binary |-
6
+ NTU2OWE0N2FkZWMzMmM4NDJkYjMwMTg0NTcyOWVhMmFlNGE4MTliZg==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ OWJiMGQ5NmQ1OGFhYTdhODIyNzYyM2U4Yjk3NzQyMWY3YzUxNDZmOGY1NDAz
10
+ MDZlMDFkM2I0MWM5MzFmNTM0NGEyNDlmZTQ3ZmI4Yzg3NzUyZDliMzlkY2Ri
11
+ MzdiYmRhOTc0ZDhiYzg2ZGVmZTkzOTVhMTkwN2MzMDE4MDU4YjI=
12
+ data.tar.gz: !binary |-
13
+ NGMxZTdkMTQ3ODkxM2MyYTkyODAzZTRhMTQ1ZWU4NGU4MjI0Y2Y2MWM2Zjdj
14
+ Y2IyMzc5NGMyNjRkYTNkM2VmYzY5ZjhkMTE4MjczNDNhYTEzMGJiMWEwNWZh
15
+ NTBiZjkxMjc2MTQ2NDM1OTMzZjhiNGQ3ZGYxYTljNzNmZTQ0NmY=
data/LICENSE.md CHANGED
@@ -1,21 +1,21 @@
1
- The MIT License
2
-
3
- Copyright (c) Marcus Ortiz, http://marcusortiz.com
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.
1
+ The MIT License
2
+
3
+ Copyright (c) Marcus Ortiz, http://marcusortiz.com
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md CHANGED
@@ -1,32 +1,50 @@
1
- # docx
2
-
3
- a ruby library/gem for interacting with `.docx` files
4
-
5
- ## usage
6
-
7
- ### basic
8
-
9
- ``` ruby
10
- require 'docx'
11
-
12
- d = Docx::Document.open('example.docx')
13
- d.each_paragraph do |p|
14
- puts d
15
- end
16
- ```
17
-
18
- ### advanced
19
-
20
- ``` ruby
21
- require 'docx'
22
-
23
- d = Docx::Document.open('example.docx')
24
- d.each_paragraph do |p|
25
- p.each_text_run do |run|
26
- run.italicized?
27
- run.bolded?
28
- run.underlined?
29
- run.formatting
30
- run.text
31
- end
32
- end
1
+ # docx
2
+
3
+ a ruby library/gem for interacting with `.docx` files
4
+
5
+ ## usage
6
+
7
+ ### install
8
+
9
+ requires ruby (only tested with 1.9.3 so far)
10
+
11
+ gem install docx
12
+
13
+ ### basic
14
+
15
+ ``` ruby
16
+ require 'docx'
17
+
18
+ d = Docx::Document.open('example.docx')
19
+ d.each_paragraph do |p|
20
+ puts d
21
+ end
22
+ ```
23
+
24
+ ### advanced
25
+
26
+ ``` ruby
27
+ require 'docx'
28
+
29
+ d = Docx::Document.open('example.docx')
30
+ d.each_paragraph do |p|
31
+ p.each_text_run do |run|
32
+ run.italicized?
33
+ run.bolded?
34
+ run.underlined?
35
+ run.formatting
36
+ run.text
37
+ end
38
+ end
39
+ ```
40
+
41
+ ## Development
42
+
43
+ ### todo
44
+
45
+ * Add better formatting identification for specific nodes and other formatting indicators (text size, paragraph spacing)
46
+ * Calculate element formatting based on values present in element properties as well as properties inherited from parents
47
+ * Default formatting of inserted elements to inherited values
48
+ * Implement formattable elements.
49
+ * Implement styles.
50
+ * Easier multi-line text insertion at a single bookmark (inserting paragraph nodes after the one containing the bookmark)
@@ -1,5 +1,7 @@
1
- require 'docx/version'
2
-
3
- module Docx
4
- autoload :Document, 'docx/document'
5
- end
1
+ require 'docx/version'
2
+
3
+ module Docx
4
+ autoload :Document, 'docx/document'
5
+ end
6
+
7
+ require 'docx/core_ext/module'
@@ -1,2 +1,3 @@
1
- require 'docx/containers/text_run'
2
- require 'docx/containers/paragraph'
1
+ require 'docx/containers/container'
2
+ require 'docx/containers/text_run'
3
+ require 'docx/containers/paragraph'
@@ -0,0 +1,20 @@
1
+ require 'docx/elements'
2
+
3
+ module Docx
4
+ module Elements
5
+ module Containers
6
+ module Container
7
+ # Relation methods
8
+ # TODO: Create a properties object, include Element
9
+ def properties
10
+ @node.at_xpath("./#{@properties_tag}")
11
+ end
12
+
13
+ # TODO: Maybe merge and then clear so there is only one text node left.
14
+ def blank!
15
+ @node.xpath(".//w:t").each {|t| t.content = '' }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,23 +1,50 @@
1
- require 'docx/containers/text_run'
2
-
3
- module Docx
4
- module Containers
5
- class Paragraph
6
- attr_accessor :text_runs
7
-
8
- def initialize(txt_runs)
9
- @text_runs = txt_runs
10
- end
11
-
12
- def to_s
13
- @text_runs.map(&:text).join('')
14
- end
15
-
16
- def each_text_run
17
- @text_runs.each { |tr| yield(tr) }
18
- end
19
-
20
- alias_method :text, :to_s
21
- end
22
- end
23
- end
1
+ require 'docx/containers/text_run'
2
+ require 'docx/containers/container'
3
+
4
+ module Docx
5
+ module Elements
6
+ module Containers
7
+ class Paragraph
8
+ include Container
9
+ include Elements::Element
10
+
11
+ TAG = 'p'
12
+
13
+ # Child elements: pPr, r, fldSimple, hlink, subDoc
14
+ # http://msdn.microsoft.com/en-us/library/office/ee364458(v=office.11).aspx
15
+ def initialize(node)
16
+ @node = node
17
+ @properties_tag = 'pPr'
18
+ end
19
+
20
+ # Handle direct text insertion into paragraph on some conditions
21
+ def text=(content)
22
+ if text_runs.size == 1
23
+ text_runs.first.text = content
24
+ elsif text_runs.size == 0
25
+ new_r = TextRun.create_within(self)
26
+ new_r.text = content
27
+ else
28
+ text_runs.each {|r| r.node.remove }
29
+ new_r = TextRun.create_within(self)
30
+ new_r.text = content
31
+ end
32
+ end
33
+
34
+ def to_s
35
+ text_runs.map(&:text).join('')
36
+ end
37
+
38
+ def text_runs
39
+ @node.xpath('w:r').map {|r_node| Containers::TextRun.new(r_node) }
40
+ end
41
+
42
+ def each_text_run
43
+ text_runs.each { |tr| yield(tr) }
44
+ end
45
+
46
+ alias_method :text, :to_s
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,35 +1,68 @@
1
- module Docx
2
- module Containers
3
- class TextRun
4
- DEFAULT_FORMATTING = {
5
- italic: false,
6
- bold: false,
7
- underline: false
8
- }
9
-
10
- attr_reader :text
11
- attr_reader :formatting
12
-
13
- def initialize(attrs)
14
- @text = attrs[:text] || ''
15
- @formatting = attrs[:formatting] || DEFAULT_FORMATTING
16
- end
17
-
18
- def to_s
19
- @text
20
- end
21
-
22
- def italicized?
23
- @formatting[:italic]
24
- end
25
-
26
- def bolded?
27
- @formatting[:bold]
28
- end
29
-
30
- def underlined?
31
- @formatting[:underline]
32
- end
33
- end
34
- end
35
- end
1
+ require 'docx/containers/container'
2
+
3
+ module Docx
4
+ module Elements
5
+ module Containers
6
+ class TextRun
7
+ include Container
8
+ include Elements::Element
9
+
10
+ DEFAULT_FORMATTING = {
11
+ italic: false,
12
+ bold: false,
13
+ underline: false
14
+ }
15
+
16
+ TAG = 'r'
17
+
18
+ attr_reader :text
19
+ attr_reader :formatting
20
+
21
+ def initialize(node)
22
+ @node = node
23
+ @text_nodes = @node.xpath('w:t').map {|t_node| Elements::Text.new(t_node) }
24
+ @properties_tag = 'rPr'
25
+ @text = parse_text || ''
26
+ @formatting = parse_formatting || DEFAULT_FORMATTING
27
+ end
28
+
29
+ def text=(content)
30
+ if @text_nodes.size == 1
31
+ @text_nodes.first.content = content
32
+ elsif @text_nodes.empty?
33
+ new_t = Elements::Text.create_within(self)
34
+ new_t.content = content
35
+ end
36
+ end
37
+
38
+ def parse_text
39
+ @text_nodes.map(&:content).join('')
40
+ end
41
+
42
+ def parse_formatting
43
+ {
44
+ italic: !@node.xpath('.//w:i').empty?,
45
+ bold: !@node.xpath('.//w:b').empty?,
46
+ underline: !@node.xpath('.//w:u').empty?
47
+ }
48
+ end
49
+
50
+ def to_s
51
+ @text
52
+ end
53
+
54
+ def italicized?
55
+ @formatting[:italic]
56
+ end
57
+
58
+ def bolded?
59
+ @formatting[:bold]
60
+ end
61
+
62
+ def underlined?
63
+ @formatting[:underline]
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +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
172
+ end
@@ -1,27 +1,54 @@
1
- require 'docx/parser'
2
-
3
- module Docx
4
- class Document
5
- attr_reader :paragraphs
6
-
7
- def initialize(path)
8
- Parser.new(File.expand_path(path)) do |p|
9
- @paragraphs = p.paragraphs
10
- end
11
- end
12
-
13
- def self.open(path)
14
- self.new(path)
15
- end
16
-
17
- def each_paragraph
18
- @paragraphs.each { |p| yield(p) }
19
- end
20
-
21
- def to_s
22
- @paragraphs.map(&:to_s).join("\n")
23
- end
24
-
25
- alias_method :text, :to_s
26
- end
27
- end
1
+ require 'docx/parser'
2
+ require 'zip/zip'
3
+
4
+ module Docx
5
+ class Document
6
+ delegate :paragraphs, :bookmarks, :to => :@parser
7
+ delegate :doc, :xml, :zip, :to => :@parser
8
+ def initialize(path, &block)
9
+ @replace = {}
10
+ if block_given?
11
+ @parser = Parser.new(File.expand_path(path), &block)
12
+ else
13
+ @parser = Parser.new(File.expand_path(path))
14
+ end
15
+ end
16
+
17
+ def self.open(path, &block)
18
+ self.new(path, &block)
19
+ end
20
+
21
+ def each_paragraph
22
+ paragraphs.each { |p| yield(p) }
23
+ end
24
+
25
+ def to_s
26
+ paragraphs.map(&:to_s).join("\n")
27
+ end
28
+
29
+ # TODO: Flesh this out to be compatible with other files
30
+ # TODO: Method to set flag on files that have been edited, probably by inserting something at the
31
+ # end of methods that make edits?
32
+ def update
33
+ @replace["word/document.xml"] = doc.serialize :save_with => 0
34
+ end
35
+
36
+ def save(path)
37
+ update
38
+ Zip::ZipFile.open(path, Zip::ZipFile::CREATE) do |out|
39
+ zip.each do |entry|
40
+ out.get_output_stream(entry.name) do |o|
41
+ if @replace[entry.name]
42
+ o.write(@replace[entry.name])
43
+ else
44
+ o.write(zip.read(entry.name))
45
+ end
46
+ end
47
+ end
48
+ end
49
+ zip.close
50
+ end
51
+
52
+ alias_method :text, :to_s
53
+ end
54
+ end
@@ -0,0 +1,3 @@
1
+ require 'docx/elements/bookmark'
2
+ require 'docx/elements/element'
3
+ require 'docx/elements/text'
@@ -0,0 +1,72 @@
1
+ require 'docx/elements/element'
2
+
3
+ module Docx
4
+ module Elements
5
+ class Bookmark
6
+ include Element
7
+ attr_accessor :name
8
+
9
+ TAG = 'bookmarkStart'
10
+
11
+ def initialize(node)
12
+ @node = node
13
+ @name = @node['w:name']
14
+ end
15
+
16
+ def insert_text_before(text)
17
+ text_run = get_run_after
18
+ text_run.text = "#{text}#{text_run.text}"
19
+ end
20
+
21
+ def insert_text_after(text)
22
+ text_run = get_run_before
23
+ text_run.text = "#{text_run.text}#{text}"
24
+ end
25
+
26
+ def insert_multiple_lines(text_array)
27
+ # Hold paragraphs to be inserted into, corresponding to the index of the strings in the text array
28
+ paragraphs = []
29
+ paragraph = self.parent_paragraph
30
+ # Remove text from paragraph
31
+ paragraph.blank!
32
+ paragraphs << paragraph
33
+ for i in 0...(text_array.size - 1)
34
+ # Copy previous paragraph
35
+ new_p = paragraphs[i].copy
36
+ # Insert as sibling of previous paragraph
37
+ new_p.insert_after(paragraphs[i])
38
+ paragraphs << new_p
39
+ end
40
+
41
+ # Insert text into corresponding newly created paragraphs
42
+ paragraphs.each_index do |index|
43
+ paragraphs[index].text = text_array[index]
44
+ end
45
+ end
46
+
47
+ def get_run_before
48
+ # at_xpath returns the first match found and preceding-sibling returns siblings in the
49
+ # order they appear in the document not the order as they appear when moving out from
50
+ # the starting node
51
+ if not (r_nodes = @node.xpath("./preceding-sibling::w:r")).empty?
52
+ r_node = r_nodes.last
53
+ Containers::TextRun.new(r_node)
54
+ else
55
+ new_r = Containers::TextRun.create_with(self)
56
+ new_r.insert_before(self)
57
+ new_r
58
+ end
59
+ end
60
+
61
+ def get_run_after
62
+ if (r_node = @node.at_xpath("./following-sibling::w:r"))
63
+ Containers::TextRun.new(r_node)
64
+ else
65
+ new_r = Containers::TextRun.create_with(self)
66
+ new_r.insert_after(self)
67
+ new_r
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,71 @@
1
+ require 'nokogiri'
2
+ require 'docx/elements'
3
+ require 'docx/containers'
4
+
5
+ module Docx
6
+ module Elements
7
+ module Element
8
+ DEFAULT_TAG = ''
9
+
10
+ def self.included(base)
11
+ base.extend(ClassMethods)
12
+ base.const_set(:TAG, Element::DEFAULT_TAG) unless base.const_defined?(:TAG)
13
+ end
14
+
15
+ attr_accessor :node
16
+ delegate :at_xpath, :xpath, :to => :@node
17
+
18
+ # TODO: Should create a docx object from this
19
+ def parent(type = '*')
20
+ @node.at_xpath("./parent::#{type}")
21
+ end
22
+
23
+ # TODO: Should create a docx paragraph from this
24
+ def parent_paragraph
25
+ Elements::Containers::Paragraph.new(parent('w:p'))
26
+ end
27
+
28
+ # Insertion methods
29
+ # Insert node as last child
30
+ def append_to(element)
31
+ @node = element.node.add_child(@node)
32
+ self
33
+ end
34
+
35
+ # Insert node as first child (after properties)
36
+ def prepend_to(element)
37
+ @node = element.node.properties.add_next_sibling(@node)
38
+ self
39
+ end
40
+
41
+ def insert_after(element)
42
+ # Returns newly re-parented node
43
+ @node = element.node.add_next_sibling(@node)
44
+ self
45
+ end
46
+
47
+ def insert_before(element)
48
+ @node = element.node.add_previous_sibling(@node)
49
+ self
50
+ end
51
+
52
+ # Creation/edit methods
53
+ def copy
54
+ self.class.new(@node.dup)
55
+ end
56
+
57
+ module ClassMethods
58
+ def create_with(element)
59
+ # Need to somehow get the xml document accessible here by default, but this is alright in the interim
60
+ self.new(Nokogiri::XML::Node.new("w:#{self.const_get(:TAG)}", element.node))
61
+ end
62
+
63
+ def create_within(element)
64
+ new_element = create_with(element)
65
+ new_element.append_to(element)
66
+ new_element
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,13 @@
1
+ module Docx
2
+ module Elements
3
+ class Text
4
+ include Element
5
+ delegate :content, :content=, :to => :@node
6
+ TAG = 't'
7
+
8
+ def initialize(node)
9
+ @node = node
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,49 +1,42 @@
1
- require 'docx/containers'
2
- require 'nokogiri'
3
- require 'zip/zip'
4
-
5
- module Docx
6
- class Parser
7
- def initialize(path)
8
- @zip = Zip::ZipFile.open(path)
9
- @xml = Nokogiri::XML(@zip.find_entry('word/document.xml').get_input_stream)
10
-
11
- if block_given?
12
- yield self
13
- @zip.close
14
- end
15
- end
16
-
17
- def paragraphs
18
- @xml.xpath('//w:document//w:body//w:p').map { |p_node| parse_paragraph_from p_node }
19
- end
20
-
21
- private
22
-
23
- def parse_paragraph_from(p_node)
24
- Containers::Paragraph.new(parse_runs_from(p_node))
25
- end
26
-
27
- def parse_runs_from(p_node)
28
- p_node.xpath('w:r').map do |r_node|
29
- rpr_node = r_node.xpath('w:rPr')
30
- Containers::TextRun.new({
31
- text: parse_text_from(r_node),
32
- formatting: parse_formatting_from(rpr_node)
33
- })
34
- end
35
- end
36
-
37
- def parse_text_from(r_node)
38
- r_node.xpath('w:t').map(&:text).join('')
39
- end
40
-
41
- def parse_formatting_from(rpr_node)
42
- {
43
- italic: !rpr_node.xpath('w:i').empty?,
44
- bold: !rpr_node.xpath('w:b').empty?,
45
- underline: !rpr_node.xpath('w:u').empty?
46
- }
47
- end
48
- end
49
- end
1
+ require 'docx/containers'
2
+ require 'docx/elements'
3
+ require 'nokogiri'
4
+ require 'zip/zip'
5
+
6
+ module Docx
7
+ class Parser
8
+ attr_reader :xml, :doc, :zip
9
+ def initialize(path)
10
+ @zip = Zip::ZipFile.open(path)
11
+ @xml = @zip.read('word/document.xml')
12
+ @doc = Nokogiri::XML(@xml)
13
+ if block_given?
14
+ yield self
15
+ @zip.close
16
+ end
17
+ end
18
+
19
+ def paragraphs
20
+ @doc.xpath('//w:document//w:body//w:p').map { |p_node| parse_paragraph_from p_node }
21
+ end
22
+
23
+ def bookmarks
24
+ bkmrks_hsh = Hash.new
25
+ bkmrks_ary = @doc.xpath('//w:bookmarkStart').map { |b_node| parse_bookmark_from b_node }
26
+ # auto-generated by office 2010
27
+ bkmrks_ary.reject! {|b| b.name == "_GoBack" }
28
+ bkmrks_ary.each {|b| bkmrks_hsh[b.name] = b }
29
+ bkmrks_hsh
30
+ end
31
+
32
+ private
33
+
34
+ def parse_paragraph_from(p_node)
35
+ Elements::Containers::Paragraph.new(p_node)
36
+ end
37
+
38
+ def parse_bookmark_from(b_node)
39
+ Elements::Bookmark.new(b_node)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,4 @@
1
+ require_relative 'document'
2
+
3
+ document = Docx::Document.new('../test/fixtures/basic.docx')
4
+ puts document.bookmarks
@@ -1,3 +1,3 @@
1
- module Docx
2
- VERSION = '0.1.0'
3
- end
1
+ module Docx
2
+ VERSION = '0.2.0'
3
+ end
metadata CHANGED
@@ -1,38 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: docx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
5
- prerelease:
4
+ version: 0.2.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Marcus Ortiz
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2012-04-12 00:00:00.000000000 Z
11
+ date: 2013-04-15 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: nokogiri
16
- requirement: &70332799492380 !ruby/object:Gem::Requirement
17
- none: false
15
+ requirement: !ruby/object:Gem::Requirement
18
16
  requirements:
19
17
  - - ~>
20
18
  - !ruby/object:Gem::Version
21
19
  version: '1.5'
22
20
  type: :runtime
23
21
  prerelease: false
24
- version_requirements: *70332799492380
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
25
27
  - !ruby/object:Gem::Dependency
26
28
  name: rubyzip
27
- requirement: &70332799491720 !ruby/object:Gem::Requirement
28
- none: false
29
+ requirement: !ruby/object:Gem::Requirement
29
30
  requirements:
30
31
  - - ~>
31
32
  - !ruby/object:Gem::Version
32
33
  version: '0.9'
33
34
  type: :runtime
34
35
  prerelease: false
35
- version_requirements: *70332799491720
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '0.9'
36
41
  description: a ruby library/gem for interacting with .docx files
37
42
  email: mportiz08@gmail.com
38
43
  executables: []
@@ -41,35 +46,41 @@ extra_rdoc_files: []
41
46
  files:
42
47
  - README.md
43
48
  - LICENSE.md
49
+ - lib/docx/containers/container.rb
44
50
  - lib/docx/containers/paragraph.rb
45
51
  - lib/docx/containers/text_run.rb
46
52
  - lib/docx/containers.rb
53
+ - lib/docx/core_ext/module.rb
47
54
  - lib/docx/document.rb
55
+ - lib/docx/elements/bookmark.rb
56
+ - lib/docx/elements/element.rb
57
+ - lib/docx/elements/text.rb
58
+ - lib/docx/elements.rb
48
59
  - lib/docx/parser.rb
60
+ - lib/docx/test.rb
49
61
  - lib/docx/version.rb
50
62
  - lib/docx.rb
51
63
  homepage: https://github.com/mportiz08/docx
52
64
  licenses: []
65
+ metadata: {}
53
66
  post_install_message:
54
67
  rdoc_options: []
55
68
  require_paths:
56
69
  - lib
57
70
  required_ruby_version: !ruby/object:Gem::Requirement
58
- none: false
59
71
  requirements:
60
72
  - - ! '>='
61
73
  - !ruby/object:Gem::Version
62
74
  version: '0'
63
75
  required_rubygems_version: !ruby/object:Gem::Requirement
64
- none: false
65
76
  requirements:
66
77
  - - ! '>='
67
78
  - !ruby/object:Gem::Version
68
79
  version: '0'
69
80
  requirements: []
70
81
  rubyforge_project:
71
- rubygems_version: 1.8.11
82
+ rubygems_version: 2.0.3
72
83
  signing_key:
73
- specification_version: 3
84
+ specification_version: 4
74
85
  summary: a ruby library/gem for interacting with .docx files
75
86
  test_files: []