Class: Uniword::Builder::DocumentBuilder

Inherits:
BaseBuilder show all
Defined in:
lib/uniword/builder/document_builder.rb

Overview

Builds and configures DocumentRoot objects.

Top-level builder for creating Word documents.

Examples:

Create a new document

doc = DocumentBuilder.new
doc.paragraph { |p| p << 'Hello World' }
doc.heading('Title', level: 1)
doc.save('output.docx')

Load and modify a document

doc = DocumentBuilder.from_file('template.docx')
doc.paragraph { |p| p << 'New content' }
doc.save('modified.docx')

Complete document

doc = DocumentBuilder.new
doc.title('Report').author('Author')
doc.theme('atlas')
doc.toc
doc.heading('Introduction', level: 1)
doc.paragraph { |p| p << 'Content...' }
doc.bullet_list { |l| l.item('First'); l.item('Second') }
doc.page_break
doc.footer { |f| f << Builder.page_number_field }
doc.save('report.docx')

Instance Attribute Summary collapse

Attributes inherited from BaseBuilder

#model

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from BaseBuilder

#build, from_model

Constructor Details

#initialize(model = nil, allocator: nil) ⇒ DocumentBuilder

Returns a new instance of DocumentBuilder.



38
39
40
41
42
43
# File 'lib/uniword/builder/document_builder.rb', line 38

def initialize(model = nil, allocator: nil)
  super(model)
  @allocator = allocator
  @model.allocator = allocator if allocator
  @footnote_builder = FootnoteBuilder.new(self, allocator: @allocator)
end

Instance Attribute Details

#allocatorObject (readonly)

Returns the value of attribute allocator.



32
33
34
# File 'lib/uniword/builder/document_builder.rb', line 32

def allocator
  @allocator
end

Class Method Details

.default_model_classObject



34
35
36
# File 'lib/uniword/builder/document_builder.rb', line 34

def self.default_model_class
  Wordprocessingml::DocumentRoot
end

.from_file(path) ⇒ DocumentBuilder

Load a document from file for manipulation

Parameters:

  • path (String)

    Path to .docx file

Returns:



49
50
51
# File 'lib/uniword/builder/document_builder.rb', line 49

def self.from_file(path)
  new(Uniword.load(path))
end

.from_template(path) ⇒ DocumentBuilder

Load a DOCX as a template, reset its body to a clean state, and seed the IdAllocator from the template's existing relationships.

Use this when you want the template's OOXML scaffolding (settings, fonts, styles, theme, content_types, namespace declarations) but will replace the document body with new content. This is the recommended path to producing Word-valid output: starting from a known-good DOCX preserves mc:Ignorable prefixes, rsid values, and other structural guarantees that Word expects.

Resets performed:

  • Body content cleared (paragraphs, tables, SDTs, bookmarks, element_order, section_properties)
  • User footnotes/endnotes cleared (separator/continuation kept)
  • custom_properties and custom_xml_items cleared
  • Stale image and customXml relationships removed
  • IdAllocator seeded from document_rels and package_rels

Parameters:

  • path (String)

    Path to .docx template file

Returns:

Raises:

  • (ArgumentError)

    if path does not exist



74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/uniword/builder/document_builder.rb', line 74

def self.from_template(path)
  raise ArgumentError, "Template not found: #{path}" unless File.exist?(path)

  root = Uniword.load(path)
  reset_template_body(root)
  clear_user_notes(root)
  root.custom_properties = nil
  root.custom_xml_items = nil
  remove_stale_relationships(root)
  seed_allocator(root)
  new(root, allocator: root.allocator)
end

Instance Method Details

#<<(element) ⇒ self

Append a top-level element (paragraph or table)

Parameters:

Returns:

  • (self)


141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/uniword/builder/document_builder.rb', line 141

