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, #default_subs, #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, #assign_numeral, #block?, #blocks?, #captioned_title, #context=, #file, #find_by, #inline?, #lineno, #list_marker_keyword, #next_adjacent_block, #number, #reindex_sections, #remove_sub, #sections, #sections?, #sub?, #title?

Methods inherited from AbstractNode

#add_role, #attr, #attr?, #block?, #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, #commit_subs, #expand_subs, #extract_passthroughs, #highlight_source, #parse_attributes, #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

Public: Initialize a Asciidoctor::Document object.

data - The AsciiDoc source data as a String or String Array. (default: nil) options - 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.

Examples

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


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
519
# 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'] = ::File.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 = false
  @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)

Public: 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)

Public: 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

Public: Get the document catalog Hash



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

def catalog
  @catalog
end

#compat_modeObject (readonly)

Public: 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)

Public: Get the Converter associated with this document



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

def converter
  @converter
end

#countersObject (readonly)

Public: Get the Hash of document counters



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

def counters
  @counters
end

#doctypeObject (readonly)

Public: 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)

Public: 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)

Public: 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)

Public: 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)

Public: 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)

Public: 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)

Public: 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)

Public: 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

Public: 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)

Public: 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) ⇒ Object

Public: Append a content Block to this Document.

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

block - The child Block to append to this parent Block

Returns The parent Block



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

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

#attribute_locked?(name) ⇒ Boolean

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

key - The attribute key to check

Returns true if the attribute is locked, false otherwise

Returns:

  • (Boolean)


888
889
890
# File 'lib/asciidoctor/document.rb', line 888

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

#authorObject

Public: Convenience method to retrieve the document attribute ‘author’

returns the full name of the author as a String



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

def author
  @attributes['author']
end

#authorsObject

Public: 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



744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
# File 'lib/asciidoctor/document.rb', line 744

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)


664
665
666
# File 'lib/asciidoctor/document.rb', line 664

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

#calloutsObject



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

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


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

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

Public: 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.



917
918
919
920
921
922
923
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
# File 'lib/asciidoctor/document.rb', line 917

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

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

name - the String name of the counter seed - the initial value as a String or Integer

returns the next number in the sequence for the specified counter



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

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

Public: 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.

name - the String attribute name

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



873
874
875
876
877
878
879
880
881
# File 'lib/asciidoctor/document.rb', line 873

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

Public: 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.

location - The Symbol location of the docinfo (e.g., :head, :footer, etc). (default: :head) suffix - 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.



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
1074
# File 'lib/asciidoctor/document.rb', line 1028

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)


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

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 = {}) ⇒ Object Also known as: name

Public: 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 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.



709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
# File 'lib/asciidoctor/document.rb', line 709

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)


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

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

#extensions?Boolean

Returns:

  • (Boolean)


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

def extensions?
  @extensions ? true : false
end

#finalize_header(unrooted_attributes, header_valid = true) ⇒ Object

Internal: Called by the parser after parsing the header and before parsing the body, even if no header is found. – QUESTION should we invoke the TreeProcessors here, passing in a phase? QUESTION is finalize_header the right name?



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

def finalize_header unrooted_attributes, header_valid = true
  clear_playback_attributes unrooted_attributes
  save_attributes
  unrooted_attributes['invalid-header'] = true unless header_valid
  unrooted_attributes
end

#first_sectionObject



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

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

#footnotesObject



634
635
636
# File 'lib/asciidoctor/document.rb', line 634

def footnotes
  @catalog[:footnotes]
end

#footnotes?Boolean

Returns:

  • (Boolean)


630
631
632
# File 'lib/asciidoctor/document.rb', line 630

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

#header?Boolean Also known as: has_header?

Returns:

  • (Boolean)


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

def header?
  @header ? true : false
end

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

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

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



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

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)


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

def nested?
  @parent_document ? true : false
end

#nofooterObject



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

def nofooter
  @attributes.key? 'nofooter'
end

#noheaderObject



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

def noheader
  @attributes.key? 'noheader'
end

#notitleObject



767
768
769
# File 'lib/asciidoctor/document.rb', line 767

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

#parse(data = nil) ⇒ Object

Public: 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.

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

Returns this [Document]



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
566
# File 'lib/asciidoctor/document.rb', line 532

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

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

Returns:

  • (Boolean)


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

def parsed?
  @parsed
end

#playback_attributes(block_attributes) ⇒ Object

Public: Replay attribute assignments at the block level



813
814
815
816
817
818
819
820
821
822
823
824
825
826
# File 'lib/asciidoctor/document.rb', line 813

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

Public: Register a reference in the document catalog



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

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, :indexterms
  when :footnotes
    @catalog[type] << value
  else
    @catalog[type] << (type == :images ? (ImageReference.new value[0], value[1]) : value) if @options[:catalog_assets]
  end
end

#resolve_id(text) ⇒ Object

Public: Scan all registered references and return the ID of the reference that matches the specified reference text.

If multiple references in the document have the same reference text, the first match in document order is used.

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.



625
626
627
628
# File 'lib/asciidoctor/document.rb', line 625

def resolve_id text
  ((@reftexts ||= @parsed ? {}.tap {|accum| @catalog[:refs].each {|id, ref| accum[ref.xreftext] = id } } : nil) ||
    {}.tap {|accum| @catalog[:refs].find {|id, ref| ref.xreftext == text ? accum[text] = id : nil } })[text]
end

#restore_attributesObject

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



829
830
831
832
# File 'lib/asciidoctor/document.rb', line 829

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

#revdateObject

Public: Convenience method to retrieve the document attribute ‘revdate’

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



763
764
765
# File 'lib/asciidoctor/document.rb', line 763

def revdate
  @attributes['revdate']
end

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

Public: 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.

name - the String attribute name value - 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.



845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
# File 'lib/asciidoctor/document.rb', line 845

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) ⇒ Object

Public: 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).

name - The String attribute name to assign value - The Object value to assign to the attribute (default: ”) overwrite - A Boolean indicating whether to assign the attribute

if already present in the attributes Hash (default: true)

Returns a [Boolean] indicating whether the assignment was performed



903
904
905
906
907
908
909
910
911
# File 'lib/asciidoctor/document.rb', line 903

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.



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

def source
  @reader.source if @reader
end

#source_linesObject

Make the raw source lines for the Document available.



660
661
662
# File 'lib/asciidoctor/document.rb', line 660

def source_lines
  @reader.source_lines if @reader
end

#titleObject

Public: Return the doctitle as a String

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



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

def title
  doctitle
end

#title=(title) ⇒ Object

Public: 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.

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

Returns the new [String] title assigned to the document header



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

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

#to_sObject



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

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

#write(output, target) ⇒ Object

Public: 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.

Returns nothing



968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
# File 'lib/asciidoctor/document.rb', line 968

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



728
729
730
# File 'lib/asciidoctor/document.rb', line 728

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