docx 0.1.0 → 0.2.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 +15 -0
- data/LICENSE.md +21 -21
- data/README.md +50 -32
- data/lib/docx.rb +7 -5
- data/lib/docx/containers.rb +3 -2
- data/lib/docx/containers/container.rb +20 -0
- data/lib/docx/containers/paragraph.rb +50 -23
- data/lib/docx/containers/text_run.rb +68 -35
- data/lib/docx/core_ext/module.rb +172 -0
- data/lib/docx/document.rb +54 -27
- data/lib/docx/elements.rb +3 -0
- data/lib/docx/elements/bookmark.rb +72 -0
- data/lib/docx/elements/element.rb +71 -0
- data/lib/docx/elements/text.rb +13 -0
- data/lib/docx/parser.rb +42 -49
- data/lib/docx/test.rb +4 -0
- data/lib/docx/version.rb +3 -3
- metadata +24 -13
checksums.yaml
ADDED
@@ -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
|
-
###
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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)
|
data/lib/docx.rb
CHANGED
data/lib/docx/containers.rb
CHANGED
@@ -1,2 +1,3 @@
|
|
1
|
-
require 'docx/containers/
|
2
|
-
require 'docx/containers/
|
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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
data/lib/docx/document.rb
CHANGED
@@ -1,27 +1,54 @@
|
|
1
|
-
require 'docx/parser'
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
end
|
16
|
-
|
17
|
-
def
|
18
|
-
|
19
|
-
end
|
20
|
-
|
21
|
-
def
|
22
|
-
|
23
|
-
end
|
24
|
-
|
25
|
-
|
26
|
-
|
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,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
|
data/lib/docx/parser.rb
CHANGED
@@ -1,49 +1,42 @@
|
|
1
|
-
require 'docx/containers'
|
2
|
-
require '
|
3
|
-
require '
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
data/lib/docx/test.rb
ADDED
data/lib/docx/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
module Docx
|
2
|
-
VERSION = '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.
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
82
|
+
rubygems_version: 2.0.3
|
72
83
|
signing_key:
|
73
|
-
specification_version:
|
84
|
+
specification_version: 4
|
74
85
|
summary: a ruby library/gem for interacting with .docx files
|
75
86
|
test_files: []
|