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, #number=, #remove_sub, #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=, #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
# 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]
    options[:to_dir] = parent_doc.options[:to_dir] if parent_doc.options[:to_dir]
    @catalog = parent_doc.catalog.merge footnotes: []
    # QUESTION should we support setting attribute in parent document from nested document?
    @attribute_overrides = attr_overrides = (parent_doc.instance_variable_get :@attribute_overrides).merge parent_doc.attributes
    attr_overrides.delete 'compat-mode'
    parent_doctype = attr_overrides.delete 'doctype'
    attr_overrides.delete 'notitle'
    attr_overrides.delete 'showtitle'
    # QUESTION if toc is hard unset in parent document, should it be hard unset in nested document?
    attr_overrides.delete 'toc'
    @attributes['toc-placement'] = (attr_overrides.delete 'toc-placement') || 'auto'
    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: [],
      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 ::String === (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) || (options.key? :extensions) ? ::Asciidoctor::Extensions : nil
    @extensions = nil # initialize further 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
  unless parent_doc
    attrs['attribute-undefined'] = Compliance.attribute_undefined
    attrs['attribute-missing'] = Compliance.attribute_missing
    attrs.update DEFAULT_ATTRIBUTES
    # TODO if lang attribute is set, @safe mode < SafeMode::SERVER, and !parent_doc,
    # load attributes from data/locale/attributes-<lang>.adoc
  end

  if standalone
    # sync embedded attribute with :standalone option value
    attr_overrides['embedded'] = nil
    attrs['copycss'] = ''
    attrs['iconfont-remote'] = ''
    attrs['stylesheet'] = ''
    attrs['webfonts'] = ''
  else
    # sync embedded attribute with :standalone option value
    attr_overrides['embedded'] = ''
    if (attr_overrides.key? 'showtitle') && (attr_overrides.keys & %w(notitle showtitle))[-1] == 'showtitle'
      attr_overrides['notitle'] = { nil => '', false => '@', '@' => false }[attr_overrides['showtitle']]
    elsif attr_overrides.key? 'notitle'
      attr_overrides['showtitle'] = { nil => '', false => '@', '@' => false }[attr_overrides['notitle']]
    else
      attrs['notitle'] = ''
    end
  end

  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.py
  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

  # 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.to_s
  end

  if (doctype_val = options[:doctype])
    attr_overrides['doctype'] = doctype_val.to_s
  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
  else
    attr_overrides['user-home'] ||= USER_HOME
  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 (ext_block = options[:extensions]).nil?
        @extensions = Extensions::Registry.new.activate self unless Extensions.groups.empty?
      elsif ::Proc === ext_block
        @extensions = Extensions.create(&ext_block).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



807
808
809
810
# File 'lib/asciidoctor/document.rb', line 807

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



900
901
902
# File 'lib/asciidoctor/document.rb', line 900

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



747
748
749
# File 'lib/asciidoctor/document.rb', line 747

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



756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
# File 'lib/asciidoctor/document.rb', line 756

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)


676
677
678
# File 'lib/asciidoctor/document.rb', line 676

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

#calloutsObject



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

def callouts
  @catalog[:callouts]
end

#contentObject



1009
1010
1011
1012
1013
# File 'lib/asciidoctor/document.rb', line 1009

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.



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
965
966
967
968
969
# File 'lib/asciidoctor/document.rb', line 929

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



567
568
569
570
571
572
573
574
575
576
577
578
# File 'lib/asciidoctor/document.rb', line 567

def counter name, seed = nil
  return @parent_document.counter name, seed if @parent_document
  if ((locked = attribute_locked? name) && (curr_val = @counters[name])) || !(curr_val = @attributes[name]).nil_or_empty?
    next_val = @counters[name] = Helpers.nextval curr_val
  elsif seed
    next_val = @counters[name] = seed == seed.to_i.to_s ? seed.to_i : seed
  else
    next_val = @counters[name] = 1
  end
  @attributes[name] = next_val unless locked
  next_val
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