def <<(element)
  case element
  when Wordprocessingml::Paragraph
    @model.body.paragraphs << element
    @model.body.append_to_element_order("p")
  when Wordprocessingml::Table
    @model.body.tables << element
    @model.body.append_to_element_order("tbl")
  when ParagraphBuilder
    @model.body.paragraphs << element.build
    @model.body.append_to_element_order("p")
  when TableBuilder
    @model.body.tables << element.build
    @model.body.append_to_element_order("tbl")
  else
    raise ArgumentError, "Cannot add #{element.class} to document"
  end
  self
end

#apply_styleset(name, strategy: :keep_existing) ⇒ self

Apply a bundled styleset to the document

Parameters:

  • name (String)

    Styleset name (e.g., 'formal', 'modern', 'elegant')

  • strategy (Symbol) (defaults to: :keep_existing)

    Conflict resolution (:keep_existing, :replace, :rename)

Returns:

  • (self)


388
389
390
391
# File 'lib/uniword/builder/document_builder.rb', line 388

def apply_styleset(name, strategy: :keep_existing)
  @model.apply_styleset(name, strategy: strategy)
  self
end

#author(value) ⇒ Object



353
354
355
356
# File 'lib/uniword/builder/document_builder.rb', line 353

def author(value)
  @model.core_properties.creator = value
  self
end

#bibliography(style: "APA") {|BibliographyBuilder| ... } ⇒ BibliographyBuilder

Create and configure bibliography sources

Parameters:

  • style (String) (defaults to: "APA")

    Citation style (default 'APA')

Yields:

Returns:



521
522
523
524
525
526
# File 'lib/uniword/builder/document_builder.rb', line 521

def bibliography(style: "APA", &block)
  bib = BibliographyBuilder.new(style: style)
  block.call(bib) if block_given?
  bib.attach(self)
  bib
end

#bibliography_placeholderself

Insert a bibliography placeholder (SDT content control)

Returns:

  • (self)


531
532
533
534
535
536
537
# File 'lib/uniword/builder/document_builder.rb', line 531

def bibliography_placeholder
  sdt = SdtBuilder.bibliography.build
  para = Wordprocessingml::Paragraph.new
  para.sdts << sdt
  self << para
  self
end

#bookmark(name) {|ParagraphBuilder| ... } ⇒ ParagraphBuilder

Create a bookmark wrapping the next content

Parameters:

  • name (String)

    Bookmark name

Yields:

Returns:



290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/uniword/builder/document_builder.rb', line 290

def bookmark(name, &block)
  id = @allocator ? @allocator.alloc_bookmark_id : begin
    @bookmark_counter ||= 0
    @bookmark_counter += 1
    @bookmark_counter.to_s
  end

  para = ParagraphBuilder.new(allocator: @allocator)
  para << Wordprocessingml::BookmarkStart.new(id: id, name: name)
  block.call(para) if block_given?
  para << Wordprocessingml::BookmarkEnd.new(id: id)
  self << para
  para
end

#bullet_list {|ListBuilder| ... } ⇒ ListBuilder

Shorthand: create a bullet list

Yields:

Returns:



281
282
283
# File 'lib/uniword/builder/document_builder.rb', line 281

def bullet_list(&)
  list(type: :bullet, &)
end

#chart(type: :bar, width: nil, height: nil) {|ChartBuilder| ... } ⇒ ChartBuilder

Insert a chart into the document

Parameters:

  • type (Symbol) (defaults to: :bar)

    Chart type (:bar, :line, :pie, default :bar)

  • width (Integer) (defaults to: nil)

    Width in EMU (default 5486400 ≈ 6 inches)

  • height (Integer) (defaults to: nil)

    Height in EMU (default 3200400 ≈ 3.5 inches)

Yields:

Returns:



546
547
548
549
550
551
552
553
554
555
556
557
558
# File 'lib/uniword/builder/document_builder.rb', line 546

def chart(type: :bar, width: nil, height: nil, &block)
  cb = ChartBuilder.new(chart_type: type)
  cb.dimensions(width: width, height: height) if width || height
  block.call(cb) if block_given?

  drawing = cb.build_drawing(self)
  run = Wordprocessingml::Run.new
  run.drawings << drawing
  para = Wordprocessingml::Paragraph.new
  para.runs << run
  self << para
  cb
end

#comment(author:, text: nil, initials: nil) {|CommentBuilder| ... } ⇒ Comment

Create a comment and store it in the document's comments collection.

Parameters:

  • author (String)

    Comment author name

  • text (String, nil) (defaults to: nil)

    Comment text

  • initials (String, nil) (defaults to: nil)

    Author initials

Yields:

Returns:

  • (Comment)

    The created Comment model



482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
# File 'lib/uniword/builder/document_builder.rb', line 482

def comment(author:, text: nil, initials: nil, &block)
  comment_id = @allocator ? @allocator.alloc_comment_id : begin
    @comment_counter ||= 0
    @comment_counter += 1
    @comment_counter.to_s
  end
  cb = CommentBuilder.new(
    author: author,
    comment_id: comment_id,
    initials: initials
  )
  cb << text if text
  block.call(cb) if block_given?
  comment_obj = cb.build
  @model.comments ||= []
  @model.comments << comment_obj
  comment_obj
end

#content_control(tag: nil, alias_name: nil, placeholder_text: nil) ⇒ SdtBuilder

Create a text content control paragraph

Parameters:

  • tag (String, nil) (defaults to: nil)

    Developer tag

  • alias_name (String, nil) (defaults to: nil)

    Display name

  • placeholder_text (String, nil) (defaults to: nil)

    Placeholder text

Returns:



507
508
509
510
511
512
513
514
# File 'lib/uniword/builder/document_builder.rb', line 507

def content_control(tag: nil, alias_name: nil, placeholder_text: nil)
  sdt = SdtBuilder.text(
    tag: tag, alias_name: alias_name,
    placeholder_text: placeholder_text
  )
  @model.body.paragraphs.last&.sdts&.<<(sdt.build)
  sdt
end

#created(value) ⇒ Object



373
374
375
376
# File 'lib/uniword/builder/document_builder.rb', line 373

def created(value)
  @model.core_properties.created = value
  self
end

#date_field(format: "M/d/yyyy") ⇒ self

Insert a date paragraph

Parameters:

  • format (String) (defaults to: "M/d/yyyy")

    Date format (default 'M/d/yyyy')

Returns:

  • (self)


580
581
582
583
# File 'lib/uniword/builder/document_builder.rb', line 580

def date_field(format: "M/d/yyyy")
  self << Builder.date_field(format: format)
  self
end

#define_style(name, base_on: "Normal") {|StyleBuilder| ... } ⇒ StyleBuilder

Define a paragraph style

Parameters:

  • name (String)

    Style name

  • base_on (String) (defaults to: "Normal")

    Base style (default 'Normal')

Yields:

Returns:



341
342
343
344
345
346
# File 'lib/uniword/builder/document_builder.rb', line 341

def define_style(name, base_on: "Normal", &block)
  style = StyleBuilder.new(name, base_on: base_on)
  block.call(style) if block_given?
  @model.styles_configuration.add_style(style.build)
  style
end

#description(value) ⇒ Object



358
359
360
361
# File 'lib/uniword/builder/document_builder.rb', line 358

def description(value)
  @model.core_properties.description = value
  self
end

#endnote(text = nil) {|ParagraphBuilder| ... } ⇒ Wordprocessingml::Run

Create an endnote and return a Run with an endnoteReference.

Parameters:

  • text (String) (defaults to: nil)

    Endnote text

Yields:

Returns:



319
320
321
# File 'lib/uniword/builder/document_builder.rb', line 319

def endnote(text = nil, &)
  @footnote_builder.endnote(text, &)
end

