bitops-docx 0.2.07

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []