Skip to content

Commit

Permalink
Also read/update headers and footers
Browse files Browse the repository at this point in the history
Inspired by PR ruby-docx#73
we adapted the code to work on top of the current
state.
  • Loading branch information
nathanvda committed Jul 1, 2021
1 parent e6e1e5d commit 353c630
Showing 1 changed file with 67 additions and 7 deletions.
74 changes: 67 additions & 7 deletions lib/docx/document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ module Docx
class Document
attr_reader :xml, :doc, :zip, :styles

DOCUMENT_PATHS = {
styles: "word/styles.xml",
rels: "word/_rels/document.xml.rels",
headers: "word/header*.xml",
footers: "word/footer*.xml",
numbering: "word/numbering.xml"
}

def initialize(path_or_io, options = {})
@replace = {}

Expand All @@ -35,7 +43,12 @@ def initialize(path_or_io, options = {})

@document_xml = document.get_input_stream.read
@doc = Nokogiri::XML(@document_xml)

load_styles
load_headers
load_footers
load_numbering

yield(self) if block_given?
ensure
@zip.close
Expand Down Expand Up @@ -64,9 +77,12 @@ def paragraphs
def bookmarks
bkmrks_hsh = {}
bkmrks_ary = @doc.xpath('//w:bookmarkStart').map { |b_node| parse_bookmark_from b_node }
bkmrks_ary += @headers.values.map { |xml_doc| xml_doc.xpath('//w:bookmarkStart').map { |b_node| parse_bookmark_from b_node } }.flatten
bkmrks_ary += @footers.values.map { |xml_doc| xml_doc.xpath('//w:bookmarkStart').map { |b_node| parse_bookmark_from b_node } }.flatten
# auto-generated by office 2010
bkmrks_ary.reject! { |b| b.name == '_GoBack' }
bkmrks_ary.each { |b| bkmrks_hsh[b.name] = b }

bkmrks_hsh
end

Expand Down Expand Up @@ -166,24 +182,68 @@ def replace_entry(entry_path, file_contents)
private

def load_styles
@styles_xml = @zip.read('word/styles.xml')
@styles = Nokogiri::XML(@styles_xml)
@rels_xml = @zip.read('word/_rels/document.xml.rels')
@rels = Nokogiri::XML(@rels_xml)
rescue Errno::ENOENT => e
warn e.message
nil
extract_single_document_from_path :styles
extract_single_document_from_path :rels
end

def load_headers
extract_multiple_documents_from_globbed_path :headers
end

def load_footers
extract_multiple_documents_from_globbed_path :footers
end

def load_numbering
extract_single_document_from_path :numbering
end

def extract_single_document_from_path(attr_name)
path = DOCUMENT_PATHS[attr_name]
if @zip.find_entry(path)
xml_doc = @zip.read(path)
self.instance_variable_set(:"@#{attr_name}", Nokogiri::XML(xml_doc))
end
end

def extract_multiple_documents_from_globbed_path(attr_name)
glob_path = DOCUMENT_PATHS[attr_name]
files = @zip.glob(glob_path).map { |h| h.name }
filename_and_contents_pairs = files.map do |file|
simple_file_name = file.sub(/^word\//, "").sub(/\.xml$/, "")
[simple_file_name, Nokogiri::XML(@zip.read(file))]
end
hash = Hash[filename_and_contents_pairs]
self.instance_variable_set(:"@#{attr_name}", hash)
end

#--
# TODO: Flesh this out to be compatible with other files
# TODO: Method to set flag on files that have been edited, probably by inserting something at the
# end of methods that make edits?
# TODO: save document.xml or document2.xml ?
#++
def update
replace_entry 'word/document.xml', doc.serialize(save_with: 0)
update_multiple_documents :headers
update_multiple_documents :footers
update_single_document :numbering
# also save styles? / rels?
end

def update_multiple_documents(attr_name)
self.instance_variable_get("@#{attr_name}").each do |simple_file_name, contents|
replace_entry("word/#{simple_file_name}.xml", contents.serialize(:save_with => 0))
end
end

def update_single_document(attr_name)
path = DOCUMENT_PATHS[attr_name]
xml_document = self.instance_variable_get("@#{attr_name}")
replace_entry path, xml_document.serialize(:save_with => 0) if xml_document
end


# generate Elements::Containers::Paragraph from paragraph XML node
def parse_paragraph_from(p_node)
Elements::Containers::Paragraph.new(p_node, document_properties)
Expand Down

0 comments on commit 353c630

Please sign in to comment.