#floating_image(path, width: nil, height: nil, alt_text: nil, align: nil, vertical_align: nil, wrap: :square, behind_text: false) ⇒ self

Insert a floating image paragraph

Parameters:

  • path (String)

    Path to image file

  • width (Integer, nil) (defaults to: nil)

    Width in EMU

  • height (Integer, nil) (defaults to: nil)

    Height in EMU

  • alt_text (String, nil) (defaults to: nil)

    Alternative text

  • align (Symbol, nil) (defaults to: nil)

    Horizontal alignment (:left, :center, :right)

  • vertical_align (Symbol, nil) (defaults to: nil)

    Vertical alignment (:top, :middle, :bottom)

  • wrap (Symbol) (defaults to: :square)

    Text wrapping (:square, :none, :top_and_bottom)

  • behind_text (Boolean) (defaults to: false)

    Place image behind text

Returns:

  • (self)


436
437
438
439
440
441
442
443
444
445
446
# File 'lib/uniword/builder/document_builder.rb', line 436

def floating_image(path, width: nil, height: nil, alt_text: nil,
                   align: nil, vertical_align: nil, wrap: :square,
                   behind_text: false)
  para = Wordprocessingml::Paragraph.new
  para.runs << ImageBuilder.create_floating_run(
    self, path, width: width, height: height, alt_text: alt_text,
                align: align, wrap: wrap, behind_text: behind_text
  )
  self << para
  self
end

Configure a footer

Parameters:

  • type (String) (defaults to: "default")

    Footer type ('default', 'first', 'even')

Yields:

Returns:



239
240
241
242
243
244
# File 'lib/uniword/builder/document_builder.rb', line 239

def footer(type: "default", &block)
  hf = HeaderFooterBuilder.new(:footer, type: type, allocator: @allocator)
  block.call(hf) if block_given?
  (@model.footers ||= {})[type] = hf.build
  hf
end

#footnote(text = nil) {|ParagraphBuilder| ... } ⇒ Wordprocessingml::Run

Create a footnote and return a Run with a footnoteReference.

Parameters:

  • text (String) (defaults to: nil)

    Footnote text

Yields:

Returns:



310
311
312
# File 'lib/uniword/builder/document_builder.rb', line 310

def footnote(text = nil, &)
  @footnote_builder.footnote(text, &)
end

#header(type: "default") {|HeaderFooterBuilder| ... } ⇒ HeaderFooterBuilder

Configure a header

Parameters:

  • type (String) (defaults to: "default")

    Header type ('default', 'first', 'even')

Yields:

Returns:



227
228
229
230
231
232
# File 'lib/uniword/builder/document_builder.rb', line 227

def header(type: "default", &block)
  hf = HeaderFooterBuilder.new(:header, type: type, allocator: @allocator)
  block.call(hf) if block_given?
  (@model.headers ||= {})[type] = hf.build
  hf
end

#heading(text, level: 1) {|ParagraphBuilder| ... } ⇒ ParagraphBuilder

Create and add a heading paragraph

Parameters:

  • text (String)

    Heading text

  • level (Integer) (defaults to: 1)

    Heading level (1-9, default 1)

Yields:

Returns:



180
181
182
183
184
185
186
# File 'lib/uniword/builder/document_builder.rb', line 180

def heading(text, level: 1)
  para = ParagraphBuilder.new(allocator: @allocator)
  para.style = "Heading#{level}"
  para << text
  self << para
  para
end

#horizontal_rule(style: "single", color: "auto", size: 6) ⇒ self

Insert a horizontal rule (paragraph with bottom border)

Parameters:

  • style (String) (defaults to: "single")

    Border style (default 'single')

  • color (String) (defaults to: "auto")

    Border color (default 'auto')

  • size (Integer) (defaults to: 6)

    Border size in eighths of a point (default 6)

Returns:

  • (self)


