bitops-docx 0.2.07

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 746f86da717d8e88492061e60c5394f5ee54b38c
4
+ data.tar.gz: 5ca6b5f0cbc0bfc26f79e9c8efbddbf11878df70
5
+ SHA512:
6
+ metadata.gz: 470948df1d7d617e731090a2336a4e2e3c8aa9789e699c66936d653d26c68d98b39ba61714f257601ea38ddde3d5a2bafa9096a3666abcd741b0bd88cf9891ea
7
+ data.tar.gz: a04e5def0dff4febe5311ca62b21f0887edeb2b9863c425c261e9cdab0efebce966f9929c0f770c76be7fe790dc9f0f497792dbacffcb013af4e1ee4d8674157
@@ -0,0 +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.
@@ -0,0 +1,114 @@
1
+ # This is a fork!!!
2
+
3
+ This library stands on the shoulders of the original [docx](https://github.com/chrahunt/docx) -- it is my hope that [@chrahunt](https://github.com/chrahunt) will pull the changes I've incorporated into this library back into his original library.
4
+
5
+ This library depends on the latest version of rubyzip and incorporates changes from these two pull requests:
6
+ * [Paragraph alignment, text run font sizes, and HTML output](https://github.com/chrahunt/docx/pull/13) from [@higginsdragon](https://github.com/higginsdragon)
7
+ * [File replacement within document](https://github.com/chrahunt/docx/pull/18) from [@tmikoss](https://github.com/tmikoss)
8
+
9
+ # docx
10
+
11
+ a ruby library/gem for interacting with `.docx` files. currently capabilities include reading paragraphs/bookmarks, inserting text at bookmarks, reading tables/rows/columns/cells and saving the document.
12
+
13
+ ## usage
14
+
15
+ ### install
16
+
17
+ requires ruby (tested with 2.1.1)
18
+
19
+ gem install docx
20
+
21
+ ### reading
22
+
23
+ ``` ruby
24
+ require 'docx'
25
+
26
+ # Create a Docx::Document object for our existing docx file
27
+ doc = Docx::Document.open('example.docx')
28
+
29
+ # Retrieve and display paragraphs
30
+ doc.paragraphs.each do |p|
31
+ puts p
32
+ end
33
+
34
+ # Retrieve and display bookmarks, returned as hash with bookmark names as keys and objects as values
35
+ doc.bookmarks.each_pair do |bookmark_name, bookmark_object|
36
+ puts bookmark_name
37
+ end
38
+ ```
39
+
40
+ ### reading tables
41
+
42
+ ``` ruby
43
+ require 'docx'
44
+
45
+ # Create a Docx::Document object for our existing docx file
46
+ doc = Docx::Document.open('tables.docx')
47
+
48
+ first_table = doc.tables[0]
49
+ puts first_table.row_count
50
+ puts first_table.column_count
51
+ puts first_table.rows[0].cells[0].text
52
+ puts first_table.columns[0].cells[0].text
53
+
54
+ # Iterate through tables
55
+ doc.tables.each do |table|
56
+ table.rows.each do |row| # Row-based iteration
57
+ row.cells.each do |cell|
58
+ puts cell.text
59
+ end
60
+ end
61
+
62
+ table.columns.each do |column| # Column-based iteration
63
+ column.cells.each do |cell|
64
+ puts cell.text
65
+ end
66
+ end
67
+ end
68
+ ```
69
+
70
+ ### writing
71
+
72
+ ``` ruby
73
+ require 'docx'
74
+
75
+ # Create a Docx::Document object for our existing docx file
76
+ doc = Docx::Document.open('example.docx')
77
+
78
+ # Insert a single line of text after one of our bookmarks
79
+ doc.bookmarks['example_bookmark'].insert_text_after("Hello world.")
80
+
81
+ # Insert multiple lines of text at our bookmark
82
+ doc.bookmarks['example_bookmark_2'].insert_multiple_lines_after(['Hello', 'World', 'foo'])
83
+
84
+ # Save document to specified path
85
+ doc.save('example-edited.docx')
86
+ ```
87
+
88
+ ### advanced
89
+
90
+ ``` ruby
91
+ require 'docx'
92
+
93
+ d = Docx::Document.open('example.docx')
94
+
95
+ # The Nokogiri::XML::Node on which an element is based can be accessed using #node
96
+ d.paragraphs.each do |p|
97
+ puts p.node.inspect
98
+ end
99
+
100
+ # The #xpath and #at_xpath methods are delegated to the node from the element, saving a step
101
+ p_element = d.paragraphs.first
102
+ p_children = p_element.xpath("//child::*") # selects all children
103
+ p_child = p_element.at_xpath("//child::*") # selects first child
104
+ ```
105
+
106
+ ## Development
107
+
108
+ ### todo
109
+
110
+ * Calculate element formatting based on values present in element properties as well as properties inherited from parents
111
+ * Default formatting of inserted elements to inherited values
112
+ * Implement formattable elements.
113
+ * Implement styles.
114
+ * Easier multi-line text insertion at a single bookmark (inserting paragraph nodes after the one containing the bookmark)
@@ -0,0 +1,7 @@
1
+ require 'docx/version'
2
+
3
+ module Docx #:nodoc:
4
+ autoload :Document, 'docx/document'
5
+ end
6
+
7
+ require 'docx/core_ext/module'
@@ -0,0 +1,4 @@
1
+ require 'docx/containers/container'
2
+ require 'docx/containers/text_run'
3
+ require 'docx/containers/paragraph'
4
+ require 'docx/containers/table'
@@ -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
+ # Erase text within an element
14
+ def blank!
15
+ @node.xpath(".//w:t").each {|t| t.content = '' }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,96 @@
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
+ def self.tag
12
+ 'p'
13
+ end
14
+
15
+
16
+ # Child elements: pPr, r, fldSimple, hlink, subDoc
17
+ # http://msdn.microsoft.com/en-us/library/office/ee364458(v=office.11).aspx
18
+ def initialize(node, document_properties = {})
19
+ @node = node
20
+ @properties_tag = 'pPr'
21
+ @document_properties = document_properties
22
+ @font_size = @document_properties[:font_size]
23
+ end
24
+
25
+ # Set text of paragraph
26
+ def text=(content)
27
+ if text_runs.size == 1
28
+ text_runs.first.text = content
29
+ elsif text_runs.size == 0
30
+ new_r = TextRun.create_within(self)
31
+ new_r.text = content
32
+ else
33
+ text_runs.each {|r| r.node.remove }
34
+ new_r = TextRun.create_within(self)
35
+ new_r.text = content
36
+ end
37
+ end
38
+
39
+ # Return text of paragraph
40
+ def to_s
41
+ text_runs.map(&:text).join('')
42
+ end
43
+
44
+ # Return paragraph as a <p></p> HTML fragment with formatting based on properties.
45
+ def to_html
46
+ html = ''
47
+ text_runs.each do |text_run|
48
+ html << text_run.to_html
49
+ end
50
+ styles = { 'font-size' => "#{font_size}pt" }
51
+ styles['text-align'] = alignment if alignment
52
+ html_tag(:p, content: html, styles: styles)
53
+ end
54
+
55
+
56
+ # Array of text runs contained within paragraph
57
+ def text_runs
58
+ @node.xpath('w:r|w:hyperlink/w:r').map { |r_node| Containers::TextRun.new(r_node, @document_properties) }
59
+ end
60
+
61
+ # Iterate over each text run within a paragraph
62
+ def each_text_run
63
+ text_runs.each { |tr| yield(tr) }
64
+ end
65
+
66
+ def aligned_left?
67
+ ['left', nil].include?(alignment)
68
+ end
69
+
70
+ def aligned_right?
71
+ alignment == 'right'
72
+ end
73
+
74
+ def aligned_center?
75
+ alignment == 'center'
76
+ end
77
+
78
+ def font_size
79
+ size_tag = @node.xpath('w:pPr//w:sz').first
80
+ size_tag ? size_tag.attributes['val'].value.to_i / 2 : @font_size
81
+ end
82
+
83
+ alias_method :text, :to_s
84
+
85
+ private
86
+
87
+ # Returns the alignment if any, or nil if left
88
+ def alignment
89
+ alignment_tag = @node.xpath('.//w:jc').first
90
+ alignment_tag ? alignment_tag.attributes['val'].value : nil
91
+ end
92
+
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,51 @@
1
+ require 'docx/containers/table_row'
2
+ require 'docx/containers/table_column'
3
+ require 'docx/containers/container'
4
+
5
+ module Docx
6
+ module Elements
7
+ module Containers
8
+ class Table
9
+ include Container
10
+ include Elements::Element
11
+
12
+ def self.tag
13
+ 'tbl'
14
+ end
15
+
16
+ def initialize(node)
17
+ @node = node
18
+ @properties_tag = 'tblGrid'
19
+ end
20
+
21
+ # Array of row
22
+ def rows
23
+ @node.xpath('w:tr').map {|r_node| Containers::TableRow.new(r_node) }
24
+ end
25
+
26
+ def row_count
27
+ @node.xpath('w:tr').count
28
+ end
29
+
30
+ # Array of column
31
+ def columns
32
+ columns_containers = []
33
+ (0..(column_count-1)).each do |i|
34
+ columns_containers[i] = Containers::TableColumn.new @node.xpath("w:tr//w:tc[#{i+1}]")
35
+ end
36
+ columns_containers
37
+ end
38
+
39
+ def column_count
40
+ @node.xpath('w:tblGrid/w:gridCol').count
41
+ end
42
+
43
+ # Iterate over each row within a table
44
+ def each_rows
45
+ rows.each { |r| yield(r) }
46
+ end
47
+
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,39 @@
1
+ require 'docx/containers/text_run'
2
+ require 'docx/containers/container'
3
+
4
+ module Docx
5
+ module Elements
6
+ module Containers
7
+ class TableCell
8
+ include Container
9
+ include Elements::Element
10
+
11
+ def self.tag
12
+ 'tc'
13
+ end
14
+
15
+ def initialize(node)
16
+ @node = node
17
+ @properties_tag = 'tcPr'
18
+ end
19
+
20
+ # Return text of paragraph's cell
21
+ def to_s
22
+ paragraphs.map(&:text).join('')
23
+ end
24
+
25
+ # Array of paragraphs contained within cell
26
+ def paragraphs
27
+ @node.xpath('w:p').map {|p_node| Containers::Paragraph.new(p_node) }
28
+ end
29
+
30
+ # Iterate over each text run within a paragraph's cell
31
+ def each_paragraph
32
+ paragraphs.each { |tr| yield(tr) }
33
+ end
34
+
35
+ alias_method :text, :to_s
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,29 @@
1
+ require 'docx/containers/table_cell'
2
+ require 'docx/containers/container'
3
+
4
+ module Docx
5
+ module Elements
6
+ module Containers
7
+ class TableColumn
8
+ include Container
9
+ include Elements::Element
10
+
11
+ def self.tag
12
+ 'w:gridCol'
13
+ end
14
+
15
+ def initialize(cell_nodes)
16
+ @node = ''
17
+ @properties_tag = ''
18
+ @cells = cell_nodes.map { |c_node| Containers::TableCell.new(c_node) }
19
+ end
20
+
21
+ # Array of cells contained within row
22
+ def cells
23
+ @cells
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,28 @@
1
+ require 'docx/containers/table_cell'
2
+ require 'docx/containers/container'
3
+
4
+ module Docx
5
+ module Elements
6
+ module Containers
7
+ class TableRow
8
+ include Container
9
+ include Elements::Element
10
+
11
+ def self.tag
12
+ 'tr'
13
+ end
14
+
15
+ def initialize(node)
16
+ @node = node
17
+ @properties_tag = ''
18
+ end
19
+
20
+ # Array of cells contained within row
21
+ def cells
22
+ @node.xpath('w:tc').map {|c_node| Containers::TableCell.new(c_node) }
23
+ end
24
+
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,92 @@
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
+ def self.tag
17
+ 'r'
18
+ end
19
+
20
+ attr_reader :text
21
+ attr_reader :formatting
22
+
23
+ def initialize(node, document_properties = {})
24
+ @node = node
25
+ @text_nodes = @node.xpath('w:t').map {|t_node| Elements::Text.new(t_node) }
26
+ @properties_tag = 'rPr'
27
+ @text = parse_text || ''
28
+ @formatting = parse_formatting || DEFAULT_FORMATTING
29
+ @document_properties = document_properties
30
+ @font_size = @document_properties[:font_size]
31
+ end
32
+
33
+ # Set text of text run
34
+ def text=(content)
35
+ if @text_nodes.size == 1
36
+ @text_nodes.first.content = content
37
+ elsif @text_nodes.empty?
38
+ new_t = Elements::Text.create_within(self)
39
+ new_t.content = content
40
+ end
41
+ end
42
+
43
+ # Returns text contained within text run
44
+ def parse_text
45
+ @text_nodes.map(&:content).join('')
46
+ end
47
+
48
+ def parse_formatting
49
+ {
50
+ italic: !@node.xpath('.//w:i').empty?,
51
+ bold: !@node.xpath('.//w:b').empty?,
52
+ underline: !@node.xpath('.//w:u').empty?
53
+ }
54
+ end
55
+
56
+ def to_s
57
+ @text
58
+ end
59
+
60
+ # Return text as a HTML fragment with formatting based on properties.
61
+ def to_html
62
+ html = @text
63
+ html = html_tag(:em, content: html) if italicized?
64
+ html = html_tag(:strong, content: html) if bolded?
65
+ styles = {}
66
+ styles['text-decoration'] = 'underline' if underlined?
67
+ # No need to be granular with font size down to the span level if it doesn't vary.
68
+ styles['font-size'] = "#{font_size}pt" if font_size != @font_size
69
+ html = html_tag(:span, content: html, styles: styles) unless styles.empty?
70
+ return html
71
+ end
72
+
73
+ def italicized?
74
+ @formatting[:italic]
75
+ end
76
+
77
+ def bolded?
78
+ @formatting[:bold]
79
+ end
80
+
81
+ def underlined?
82
+ @formatting[:underline]
83
+ end
84
+
85
+ def font_size
86
+ size_tag = @node.xpath('w:rPr//w:sz').first
87
+ size_tag ? size_tag.attributes['val'].value.to_i / 2 : @font_size
88
+ end
89
+ end
90
+ end
91
+ end
92
+ 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
@@ -0,0 +1,148 @@
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
@@ -0,0 +1,3 @@
1
+ require 'docx/elements/bookmark'
2
+ require 'docx/elements/element'
3
+ require 'docx/elements/text'
@@ -0,0 +1,79 @@
1
+ require 'docx/elements/element'
2
+
3
+ module Docx
4
+ module Elements
5
+ class Bookmark
6
+ include Element
7
+ attr_accessor :name
8
+
9
+ def self.tag
10
+ 'bookmarkStart'
11
+ end
12
+
13
+ def initialize(node)
14
+ @node = node
15
+ @name = @node['w:name']
16
+ end
17
+
18
+ # Insert text before bookmarkStart node
19
+ def insert_text_before(text)
20
+ text_run = get_run_after
21
+ text_run.text = "#{text}#{text_run.text}"
22
+ end
23
+
24
+ # Insert text after bookmarkStart node
25
+ def insert_text_after(text)
26
+ text_run = get_run_before
27
+ text_run.text = "#{text_run.text}#{text}"
28
+ end
29
+
30
+ # insert multiple lines starting with paragraph containing bookmark node.
31
+ def insert_multiple_lines(text_array)
32
+ # Hold paragraphs to be inserted into, corresponding to the index of the strings in the text array
33
+ paragraphs = []
34
+ paragraph = self.parent_paragraph
35
+ # Remove text from paragraph
36
+ paragraph.blank!
37
+ paragraphs << paragraph
38
+ for i in 0...(text_array.size - 1)
39
+ # Copy previous paragraph
40
+ new_p = paragraphs[i].copy
41
+ # Insert as sibling of previous paragraph
42
+ new_p.insert_after(paragraphs[i])
43
+ paragraphs << new_p
44
+ end
45
+
46
+ # Insert text into corresponding newly created paragraphs
47
+ paragraphs.each_index do |index|
48
+ paragraphs[index].text = text_array[index]
49
+ end
50
+ end
51
+
52
+ # Get text run immediately prior to bookmark node
53
+ def get_run_before
54
+ # at_xpath returns the first match found and preceding-sibling returns siblings in the
55
+ # order they appear in the document not the order as they appear when moving out from
56
+ # the starting node
57
+ if not (r_nodes = @node.xpath("./preceding-sibling::w:r")).empty?
58
+ r_node = r_nodes.last
59
+ Containers::TextRun.new(r_node)
60
+ else
61
+ new_r = Containers::TextRun.create_with(self)
62
+ new_r.insert_before(self)
63
+ new_r
64
+ end
65
+ end
66
+
67
+ # Get text run immediately after bookmark node
68
+ def get_run_after
69
+ if (r_node = @node.at_xpath("./following-sibling::w:r"))
70
+ Containers::TextRun.new(r_node)
71
+ else
72
+ new_r = Containers::TextRun.create_with(self)
73
+ new_r.insert_after(self)
74
+ new_r
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,96 @@
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
+ # Ensure that a 'tag' corresponding to the XML element that defines the element is defined
11
+ def self.included(base)
12
+ base.extend(ClassMethods)
13
+ base.const_set(:TAG, Element::DEFAULT_TAG) unless base.const_defined?(:TAG)
14
+ end
15
+
16
+ attr_accessor :node
17
+ delegate :at_xpath, :xpath, :to => :@node
18
+
19
+ # TODO: Should create a docx object from this
20
+ def parent(type = '*')
21
+ @node.at_xpath("./parent::#{type}")
22
+ end
23
+
24
+ # Get parent paragraph of element
25
+ def parent_paragraph
26
+ Elements::Containers::Paragraph.new(parent('w:p'))
27
+ end
28
+
29
+ # Insertion methods
30
+ # Insert node as last child
31
+ def append_to(element)
32
+ @node = element.node.add_child(@node)
33
+ self
34
+ end
35
+
36
+ # Insert node as first child (after properties)
37
+ def prepend_to(element)
38
+ @node = element.node.properties.add_next_sibling(@node)
39
+ self
40
+ end
41
+
42
+ def insert_after(element)
43
+ # Returns newly re-parented node
44
+ @node = element.node.add_next_sibling(@node)
45
+ self
46
+ end
47
+
48
+ def insert_before(element)
49
+ @node = element.node.add_previous_sibling(@node)
50
+ self
51
+ end
52
+
53
+ # Creation/edit methods
54
+ def copy
55
+ self.class.new(@node.dup)
56
+ end
57
+
58
+ # A method to wrap content in an HTML tag.
59
+ # Currently used in paragraph and text_run for the to_html methods
60
+ #
61
+ # content:: The base text content for the tag.
62
+ # styles:: Hash of the inline CSS styles to be applied. e.g.
63
+ # { 'font-size' => '12pt', 'text-decoration' => 'underline' }
64
+ #
65
+ def html_tag(name, options = {})
66
+ content = options[:content]
67
+ styles = options[:styles]
68
+
69
+ html = "<#{name.to_s}"
70
+ unless styles.nil? || styles.empty?
71
+ styles_array = []
72
+ styles.each do |property, value|
73
+ styles_array << "#{property.to_s}:#{value};"
74
+ end
75
+ html << " style=\"#{styles_array.join('')}\""
76
+ end
77
+ html << ">"
78
+ html << content if content
79
+ html << "</#{name.to_s}>"
80
+ end
81
+
82
+ module ClassMethods
83
+ def create_with(element)
84
+ # Need to somehow get the xml document accessible here by default, but this is alright in the interim
85
+ self.new(Nokogiri::XML::Node.new("w:#{self.tag}", element.node))
86
+ end
87
+
88
+ def create_within(element)
89
+ new_element = create_with(element)
90
+ new_element.append_to(element)
91
+ new_element
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,17 @@
1
+ module Docx
2
+ module Elements
3
+ class Text
4
+ include Element
5
+ delegate :content, :content=, :to => :@node
6
+
7
+ def self.tag
8
+ 't'
9
+ end
10
+
11
+
12
+ def initialize(node)
13
+ @node = node
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module Docx #:nodoc:
2
+ VERSION = '0.2.07'
3
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bitops-docx
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.07
5
+ platform: ruby
6
+ authors:
7
+ - Christopher Hunt
8
+ - Marcus Ortiz
9
+ - Higgins Dragon
10
+ - Toms Mikoss
11
+ - Sebastian Wittenkamp
12
+ autorequire:
13
+ bindir: bin
14
+ cert_chain: []
15
+ date: 2014-08-03 00:00:00.000000000 Z
16
+ dependencies:
17
+ - !ruby/object:Gem::Dependency
18
+ name: nokogiri
19
+ requirement: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - "~>"
22
+ - !ruby/object:Gem::Version
23
+ version: '1.5'
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - "~>"
29
+ - !ruby/object:Gem::Version
30
+ version: '1.5'
31
+ - !ruby/object:Gem::Dependency
32
+ name: rubyzip
33
+ requirement: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - "~>"
36
+ - !ruby/object:Gem::Version
37
+ version: 1.1.6
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - "~>"
43
+ - !ruby/object:Gem::Version
44
+ version: 1.1.6
45
+ - !ruby/object:Gem::Dependency
46
+ name: rspec
47
+ requirement: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ type: :development
53
+ prerelease: false
54
+ version_requirements: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ description: a ruby library/gem for interacting with .docx files
60
+ email:
61
+ - sebastian@bitops.io
62
+ executables: []
63
+ extensions: []
64
+ extra_rdoc_files: []
65
+ files:
66
+ - LICENSE.md
67
+ - README.md
68
+ - lib/docx.rb
69
+ - lib/docx/containers.rb
70
+ - lib/docx/containers/container.rb
71
+ - lib/docx/containers/paragraph.rb
72
+ - lib/docx/containers/table.rb
73
+ - lib/docx/containers/table_cell.rb
74
+ - lib/docx/containers/table_column.rb
75
+ - lib/docx/containers/table_row.rb
76
+ - lib/docx/containers/text_run.rb
77
+ - lib/docx/core_ext/module.rb
78
+ - lib/docx/document.rb
79
+ - lib/docx/elements.rb
80
+ - lib/docx/elements/bookmark.rb
81
+ - lib/docx/elements/element.rb
82
+ - lib/docx/elements/text.rb
83
+ - lib/docx/version.rb
84
+ homepage: https://github.com/bitops/docx
85
+ licenses: []
86
+ metadata: {}
87
+ post_install_message:
88
+ rdoc_options: []
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ requirements: []
102
+ rubyforge_project:
103
+ rubygems_version: 2.2.2
104
+ signing_key:
105
+ specification_version: 4
106
+ summary: a ruby library/gem for interacting with .docx files
107
+ test_files: []