885
886
887
888
889
890
891
892
893
# File 'lib/asciidoctor/document.rb', line 885

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.



1027
1028
1029
1030
1031
1032
1033
1034
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
# File 'lib/asciidoctor/document.rb', line 1027

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

    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)


1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
# File 'lib/asciidoctor/document.rb', line 1075

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.



721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
# File 'lib/asciidoctor/document.rb', line 721

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)


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

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

#extensions?Boolean

Returns:

  • (Boolean)


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

def extensions?
  @extensions ? true : false
end

#first_sectionObject



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

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

#footnotesObject



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

def footnotes
  @catalog[:footnotes]
end

#footnotes?Boolean

Returns:

  • (Boolean)


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

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

#header?Boolean Also known as: has_header?

Returns:

  • (Boolean)


795
796
797
# File 'lib/asciidoctor/document.rb', line 795

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



586
587
588
# File 'lib/asciidoctor/document.rb', line 586

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)


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

def nested?
  @parent_document ? true : false
end

#nofooterObject



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

def nofooter
  @attributes.key? 'nofooter'
end

#noheaderObject



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

def noheader
  @attributes.key? 'noheader'
end

#notitleObject



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

def notitle
  @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:



520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
# File 'lib/asciidoctor/document.rb', line 520

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)


557
558
559
# File 'lib/asciidoctor/document.rb', line 557

def parsed?
  @parsed
end

#playback_attributes(block_attributes) ⇒ Object

Replay attribute assignments at the block level



825
826
827
828
829
830
831
832
833
834
835
836
837
838
# File 'lib/asciidoctor/document.rb', line 825

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



593
594
595
596
597
598
599
600
601
602
603
604
605
# File 'lib/asciidoctor/document.rb', line 593

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.



612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
# File 'lib/asciidoctor/document.rb', line 612

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
    resolved_id = nil
    # @reftexts is set eagerly to prevent nested lazy init
    @reftexts = accum = {}
    @catalog[:refs].each do |id, ref|
      # NOTE short-circuit early since we're throwing away this table anyway
      if (xreftext = ref.xreftext) == text
        resolved_id = id
        break
      end
      accum[xreftext] ||= id
    end
    @reftexts = nil
    resolved_id
  end
end

#restore_attributesObject

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



841
842
843
844
# File 'lib/asciidoctor/document.rb', line 841

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



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

def revdate
  @attributes['revdate']
end

#sections?Boolean

Check whether this Document has any child Section objects.

Returns:

  • (Boolean)

    Returns A Boolean to indicate whether this Document has child Section objects



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

def sections?
  @next_section_index > 0
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.



857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
# File 'lib/asciidoctor/document.rb', line 857

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



915
916
917
918
919
920
921
922
923
# File 'lib/asciidoctor/document.rb', line 915

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.



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

def source
  @reader.source if @reader
end

#source_linesObject

Make the raw source lines for the Document available.



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

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



683
684
685
# File 'lib/asciidoctor/document.rb', line 683

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 specified String title



695
696
697
698
699
700
# File 'lib/asciidoctor/document.rb', line 695

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

#to_sObject



1086
1087
1088
# File 'lib/asciidoctor/document.rb', line 1086

def to_s
  %(#<#{self.class}@#{object_id} {doctype: #{doctype.inspect}, doctitle: #{(@header && @header.title).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 output to that method. Otherwise, write the output to the specified file. In the latter case, this method ensures the output has a trailing newline if the target responds to write and the output is not empty.

Parameters:

  • output

    The output to write. Unless the converter responds to write, this object is expected to be a String.

  • target

    The file to write, either a File object or a String path.



986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
# File 'lib/asciidoctor/document.rb', line 986

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



740
741
742
# File 'lib/asciidoctor/document.rb', line 740

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