diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a5713b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+.DS_Store
+log/*
+tmp/**/*
+*.swp
+*.swo
+nbproject
+.project
+.idea
+.idea/**
+*~
+**/*~
+
diff --git a/README.textile b/README.textile
new file mode 100644
index 0000000..b43bd1c
--- /dev/null
+++ b/README.textile
@@ -0,0 +1,45 @@
+h1. DocX Builder
+
+_DocX Builder_ is a small utility to help you compose a docx (Microsoft Word 2007) based on a template's XML.
+
+The @slice_template.rb@ can be used separately for non-docx applications.
+
+The steps:
+# Create a docx file with _Microsoft Word_ or _OpenOffice.org_
+# Unzip it (the docx file is actually a ZIP package)
+# Create your template from @word/document.xml@
+# Open the template and reformat it (I used RubyMine)
+# Find the parts you need to generate programmatically, and mark them up with ... , see @example/plan_report_template.xml@
+# Remove the formatting (compact the XML)
+# Create your builder, see @example.rb@
+
+h2. The example explained
+
+pre.
+ _____head______ _________area_______________________________________ _foot_
+ | | | |
+ | | ________goal________________________| |
+ | | | | |
+ | | | _______objective____| |
+ | | | | | |
+ | (Plan Name) | (Area Name) | (Goal Name) | (Objective Name) | |
+ _____________________________________________________________________________
+ XML DOCUMENT
+
+You can assign a text to a placeholder:
+
+pre.
+ template['head']['Plan Name'] = @plan.name
+
+Or you can replace a slice with a string, that can be composed by multiplying that slice:
+
+pre.
+ template['area'] =
+ @plan.areas.map do |area|
+
+ area_slice = template['area'].clone
+ area_slice['Area Name'] = area.description
+ area_slice['goal'] = '...'
+ end
+
+See @example.rb@
\ No newline at end of file
diff --git a/docx_builder.rb b/docx_builder.rb
new file mode 100644
index 0000000..a53c68e
--- /dev/null
+++ b/docx_builder.rb
@@ -0,0 +1,38 @@
+require 'slice_template'
+
+class DocxBuilder
+ def initialize(template_filename, template_dirname)
+ @template_filename = template_filename
+ @template_dirname = template_dirname
+ end
+
+ def build
+ template = SliceTemplate.new(@template_filename);
+ yield template
+ build_docx(template.render)
+ end
+
+ private
+
+ def build_docx(content)
+ docx_content = nil
+ in_temp_dir do |temp_dir|
+ system("cp -r #{@template_dirname} #{temp_dir}/plan_report")
+ open("#{temp_dir}/plan_report/word/document.xml", "w") do |file|
+ file.write(content)
+ end
+ system("cd #{temp_dir}/plan_report; zip -r ../plan_report.docx *")
+ docx_content = File.read("#{temp_dir}/plan_report.docx")
+ end
+ docx_content
+ end
+
+ def in_temp_dir
+ temp_dir = "/tmp/docx_#{Time.now.to_f.to_s}"
+ Dir.mkdir(temp_dir)
+ yield(temp_dir)
+ system("rm -Rf #{temp_dir}")
+ end
+end
+
+
diff --git a/example.docx b/example.docx
new file mode 100644
index 0000000..ebf8ed8
Binary files /dev/null and b/example.docx differ
diff --git a/example.rb b/example.rb
new file mode 100644
index 0000000..a02a6fd
--- /dev/null
+++ b/example.rb
@@ -0,0 +1,56 @@
+require 'docx_builder'
+
+
+plan_struct = Struct.new(:name, :areas, :goals_by_area, :objectives_by_goal)
+area_struct = Struct.new(:description, :id)
+goal_struct = Struct.new(:description, :id)
+objective_struct = Struct.new(:description)
+@plan = plan_struct.new
+@plan.name = 'Business Plan for 2011'
+@plan.areas = [area_struct.new('Software Development', 1), area_struct.new('Cooking', 2)]
+@plan.goals_by_area = {
+ 1 => [ goal_struct.new('Create a new app', 1), goal_struct.new('Create another app', 2)],
+ 2 => [ goal_struct.new('Make a new recipe', 3), goal_struct.new('Open a restaurant', 4)],
+}
+@plan.objectives_by_goal = {
+ 1 => [ objective_struct.new('It should be interesting'), objective_struct.new('It should be simple') ],
+ 2 => [ objective_struct.new('It should be revolutionary'), objective_struct.new('It should be unique') ],
+ 3 => [ objective_struct.new('Make a unique recipe'), objective_struct.new('Make a tasty recipe') ],
+ 4 => [ objective_struct.new('Serve high quality food'), objective_struct.new('Make it cheap') ],
+}
+
+
+file_path = "#{File.dirname(__FILE__)}/example/plan_report_template.xml"
+dir_path = "#{File.dirname(__FILE__)}/example/plan_report_template"
+
+report = DocxBuilder.new(file_path, dir_path).build do |template|
+
+ template['head']['Plan Name'] = @plan.name
+ template['area'] =
+ @plan.areas.map do |area|
+
+ area_slice = template['area'].clone
+ area_slice['Area Name'] = area.description
+ area_slice['goal'] =
+ @plan.goals_by_area[area.id].map do |goal|
+
+ goal_slice = template['goal'].clone
+ goal_slice['Goal Name'] = goal.description
+ goal_slice['objective'] =
+ @plan.objectives_by_goal[goal.id].map do |objective|
+ objective_slice = template['objective'].clone
+ objective_slice['Objective Name'] = objective.description
+ objective_slice
+ end
+ goal_slice
+ end
+ area_slice
+ end
+end
+
+
+open("example.docx", "w") { |file| file.write(report) }
+
+# ... or in a Rails controller:
+# response.headers['Content-disposition'] = 'attachment; filename=plan_report.docx'
+# render :text => report, :content_type => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
diff --git a/example/plan_report_template.xml b/example/plan_report_template.xml
new file mode 100644
index 0000000..d82a7dd
--- /dev/null
+++ b/example/plan_report_template.xml
@@ -0,0 +1,2 @@
+
+Strategic Plan: (Plan Name)Area: (Area Name)Goal: (Goal Name)Objectives:(Objective Name)
\ No newline at end of file
diff --git a/example/plan_report_template/[Content_Types].xml b/example/plan_report_template/[Content_Types].xml
new file mode 100644
index 0000000..e7ffd57
--- /dev/null
+++ b/example/plan_report_template/[Content_Types].xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/example/plan_report_template/_rels/.rels b/example/plan_report_template/_rels/.rels
new file mode 100644
index 0000000..f0b72e7
--- /dev/null
+++ b/example/plan_report_template/_rels/.rels
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/example/plan_report_template/docProps/app.xml b/example/plan_report_template/docProps/app.xml
new file mode 100644
index 0000000..0e51a38
--- /dev/null
+++ b/example/plan_report_template/docProps/app.xml
@@ -0,0 +1,2 @@
+
+0
\ No newline at end of file
diff --git a/example/plan_report_template/docProps/core.xml b/example/plan_report_template/docProps/core.xml
new file mode 100644
index 0000000..8a7da19
--- /dev/null
+++ b/example/plan_report_template/docProps/core.xml
@@ -0,0 +1,2 @@
+
+2010-05-28T19:47:48.00ZLevente Bagi0
\ No newline at end of file
diff --git a/example/plan_report_template/word/_rels/document.xml.rels b/example/plan_report_template/word/_rels/document.xml.rels
new file mode 100644
index 0000000..2850fab
--- /dev/null
+++ b/example/plan_report_template/word/_rels/document.xml.rels
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/example/plan_report_template/word/document.xml b/example/plan_report_template/word/document.xml
new file mode 100644
index 0000000..a09fac0
--- /dev/null
+++ b/example/plan_report_template/word/document.xml
@@ -0,0 +1,2 @@
+
+Strategic Plan: (Strategic Plan Name)Area: (Area 1)Goal: (Goal 1)Objectives:(Objective 1)(Objective 2)(Objective 3)Goal: (Goal 2)Objectives:(Objective 1)(Objective 2)(Objective 3)Goal: (Goal 3)Objectives:(Objective 1)(Objective 2)(Objective 3)Area: (Area 2)Goal: (Goal 1)Objectives:(Objective 1)(Objective 2)(Objective 3)Goal: (Goal 2)Objectives:(Objective 1)(Objective 2)(Objective 3)Goal: (Goal 3)Objectives:(Objective 1)(Objective 2)(Objective 3)Area: (Area 3)Goal: (Goal 1)Objectives:(Objective 1)(Objective 2)(Objective 3)Goal: (Goal 2)Objectives:(Objective 1)(Objective 2)(Objective 3)Goal: (Goal 3)Objectives:(Objective 1)(Objective 2)(Objective 3)
\ No newline at end of file
diff --git a/example/plan_report_template/word/fontTable.xml b/example/plan_report_template/word/fontTable.xml
new file mode 100644
index 0000000..773d81f
--- /dev/null
+++ b/example/plan_report_template/word/fontTable.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/example/plan_report_template/word/numbering.xml b/example/plan_report_template/word/numbering.xml
new file mode 100644
index 0000000..55c8260
--- /dev/null
+++ b/example/plan_report_template/word/numbering.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/example/plan_report_template/word/styles.xml b/example/plan_report_template/word/styles.xml
new file mode 100644
index 0000000..c6f967f
--- /dev/null
+++ b/example/plan_report_template/word/styles.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/slice_template.rb b/slice_template.rb
new file mode 100644
index 0000000..2d83b6c
--- /dev/null
+++ b/slice_template.rb
@@ -0,0 +1,105 @@
+class SliceTemplate
+
+ attr_reader :content
+ attr_accessor :slices
+
+
+ def initialize(filename)
+ @content = File.read(filename)
+ @slices = {}
+ parse
+ end
+
+ def initialize_copy(other)
+ @content = other.content.clone
+ @slices = other.slices.clone
+ end
+
+ def [](slice_name)
+ @slices[slice_name] = Slice.new(@slices[slice_name]) unless @slices[slice_name].instance_of?(Slice)
+ @slices[slice_name]
+ end
+
+ def []=(slice_name, value)
+ if value.instance_of?(Array)
+ @slices[slice_name] = value.map do |item|
+ item.instance_of?(Slice) ? item.render : item
+ end
+ else
+ @slices[slice_name] = value
+ end
+ end
+
+ def render
+ render_string(@content)
+ end
+
+ alias_method :to_s, :render
+
+
+ private
+
+
+ def parse
+ parse_string(@content)
+ end
+
+ def parse_string(s)
+ s.gsub!(/<\!-- BEGIN ([^>]+) -->(.+)<\!-- END \1 -->/m).each do |match|
+ slice_name, content = [$1.downcase, $2]
+ parse_string(content)
+ @slices[slice_name] = content
+ "(#{slice_name})"
+ end
+ end
+
+ def render_string(s)
+ return if s.nil?
+ s.gsub(/\(([\w\d _]+)\)/) do |match|
+ slot_name = $1
+ slice = @slices[slot_name]
+ slice.nil? ? match : render_string(slice.to_s)
+ end
+ end
+
+
+ class Slice
+ attr_reader :slots
+
+ def initialize(content)
+ @content = content
+ parse
+ end
+
+ def parse
+ @slots = Hash[@content.scan(/\(([\w\d _]+)\)/).map{|slot_name| [slot_name, "(#{slot_name})"] }]
+ end
+
+ def [](slot_name)
+ @slots[slot_name]
+ end
+
+ def []=(slot_name, value)
+ @slots[slot_name] = value
+ end
+
+ def set(slot_name, value)
+ @slots[slot_name] = value
+ self
+ end
+
+ def render
+ @content.gsub(/\(([\w\d _]+)\)/) do |match|
+ slot_name = $1
+ @slots[slot_name]
+ end
+ end
+
+ alias_method :to_s, :render
+
+ def initialize_copy(other)
+ @slots = other.slots.clone
+ end
+ end
+
+end
\ No newline at end of file