Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ doc.tables.each do |table|
puts cell.text
end
end

table.columns.each do |column| # Column-based iteration
column.cells.each do |cell|
puts cell.text
Expand All @@ -84,6 +84,10 @@ doc.bookmarks['example_bookmark'].insert_text_after("Hello world.")
# Insert multiple lines of text at our bookmark
doc.bookmarks['example_bookmark_2'].insert_multiple_lines_after(['Hello', 'World', 'foo'])

# The previous methods can also be passed a hash specifying formatting e.g.
doc.bookmarks['example_bookmark'].insert_text_after("Hello world.",
{ bold: true, font: 'Times New Roman', font_size: 20, color: 'FF0000' })

# Remove paragraphs
doc.paragraphs.each do |p|
p.remove! if p.to_s =~ /TODO/
Expand Down Expand Up @@ -117,6 +121,6 @@ p_child = p_element.at_xpath("//child::*") # selects first child

* Calculate element formatting based on values present in element properties as well as properties inherited from parents
* Default formatting of inserted elements to inherited values
* Implement formattable elements.
* Implement formattable tables.
* Implement styles.
* Easier multi-line text insertion at a single bookmark (inserting paragraph nodes after the one containing the bookmark)
16 changes: 12 additions & 4 deletions lib/docx/containers/paragraph.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
require 'docx/containers/text_run'
require 'docx/containers/container'
require 'docx/formatting/paragraph_formatting'

module Docx
module Elements
module Containers
class Paragraph
include Container
include Elements::Element
include ParagraphFormatting

def self.tag
'p'
end

attr_reader :properties_tag
alias_method :formatting, :parse_formatting

# Child elements: pPr, r, fldSimple, hlink, subDoc
# http://msdn.microsoft.com/en-us/library/office/ee364458(v=office.11).aspx
Expand All @@ -36,6 +40,12 @@ def text=(content)
end
end

# Set text of paragraph with formatting
def set_text(content, formatting={})
self.text = content
text_runs.each { |tr| tr.apply_formatting(formatting) }
end

# Return text of paragraph
def to_s
text_runs.map(&:text).join('')
Expand Down Expand Up @@ -79,17 +89,15 @@ def font_size
size_tag = @node.xpath('w:pPr//w:sz').first
size_tag ? size_tag.attributes['val'].value.to_i / 2 : @font_size
end

alias_method :text, :to_s

private

# Returns the alignment if any, or nil if left
def alignment
alignment_tag = @node.xpath('.//w:jc').first
alignment_tag ? alignment_tag.attributes['val'].value : nil
formatting[:alignment]
end

end
end
end
Expand Down
50 changes: 21 additions & 29 deletions lib/docx/containers/text_run.rb
Original file line number Diff line number Diff line change
@@ -1,32 +1,29 @@
require 'docx/containers/container'
require 'docx/formatting/text_run_formatting'

module Docx
module Elements
module Containers
class TextRun
include Container
include Elements::Element
include TextRunFormatting

DEFAULT_FORMATTING = {
italic: false,
bold: false,
underline: false
}

def self.tag
'r'
end

attr_reader :text
attr_reader :formatting

attr_reader :document_properties
attr_reader :properties_tag
alias_method :formatting, :parse_formatting

def initialize(node, document_properties = {})
@node = node
@document_properties = document_properties
@text_nodes = @node.xpath('w:t').map {|t_node| Elements::Text.new(t_node) }
@properties_tag = 'rPr'
@text = parse_text || ''
@formatting = parse_formatting || DEFAULT_FORMATTING
@document_properties = document_properties
@font_size = @document_properties[:font_size]
end

Expand All @@ -40,19 +37,17 @@ def text=(content)
end
end

# Set the text of text run with formatting
def set_text(content, formatting={})
self.text = content
apply_formatting(formatting)
end

# Returns text contained within text run
def parse_text
@text_nodes.map(&:content).join('')
end

def parse_formatting
{
italic: [email protected]('.//w:i').empty?,
bold: [email protected]('.//w:b').empty?,
underline: [email protected]('.//w:u').empty?
}
end

def to_s
@text
end
Expand All @@ -65,26 +60,23 @@ def to_html
styles = {}
styles['text-decoration'] = 'underline' if underlined?
# No need to be granular with font size down to the span level if it doesn't vary.
styles['font-size'] = "#{font_size}pt" if font_size != @font_size
styles['font-size'] = "#{font_size}pt" if font_size != @font_size
styles['font-family'] = %Q["#{formatting[:font]}"] if formatting[:font]
styles['color'] = "##{formatting[:color]}" if formatting[:color]
html = html_tag(:span, content: html, styles: styles) unless styles.empty?
return html
end

def italicized?
@formatting[:italic]
formatting[:italic]
end

def bolded?
@formatting[:bold]
end

def underlined?
@formatting[:underline]
formatting[:bold]
end

def font_size
size_tag = @node.xpath('w:rPr//w:sz').first
size_tag ? size_tag.attributes['val'].value.to_i / 2 : @font_size
def underlined?
formatting[:underline]
end
end
end
Expand Down
7 changes: 6 additions & 1 deletion lib/docx/document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ def initialize(path, &block)
# This stores the current global document properties, for now
def document_properties
{
font_size: font_size
font_size: font_size,
font: font
}
end

Expand Down Expand Up @@ -74,6 +75,10 @@ def font_size
size_tag ? size_tag.attributes['val'].value.to_i / 2 : nil
end

