Rails Microsoft Word, XML databinding, repeat rows



Those willing to jump straight to my questions can go to the last big block of code "Please help with". Everything is summarized in the said code block code comments


The story


The famous problem of inserting repeating content, like table rows, into a word template, using the rails framework.


I decided to implement a 'cleaner' solution for replacing some variables in a Word document with rails, using XML databinding. This solution works very well for non-repetitive content, but for repetitive content, a little extra dirty work must be done and I need help with it.


No C#, No Visual, just plain olde ruby on rails & XML


The databinded document


I have a Word document with some content controls, tagged with "human-readable" text, so my users know what should be inside.


I have used Word 2007 Content Control Toolkit to add some custom XML to a .docx file. Therefore in each .docx I have some customXml/itemsx.xml that contains my custom XML.


I have manually databinded this XML to text content control I have in my word template, using drag & drop with Word 2007 Content Control Toolkit.


The replacing process with nokogiri


Basically I already have some code that replaces every XML node by the corresponding value from a hash. For example if I provide this hash to my function :



variables = {
"some_xml-node" => "some_value"
}


It will properly replace XML in customXml/itemsx.xml of .docx file :



<root> <some> <xml-node>some_value</xml-node></some> </root>


So this is taken care of !


The repetitive content


Now as I said, this works perfectly for non-repetitive content. For repetitive content (in my case I want to repeat some <w:tr> in a document), the solution I'd like to go with, is



  1. Manually insert some tags in word/document.xml of .docx file (this is dirty, but hell I can't think of anything else) before every <tr> that needs to be duplicated

  2. In rails, parse the XML and locate the <tr> that needs duplicating using Nokogiri

  3. Copy the tr as many times as I need

  4. Look at some text inside this <tr>, find the databinding (which looks like <w:dataBinding w:xpath="/root[1]/movies[1]/movie[1]/name[1]"

  5. Replace movie[1] by movie[index]

  6. Repeat for every table that needs <tr> duplication


With this solution Therefore I ensure 100% compatibility with my existing system ! It's some kind of preprocessing...


Please help with


Here's my code so far, please help me with some parts :



def dynamic_table_content(zip, repeat_tags, contents)
doc = zip.find_entry("word/document.xml")
xml = Nokogiri::XML.parse(doc.get_input_dtream)

# repeat_tags = [ {"tag" => My_Custom_Tag_ID", "repeatable-content" => "movie"},...]
repeat_tags.each do |rpt|

content = contents[rpt[:repeatable_content]]
# content = [ {"name" => "X-Men", "year" => 1998, "property-xxx" => 42, ...}, ...]
content_name = rpt[:repeateable_content].to_s
# the 'movie' of '/root[1]/movies[1]/movie[1]/name[1]' (see below)

puts "Processing #{rpt[:tag]}, adding #{content_name}s"

# Word document.xml code looks like this :
# <!-- My_Custom_Tag_ID_inserted_manually -->
# <w:tr ...>
# ...
# <w:dataBinding w:xpath="/root[1]/movies[1]/movie[1]/name[1]>
# ...
# </w:tr>

# Find starting <w:tr > tag containing row using rpt[:tag]
base_tr_node = # HELP ME with this please !

# Duplicate it as many times as we want.
content.each_with_index do |content, index|
puts "Adding #{content_name} : #{content}.to_s"

new_tr_node = base_tr_node.add_next_sibling(base_tr_node)

# inside this new node there are many
# <w:dataBinding w:xpath="/root[1]/movies[1]/movie[1]/name[1]>
# <w:dataBinding w:xpath="/root[1]/movies[1]/movie[1]/year[1]>
# ..../movie[1]/property-xxx[1]
# GOAL : replace every movie[1] by movie[index]

new_tr_node.do_commented_stuff # Help with this please !
# Maybe, it would be something like
# new_tr_node.gsub("(#{content_name})\[([1-9]+)\]", "\1\[#{index}\]")
# ... But new_tr_node is a nokogiri element so .gsub doesn't exist
end
end
@replace["word/document.xml"] = xml.serialize :save_zip_with => 0
end

No comments:

Post a Comment