399
400
401
402
403
404
405
406
407
# File 'lib/uniword/builder/document_builder.rb', line 399

def horizontal_rule(style: "single", color: "auto", size: 6)
  para = ParagraphBuilder.new(allocator: @allocator)
  para.borders(
    bottom: { style: style, color: color, size: size }
  )
  para.spacing(after: 0)
  self << para
  self
end

#image(path, width: nil, height: nil, alt_text: nil) ⇒ self

Insert an inline image paragraph

Parameters:

  • path (String)

    Path to image file

  • width (Integer, nil) (defaults to: nil)

    Width in EMU (914400 = 1 inch)

  • height (Integer, nil) (defaults to: nil)

    Height in EMU

  • alt_text (String, nil) (defaults to: nil)

    Alternative text

Returns:

  • (self)


416
417
418
419
420
421
422
423
# File 'lib/uniword/builder/document_builder.rb', line 416

def image(path, width: nil, height: nil, alt_text: nil)
  para = Wordprocessingml::Paragraph.new
  para.runs << ImageBuilder.create_run(
    self, path, width: width, height: height, alt_text: alt_text
  )
  self << para
  self
end

#keywords(value) ⇒ Object



368
369
370
371
# File 'lib/uniword/builder/document_builder.rb', line 368

def keywords(value)
  @model.core_properties.keywords = value
  self
end

#list(type: :bullet) {|ListBuilder| ... } ⇒ ListBuilder

Create a list (bulleted or numbered)

Parameters:

  • type (Symbol) (defaults to: :bullet)

    List type (:bullet, :decimal, :roman, :letter)

Yields:

Returns:



263
264
265
266
267
# File 'lib/uniword/builder/document_builder.rb', line 263

def list(type: :bullet, &block)
  lb = ListBuilder.new(self, type: type)
  block.call(lb) if block_given?
  lb
end

#modified(value) ⇒ Object



378
379
380
381
# File 'lib/uniword/builder/document_builder.rb', line 378

def modified(value)
  @model.core_properties.modified = value
  self
end

#numbered_list {|ListBuilder| ... } ⇒ ListBuilder

Shorthand: create a numbered list

Yields:

Returns:



273
274
275
# File 'lib/uniword/builder/document_builder.rb', line 273

def numbered_list(&)
  list(type: :decimal, &)
end

#page_breakself

Insert a page break

Returns:

  • (self)


191
192
193
194
195
196
# File 'lib/uniword/builder/document_builder.rb', line 191

def page_break
  self << Wordprocessingml::Paragraph.new(
    runs: [Builder.page_break]
  )
  self
end

#page_numberself

Insert a page number paragraph

Returns:

  • (self)


563
564
565
566
# File 'lib/uniword/builder/document_builder.rb', line 563

def page_number
  self << Builder.page_number_field
  self
end

#paragraph(text = nil) {|ParagraphBuilder| ... } ⇒ ParagraphBuilder

Create and add a paragraph to the document

Parameters:

  • text (String, nil) (defaults to: nil)

    Optional text content

Yields:

Returns:



166
167
168
169
170
171
172
# File 'lib/uniword/builder/document_builder.rb', line 166

def paragraph(text = nil, &block)
  para = ParagraphBuilder.new(allocator: @allocator)
  para << text if text
  block.call(para) if block_given?
  self << para
  para
end

#save(path) ⇒ Object

Save document to file

Parameters:

  • path (String)

    Output file path



597
598
599
# File 'lib/uniword/builder/document_builder.rb', line 597

def save(path)
  @model.to_file(path)
end

#section(type: "nextPage") {|SectionBuilder| ... } ⇒ SectionBuilder

Configure section properties

Parameters:

  • type (String) (defaults to: "nextPage")

    Section break type ('nextPage', 'continuous', 'evenPage', 'oddPage')

Yields:

Returns:



214
215
216
217
218
219
220
# File 'lib/uniword/builder/document_builder.rb', line 214

