Class: Asciidoctor::Document

Inherits:
AbstractBlock show all
Defined in:
lib/asciidoctor/document.rb

Overview

can take the process to completion by calling the #convert method.

Defined Under Namespace

Classes: AttributeEntry, Author, Footnote, ImageReference, Title

Constant Summary

Constants included from Substitutors

Substitutors::CAN, Substitutors::CGI, Substitutors::DEL, Substitutors::ESC_R_SB, Substitutors::HighlightedPassSlotRx, Substitutors::PASS_END, Substitutors::PASS_START, Substitutors::PLUS, Substitutors::PassSlotRx, Substitutors::QuotedTextSniffRx, Substitutors::RS, Substitutors::R_SB, Substitutors::SUB_GROUPS, Substitutors::SUB_HINTS, Substitutors::SUB_OPTIONS, Substitutors::SpecialCharsRx, Substitutors::SpecialCharsTr

Instance Attribute Summary collapse

Attributes inherited from AbstractBlock

#blocks, #caption, #content_model, #level, #numeral, #source_location, #style, #subs

Attributes inherited from AbstractNode

#attributes, #context, #document, #id, #node_name, #parent

Instance Method Summary collapse

Methods inherited from AbstractBlock

#alt, #assign_caption, #block?, #blocks?, #captioned_title, #context=, #file, #find_by, #inline?, #lineno, #list_marker_keyword, #next_adjacent_block, #number, #remove_sub, #sections, #sections?, #sub?, #title?

Methods inherited from AbstractNode

#add_role, #attr, #attr?, #block?, #enabled_options, #generate_data_uri, #generate_data_uri_from_uri, #has_role?, #icon_uri, #image_uri, #inline?, #is_uri?, #media_uri, #normalize_asset_path, #normalize_system_path, #normalize_web_path, #option?, #read_asset, #read_contents, #reftext, #reftext?, #remove_attr, #remove_role, #role, #role?, #roles, #set_attr, #set_option, #update_attributes

Methods included from Substitutors

#apply_header_subs, #apply_normal_subs, #apply_reftext_subs, #apply_subs, #expand_subs, #extract_passthroughs, #highlight_source, #resolve_block_subs, #resolve_lines_to_highlight, #resolve_pass_subs, #resolve_subs, #restore_passthroughs, #sub_attributes, #sub_callouts, #sub_macros, #sub_post_replacements, #sub_quotes, #sub_replacements, #sub_source, #sub_specialchars

Methods included from Logging

#logger, #message_with_context

Constructor Details

#initialize(data = nil, options = {}) ⇒ Document

Initialize a Asciidoctor::Document object.

Examples:

data = File.read filename
doc = Asciidoctor::Document.new data
puts doc.convert

Parameters:

  • data (defaults to: nil)

    The AsciiDoc source data as a String or String Array. (default: nil)

  • options (defaults to: {})

    A Hash of options to control processing (e.g., safe mode value (:safe), backend (:backend), standalone enclosure (:standalone), custom attributes (:attributes)). (default: {})

  • Duplication

    of the options Hash is handled in the enclosing API.



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
# File 'lib/asciidoctor/document.rb', line 253