def font
font_tag = @styles.at_xpath('//w:docDefaults//w:rPrDefault//w:rPr//w:rFonts')
font_tag ? font_tag['w:ascii'] : nil
end
##
# *Deprecated*
#
Expand Down
24 changes: 12 additions & 12 deletions lib/docx/elements/bookmark.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module Elements
class Bookmark
include Element
attr_accessor :name

def self.tag
'bookmarkStart'
end
Expand All @@ -15,20 +15,20 @@ def initialize(node)
@name = @node['w:name']
end

# Insert text before bookmarkStart node
def insert_text_before(text)
# Insert text before bookmarkStart node with optional formatting
def insert_text_before(text, formatting={})
text_run = get_run_after
text_run.text = "#{text}#{text_run.text}"
text_run.set_text("#{text}#{text_run.text}", formatting)
end

# Insert text after bookmarkStart node
def insert_text_after(text)
# Insert text after bookmarkStart node with optional formatting
def insert_text_after(text, formatting={})
text_run = get_run_before
text_run.text = "#{text_run.text}#{text}"
text_run.set_text("#{text_run.text}#{text}", formatting)
end

# insert multiple lines starting with paragraph containing bookmark node.
def insert_multiple_lines(text_array)
# insert multiple lines starting with paragraph containing bookmark node. With optional formatting
def insert_multiple_lines(text_array, formatting={})
# Hold paragraphs to be inserted into, corresponding to the index of the strings in the text array
paragraphs = []
paragraph = self.parent_paragraph
Expand All @@ -45,13 +45,13 @@ def insert_multiple_lines(text_array)

# Insert text into corresponding newly created paragraphs
paragraphs.each_index do |index|
paragraphs[index].text = text_array[index]
paragraphs[index].set_text(text_array[index], formatting)
end
end

# Get text run immediately prior to bookmark node
def get_run_before
# at_xpath returns the first match found and preceding-sibling returns siblings in the
# at_xpath returns the first match found and preceding-sibling returns siblings in the
# order they appear in the document not the order as they appear when moving out from
# the starting node
if not (r_nodes = @node.xpath("./preceding-sibling::w:r")).empty?
Expand All @@ -76,4 +76,4 @@ def get_run_after
end
end
end
end
end
2 changes: 1 addition & 1 deletion lib/docx/elements/element.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,4 @@ def create_within(element)
end
end
end
end
end
14 changes: 14 additions & 0 deletions lib/docx/formatting/formatting.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module Docx
module Formatting
def add_property(tag)
property_node.remove if properties_node.at_xpath(".//w:#{tag}") # Remove and replace property
properties_node.add_child("<w:#{tag}/>").first
end

def properties_node
properties = node.at_xpath(".//w:#{properties_tag}")
# Should a paragraph formatting node not exist create one
properties ||= node.prepend_child("<w:#{properties_tag}></w:#{properties_tag}>").first
end
end
end
27 changes: 27 additions & 0 deletions lib/docx/formatting/paragraph_formatting.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
require 'docx/formatting/formatting'

module Docx
module ParagraphFormatting
include Formatting

def apply_formatting(formatting)
if (formatting[:alignment])
alignment_node = add_property('jc')
alignment_node['w:val'] = formatting[:alignment]
end
end

def parse_formatting
formatting = {}
alignment_node = node.at_xpath('.//w:jc')
formatting[:alignment] = alignment_node ? alignment_node['w:val'] : nil
formatting
end

def self.default_formatting
{
alignment: nil
}
end
end
end
53 changes: 53 additions & 0 deletions lib/docx/formatting/text_run_formatting.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
require 'docx/formatting/formatting'

module Docx
module TextRunFormatting
include Formatting

def apply_formatting(formatting)
if (formatting[:font])
font_node = add_property('rFonts')
font_node["w:ascii"] = formatting[:font]
font_node["w:hAnsi"] = formatting[:font]
end
if (formatting[:font_size])
font_size_node = add_property('sz')
font_size_node['w:val'] = formatting[:font_size] * 2 # Font size is stored in half-points
end
add_property('i') if formatting[:italic]
add_property('b') if formatting[:bold]
add_property('u') if formatting[:underline]
if (formatting[:color])
color_node = add_property('color')
color_node["w:val"] = formatting[:color]
end
end

def parse_formatting()
formatting = {}
formatting[:italic] = !node.xpath('.//w:i').empty?
formatting[:bold] = !node.xpath('.//w:b').empty?
formatting[:underline] = !node.xpath('.//w:u').empty?
font_node = node.at_xpath('.//w:rFonts')
formatting[:font] = font_node ? font_node['w:ascii'] : document_properties[:font]
formatting[:font_size] = font_size
color_node = node.at_xpath('.//w:color')
formatting[:color] = color_node ? color_node['w:val'] : nil
formatting
end

def self.default_formatting(document_properties)
{
italic: false, bold: false, underline: false,
font: document_properties[:font],
font_size: document_properties[:font_size],
color: nil
}
end

def font_size
size_tag = @node.at_xpath('.//w:sz')
size_tag ? size_tag.attributes['val'].value.to_i / 2 : @document_properties[:font_size]
end
end
end
2 changes: 1 addition & 1 deletion lib/docx/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Docx #:nodoc:
VERSION = '0.2.07'
VERSION = '0.3.0'
end
Loading