diff --git a/examples/dsl b/examples/dsl
new file mode 100755
index 00000000..562b8245
--- /dev/null
+++ b/examples/dsl
@@ -0,0 +1,171 @@
+#!/usr/bin/env ruby
+
+$:.push Dir.pwd + "/lib"
+require "ox"
+require "openxml/docx"
+require "openxml/drawingml"
+
+ROWS = 8
+COLUMNS = 4
+
+def build_docx
+ docx = OpenXml::Docx::Package.new
+
+ heading = OpenXml::Docx::Elements::Paragraph.new
+ .paragraph_style("Heading1")
+
+ heading << OpenXml::Docx::Elements::Run.new("A Table of Some Sort")
+ docx.document << heading
+
+ table = OpenXml::Docx::Elements::Table.new(scaffold: true)
+ .width(5000)
+ .width_unit(:pct)
+ .table_style("TableGrid")
+
+ ROWS.times do
+ row = OpenXml::Docx::Elements::TableRow.new
+
+ COLUMNS.times do
+ cell = OpenXml::Docx::Elements::TableCell.new
+
+ paragraph = OpenXml::Docx::Elements::Paragraph.new
+ .paragraph_style("CellText")
+ line_one = OpenXml::Docx::Elements::Run.new("Lorem ipsum dolor sit amet, consectetur adipiscing elit.")
+ line_one << OpenXml::Docx::Elements::Break.new
+ line_two = OpenXml::Docx::Elements::Run.new("Donec a diam lectus. Sed sit amet ipsum mauris.")
+
+ paragraph << line_one
+ paragraph << line_two
+ cell << paragraph
+ row << cell
+ end
+
+ table << row
+ end
+
+ docx.document << table
+
+ # ochanomizu.jpg
+ # 96 ppi
+ # 393px x 599px
+ image_width = (393 * (96.0 / 300) * 12700).round # Rough conversion to EMU: 1pt = 12700emu
+ image_height = (599 * (96.0 / 300) * 12700).round
+ image_rid = docx.embed_image(path: File.join(File.dirname(__FILE__), "ochanomizu.jpg"))
+ inline_image = OpenXml::Docx::Elements::Paragraph.new do
+ alignment(:center)
+ push OpenXml::Docx::Elements::Run.new
+ .push(OpenXml::Docx::Elements::Drawing.new(scaffold: true, width: image_width, height: image_height, anchor_type: :inline, wrap_type: :top_and_bottom) do
+ graphic << OpenXml::DrawingML::Elements::GraphicData.new
+ .uri(OpenXml::DrawingML::Elements::GraphicData.data_types[:picture])
+ .push(OpenXml::DrawingML::Elements::Picture.new(scaffold: true, image_rid: image_rid, width: image_width, height: image_height))
+ end)
+ end
+ docx.document << inline_image
+
+ textbox_width = 6 * 72 * 12700
+ textbox_height = 2 * 72 * 12700
+ floating_textbox = OpenXml::Docx::Elements::Paragraph.new do
+ push(OpenXml::Docx::Elements::Run.new do
+ push(OpenXml::Docx::Elements::Drawing.new(scaffold: true, width: textbox_width, height: textbox_height, wrap_type: :top_and_bottom) do
+ graphic << OpenXml::DrawingML::Elements::GraphicData.new
+ .uri(OpenXml::DrawingML::Elements::GraphicData.data_types[:wordprocessing_shape])
+ .push(OpenXml::Docx::Elements::WordProcessingShapesShape.new(scaffold: true, width: textbox_width, height: textbox_height, textbox: true) do
+ text_content << OpenXml::Docx::Elements::Paragraph.new
+ .alignment(:center)
+ .paragraph_style("Heading1")
+ .push(OpenXml::Docx::Elements::Run.new("Hello, Textboxes!"))
+ end)
+ end)
+ end)
+ end
+ docx.document << floating_textbox
+
+ docx.document << OpenXml::Docx::Section.new
+ .page_size
+ .height(15840)
+ .width(12240)
+ .orientation(:portrait)
+ .end_chain
+ .page_margins
+ .bottom(720)
+ .footer(360)
+ .gutter(0)
+ .header(360)
+ .left(720)
+ .right(720)
+ .top(720)
+ .end_chain
+
+ docx.styles << build_heading_style
+ docx.styles << build_table_style
+ docx.styles << build_cell_text_style
+
+ filename = "docx_test_dsl.docx"
+ system "rm -f ~/Desktop/#{filename}" # -f so that we don't have an error if the file doesn't exist
+ docx.save File.expand_path("~/Desktop/#{filename}")
+ exec "open ~/Desktop/#{filename}"
+end
+
+def build_table_style
+ OpenXml::Docx::Style.new(:table)
+ .id("TableGrid")
+ .style_name("Table Grid")
+ .primary_style(true)
+ .table
+ .table_borders do
+ all_tags.each do |tag_name|
+ push OpenXml::Docx::Properties::TableBorder.new(tag_name, :single)
+ .color("000000")
+ .width(8)
+ end
+ end
+ .table_cell_margins do
+ vertical_tags.each do |tag_name|
+ push OpenXml::Docx::Properties::TableCellMargin.new(tag_name)
+ .type(:dxa)
+ .width(0)
+ end
+ horizontal_tags.each do |tag_name|
+ push OpenXml::Docx::Properties::TableCellMargin.new(tag_name)
+ .type(:dxa)
+ .width(108)
+ end
+ end
+ .end_chain
+end
+
+def build_heading_style
+ OpenXml::Docx::Style.new(:paragraph)
+ .id("Heading1")
+ .style_name("Heading 1")
+ .primary_style(true)
+ .paragraph
+ .alignment(:center)
+ .end_chain
+ .character
+ .bold(true)
+ .font_size(48)
+ .end_chain
+end
+
+def build_cell_text_style
+ OpenXml::Docx::Style.new(:paragraph)
+ .id("CellText")
+ .style_name("Table Cell")
+ .primary_style(true)
+ .paragraph
+ .alignment(:center)
+ .text_alignment(:center)
+ .end_chain
+ .character
+ .font
+ .ascii("Times New Roman")
+ .high_ansi("Times New Roman")
+ .end_chain
+ .font_size(20)
+ .end_chain
+end
+
+# Do the thing!
+build_docx
+
diff --git a/lib/openxml/docx/attribute_builder.rb b/lib/openxml/docx/attribute_builder.rb
index d5ba18ed..ede917d6 100644
--- a/lib/openxml/docx/attribute_builder.rb
+++ b/lib/openxml/docx/attribute_builder.rb
@@ -204,11 +204,9 @@ def self.included(base)
module ClassMethods
def attribute(name, expects: nil, one_of: nil, displays_as: nil, namespace: nil, matches: nil, deprecated: false)
- bad_names = %w(tag name namespace properties_tag)
+ bad_names = %w(tag name namespace properties_tag class)
raise ArgumentError if bad_names.member? name
- attr_reader name
-
define_method "#{name}=" do |value|
valid_in?(value, one_of) unless one_of.nil?
send(expects, value) unless expects.nil?
@@ -216,6 +214,13 @@ def attribute(name, expects: nil, one_of: nil, displays_as: nil, namespace: nil,
instance_variable_set "@#{name}", value
end
+ # Attributes will return the element, properties will return property
+ define_method "#{name}" do |*args|
+ return instance_variable_get "@#{name}" if args.empty?
+ public_send(:"#{name}=", args.first)
+ self
+ end
+
camelized_name = name.to_s.gsub(/_([a-z])/i) { $1.upcase }.to_sym
attributes[name] = [displays_as || camelized_name, namespace || @attribute_namespace]
end
diff --git a/lib/openxml/docx/chainable_nested_context.rb b/lib/openxml/docx/chainable_nested_context.rb
new file mode 100644
index 00000000..493e7d15
--- /dev/null
+++ b/lib/openxml/docx/chainable_nested_context.rb
@@ -0,0 +1,9 @@
+module OpenXml
+ module Docx
+ module ChainableNestedContext
+ def end_chain
+ @self_was
+ end
+ end
+ end
+end
diff --git a/lib/openxml/docx/elements/container.rb b/lib/openxml/docx/elements/container.rb
index 0df92ced..4b8cd7ca 100644
--- a/lib/openxml/docx/elements/container.rb
+++ b/lib/openxml/docx/elements/container.rb
@@ -6,12 +6,20 @@ class Container < Element
attr_reader :children
- def initialize
+ def initialize(options={})
@children = []
+ super
end
def <<(child)
children << child
+ self
+ end
+ alias :push :<<
+
+ def concat(new_children)
+ Array(new_children).each { |child| self.push child }
+ self
end
def to_xml(xml)
@@ -21,6 +29,13 @@ def to_xml(xml)
}
end
+ def method_missing(method, *args, &block)
+ found_child = children.select { |child| child.name == method.to_s }
+ return if found_child.empty?
+ return found_child.first if found_child.count == 1
+ found_child
+ end
+
private
def properties_tag
diff --git a/lib/openxml/docx/elements/drawing.rb b/lib/openxml/docx/elements/drawing.rb
index bbf325a7..479e7365 100644
--- a/lib/openxml/docx/elements/drawing.rb
+++ b/lib/openxml/docx/elements/drawing.rb
@@ -1,8 +1,124 @@
+require "openxml/docx/elements/word_processing_drawing_inline"
+require "openxml/docx/elements/word_processing_drawing_anchor"
+require "openxml/docx/elements/word_processing_drawing_simple_position"
+require "openxml/docx/elements/word_processing_drawing_position_h"
+require "openxml/docx/elements/word_processing_drawing_position_v"
+require "openxml/docx/elements/word_processing_drawing_position_offset"
+require "openxml/docx/elements/word_processing_drawing_extent"
+require "openxml/docx/elements/word_processing_drawing_wrap_through"
+require "openxml/docx/elements/word_processing_drawing_wrap_tight"
+require "openxml/docx/elements/word_processing_drawing_wrap_polygon"
+require "openxml/docx/elements/word_processing_drawing_wrap_square"
+require "openxml/docx/elements/word_processing_drawing_wrap_top_and_bottom"
+require "openxml/docx/elements/word_processing_drawing_wrap_none"
+require "openxml/docx/elements/word_processing_drawing_wrap_coordinate"
+require "openxml/docx/elements/word_processing_drawing_object_nv_properties"
+require "openxml/drawingml/elements/graphic"
+
module OpenXml
module Docx
module Elements
class Drawing < Container
tag :drawing
+ attr_reader :graphic
+
+ def self.next_index
+ @index = (@index || 0) + 1
+ end
+
+ private
+
+ def build_scaffold
+ inline = options.fetch(:anchor_type, :floating) == :inline
+ width = options.fetch(:width, 0)
+ height = options.fetch(:height, 0)
+ wrap_type = options.fetch(:wrap_type, :none)
+ x_pos, y_pos = options.fetch(:position, [0, 0])
+
+ anchor = if inline
+ OpenXml::Docx::Elements::WordProcessingDrawingInline.new
+ .distance_from_bottom(0)
+ .distance_from_top(0)
+ .distance_from_left(0)
+ .distance_from_right(0)
+ else
+ OpenXml::Docx::Elements::WordProcessingDrawingAnchor.new
+ .distance_from_bottom(0)
+ .distance_from_top(0)
+ .distance_from_left(0)
+ .distance_from_right(0)
+ .allow_overlap(true)
+ .behind_document(false)
+ .layout_in_cell(true)
+ .locked(false)
+ .z_index(1000)
+ .simple_position(false)
+ .push(OpenXml::Docx::Elements::WordProcessingDrawingSimplePosition.new do |sp|
+ sp.x = x_pos
+ sp.y = y_pos
+ end)
+ .push(OpenXml::Docx::Elements::WordProcessingDrawingPositionH.new do |ph|
+ ph.relative_from(:column)
+ ph.push OpenXml::Docx::Elements::WordProcessingDrawingPositionOffset.new
+ .value(x_pos)
+ end)
+ .push(OpenXml::Docx::Elements::WordProcessingDrawingPositionV.new do |ph|
+ ph.relative_from(:paragraph)
+ ph.push OpenXml::Docx::Elements::WordProcessingDrawingPositionOffset.new
+ .value(y_pos)
+ end)
+ end
+
+ anchor << OpenXml::Docx::Elements::WordProcessingDrawingExtent.new
+ .extent_length(width)
+ .extent_width(height)
+
+ unless inline
+ case wrap_type
+ when :polygon
+ coordinates = options.fetch(:wrap_coordinates, [[0,0], [0,height], [width, height], [width, 0]])
+ wrap_margins = options.fetch(:wrap_margins, {})
+ behavior_klass = options.fetch(:wrap_behavior, :tight) == :through ? OpenXml::Docx::Elements::WordProcessingDrawingWrapThrough : OpenXml::Docx::Elements::WordProcessingDrawingWrapTight
+ anchor << behavior_klass.new do
+ distance_from_left(wrap_margins.fetch(:left, 0))
+ distance_from_right(wrap_margins.fetch(:right, 0))
+ wrap_text(:bothSides)
+ push(OpenXml::Docx::Elements::WordProcessingDrawingWrapPolygon.new do
+ coordinates.each_with_index do |(x, y), index|
+ coordinate_type = index == 0 ? :start : :lineTo
+ push OpenXml::Docx::Elements::WordProcessingDrawingWrapCoordinate.new(coordinate_type).x(x).y(y)
+ end
+ end)
+ end
+ when :square
+ wrap_margins = options.fetch(:wrap_margins, {})
+ anchor << OpenXml::Docx::Elements::WordProcessingDrawingWrapSquare.new
+ .distance_from_bottom(wrap_margins.fetch(:bottom, 0))
+ .distance_from_left(wrap_margins.fetch(:left, 0))
+ .distance_from_right(wrap_margins.fetch(:right, 0))
+ .distance_from_top(wrap_margins.fetch(:top, 0))
+ .wrap_text(:bothSides)
+ when :top_and_bottom
+ wrap_margins = options.fetch(:wrap_margins, {})
+ anchor << OpenXml::Docx::Elements::WordProcessingDrawingWrapTopAndBottom.new
+ .distance_from_bottom(wrap_margins.fetch(:bottom, 0))
+ .distance_from_top(wrap_margins.fetch(:top, 0))
+ else
+ anchor << OpenXml::Docx::Elements::WordProcessingDrawingWrapNone.new
+ end
+ end
+
+ anchor << OpenXml::Docx::Elements::WordProcessingDrawingObjectNvProperties.new
+ .description(options[:filename] || options.fetch(:name, "Drawing"))
+ .object_name(options.fetch(:name, "Drawing"))
+ .id(self.class.next_index)
+
+ @graphic = OpenXml::DrawingML::Elements::Graphic.new
+ anchor << @graphic
+
+ push(anchor)
+ end
+
end
end
end
diff --git a/lib/openxml/docx/elements/element.rb b/lib/openxml/docx/elements/element.rb
index c943cfa3..9ff4fb3b 100644
--- a/lib/openxml/docx/elements/element.rb
+++ b/lib/openxml/docx/elements/element.rb
@@ -15,7 +15,7 @@ def tag(*args)
def name(*args)
@property_name = args.first if args.any?
- @name
+ @property_name
end
def namespace(*args)
@@ -25,6 +25,26 @@ def namespace(*args)
end
+ def initialize(options={})
+ if options.fetch(:scaffold, false)
+ @options = options # Make available for scaffolding if needed
+ build_scaffold
+ end
+
+ options.each do |(attr_name, value)|
+ self.public_send("#{attr_name}=", value) if self.respond_to? :"#{attr_name}="
+ end
+
+ if block_given?
+ block = Proc.new
+ if block.arity == 0
+ instance_eval(&block)
+ else
+ yield self
+ end
+ end
+ end
+
def tag
self.class.tag || default_tag
end
@@ -42,6 +62,12 @@ def to_xml(xml)
end
private
+ attr_reader :options
+
+ # Override in subclasses to set up default attributes, properties, and children
+ # when the `build` class method is used to construct the object
+ def build_scaffold
+ end
def default_tag
(class_name[0, 1].downcase + class_name[1..-1]).to_sym
diff --git a/lib/openxml/docx/elements/run.rb b/lib/openxml/docx/elements/run.rb
index bbdafd1a..fed08d23 100644
--- a/lib/openxml/docx/elements/run.rb
+++ b/lib/openxml/docx/elements/run.rb
@@ -1,3 +1,5 @@
+require "openxml/docx/elements/text"
+
module OpenXml
module Docx
module Elements
@@ -45,6 +47,12 @@ class Run < Container
property :shading
property :underline
+ def initialize(text=nil, options={})
+ # More performant than &block: http://mudge.name/2011/01/26/passing-blocks-in-ruby-without-block.html
+ block_given? ? super(options, &Proc.new) : super(options)
+ push OpenXml::Docx::Elements::Text.new(text).space(options.fetch(:text_spacing, :preserve)) unless text.nil?
+ end
+
end
end
end
diff --git a/lib/openxml/docx/elements/table.rb b/lib/openxml/docx/elements/table.rb
index 4502860e..a81f297c 100644
--- a/lib/openxml/docx/elements/table.rb
+++ b/lib/openxml/docx/elements/table.rb
@@ -24,6 +24,28 @@ class Table < Container
property :table_p_pr
property :table_width
+ def width(*args)
+ return table_width.width if args.empty?
+ table_width.width = args.first
+ table_width.type = :dxa if table_width.type == :auto
+ self
+ end
+
+ def width_unit(*args)
+ return table_width.type if args.empty?
+ table_width.type = args.first
+ self
+ end
+
+ private
+
+ def build_scaffold
+ table_width.type = :auto
+ table_width.width = 0
+ table_layout.type = :fixed
+ push OpenXml::Docx::Elements::TableGrid.new
+ end
+
end
end
end
diff --git a/lib/openxml/docx/elements/word_processing_drawing_position_offset.rb b/lib/openxml/docx/elements/word_processing_drawing_position_offset.rb
index f353a670..3d39291a 100644
--- a/lib/openxml/docx/elements/word_processing_drawing_position_offset.rb
+++ b/lib/openxml/docx/elements/word_processing_drawing_position_offset.rb
@@ -9,8 +9,10 @@ def <<(text)
value << text if text.is_a? String
end
- def value
- @value ||= ""
+ def value(*args)
+ return @value ||= "" unless args.any?
+ @value = args.first.to_s
+ self
end
def value=(new_value)
diff --git a/lib/openxml/docx/elements/word_processing_shapes_shape.rb b/lib/openxml/docx/elements/word_processing_shapes_shape.rb
index 0f869312..2bdf8460 100644
--- a/lib/openxml/docx/elements/word_processing_shapes_shape.rb
+++ b/lib/openxml/docx/elements/word_processing_shapes_shape.rb
@@ -5,7 +5,61 @@ class WordProcessingShapesShape < Container
tag :wsp
namespace :wps
attribute :normal_east_asian_flow, expects: :boolean, displays_as: :normalEastAsianFlow
+
+ attr_reader :text_content
+
+ private
+
+ def build_scaffold
+ width = options.fetch(:width, 0)
+ height = options.fetch(:height, 0)
+ textbox = options.fetch(:textbox, false)
+ shape_preset = options.fetch(:shape_preset, :rect)
+
+ push OpenXml::DrawingML::Elements::NonVisualShapeDrawingProperties.new
+ .textbox(textbox)
+ push(OpenXml::Docx::Elements::WordProcessingShapesShapeProperties.new do
+ push(OpenXml::DrawingML::Elements::TransformEffect.new do
+ push OpenXml::DrawingML::Elements::Offset.new
+ .x(0)
+ .y(0)
+ push OpenXml::DrawingML::Elements::Extents.new
+ .extent_length(width)
+ .extent_width(height)
+ end)
+ push OpenXml::DrawingML::Elements::PresetGeometry.new
+ .preset(shape_preset)
+ .push(OpenXml::DrawingML::Elements::AdjustValuesList.new)
+ push OpenXml::DrawingML::Elements::NoFill.new
+ push OpenXml::DrawingML::Elements::Outline.new
+ .push(OpenXml::DrawingML::Elements::NoFill.new)
+ end)
+
+ if textbox
+ @text_content = OpenXml::Docx::Elements::TextboxContent.new
+ push OpenXml::Docx::Elements::WordProcessingShapesTextualContent.new
+ .push(@text_content)
+ end
+
+ push OpenXml::Docx::Elements::WordProcessingShapesBodyProperties.new
+ .rotation(0)
+ .paragraph_spacing(false)
+ .vertical_overflow(:overflow)
+ .horizontal_overflow(:overflow)
+ .vertical(:horz)
+ .wrap(:square)
+ .number_of_columns(1)
+ .anchor(:t)
+ .anchor_center(false)
+ .force_anti_alias(false)
+ .compatible_line_spacing(true)
+ .push(OpenXml::DrawingML::Elements::PresetTextWarp.new
+ .preset(:textNoShape)
+ .push(OpenXml::DrawingML::Elements::AdjustValuesList.new))
+ end
+
end
end
end
end
+
diff --git a/lib/openxml/docx/properties/base_property.rb b/lib/openxml/docx/properties/base_property.rb
index d2d1403d..9fbcf40c 100644
--- a/lib/openxml/docx/properties/base_property.rb
+++ b/lib/openxml/docx/properties/base_property.rb
@@ -20,7 +20,7 @@ def tag(*args)
def name(*args)
@property_name = args.first if args.any?
- @name
+ @property_name
end
def namespace(*args)
@@ -29,12 +29,21 @@ def namespace(*args)
end
end
- def initialize(tag=nil, *args)
+ def initialize(tag=nil, *args, scaffold: true)
return unless self.class.allowed_tags
unless self.class.allowed_tags.include?(tag)
raise ArgumentError, "Invalid tag name for #{name}: #{tag.inspect}. It should be one of #{self.class.allowed_tags.join(", ")}."
end
@tag = tag
+ build_scaffold if scaffold
+
+ if block_given?
+ if block.arity == 0
+ instance_eval(&block)
+ else
+ yield self
+ end
+ end
end
def render?
@@ -67,6 +76,9 @@ def default_namespace
private
+ def build_scaffold
+ end
+
def class_name
self.class.to_s.split(/::/).last
end
diff --git a/lib/openxml/docx/properties/container_property.rb b/lib/openxml/docx/properties/container_property.rb
index 45565466..4348c9bb 100644
--- a/lib/openxml/docx/properties/container_property.rb
+++ b/lib/openxml/docx/properties/container_property.rb
@@ -17,13 +17,21 @@ def child_class(*args)
alias :child_classes :child_class
end
- def initialize
+ def initialize(*args)
@children = []
+ super
end
def <<(child)
raise ArgumentError, invalid_child_message unless valid_child?(child)
children << child
+ self
+ end
+ alias :push :<<
+
+ def concat(new_children)
+ Array(new_children).each { |child| self.push child }
+ self
end
def each(*args, &block)
@@ -42,6 +50,13 @@ def to_xml(xml)
}
end
+ def method_missing(method, *args, &block)
+ found_child = children.select { |child| child.name == method.to_s }
+ return if found_child.empty?
+ return found_child.first if found_child.count == 1
+ found_child
+ end
+
private
attr_reader :children
diff --git a/lib/openxml/docx/properties/table_borders.rb b/lib/openxml/docx/properties/table_borders.rb
index 939bf5c1..e3e5eb95 100644
--- a/lib/openxml/docx/properties/table_borders.rb
+++ b/lib/openxml/docx/properties/table_borders.rb
@@ -7,6 +7,18 @@ class TableBorders < ContainerProperty
tag :tblBorders
child_class :table_border
+ def vertical_tags
+ %i(left right insideV)
+ end
+
+ def horizontal_tags
+ %i(top bottom insideH)
+ end
+
+ def all_tags
+ vertical_tags + horizontal_tags
+ end
+
end
end
end
diff --git a/lib/openxml/docx/properties/table_cell_margins.rb b/lib/openxml/docx/properties/table_cell_margins.rb
index 4222718a..54acd37d 100644
--- a/lib/openxml/docx/properties/table_cell_margins.rb
+++ b/lib/openxml/docx/properties/table_cell_margins.rb
@@ -7,6 +7,18 @@ class TableCellMargins < ContainerProperty
tag :tblCellMar
child_class :table_cell_margin
+ def vertical_tags
+ %i(top bottom)
+ end
+
+ def horizontal_tags
+ %i(left right)
+ end
+
+ def all_tags
+ vertical_tags + horizontal_tags
+ end
+
end
end
end
diff --git a/lib/openxml/docx/property_builder.rb b/lib/openxml/docx/property_builder.rb
index 5aaab536..ec138a7d 100644
--- a/lib/openxml/docx/property_builder.rb
+++ b/lib/openxml/docx/property_builder.rb
@@ -1,3 +1,5 @@
+require "openxml/docx/chainable_nested_context"
+
module OpenXml
module Docx
module PropertyBuilder
@@ -13,8 +15,6 @@ def properties_tag(*args)
end
def value_property(name, as: nil)
- attr_reader name
-
properties[name] = (as || name).to_s
class_eval <<-CODE, __FILE__, __LINE__ + 1
@@ -24,6 +24,12 @@ def #{name}=(value)
prop_class = OpenXml::Docx::Properties.const_get class_name
instance_variable_set "@#{name}", prop_class.new(value)
end
+
+ def #{name}(*args)
+ return instance_variable_get "@#{name}" if args.empty?
+ public_send :"#{name}=", args.first
+ self
+ end
CODE
end
@@ -31,16 +37,30 @@ def property(name, as: nil)
properties[name] = (as || name).to_s
class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}
+ def #{name}(*args)
property_key = "#{name}".to_sym
class_name = properties[property_key].split("_").map(&:capitalize).join
prop_class = OpenXml::Docx::Properties.const_get class_name
if instance_variable_get("@#{name}").nil?
- instance_variable_set "@#{name}", prop_class.new
+ instance_variable_set "@#{name}", prop_class.new(*args)
end
- instance_variable_get "@#{name}"
+ prop_instance = instance_variable_get "@#{name}"
+
+ if block_given?
+ block = Proc.new
+ if block.arity == 0
+ prop_instance.instance_eval(&block)
+ else
+ yield self
+ end
+ return self
+ end
+
+ prop_instance.extend OpenXml::Docx::ChainableNestedContext
+ prop_instance.instance_variable_set "@self_was", self
+ prop_instance
end
CODE
end
@@ -68,7 +88,6 @@ def properties
def properties_tag
self.class.properties_tag
end
-
end
end
end
diff --git a/lib/openxml/docx/style.rb b/lib/openxml/docx/style.rb
index 85cd393e..11fe349f 100644
--- a/lib/openxml/docx/style.rb
+++ b/lib/openxml/docx/style.rb
@@ -1,10 +1,12 @@
+require "openxml/docx/chainable_nested_context"
+
module OpenXml
module Docx
class Style
include AttributeBuilder
include PropertyBuilder
- attr_reader :paragraph, :character, :table, :type
+ attr_reader :type
attribute :custom, expects: :boolean, displays_as: :customStyle, namespace: :w
attribute :default, expects: :boolean, namespace: :w
@@ -35,6 +37,21 @@ def type=(value)
send "install_#{value}_properties"
end
+ def paragraph
+ @paragraph.instance_variable_set "@self_was", self
+ @paragraph
+ end
+
+ def character
+ @character.instance_variable_set "@self_was", self
+ @character
+ end
+
+ def table
+ @table.instance_variable_set "@self_was", self
+ @table
+ end
+
def tag
:style
end
@@ -69,18 +86,22 @@ def install_paragraph_properties
@table = nil
@character = OpenXml::Docx::Elements::Run.new
@paragraph = OpenXml::Docx::Elements::Paragraph.new
+ @character.extend OpenXml::Docx::ChainableNestedContext
+ @paragraph.extend OpenXml::Docx::ChainableNestedContext
end
def install_character_properties
@paragraph = nil
@table = nil
@character = OpenXml::Docx::Elements::Run.new
+ @character.extend OpenXml::Docx::ChainableNestedContext
end
def install_table_properties
@character = nil
@paragraph = nil
@table = OpenXml::Docx::Elements::Table.new
+ @table.extend OpenXml::Docx::ChainableNestedContext
end
def property_xml(xml)
diff --git a/lib/openxml/drawingml/elements/graphic_data.rb b/lib/openxml/drawingml/elements/graphic_data.rb
index 5250cb54..3eaf9be8 100644
--- a/lib/openxml/drawingml/elements/graphic_data.rb
+++ b/lib/openxml/drawingml/elements/graphic_data.rb
@@ -6,6 +6,11 @@ class GraphicData < OpenXml::Docx::Elements::Container
attribute :uri, expects: :string
+ def self.data_types
+ { picture: "http://schemas.openxmlformats.org/drawingml/2006/picture",
+ wordprocessing_shape: "http://schemas.microsoft.com/office/word/2010/wordprocessingShape" }
+ end
+
end
end
end
diff --git a/lib/openxml/drawingml/elements/picture.rb b/lib/openxml/drawingml/elements/picture.rb
index f8223ba0..55ab0760 100644
--- a/lib/openxml/drawingml/elements/picture.rb
+++ b/lib/openxml/drawingml/elements/picture.rb
@@ -1,9 +1,85 @@
+require "openxml/drawingml/elements/non_visual_picture_properties"
+require "openxml/drawingml/elements/non_visual_drawing_properties"
+require "openxml/drawingml/elements/non_visual_picture_drawing_properties"
+require "openxml/drawingml/elements/picture_locks"
+require "openxml/drawingml/elements/blip_fill"
+require "openxml/drawingml/elements/blip"
+require "openxml/drawingml/elements/stretch"
+require "openxml/drawingml/elements/fill_rectangle"
+require "openxml/drawingml/elements/source_rectangle"
+require "openxml/drawingml/elements/picture_shape_properties"
+require "openxml/drawingml/elements/transform_effect"
+require "openxml/drawingml/elements/offset"
+require "openxml/drawingml/elements/extents"
+require "openxml/drawingml/elements/preset_geometry"
+require "openxml/drawingml/elements/adjust_values_list"
+require "openxml/drawingml/elements/no_fill"
+require "openxml/drawingml/elements/outline"
+
module OpenXml
module DrawingML
module Elements
class Picture < OpenXml::Docx::Elements::Container
namespace :pic
tag :pic
+
+ def self.next_index
+ @index = (@index || 0) + 1
+ end
+
+ private
+
+ def build_scaffold
+ image_rid = options[:image_rid]
+ return unless image_rid
+
+ picture_desc = options.fetch(:name, "Image")
+ picture_name = options.fetch(:filename, picture_desc)
+ width = options.fetch(:width, 0)
+ height = options.fetch(:height, 0)
+
+ # More non-visual Properties
+ picture_index = self.class.next_index
+ push(OpenXml::DrawingML::Elements::NonVisualPictureProperties.new do
+ push OpenXml::DrawingML::Elements::NonVisualDrawingProperties.new
+ .id(picture_index)
+ .picture_name(picture_name)
+ .description(picture_desc)
+ push OpenXml::DrawingML::Elements::NonVisualPictureDrawingProperties.new
+ .push(OpenXml::DrawingML::Elements::PictureLocks.new
+ .disallow_aspect_ratio_changes(true)
+ .disallow_arrowhead_changes(true))
+ end)
+
+ # The actual image
+ push(OpenXml::DrawingML::Elements::BlipFill.new do
+ push OpenXml::DrawingML::Elements::Blip.new
+ .embed(image_rid)
+ push OpenXml::DrawingML::Elements::Stretch.new
+ .push(OpenXml::DrawingML::Elements::FillRectangle.new)
+ push OpenXml::DrawingML::Elements::SourceRectangle.new
+ end)
+
+ # Shape Properties
+ push(OpenXml::DrawingML::Elements::PictureShapeProperties.new do
+ push(OpenXml::DrawingML::Elements::TransformEffect.new do
+ push OpenXml::DrawingML::Elements::Offset.new
+ .x(0)
+ .y(0)
+ push OpenXml::DrawingML::Elements::Extents.new
+ .extent_length(width)
+ .extent_width(height)
+ end)
+ push(OpenXml::DrawingML::Elements::PresetGeometry.new do
+ preset(:rect)
+ push OpenXml::DrawingML::Elements::AdjustValuesList.new
+ end)
+ push OpenXml::DrawingML::Elements::NoFill.new
+ push OpenXml::DrawingML::Elements::Outline.new
+ .push(OpenXml::DrawingML::Elements::NoFill.new)
+ end)
+ end
+
end
end
end
diff --git a/spec/elements/bidi_embed_spec.rb b/spec/elements/bidi_embed_spec.rb
index f30a2c9b..4315a733 100644
--- a/spec/elements/bidi_embed_spec.rb
+++ b/spec/elements/bidi_embed_spec.rb
@@ -30,6 +30,6 @@
instance << run
end
- it_should_output "\n \n Smucker's Preserves\n \n ", assign: false
+ it_should_output "\n \n Smucker's Preserves\n \n ", assign: false
end
end
diff --git a/spec/elements/bidi_override_spec.rb b/spec/elements/bidi_override_spec.rb
index 1b329eba..eba3a392 100644
--- a/spec/elements/bidi_override_spec.rb
+++ b/spec/elements/bidi_override_spec.rb
@@ -30,6 +30,6 @@
instance << run
end
- it_should_output "\n \n Smucker's Preserves\n \n ", assign: false
+ it_should_output "\n \n Smucker's Preserves\n \n ", assign: false
end
end
diff --git a/spec/elements/drawing_spec.rb b/spec/elements/drawing_spec.rb
index 5a284e59..9f01d982 100644
--- a/spec/elements/drawing_spec.rb
+++ b/spec/elements/drawing_spec.rb
@@ -8,4 +8,7 @@
with_no_attributes_set do
it_should_output "", assign: false
end
+
+ it_should_scaffold_itself_correctly
+
end
diff --git a/spec/elements/drawingml/picture_spec.rb b/spec/elements/drawingml/picture_spec.rb
index 1621c093..58997a15 100644
--- a/spec/elements/drawingml/picture_spec.rb
+++ b/spec/elements/drawingml/picture_spec.rb
@@ -5,4 +5,6 @@
it_should_use tag: :pic, name: "picture"
+ it_should_scaffold_itself_correctly(image_rid: "rId5")
+
end
diff --git a/spec/elements/ruby_spec.rb b/spec/elements/ruby_spec.rb
index 4a3a863c..8d6bad58 100644
--- a/spec/elements/ruby_spec.rb
+++ b/spec/elements/ruby_spec.rb
@@ -39,7 +39,7 @@
instance.ruby = run
end
- it_should_output "\n \n \n \n \n \n tō\n \n \n \n \n \n \n \n 東̄\n \n \n ", assign: false
+ it_should_output "\n \n \n \n \n \n tō\n \n \n \n \n \n \n \n 東̄\n \n \n ", assign: false
end
context "with base text, ruby text and properties set" do
@@ -60,7 +60,7 @@
instance.alignment = :left
end
- it_should_output "\n \n \n \n \n \n \n \n \n tō\n \n \n \n \n \n \n \n 東̄\n \n \n ", assign: false
+ it_should_output "\n \n \n \n \n \n \n \n \n tō\n \n \n \n \n \n \n \n 東̄\n \n \n ", assign: false
end
end
diff --git a/spec/elements/table_spec.rb b/spec/elements/table_spec.rb
index c30bc163..9cd8545e 100644
--- a/spec/elements/table_spec.rb
+++ b/spec/elements/table_spec.rb
@@ -17,5 +17,6 @@
end
it_should_output_correct_xml
+ it_should_scaffold_itself_correctly
end
diff --git a/spec/elements/word_processing_shapes_shape_spec.rb b/spec/elements/word_processing_shapes_shape_spec.rb
index d1180668..3683d930 100644
--- a/spec/elements/word_processing_shapes_shape_spec.rb
+++ b/spec/elements/word_processing_shapes_shape_spec.rb
@@ -20,4 +20,6 @@
end
end
+ it_should_scaffold_itself_correctly
+
end
diff --git a/spec/properties/footer_reference_spec.rb b/spec/properties/footer_reference_spec.rb
index c9dbb45c..29268db2 100644
--- a/spec/properties/footer_reference_spec.rb
+++ b/spec/properties/footer_reference_spec.rb
@@ -26,7 +26,7 @@
end
with_these_attributes_set(id: "rId5", type: :default) do
- it_should_output "", assign: false
+ it_should_output "", assign: false
end
end
diff --git a/spec/properties/header_reference_spec.rb b/spec/properties/header_reference_spec.rb
index 51487e51..cb790497 100644
--- a/spec/properties/header_reference_spec.rb
+++ b/spec/properties/header_reference_spec.rb
@@ -26,7 +26,7 @@
end
with_these_attributes_set(id: "rId5", type: :default) do
- it_should_output "", assign: false
+ it_should_output "", assign: false
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index e5b2159d..6a924be8 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -13,4 +13,3 @@
require "openxml/docx"
require "openxml/drawingml"
require "openxml/vml"
-require "nokogiri"
diff --git a/spec/support/data/elements/break_element.xml b/spec/support/data/elements/break_element.xml
index 253f3d57..19277cf3 100644
--- a/spec/support/data/elements/break_element.xml
+++ b/spec/support/data/elements/break_element.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/spec/support/data/elements/break_with_attributes_element.xml b/spec/support/data/elements/break_with_attributes_element.xml
index 1ba83f34..a0533b42 100644
--- a/spec/support/data/elements/break_with_attributes_element.xml
+++ b/spec/support/data/elements/break_with_attributes_element.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/spec/support/data/elements/grid_column_element.xml b/spec/support/data/elements/grid_column_element.xml
index c90d92ee..c97f727f 100644
--- a/spec/support/data/elements/grid_column_element.xml
+++ b/spec/support/data/elements/grid_column_element.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/spec/support/data/elements/paragraph_element.xml b/spec/support/data/elements/paragraph_element.xml
index 6b608a96..8eab8ab6 100644
--- a/spec/support/data/elements/paragraph_element.xml
+++ b/spec/support/data/elements/paragraph_element.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/spec/support/data/elements/paragraph_with_runs_element.xml b/spec/support/data/elements/paragraph_with_runs_element.xml
index 5eda86f7..2838704c 100644
--- a/spec/support/data/elements/paragraph_with_runs_element.xml
+++ b/spec/support/data/elements/paragraph_with_runs_element.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/spec/support/data/elements/paragraph_with_section_properties_element.xml b/spec/support/data/elements/paragraph_with_section_properties_element.xml
index e8754608..533a2b44 100644
--- a/spec/support/data/elements/paragraph_with_section_properties_element.xml
+++ b/spec/support/data/elements/paragraph_with_section_properties_element.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/spec/support/data/elements/run_element.xml b/spec/support/data/elements/run_element.xml
index 4244d5a5..d2ad94af 100644
--- a/spec/support/data/elements/run_element.xml
+++ b/spec/support/data/elements/run_element.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/spec/support/data/elements/scaffolded_drawing_element.xml b/spec/support/data/elements/scaffolded_drawing_element.xml
new file mode 100644
index 00000000..91c37311
--- /dev/null
+++ b/spec/support/data/elements/scaffolded_drawing_element.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+
+
+
+
+
+
diff --git a/spec/support/data/elements/scaffolded_picture_element.xml b/spec/support/data/elements/scaffolded_picture_element.xml
new file mode 100644
index 00000000..8ea2ca60
--- /dev/null
+++ b/spec/support/data/elements/scaffolded_picture_element.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spec/support/data/elements/scaffolded_table_element.xml b/spec/support/data/elements/scaffolded_table_element.xml
new file mode 100644
index 00000000..072207eb
--- /dev/null
+++ b/spec/support/data/elements/scaffolded_table_element.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/spec/support/data/elements/scaffolded_word_processing_shapes_shape_element.xml b/spec/support/data/elements/scaffolded_word_processing_shapes_shape_element.xml
new file mode 100644
index 00000000..b5fbaf73
--- /dev/null
+++ b/spec/support/data/elements/scaffolded_word_processing_shapes_shape_element.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spec/support/data/elements/symbol_element.xml b/spec/support/data/elements/symbol_element.xml
index f6ecb506..4347b90b 100644
--- a/spec/support/data/elements/symbol_element.xml
+++ b/spec/support/data/elements/symbol_element.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/spec/support/data/elements/table_cell_element.xml b/spec/support/data/elements/table_cell_element.xml
index 4b74d05c..a68e91ef 100644
--- a/spec/support/data/elements/table_cell_element.xml
+++ b/spec/support/data/elements/table_cell_element.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/spec/support/data/elements/table_element.xml b/spec/support/data/elements/table_element.xml
index 3bdcac72..46d578c7 100644
--- a/spec/support/data/elements/table_element.xml
+++ b/spec/support/data/elements/table_element.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/spec/support/data/elements/table_grid_element.xml b/spec/support/data/elements/table_grid_element.xml
index 99e5e8a6..23490a50 100644
--- a/spec/support/data/elements/table_grid_element.xml
+++ b/spec/support/data/elements/table_grid_element.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/spec/support/data/elements/table_row_element.xml b/spec/support/data/elements/table_row_element.xml
index 19339276..911f0c85 100644
--- a/spec/support/data/elements/table_row_element.xml
+++ b/spec/support/data/elements/table_row_element.xml
@@ -1,4 +1,4 @@
-
+
diff --git a/spec/support/data/elements/text_element.xml b/spec/support/data/elements/text_element.xml
index c926eaef..7f3ca72e 100644
--- a/spec/support/data/elements/text_element.xml
+++ b/spec/support/data/elements/text_element.xml
@@ -1,4 +1,4 @@
-
+
Banana
diff --git a/spec/support/element_test_macros.rb b/spec/support/element_test_macros.rb
index 94b2db11..be6f2175 100644
--- a/spec/support/element_test_macros.rb
+++ b/spec/support/element_test_macros.rb
@@ -5,7 +5,7 @@ def element_xml(part)
end
def xml(obj)
- doc = Nokogiri::XML::Builder.new do |xml|
+ doc = OpenXml::Builder.new do |xml|
xml.root(root_namespaces) {
obj.to_xml(xml)
}
@@ -14,7 +14,7 @@ def xml(obj)
end
def doc_pattern
- /<\?xml\sversion="1.0"\?>\n\n\s+([^\s].+)\n<\/root>/m
+ /<\?xml\sversion="1.0"\sencoding=\"utf-8\"\?>\n\n\s+([^\s].+)\n<\/root>/m
end
def self.included(base)
@@ -55,7 +55,29 @@ def it_should_output_correct_xml(node_xml: nil)
node_xml = node_xml.gsub(/(.)([A-Z])/, '\1_\2').downcase
end
- generated_xml = Nokogiri::XML::Builder.new do |xml|
+ generated_xml = OpenXml::Builder.new do |xml|
+ xml.root("xmlns:w" => "http://wnamespace.org") {
+ instance.to_xml(xml)
+ }
+ end.to_xml
+
+ expect(generated_xml).to eq(element_xml(node_xml) + "\n")
+ end
+ end
+
+ def it_should_scaffold_itself_correctly(options={})
+ it "should correctly scaffold itself and its children" do
+ node_xml = options[:node_xml]
+ if node_xml.nil?
+ node_xml = described_class.to_s.split(/::/).last
+ node_xml = node_xml.gsub(/(.)([A-Z])/, '\1_\2').downcase
+ end
+ node_xml = "scaffolded_#{node_xml}"
+
+ options.merge!({scaffold: true})
+ @instance = described_class.new(options)
+
+ generated_xml = OpenXml::Builder.new do |xml|
xml.root("xmlns:w" => "http://wnamespace.org") {
instance.to_xml(xml)
}
diff --git a/spec/support/property_test_macros.rb b/spec/support/property_test_macros.rb
index 8d56223e..cdc53c33 100644
--- a/spec/support/property_test_macros.rb
+++ b/spec/support/property_test_macros.rb
@@ -1,7 +1,7 @@
module PropertyTestMacros
def xml(obj)
- doc = Nokogiri::XML::Builder.new do |xml|
+ doc = OpenXml::Builder.new do |xml|
xml.root("xmlns:w" => "http://wnamespace.org") {
obj.to_xml(xml)
}
@@ -10,7 +10,7 @@ def xml(obj)
end
def doc_pattern
- /<\?xml\sversion="1.0"\?>\n\n\s+([^\s].+)\n<\/root>/m
+ /<\?xml\sversion="1.0"\sencoding=\"utf-8\"\?>\n\n\s+([^\s].+)\n<\/root>/m
end
def self.included(base)
diff --git a/spec/support/style_test_macros.rb b/spec/support/style_test_macros.rb
index 7a5e4377..88c3418d 100644
--- a/spec/support/style_test_macros.rb
+++ b/spec/support/style_test_macros.rb
@@ -18,7 +18,7 @@ def it_should_output_correct_xml(style_xml: nil)
style_xml = style_xml.gsub(/(.)([A-Z])/, '\1_\2').downcase
end
- generated_xml = Nokogiri::XML::Builder.new do |xml|
+ generated_xml = OpenXml::Builder.new do |xml|
xml.styleFoo("xmlns:w" => "http://wnamespace.com") {
style.build_xml(xml)
}
diff --git a/spec/support/value_property_test_macros.rb b/spec/support/value_property_test_macros.rb
index ced03760..4cfc1b24 100644
--- a/spec/support/value_property_test_macros.rb
+++ b/spec/support/value_property_test_macros.rb
@@ -1,7 +1,7 @@
module ValuePropertyTestMacros
def xml(obj)
- doc = Nokogiri::XML::Builder.new do |xml|
+ doc = OpenXml::Builder.new do |xml|
xml.root("xmlns:w" => "http://wnamespace.org") {
obj.to_xml(xml)
}
@@ -10,7 +10,7 @@ def xml(obj)
end
def doc_pattern
- /<\?xml\sversion="1.0"\?>\n\n\s+([^\s].+)\n<\/root>/m
+ /<\?xml\sversion="1.0"\sencoding=\"utf-8\"\?>\n\n\s+([^\s].+)\n<\/root>/m
end
def self.included(base)