def initialize data = nil, options = {}
  super self, :document

  if (parent_doc = options.delete :parent)
    @parent_document = parent_doc
    options[:base_dir] ||= parent_doc.base_dir
    options[:catalog_assets] = true if parent_doc.options[:catalog_assets]
    @catalog = parent_doc.catalog.merge footnotes: []
    # QUESTION should we support setting attribute in parent document from nested document?
    # NOTE we must dup or else all the assignments to the overrides clobbers the real attributes
    @attribute_overrides = attr_overrides = parent_doc.attributes.merge
    parent_doctype = attr_overrides.delete 'doctype'
    attr_overrides.delete 'compat-mode'
    attr_overrides.delete 'toc'
    attr_overrides.delete 'toc-placement'
    attr_overrides.delete 'toc-position'
    @safe = parent_doc.safe
    @attributes['compat-mode'] = '' if (@compat_mode = parent_doc.compat_mode)
    @outfilesuffix = parent_doc.outfilesuffix
    @sourcemap = parent_doc.sourcemap
    @timings = nil
    @path_resolver = parent_doc.path_resolver
    @converter = parent_doc.converter
    initialize_extensions = nil
    @extensions = parent_doc.extensions
    @syntax_highlighter = parent_doc.syntax_highlighter
  else
    @parent_document = nil
    @catalog = {
      ids: {}, # deprecated; kept for backwards compatibility with converters
      refs: {},
      footnotes: [],
      links: [],
      images: [],
      #indexterms: [],
      callouts: Callouts.new,
      includes: {},
    }
    # copy attributes map and normalize keys
    # attribute overrides are attributes that can only be set from the commandline
    # a direct assignment effectively makes the attribute a constant
    # a nil value or name with leading or trailing ! will result in the attribute being unassigned
    @attribute_overrides = attr_overrides = {}
    (options[:attributes] || {}).each do |key, val|
      if key.end_with? '@'
        if key.start_with? '!'
          key, val = (key.slice 1, key.length - 2), false
        elsif key.end_with? '!@'
          key, val = (key.slice 0, key.length - 2), false
        else
          key, val = key.chop, %(#{val}@)
        end
      elsif key.start_with? '!'
        key, val = (key.slice 1, key.length), val == '@' ? false : nil
      elsif key.end_with? '!'
        key, val = key.chop, val == '@' ? false : nil
      end
      attr_overrides[key.downcase] = val
    end
    if (to_file = options[:to_file])
      attr_overrides['outfilesuffix'] = Helpers.extname to_file
    end
    # safely resolve the safe mode from const, int or string
    if !(safe_mode = options[:safe])
      @safe = SafeMode::SECURE
    elsif ::Integer === safe_mode
      # be permissive in case API user wants to define new levels
      @safe = safe_mode
    else
      @safe = (SafeMode.value_for_name safe_mode) rescue SafeMode::SECURE
    end
    input_mtime = options.delete :input_mtime
    @compat_mode = attr_overrides.key? 'compat-mode'
    @sourcemap = options[:sourcemap]
    @timings = options.delete :timings
    @path_resolver = PathResolver.new
    initialize_extensions = (defined? ::Asciidoctor::Extensions) ? true : nil
    @extensions = nil # initialize furthur down if initialize_extensions is true
    options[:standalone] = options[:header_footer] if (options.key? :header_footer) && !(options.key? :standalone)
  end

  @parsed = @reftexts = @header = @header_attributes = nil
  @counters = {}
  @attributes_modified = ::Set.new
  @docinfo_processor_extensions = {}
  standalone = options[:standalone]
  (@options = options).freeze

  attrs = @attributes
  #attrs['encoding'] = 'UTF-8'
  attrs['sectids'] = ''
  attrs['toc-placement'] = 'auto'
  if standalone
    attrs['copycss'] = ''
    # sync embedded attribute with :standalone option value
    attr_overrides['embedded'] = nil
  else
    attrs['notitle'] = ''
    # sync embedded attribute with :standalone option value
    attr_overrides['embedded'] = ''
  end
  attrs['stylesheet'] = ''
  attrs['webfonts'] = ''
  attrs['prewrap'] = ''
  attrs['attribute-undefined'] = Compliance.attribute_undefined
  attrs['attribute-missing'] = Compliance.attribute_missing
  attrs['iconfont-remote'] = ''

  # language strings
  # TODO load these based on language settings
  attrs['caution-caption'] = 'Caution'
  attrs['important-caption'] = 'Important'
  attrs['note-caption'] = 'Note'
  attrs['tip-caption'] = 'Tip'
  attrs['warning-caption'] = 'Warning'
  attrs['example-caption'] = 'Example'
  attrs['figure-caption'] = 'Figure'
  #attrs['listing-caption'] = 'Listing'
  attrs['table-caption'] = 'Table'
  attrs['toc-title'] = 'Table of Contents'
  #attrs['preface-title'] = 'Preface'
  attrs['section-refsig'] = 'Section'
  attrs['part-refsig'] = 'Part'
  attrs['chapter-refsig'] = 'Chapter'
  attrs['appendix-caption'] = attrs['appendix-refsig'] = 'Appendix'
  attrs['untitled-label'] = 'Untitled'
  attrs['version-label'] = 'Version'
  attrs['last-update-label'] = 'Last updated'

  attr_overrides['asciidoctor'] = ''
  attr_overrides['asciidoctor-version'] = ::Asciidoctor::VERSION

  attr_overrides['safe-mode-name'] = (safe_mode_name = SafeMode.name_for_value @safe)
  attr_overrides["safe-mode-#{safe_mode_name}"] = ''
  attr_overrides['safe-mode-level'] = @safe

  # the only way to set the max-include-depth attribute is via the API; default to 64 like AsciiDoc Python
  attr_overrides['max-include-depth'] ||= 64

  # the only way to set the allow-uri-read attribute is via the API; disabled by default
  attr_overrides['allow-uri-read'] ||= nil

  attr_overrides['user-home'] = USER_HOME

  # remap legacy attribute names
  attr_overrides['sectnums'] = attr_overrides.delete 'numbered' if attr_overrides.key? 'numbered'
  attr_overrides['hardbreaks-option'] = attr_overrides.delete 'hardbreaks' if attr_overrides.key? 'hardbreaks'

  # If the base_dir option is specified, it overrides docdir and is used as the root for relative
  # paths. Otherwise, the base_dir is the directory of the source file (docdir), if set, otherwise
  # the current directory.
  if (base_dir_val = options[:base_dir])
    @base_dir = (attr_overrides['docdir'] = ::File.expand_path base_dir_val)
  elsif attr_overrides['docdir']
    @base_dir = attr_overrides['docdir']
  else
    #logger.warn 'setting base_dir is recommended when working with string documents' unless nested?
    @base_dir = attr_overrides['docdir'] = ::Dir.pwd
  end

  # allow common attributes backend and doctype to be set using options hash, coerce values to string
  if (backend_val = options[:backend])
    attr_overrides['backend'] = %(#{backend_val})
  end

  if (doctype_val = options[:doctype])
    attr_overrides['doctype'] = %(#{doctype_val})
  end

  if @safe >= SafeMode::SERVER
    # restrict document from setting copycss, source-highlighter and backend
    attr_overrides['copycss'] ||= nil
    attr_overrides['source-highlighter'] ||= nil
    attr_overrides['backend'] ||= DEFAULT_BACKEND
    # restrict document from seeing the docdir and trim docfile to relative path
    if !parent_doc && attr_overrides.key?('docfile')
      attr_overrides['docfile'] = attr_overrides['docfile'][(attr_overrides['docdir'].length + 1)..-1]
    end
    attr_overrides['docdir'] = ''
    attr_overrides['user-home'] = '.'
    if @safe >= SafeMode::SECURE
      attr_overrides['max-attribute-value-size'] = 4096 unless attr_overrides.key? 'max-attribute-value-size'
      # assign linkcss (preventing css embedding) unless explicitly disabled from the commandline or API
      #attr_overrides['linkcss'] = (attr_overrides.fetch 'linkcss', '') || nil
      attr_overrides['linkcss'] = '' unless attr_overrides.key? 'linkcss'
      # restrict document from enabling icons
      attr_overrides['icons'] ||= nil
    end
  end

  # the only way to set the max-attribute-value-size attribute is via the API; disabled by default
  @max_attribute_value_size = (size = (attr_overrides['max-attribute-value-size'] ||= nil)) ? size.to_i.abs : nil

  attr_overrides.delete_if do |key, val|
    if val
      # a value ending in @ allows document to override value
      if ::String === val && (val.end_with? '@')
        val, verdict = val.chop, true
      end
      attrs[key] = val
    else
      # a nil or false value both unset the attribute; only a nil value locks it
      attrs.delete key
      verdict = val == false
    end
    verdict
  end

  if parent_doc
    @backend = attrs['backend']
    # reset doctype unless it matches the default value
    unless (@doctype = attrs['doctype'] = parent_doctype) == DEFAULT_DOCTYPE
      update_doctype_attributes DEFAULT_DOCTYPE
    end

    # don't need to do the extra processing within our own document
    # FIXME line info isn't reported correctly within include files in nested document
    @reader = Reader.new data, options[:cursor]
    @source_location = @reader.cursor if @sourcemap

    # Now parse the lines in the reader into blocks
    # Eagerly parse (for now) since a subdocument is not a publicly accessible object
    Parser.parse @reader, self

    # should we call some sort of post-parse function?
    restore_attributes
    @parsed = true
  else
    # setup default backend and doctype
    @backend = nil
    if (initial_backend = attrs['backend'] || DEFAULT_BACKEND) == 'manpage'
      @doctype = attrs['doctype'] = attr_overrides['doctype'] = 'manpage'
    else
      @doctype = (attrs['doctype'] ||= DEFAULT_DOCTYPE)
    end
    update_backend_attributes initial_backend, true

    # dynamic intrinstic attribute values

    #attrs['indir'] = attrs['docdir']
    #attrs['infile'] = attrs['docfile']

    # fallback directories
    attrs['stylesdir'] ||= '.'
    attrs['iconsdir'] ||= %(#{attrs.fetch 'imagesdir', './images'}/icons)

    fill_datetime_attributes attrs, input_mtime

    if initialize_extensions
      if (ext_registry = options[:extension_registry])
        # QUESTION should we warn if the value type of this option is not a registry
        if Extensions::Registry === ext_registry || ((defined? ::AsciidoctorJ::Extensions::ExtensionRegistry) &&
            ::AsciidoctorJ::Extensions::ExtensionRegistry === ext_registry)
          @extensions = ext_registry.activate self
        end
      elsif ::Proc === (ext_block = options[:extensions])
        @extensions = Extensions.create(&ext_block).activate self
      elsif !Extensions.groups.empty?
        @extensions = Extensions::Registry.new.activate self
      end
    end

    @reader = PreprocessorReader.new self, data, (Reader::Cursor.new attrs['docfile'], @base_dir), normalize: true
    @source_location = @reader.cursor if @sourcemap
  end
end

Instance Attribute Details

#backendObject (readonly)

Get the cached value of the backend attribute for this document



190
191
192
# File 'lib/asciidoctor/document.rb', line 190

def backend
  @backend
end

#base_dirObject (readonly)

Get the String base directory for converting this document.

Defaults to directory of the source file. If the source is a string, defaults to the current directory.



214
215
216
# File 'lib/asciidoctor/document.rb', line 214

def base_dir
  @base_dir
end

#catalogObject (readonly) Also known as: references

Get the document catalog Hash



199
200
201
# File 'lib/asciidoctor/document.rb', line 199

def catalog
  @catalog
end

#compat_modeObject (readonly)

Get the Boolean AsciiDoc compatibility mode

enabling this attribute activates the following syntax changes:

* single quotes as constrained emphasis formatting marks
* single backticks parsed as inline literal, formatted as monospace
* single plus parsed as constrained, monospaced inline formatting
* double plus parsed as constrained, monospaced inline formatting


187
188
189
# File 'lib/asciidoctor/document.rb', line 187

def compat_mode
  @compat_mode
end

#converterObject (readonly)

Get the Converter associated with this document



232
233
234
# File 'lib/asciidoctor/document.rb', line 232

def converter
  @converter
end

#countersObject (readonly)

Get the Hash of document counters



205
206
207
# File 'lib/asciidoctor/document.rb', line 205

def counters
  @counters
end

#doctypeObject (readonly)

Get the cached value of the doctype attribute for this document



193
194
195
# File 'lib/asciidoctor/document.rb', line 193

def doctype
  @doctype
end

#extensionsObject (readonly)

Get the activated Extensions::Registry associated with this document.



238
239
240
# File 'lib/asciidoctor/document.rb', line 238

def extensions
  @extensions
end

#headerObject (readonly)

Get the level-0 Section (i.e., doctitle). (Only stores the title, not the header attributes).



208
209
210
# File 'lib/asciidoctor/document.rb', line 208

def header
  @header
end

#optionsObject (readonly)

Get the Hash of resolved options used to initialize this Document



217
218
219
# File 'lib/asciidoctor/document.rb', line 217

def options
  @options
end

#outfilesuffixObject (readonly)

Get the outfilesuffix defined at the end of the header.



220
221
222
# File 'lib/asciidoctor/document.rb', line 220

def outfilesuffix
  @outfilesuffix
end

#parent_documentObject (readonly)

Get a reference to the parent Document of this nested document.



223
224
225
# File 'lib/asciidoctor/document.rb', line 223

def parent_document
  @parent_document
end

#path_resolverObject (readonly)

Get/Set the PathResolver instance used to resolve paths in this Document.



229
230
231
# File 'lib/asciidoctor/document.rb', line 229

def path_resolver
  @path_resolver
end

#readerObject (readonly)

Get the Reader associated with this document



226
227
228
# File 'lib/asciidoctor/document.rb', line 226

def reader
  @reader
end

#safeObject (readonly)

Public A read-only integer value indicating the level of security that should be enforced while processing this document. The value must be set in the Document constructor using the :safe option.

A value of 0 (UNSAFE) disables any of the security features enforced by Asciidoctor (Ruby is still subject to its own restrictions).

A value of 1 (SAFE) closely parallels safe mode in AsciiDoc. In particular, it prevents access to files which reside outside of the parent directory of the source file and disables any macro other than the include directive.

A value of 10 (SERVER) disallows the document from setting attributes that would affect the conversion of the document, in addition to all the security features of SafeMode::SAFE. For instance, this level forbids changing the backend or source-highlighter using an attribute defined in the source document header. This is the most fundamental level of security for server deployments (hence the name).

A value of 20 (SECURE) disallows the document from attempting to read files from the file system and including the contents of them into the document, in addition to all the security features of SafeMode::SECURE. In particular, it disallows use of the include::[] directive and the embedding of binary content (data uri), stylesheets and JavaScripts referenced by the document. (Asciidoctor and trusted extensions may still be allowed to embed trusted content into the document).

Since Asciidoctor is aiming for wide adoption, 20 (SECURE) is the default value and is recommended for server deployments.

A value of 100 (PARANOID) is planned to disallow the use of passthrough macros and prevents the document from setting any known attributes in addition to all the security features of SafeMode::SECURE. Please note that this level is not currently implemented (and therefore not enforced)!



176
177
178
# File 'lib/asciidoctor/document.rb', line 176

def safe
  @safe
end

#sourcemapObject

Get or set the Boolean flag that indicates whether source map information should be tracked by the parser



196
197
198
# File 'lib/asciidoctor/document.rb', line 196

def sourcemap
  @sourcemap
end

#syntax_highlighterObject (readonly)

Get the SyntaxHighlighter associated with this document



235
236
237
# File 'lib/asciidoctor/document.rb', line 235

def syntax_highlighter
  @syntax_highlighter
end

Instance Method Details

#<<(block) ⇒ The

Append a content Block to this Document.

If the child block is a Section, assign an index to it.

Parameters:

  • block

    The child Block to append to this parent Block

Returns:

  • (The)

    parent Block



802
803
804
805
# File 'lib/asciidoctor/document.rb', line 802

def << block
  assign_numeral block if block.context == :section
  super
end

#attribute_locked?(name) ⇒ Boolean

Determine if the attribute has been locked by being assigned in document options

Parameters:

  • key

    The attribute key to check

Returns:

  • (Boolean)

    true if the attribute is locked, false otherwise



895
896
897
# File 'lib/asciidoctor/document.rb', line 895

def attribute_locked?(name)
  @attribute_overrides.key?(name)
end

#authorObject

Convenience method to retrieve the document attribute ‘author’

returns the full name of the author as a String



742
743
744
# File 'lib/asciidoctor/document.rb', line 742

def author
  @attributes['author']
end

#authorsObject

Convenience method to retrieve the authors of this document as an Array of Author objects.

This method is backed by the author-related attributes on the document.

returns the authors of this document as an Array



751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
# File 'lib/asciidoctor/document.rb', line 751

def authors
  if (attrs = @attributes).key? 'author'
    authors = [(Author.new attrs['author'], attrs['firstname'], attrs['middlename'], attrs['lastname'], attrs['authorinitials'], attrs['email'])]
    if (num_authors = attrs['authorcount'] || 0) > 1
      idx = 1
      while idx < num_authors
        idx += 1
        authors << (Author.new attrs[%(author_#{idx})], attrs[%(firstname_#{idx})], attrs[%(middlename_#{idx})], attrs[%(lastname_#{idx})], attrs[%(authorinitials_#{idx})], attrs[%(email_#{idx})])
      end
    end
    authors
  else
    []
  end
end

#basebackend?(base) ⇒ Boolean

Returns:

  • (Boolean)


671
672
673
# File 'lib/asciidoctor/document.rb', line 671

def basebackend? base
  @attributes['basebackend'] == base
end

#calloutsObject



645
646
647
# File 'lib/asciidoctor/document.rb', line 645

def callouts
  @catalog[:callouts]
end

#contentObject

def convert_to target, opts = {}

start = ::Time.now.to_f if (monitor = opts[:monitor])
output = (r = converter opts).convert
monitor[:convert] = ::Time.now.to_f - start if monitor

unless target.respond_to? :write
  @attributes['outfile'] = target = ::File.expand_path target
  @attributes['outdir'] = ::File.dirname target
end

start = ::Time.now.to_f if monitor
r.write output, target
monitor[:write] = ::Time.now.to_f - start if monitor

output

end



1017
1018
1019
1020
1021
# File 'lib/asciidoctor/document.rb', line 1017

def content
  # NOTE per AsciiDoc-spec, remove the title before converting the body
  @attributes.delete('title')
  super
end

#convert(opts = {}) ⇒ Object Also known as: render

Convert the AsciiDoc document using the templates loaded by the Converter. If a :template_dir is not specified, or a template is missing, the converter will fall back to using the appropriate built-in template.



924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
# File 'lib/asciidoctor/document.rb', line 924

def convert opts = {}
  @timings.start :convert if @timings
  parse unless @parsed
  unless @safe >= SafeMode::SERVER || opts.empty?
    # QUESTION should we store these on the Document object?
    @attributes.delete 'outfile' unless (@attributes['outfile'] = opts['outfile'])
    @attributes.delete 'outdir' unless (@attributes['outdir'] = opts['outdir'])
  end

  # QUESTION should we add extensions that execute before conversion begins?

  if doctype == 'inline'
    if (block = @blocks[0] || @header)
      if block.content_model == :compound || block.content_model == :empty
        logger.warn 'no inline candidate; use the inline doctype to convert a single paragragh, verbatim, or raw block'
      else
        output = block.content
      end
    end
  else
    if opts.key? :standalone
      transform = opts[:standalone] ? 'document' : 'embedded'
    elsif opts.key? :header_footer
      transform = opts[:header_footer] ? 'document' : 'embedded'
    else
      transform = @options[:standalone] ? 'document' : 'embedded'
    end
    output = @converter.convert self, transform
  end

  unless @parent_document
    if (exts = @extensions) && exts.postprocessors?
      exts.postprocessors.each do |ext|
        output = ext.process_method[self, output]
      end
    end
  end

  @timings.record :convert if @timings
  output
end

#counter(name, seed = nil) ⇒ Object

Get the named counter and take the next number in the sequence.

Parameters:

  • name

    the String name of the counter

  • seed (defaults to: nil)

    the initial value as a String or Integer

  • returns

    the next number in the sequence for the specified counter



578
579
580
581
582
583
584
585
586
587
# File 'lib/asciidoctor/document.rb', line 578

def counter name, seed = nil
  return @parent_document.counter name, seed if @parent_document
  if (attr_seed = !(attr_val = @attributes[name]).nil_or_empty?) && (@counters.key? name)
    @attributes[name] = @counters[name] = Helpers.nextval attr_val
  elsif seed
    @attributes[name] = @counters[name] = seed == seed.to_i.to_s ? seed.to_i : seed
  else
    @attributes[name] = @counters[name] = Helpers.nextval attr_seed ? attr_val : 0
  end
end

#delete_attribute(name) ⇒ Object

Delete the specified attribute from the document if the name is not locked

If the attribute is locked, false is returned. Otherwise, the attribute is deleted.

Parameters:

  • name

    the String attribute name

  • returns

    true if the attribute was deleted, false if it was not because it’s locked



880
881
882
883
884
885
886
887
888
# File 'lib/asciidoctor/document.rb', line 880

def delete_attribute(name)
  if attribute_locked?(name)
    false
  else
    @attributes.delete(name)
    @attributes_modified << name
    true
  end
end

#docinfo(location = :head, suffix = nil) ⇒ Object

Read the docinfo file(s) for inclusion in the document template

If the docinfo1 attribute is set, read the docinfo.ext file. If the docinfo attribute is set, read the doc-name.docinfo.ext file. If the docinfo2 attribute is set, read both files in that order.

Parameters:

  • location (defaults to: :head)

    The Symbol location of the docinfo (e.g., :head, :footer, etc). (default: :head)

  • suffix (defaults to: nil)

    The suffix of the docinfo file(s). If not set, the extension will be set to the outfilesuffix. (default: nil)

  • returns

    The contents of the docinfo file(s) or empty string if no files are

  • found

    or the safe mode is secure or greater.



1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
# File 'lib/asciidoctor/document.rb', line 1035

def docinfo location = :head, suffix = nil
  if safe < SafeMode::SECURE
    qualifier = %(-#{location}) unless location == :head
    suffix = @outfilesuffix unless suffix

    if (docinfo = @attributes['docinfo']).nil_or_empty?
      if @attributes.key? 'docinfo2'
        docinfo = ['private', 'shared']
      elsif @attributes.key? 'docinfo1'
        docinfo = ['shared']
      else
        docinfo = docinfo ? ['private'] : nil
      end
    else
      docinfo = docinfo.split(',').map {|it| it.strip }
    end

    if docinfo
      content = []
      docinfo_file, docinfo_dir, docinfo_subs = %(docinfo#{qualifier}#{suffix}), @attributes['docinfodir'], resolve_docinfo_subs
      unless (docinfo & ['shared', %(shared-#{location})]).empty?
        docinfo_path = normalize_system_path docinfo_file, docinfo_dir
        # NOTE normalizing the lines is essential if we're performing substitutions
        if (shared_docinfo = read_asset docinfo_path, normalize: true)
          content << (apply_subs shared_docinfo, docinfo_subs)
        end
      end

      unless @attributes['docname'].nil_or_empty? || (docinfo & ['private', %(private-#{location})]).empty?
        docinfo_path = normalize_system_path %(#{@attributes['docname']}-#{docinfo_file}), docinfo_dir
        # NOTE normalizing the lines is essential if we're performing substitutions
        if (private_docinfo = read_asset docinfo_path, normalize: true)
          content << (apply_subs private_docinfo, docinfo_subs)
        end
      end
    end
  end

  # TODO allow document to control whether extension docinfo is contributed
  if @extensions && (docinfo_processors? location)
    ((content || []).concat @docinfo_processor_extensions[location].map {|ext| ext.process_method[self] }.compact).join LF
  elsif content
    content.join LF
  else
    ''
  end
end

#docinfo_processors?(location = :head) ⇒ Boolean

Returns:

  • (Boolean)


1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
# File 'lib/asciidoctor/document.rb', line 1083

def docinfo_processors?(location = :head)
  if @docinfo_processor_extensions.key?(location)
    # false means we already performed a lookup and didn't find any
    @docinfo_processor_extensions[location] != false
  elsif @extensions && @document.extensions.docinfo_processors?(location)
    !!(@docinfo_processor_extensions[location] = @document.extensions.docinfo_processors(location))
  else
    @docinfo_processor_extensions[location] = false
  end
end

#doctitle(opts = {}) ⇒ Title Also known as: name

Resolves the primary title for the document

Searches the locations to find the first non-empty value:

* document-level attribute named title
* header title (known as the document title)
* title of the first section
* document-level attribute named untitled-label (if :use_fallback option is set)

If no value can be resolved, nil is returned.

If the :partition attribute is specified, the value is parsed into an Document::Title object. If the :sanitize attribute is specified, XML elements are removed from the value.

TODO separate sanitization by type (:cdata for HTML/XML, :plain_text for non-SGML, false for none)

Returns:

  • (Title)

    Returns the resolved title as a Title if the :partition option is passed or a [String] if not or nil if no value can be resolved.



716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
# File 'lib/asciidoctor/document.rb', line 716

def doctitle opts = {}
  unless (val = @attributes['title'])
    if (sect = first_section)
      val = sect.title
    elsif !(opts[:use_fallback] && (val = @attributes['untitled-label']))
      return
    end
  end

  if (separator = opts[:partition])
    Title.new val, opts.merge({ separator: (separator == true ? @attributes['title-separator'] : separator) })
  elsif opts[:sanitize] && val.include?('<')
    val.gsub(XmlSanitizeRx, '').squeeze(' ').strip
  else
    val
  end
end

#embedded?Boolean

Returns:

  • (Boolean)


653
654
655
# File 'lib/asciidoctor/document.rb', line 653

def embedded?
  @attributes.key? 'embedded'
end

#extensions?Boolean

Returns:

  • (Boolean)


657
658
659
# File 'lib/asciidoctor/document.rb', line 657

def extensions?
  @extensions ? true : false
end

#first_sectionObject



786
787
788
# File 'lib/asciidoctor/document.rb', line 786

def first_section
  @header || @blocks.find {|e| e.context == :section }
end

#footnotesObject



641
642
643
# File 'lib/asciidoctor/document.rb', line 641

def footnotes
  @catalog[:footnotes]
end

#footnotes?Boolean

Returns:

  • (Boolean)


637
638
639
# File 'lib/asciidoctor/document.rb', line 637

def footnotes?
  @catalog[:footnotes].empty? ? false : true
end

#header?Boolean Also known as: has_header?

Returns:

  • (Boolean)


790
791
792
# File 'lib/asciidoctor/document.rb', line 790

def header?
  @header ? true : false
end

#increment_and_store_counter(counter_name, block) ⇒ Object Also known as: counter_increment

Increment the specified counter and store it in the block’s attributes

Parameters:

  • counter_name

    the String name of the counter attribute

  • block

    the Block on which to save the counter

  • returns

    the next number in the sequence for the specified counter



595
596
597
# File 'lib/asciidoctor/document.rb', line 595

def increment_and_store_counter counter_name, block
  ((AttributeEntry.new counter_name, (counter counter_name)).save_to block.attributes).value
end

#nested?Boolean

Returns:

  • (Boolean)


649
650
651
# File 'lib/asciidoctor/document.rb', line 649

def nested?
  @parent_document ? true : false
end

#nofooterObject



782
783
784
# File 'lib/asciidoctor/document.rb', line 782

def nofooter
  @attributes.key? 'nofooter'
end

#noheaderObject



778
779
780
# File 'lib/asciidoctor/document.rb', line 778

def noheader
  @attributes.key? 'noheader'
end

#notitleObject



774
775
776
# File 'lib/asciidoctor/document.rb', line 774

def notitle
  !@attributes.key?('showtitle') && @attributes.key?('notitle')
end

#parse(data = nil) ⇒ Document

Parse the AsciiDoc source stored in the Reader into an abstract syntax tree.

If the data parameter is not nil, create a new PreprocessorReader and assigned it to the reader property of this object. Otherwise, continue with the reader that was created in #initialize. Pass the reader to Parser.parse to parse the source data into an abstract syntax tree.

If parsing has already been performed, this method returns without performing any processing.

Parameters:

  • data (defaults to: nil)

    The optional replacement AsciiDoc source data as a String or String Array. (default: nil)

Returns:



531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
# File 'lib/asciidoctor/document.rb', line 531

def parse data = nil
  if @parsed
    self
  else
    doc = self
    # create reader if data is provided (used when data is not known at the time the Document object is created)
    if data
      @reader = PreprocessorReader.new doc, data, (Reader::Cursor.new @attributes['docfile'], @base_dir), normalize: true
      @source_location = @reader.cursor if @sourcemap
    end

    if (exts = @parent_document ? nil : @extensions) && exts.preprocessors?
      exts.preprocessors.each do |ext|
        @reader = ext.process_method[doc, @reader] || @reader
      end
    end

    # Now parse the lines in the reader into blocks
    Parser.parse @reader, doc, header_only: @options[:parse_header_only]

    # should we call sort of post-parse function?
    restore_attributes

    if exts && exts.tree_processors?
      exts.tree_processors.each do |ext|
        if (result = ext.process_method[doc]) && Document === result && result != doc
          doc = result
        end
      end
    end

    @parsed = true
    doc
  end
end

#parsed?Boolean

Returns whether the source lines of the document have been parsed.

Returns:

  • (Boolean)


568
569
570
# File 'lib/asciidoctor/document.rb', line 568

def parsed?
  @parsed
end

#playback_attributes(block_attributes) ⇒ Object

Replay attribute assignments at the block level



820
821
822
823
824
825
826
827
828
829
830
831
832
833
# File 'lib/asciidoctor/document.rb', line 820

def playback_attributes(block_attributes)
  if block_attributes.key? :attribute_entries
    block_attributes[:attribute_entries].each do |entry|
      name = entry.name
      if entry.negate
        @attributes.delete name
        @compat_mode = false if name == 'compat-mode'
      else
        @attributes[name] = entry.value
        @compat_mode = true if name == 'compat-mode'
      end
    end
  end
end

#register(type, value) ⇒ Object

Register a reference in the document catalog



602
603
604
605
606
607
608
609
610
611
612
613
614
# File 'lib/asciidoctor/document.rb', line 602

def register type, value
  case type
  when :ids # deprecated
    register :refs, [(id = value[0]), (Inline.new self, :anchor, value[1], type: :ref, id: id)]
  when :refs
    @catalog[:refs][value[0]] ||= (ref = value[1])
    ref
  when :footnotes
    @catalog[type] << value
  else
    @catalog[type] << (type == :images ? (ImageReference.new value, @attributes['imagesdir']) : value) if @options[:catalog_assets]
  end
end

#resolve_id(text) ⇒ Object

Scan registered references and return the ID of the first reference that matches the specified reference text.

Parameters:

  • text

    The String reference text to compare to the converted reference text of each registered reference.

Returns:

  • the String ID of the first reference with matching reference text or nothing if no reference is found.



621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
# File 'lib/asciidoctor/document.rb', line 621

def resolve_id text
  if @reftexts
    @reftexts[text]
  elsif @parsed
    # @reftexts is set eagerly to prevent nested lazy init
    (@reftexts = {}).tap {|accum| @catalog[:refs].each {|id, ref| accum[ref.xreftext] ||= id } }[text]
  else
    # @reftexts is set eagerly to prevent nested lazy init
    resolved_id = nil
    # NOTE short-circuit early since we're throwing away this table
    (@reftexts = {}).tap {|accum| @catalog[:refs].each {|id, ref| (xreftext = ref.xreftext) == text ? (break (resolved_id = id)) : (accum[xreftext] ||= id) } }
    @reftexts = nil
    resolved_id
  end
end

#restore_attributesObject

Restore the attributes to the previously saved state (attributes in header)



836
837
838
839
# File 'lib/asciidoctor/document.rb', line 836

def restore_attributes
  @catalog[:callouts].rewind unless @parent_document
  @attributes.replace @header_attributes
end

#revdateObject

Convenience method to retrieve the document attribute ‘revdate’

returns the date of last revision for the document as a String



770
771
772
# File 'lib/asciidoctor/document.rb', line 770

def revdate
  @attributes['revdate']
end

#set_attribute(name, value = '') ⇒ Object

Set the specified attribute on the document if the name is not locked

If the attribute is locked, false is returned. Otherwise, the value is assigned to the attribute name after first performing attribute substitutions on the value. If the attribute name is ‘backend’ or ‘doctype’, then the value of backend-related attributes are updated.

Parameters:

  • name

    the String attribute name

  • value (defaults to: '')

    the String attribute value; must not be nil (optional, default: ”)

Returns:

  • the substituted value if the attribute was set or nil if it was not because it’s locked.



852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
# File 'lib/asciidoctor/document.rb', line 852

def set_attribute name, value = ''
  unless attribute_locked? name
    value = apply_attribute_value_subs value unless value.empty?
    # NOTE if @header_attributes is set, we're beyond the document header
    if @header_attributes
      @attributes[name] = value
    else
      case name
      when 'backend'
        update_backend_attributes value, (@attributes_modified.delete? 'htmlsyntax') && value == @backend
      when 'doctype'
        update_doctype_attributes value
      else
        @attributes[name] = value
      end
      @attributes_modified << name
    end
    value
  end
end

#set_header_attribute(name, value = '', overwrite = true) ⇒ Boolean

Assign a value to the specified attribute in the document header.

The assignment will be visible when the header attributes are restored, typically between processor phases (e.g., between parse and convert).

Parameters:

  • name

    The String attribute name to assign

  • value (defaults to: '')

    The Object value to assign to the attribute (default: ”)

  • overwrite (defaults to: true)

    A Boolean indicating whether to assign the attribute if already present in the attributes Hash (default: true)

Returns:

  • (Boolean)

    Returns a Boolean indicating whether the assignment was performed



910
911
912
913
914
915
916
917
918
# File 'lib/asciidoctor/document.rb', line 910

def set_header_attribute name, value = '', overwrite = true
  attrs = @header_attributes || @attributes
  if overwrite == false && (attrs.key? name)
    false
  else
    attrs[name] = value
    true
  end
end

#sourceObject

Make the raw source for the Document available.



662
663
664
# File 'lib/asciidoctor/document.rb', line 662

def source
  @reader.source if @reader
end

#source_linesObject

Make the raw source lines for the Document available.



667
668
669
# File 'lib/asciidoctor/document.rb', line 667

def source_lines
  @reader.source_lines if @reader
end

#titleString

Return the doctitle as a String

Returns:

  • (String)

    Returns the resolved doctitle as a String or nil if a doctitle cannot be resolved



678
679
680
# File 'lib/asciidoctor/document.rb', line 678

def title
  doctitle
end

#title=(title) ⇒ String

Set the title on the document header

Set the title of the document header to the specified value. If the header does not exist, it is first created.

Parameters:

  • title

    the String title to assign as the title of the document header

Returns:

  • (String)

    Returns the new String title assigned to the document header



690
691
692
693
694
695
# File 'lib/asciidoctor/document.rb', line 690

def title= title
  unless (sect = @header)
    (sect = (@header = Section.new self, 0)).sectname = 'header'
  end
  sect.title = title
end

#to_sObject



1094
1095
1096
# File 'lib/asciidoctor/document.rb', line 1094

def to_s
  %(#<#{self.class}@#{object_id} {doctype: #{doctype.inspect}, doctitle: #{(@header != nil ? @header.title : nil).inspect}, blocks: #{@blocks.size}}>)
end

#write(output, target) ⇒ void

This method returns an undefined value.

Write the output to the specified file

If the converter responds to :write, delegate the work of writing the file to that method. Otherwise, write the output the specified file.



975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
# File 'lib/asciidoctor/document.rb', line 975

def write output, target
  @timings.start :write if @timings
  if Writer === @converter
    @converter.write output, target
  else
    if target.respond_to? :write
      # QUESTION should we set encoding using target.set_encoding?
      unless output.nil_or_empty?
        target.write output.chomp
        # ensure there's a trailing endline
        target.write LF
      end
    else
      ::File.write target, output, mode: FILE_WRITE_MODE
    end
    if @backend == 'manpage' && ::String === target && (@converter.class.respond_to? :write_alternate_pages)
      @converter.class.write_alternate_pages @attributes['mannames'], @attributes['manvolnum'], target
    end
  end
  @timings.record :write if @timings
  nil
end

#xreftext(xrefstyle = nil) ⇒ Object



735
736
737
# File 'lib/asciidoctor/document.rb', line 735

def xreftext xrefstyle = nil
  (val = reftext) && !val.empty? ? val : title
end