Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DSL #59

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open

DSL #59

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
171 changes: 171 additions & 0 deletions examples/dsl
Original file line number Diff line number Diff line change
@@ -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

11 changes: 8 additions & 3 deletions lib/openxml/docx/attribute_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -204,18 +204,23 @@ 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?
matches?(value, matches) unless matches.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
Expand Down
9 changes: 9 additions & 0 deletions lib/openxml/docx/chainable_nested_context.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module OpenXml
module Docx
module ChainableNestedContext
def end_chain
@self_was
end
end
end
end
17 changes: 16 additions & 1 deletion lib/openxml/docx/elements/container.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
116 changes: 116 additions & 0 deletions lib/openxml/docx/elements/drawing.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Loading