def section(type: "nextPage", &block)
  sec = SectionBuilder.new
  sec.type = type
  block.call(sec) if block_given?
  @model.body.section_properties ||= sec.build
  sec
end

#subject(value) ⇒ Object



363
364
365
366
# File 'lib/uniword/builder/document_builder.rb', line 363

def subject(value)
  @model.core_properties.subject = value
  self
end

#table {|TableBuilder| ... } ⇒ TableBuilder

Create and add a table to the document

Yields:

Returns:



202
203
204
205
206
207
# File 'lib/uniword/builder/document_builder.rb', line 202

def table(&block)
  tbl = TableBuilder.new
  block.call(tbl) if block_given?
  self << tbl
  tbl
end

#theme(name = nil) {|ThemeBuilder| ... } ⇒ ThemeBuilder

Apply or configure a document theme

Parameters:

  • name (String, nil) (defaults to: nil)

    Theme name to apply

Yields:

Returns:



328
329
330
331
332
333
# File 'lib/uniword/builder/document_builder.rb', line 328

def theme(name = nil, &block)
  tb = ThemeBuilder.new(self)
  tb.apply(name) if name
  block.call(tb) if block_given?
  tb
end

#time_field(format: "h:mm:ss am/pm") ⇒ self

Insert a time paragraph

Parameters:

  • format (String) (defaults to: "h:mm:ss am/pm")

    Time format (default 'h:mm:ss am/pm')

Returns:

  • (self)


589
590
591
592
# File 'lib/uniword/builder/document_builder.rb', line 589

def time_field(format: "h:mm:ss am/pm")
  self << Builder.time_field(format: format)
  self
end

#title(value) ⇒ Object



348
349
350
351
# File 'lib/uniword/builder/document_builder.rb', line 348

def title(value)
  @model.core_properties.title = value
  self
end

#toc(title: "Table of Contents", styles: nil) ⇒ self

Insert a Table of Contents

Parameters:

  • title (String) (defaults to: "Table of Contents")

    TOC title (default 'Table of Contents')

  • styles (Array<String>, nil) (defaults to: nil)

    Heading styles to include

Returns:

  • (self)


251
252
253
254
255
256
# File 'lib/uniword/builder/document_builder.rb', line 251

def toc(title: "Table of Contents", styles: nil)
  TocBuilder.build(title: title, styles: styles).each do |para|
    self << para
  end
  self
end

#total_pagesself

Insert a total pages paragraph

Returns:

  • (self)


571
572
573
574
# File 'lib/uniword/builder/document_builder.rb', line 571

def total_pages
  self << Builder.total_pages_field
  self
end

#watermark(text, font: "Calibri", size: 60, color: nil, opacity: "0.3", angle: -45)) ⇒ self

Add a watermark to the document header

Parameters:

  • text (String, nil)

    Watermark text (nil to clear)

  • font (String) (defaults to: "Calibri")

    Font name (default 'Calibri')

  • size (Integer) (defaults to: 60)

    Font size in points (default 60)

  • color (String) (defaults to: nil)

    Fill color hex (default 'D0D0D0')

  • opacity (String) (defaults to: "0.3")

    Opacity '0.0' to '1.0' (default '0.3')

  • angle (Integer) (defaults to: -45))

    Rotation angle in degrees (default -45)

Returns:

  • (self)


457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
# File 'lib/uniword/builder/document_builder.rb', line 457

def watermark(text, font: "Calibri", size: 60, color: nil,
              opacity: "0.3", angle: -45)
  if text.nil?
    (@model.headers ||= {}).delete("default")
    return self
  end

  para = WatermarkBuilder.build_paragraph(
    text, font: font, size: size, color: color,
          opacity: opacity, angle: angle
  )

  header = Wordprocessingml::Header.new
  header.paragraphs << para
  (@model.headers ||= {})["default"] = header
  self
end