"Fossies" - the Fresh Open Source Software Archive

Member "asciidoctor-2.0.10/lib/asciidoctor/abstract_block.rb" (1 Jun 2019, 18391 Bytes) of package /linux/www/asciidoctor-2.0.10.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Ruby source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the last Fossies "Diffs" side-by-side code changes report for "abstract_block.rb": 2.0.8_vs_2.0.9.

    1 # frozen_string_literal: true
    2 module Asciidoctor
    3 class AbstractBlock < AbstractNode
    4   # Public: Get the Array of {AbstractBlock} child blocks for this block. Only applies if content model is :compound.
    5   attr_reader :blocks
    6 
    7   # Public: Set the caption for this block.
    8   attr_writer :caption
    9 
   10   # Public: Describes the type of content this block accepts and how it should be converted. Acceptable values are:
   11   # * :compound - this block contains other blocks
   12   # * :simple - this block holds a paragraph of prose that receives normal substitutions
   13   # * :verbatim - this block holds verbatim text (displayed "as is") that receives verbatim substitutions
   14   # * :raw - this block holds unprocessed content passed directly to the output with no sustitutions applied
   15   # * :empty - this block has no content
   16   attr_accessor :content_model
   17 
   18   # Public: Set the Integer level of this {Section} or the level of the Section to which this {AbstractBlock} belongs.
   19   attr_accessor :level
   20 
   21   # Public: Get/Set the String numeral of this block (if section, relative to parent, otherwise absolute).
   22   # Only assigned to section if automatic section numbering is enabled.
   23   # Only assigned to formal block (block with title) if corresponding caption attribute is present.
   24   attr_accessor :numeral
   25 
   26   # Public: Gets/Sets the location in the AsciiDoc source where this block begins.
   27   attr_accessor :source_location
   28 
   29   # Public: Get/Set the String style (block type qualifier) for this block.
   30   attr_accessor :style
   31 
   32   # Public: Substitutions to be applied to content in this block.
   33   attr_reader :subs
   34 
   35   def initialize parent, context, opts = {}
   36     super
   37     @content_model = :compound
   38     @blocks = []
   39     @subs = []
   40     @id = @title = @caption = @numeral = @style = @default_subs = @source_location = nil
   41     if context == :document || context == :section
   42       @level = @next_section_index = 0
   43       @next_section_ordinal = 1
   44     elsif AbstractBlock === parent
   45       @level = parent.level
   46     else
   47       @level = nil
   48     end
   49   end
   50 
   51   def block?
   52     true
   53   end
   54 
   55   def inline?
   56     false
   57   end
   58 
   59   # Public: Get the source file where this block started
   60   def file
   61     @source_location && @source_location.file
   62   end
   63 
   64   # Public: Get the source line number where this block started
   65   def lineno
   66     @source_location && @source_location.lineno
   67   end
   68 
   69   # Public: Get the converted String content for this Block.  If the block
   70   # has child blocks, the content method should cause them to be
   71   # converted and returned as content that can be included in the
   72   # parent block's template.
   73   def convert
   74     @document.playback_attributes @attributes
   75     converter.convert self
   76   end
   77 
   78   # Deprecated: Use {AbstractBlock#convert} instead.
   79   alias render convert
   80 
   81   # Public: Get the converted result of the child blocks by converting the
   82   # children appropriate to content model that this block supports.
   83   def content
   84     @blocks.map {|b| b.convert }.join LF
   85   end
   86 
   87   # Public: Update the context of this block.
   88   #
   89   # This method changes the context of this block. It also updates the node name accordingly.
   90   #
   91   # context - the context Symbol context to assign to this block
   92   #
   93   # Returns the new context Symbol assigned to this block
   94   def context= context
   95     @node_name = (@context = context).to_s
   96   end
   97 
   98   # Public: Append a content block to this block's list of blocks.
   99   #
  100   # block - The new child block.
  101   #
  102   # Examples
  103   #
  104   #   block = Block.new(parent, :preamble, content_model: :compound)
  105   #
  106   #   block << Block.new(block, :paragraph, source: 'p1')
  107   #   block << Block.new(block, :paragraph, source: 'p2')
  108   #   block.blocks?
  109   #   # => true
  110   #   block.blocks.size
  111   #   # => 2
  112   #
  113   # Returns The parent Block
  114   def << block
  115     block.parent = self unless block.parent == self
  116     @blocks << block
  117     self
  118   end
  119 
  120   # NOTE append alias required for adapting to a Java API
  121   alias append <<
  122 
  123   # Public: Determine whether this Block contains block content
  124   #
  125   # Returns A Boolean indicating whether this Block has block content
  126   def blocks?
  127     @blocks.empty? ? false : true
  128   end
  129 
  130   # Public: Check whether this block has any child Section objects.
  131   #
  132   # Only applies to Document and Section instances
  133   #
  134   # Returns A [Boolean] to indicate whether this block has child Section objects
  135   def sections?
  136     @next_section_index > 0
  137   end
  138 
  139   # Deprecated: Legacy property to get the String or Integer numeral of this section.
  140   def number
  141     (Integer @numeral) rescue @numeral
  142   end
  143 
  144   # Public: Walk the document tree and find all block-level nodes that match the specified selector (context, style, id,
  145   # role, and/or custom filter).
  146   #
  147   # If a Ruby block is given, it's applied as a supplemental filter. If the filter returns true (which implies :accept),
  148   # the node is accepted and node traversal continues. If the filter returns false (which implies :skip), the node is
  149   # skipped, but its children are still visited. If the filter returns :reject, the node and all its descendants are
  150   # rejected. If the filter returns :prune, the node is accepted, but its descendants are rejected. If no selector
  151   # or filter block is supplied, all block-level nodes in the tree are returned.
  152   #
  153   # Examples
  154   #
  155   #   doc.find_by context: :section
  156   #   #=> Asciidoctor::Section@14459860 { level: 0, title: "Hello, AsciiDoc!", blocks: 0 }
  157   #   #=> Asciidoctor::Section@14505460 { level: 1, title: "First Section", blocks: 1 }
  158   #
  159   #   doc.find_by(context: :section) {|section| section.level == 1 }
  160   #   #=> Asciidoctor::Section@14505460 { level: 1, title: "First Section", blocks: 1 }
  161   #
  162   #   doc.find_by context: :listing, style: 'source'
  163   #   #=> Asciidoctor::Block@13136720 { context: :listing, content_model: :verbatim, style: "source", lines: 1 }
  164   #
  165   # Returns An Array of block-level nodes that match the filter or an empty Array if no matches are found
  166   #--
  167   # TODO support jQuery-style selector (e.g., image.thumb)
  168   def find_by selector = {}, &block
  169     find_by_internal selector, (result = []), &block
  170   rescue ::StopIteration
  171     result
  172   end
  173 
  174   alias query find_by
  175 
  176   # Move to the next adjacent block in document order. If the current block is the last
  177   # item in a list, this method will return the following sibling of the list block.
  178   def next_adjacent_block
  179     unless @context == :document
  180       if (p = @parent).context == :dlist && @context == :list_item
  181         (sib = p.items[(p.items.find_index {|terms, desc| (terms.include? self) || desc == self }) + 1]) ? sib : p.next_adjacent_block
  182       else
  183         (sib = p.blocks[(p.blocks.find_index self) + 1]) ? sib : p.next_adjacent_block
  184       end
  185     end
  186   end
  187 
  188   # Public: Get the Array of child Section objects
  189   #
  190   # Only applies to Document and Section instances
  191   #
  192   # Examples
  193   #
  194   #   doc << (sect1 = Section.new doc, 1)
  195   #   sect1.title = 'Section 1'
  196   #   para1 = Block.new sect1, :paragraph, source: 'Paragraph 1'
  197   #   para2 = Block.new sect1, :paragraph, source: 'Paragraph 2'
  198   #   sect1 << para1 << para2
  199   #   sect1 << (sect1_1 = Section.new sect1, 2)
  200   #   sect1_1.title = 'Section 1.1'
  201   #   sect1_1 << (Block.new sect1_1, :paragraph, source: 'Paragraph 3')
  202   #   sect1.blocks?
  203   #   # => true
  204   #   sect1.blocks.size
  205   #   # => 3
  206   #   sect1.sections.size
  207   #   # => 1
  208   #
  209   # Returns an [Array] of Section objects
  210   def sections
  211     @blocks.select {|block| block.context == :section }
  212   end
  213 
  214   # Public: Returns the converted alt text for this block image.
  215   #
  216   # Returns the [String] value of the alt attribute with XML special character
  217   # and replacement substitutions applied.
  218   def alt
  219     if (text = @attributes['alt'])
  220       if text == @attributes['default-alt']
  221         sub_specialchars text
  222       else
  223         text = sub_specialchars text
  224         (ReplaceableTextRx.match? text) ? (sub_replacements text) : text
  225       end
  226     else
  227       ''
  228     end
  229   end
  230 
  231   # Gets the caption for this block.
  232   #
  233   # This method routes the deprecated use of the caption method on an
  234   # admonition block to the textlabel attribute.
  235   #
  236   # Returns the [String] caption for this block (or the value of the textlabel
  237   # attribute if this is an admonition block).
  238   def caption
  239     @context == :admonition ? @attributes['textlabel'] : @caption
  240   end
  241 
  242   # Public: Convenience method that returns the interpreted title of the Block
  243   # with the caption prepended.
  244   #
  245   # Concatenates the value of this Block's caption instance variable and the
  246   # return value of this Block's title method. No space is added between the
  247   # two values. If the Block does not have a caption, the interpreted title is
  248   # returned.
  249   #
  250   # Returns the converted String title prefixed with the caption, or just the
  251   # converted String title if no caption is set
  252   def captioned_title
  253     %(#{@caption}#{title})
  254   end
  255 
  256   # Public: Retrieve the list marker keyword for the specified list type.
  257   #
  258   # For use in the HTML type attribute.
  259   #
  260   # list_type - the type of list; default to the @style if not specified
  261   #
  262   # Returns the single-character [String] keyword that represents the marker for the specified list type
  263   def list_marker_keyword list_type = nil
  264     ORDERED_LIST_KEYWORDS[list_type || @style]
  265   end
  266 
  267   # Public: Get the String title of this Block with title substitions applied
  268   #
  269   # The following substitutions are applied to block and section titles:
  270   #
  271   # :specialcharacters, :quotes, :replacements, :macros, :attributes and :post_replacements
  272   #
  273   # Examples
  274   #
  275   #   block.title = "Foo 3^ # {two-colons} Bar(1)"
  276   #   block.title
  277   #   => "Foo 3^ # :: Bar(1)"
  278   #
  279   # Returns the converted String title for this Block, or nil if the source title is falsy
  280   def title
  281     # prevent substitutions from being applied to title multiple times
  282     @converted_title ||= @title && (apply_title_subs @title)
  283   end
  284 
  285   # Public: A convenience method that checks whether the title of this block is defined.
  286   #
  287   # Returns a [Boolean] indicating whether this block has a title.
  288   def title?
  289     @title ? true : false
  290   end
  291 
  292   # Public: Set the String block title.
  293   #
  294   # Returns the new String title assigned to this Block
  295   def title= val
  296     @converted_title = nil
  297     @title = val
  298   end
  299 
  300   # Public: A convenience method that checks whether the specified
  301   # substitution is enabled for this block.
  302   #
  303   # name - The Symbol substitution name
  304   #
  305   # Returns A Boolean indicating whether the specified substitution is
  306   # enabled for this block
  307   def sub? name
  308     @subs.include? name
  309   end
  310 
  311   # Public: Remove a substitution from this block
  312   #
  313   # sub  - The Symbol substitution name
  314   #
  315   # Returns nothing
  316   def remove_sub sub
  317     @subs.delete sub
  318     nil
  319   end
  320 
  321   # Public: Generate cross reference text (xreftext) that can be used to refer
  322   # to this block.
  323   #
  324   # Use the explicit reftext for this block, if specified, retrieved from the
  325   # {#reftext} method. Otherwise, if this is a section or captioned block (a
  326   # block with both a title and caption), generate the xreftext according to
  327   # the value of the xrefstyle argument (e.g., full, short). This logic may
  328   # leverage the {Substitutors#sub_quotes} method to apply formatting to the
  329   # text. If this is not a captioned block, return the title, if present, or
  330   # nil otherwise.
  331   #
  332   # xrefstyle - An optional String that specifies the style to use to format
  333   #             the xreftext ('full', 'short', or 'basic') (default: nil).
  334   #
  335   # Returns the generated [String] xreftext used to refer to this block or
  336   # nothing if there isn't sufficient information to generate one.
  337   def xreftext xrefstyle = nil
  338     if (val = reftext) && !val.empty?
  339       val
  340     # NOTE xrefstyle only applies to blocks with a title and a caption or number
  341     elsif xrefstyle && @title && @caption
  342       case xrefstyle
  343       when 'full'
  344         quoted_title = sub_placeholder (sub_quotes @document.compat_mode ? %q(``%s'') : '"`%s`"'), title
  345         if @numeral && (caption_attr_name = CAPTION_ATTR_NAMES[@context]) && (prefix = @document.attributes[caption_attr_name])
  346           %(#{prefix} #{@numeral}, #{quoted_title})
  347         else
  348           %(#{@caption.chomp '. '}, #{quoted_title})
  349         end
  350       when 'short'
  351         if @numeral && (caption_attr_name = CAPTION_ATTR_NAMES[@context]) && (prefix = @document.attributes[caption_attr_name])
  352           %(#{prefix} #{@numeral})
  353         else
  354           @caption.chomp '. '
  355         end
  356       else # 'basic'
  357         title
  358       end
  359     else
  360       title
  361     end
  362   end
  363 
  364   # Public: Generate and assign caption to block if not already assigned.
  365   #
  366   # If the block has a title and a caption prefix is available for this block,
  367   # then build a caption from this information, assign it a number and store it
  368   # to the caption attribute on the block.
  369   #
  370   # If a caption has already been assigned to this block, do nothing.
  371   #
  372   # The parts of a complete caption are: <prefix> <number>. <title>
  373   # This partial caption represents the part the precedes the title.
  374   #
  375   # value           - The String caption to assign to this block or nil to use document attribute.
  376   # caption_context - The Symbol context to use when resolving caption-related attributes. If not provided, the name of
  377   #                   the context for this block is used. Only certain contexts allow the caption to be looked up.
  378   #                   (default: @context)
  379   #
  380   # Returns nothing.
  381   def assign_caption value, caption_context = @context
  382     unless @caption || !@title || (@caption = value || @document.attributes['caption'])
  383       if (attr_name = CAPTION_ATTR_NAMES[caption_context]) && (prefix = @document.attributes[attr_name])
  384         @caption = %(#{prefix} #{@numeral = @document.increment_and_store_counter %(#{caption_context}-number), self}. )
  385         nil
  386       end
  387     end
  388   end
  389 
  390   # Internal: Assign the next index (0-based) and numeral (1-based) to the section.
  391   # If the section is an appendix, the numeral is a letter (starting with A). This
  392   # method also assigns the appendix caption.
  393   #
  394   # section - The section to which to assign the next index and numeral.
  395   #
  396   # Assign to the specified section the next index and, if the section is
  397   # numbered, the numeral within this block (its parent).
  398   #
  399   # Returns nothing
  400   def assign_numeral section
  401     @next_section_index = (section.index = @next_section_index) + 1
  402     if (like = section.numbered)
  403       if (sectname = section.sectname) == 'appendix'
  404         section.numeral = @document.counter 'appendix-number', 'A'
  405         section.caption = (caption = @document.attributes['appendix-caption']) ? %(#{caption} #{section.numeral}: ) : %(#{section.numeral}. )
  406       # NOTE currently chapters in a book doctype are sequential even for multi-part books (see #979)
  407       elsif sectname == 'chapter' || like == :chapter
  408         section.numeral = (@document.counter 'chapter-number', 1).to_s
  409       else
  410         section.numeral = sectname == 'part' ? (Helpers.int_to_roman @next_section_ordinal) : @next_section_ordinal.to_s
  411         @next_section_ordinal += 1
  412       end
  413     end
  414     nil
  415   end
  416 
  417   # Internal: Reassign the section indexes
  418   #
  419   # Walk the descendents of the current Document or Section
  420   # and reassign the section 0-based index value to each Section
  421   # as it appears in document order.
  422   #
  423   # IMPORTANT You must invoke this method on a node after removing
  424   # child sections or else the internal counters will be off.
  425   #
  426   # Returns nothing
  427   def reindex_sections
  428     @next_section_index = 0
  429     @next_section_ordinal = 1
  430     @blocks.each do |block|
  431       if block.context == :section
  432         assign_numeral block
  433         block.reindex_sections
  434       end
  435     end
  436   end
  437 
  438   protected
  439 
  440   # Internal: Performs the work for find_by, but does not handle the StopIteration exception.
  441   def find_by_internal selector = {}, result = [], &block
  442     if ((any_context = (context_selector = selector[:context]) ? nil : true) || context_selector == @context) &&
  443         (!(style_selector = selector[:style]) || style_selector == @style) &&
  444         (!(role_selector = selector[:role]) || (has_role? role_selector)) &&
  445         (!(id_selector = selector[:id]) || id_selector == @id)
  446       if block_given?
  447         if (verdict = yield self)
  448           case verdict
  449           when :prune
  450             result << self
  451             raise ::StopIteration if id_selector
  452             return result
  453           when :reject
  454             raise ::StopIteration if id_selector
  455             return result
  456           when :stop
  457             raise ::StopIteration
  458           else
  459             result << self
  460             raise ::StopIteration if id_selector
  461           end
  462         elsif id_selector
  463           raise ::StopIteration
  464         end
  465       else
  466         result << self
  467         raise ::StopIteration if id_selector
  468       end
  469     end
  470     case @context
  471     when :document
  472       unless context_selector == :document
  473         # process document header as a section, if present
  474         if header? && (any_context || context_selector == :section)
  475           @header.find_by_internal selector, result, &block
  476         end
  477         @blocks.each do |b|
  478           next if (context_selector == :section && b.context != :section) # optimization
  479           b.find_by_internal selector, result, &block
  480         end
  481       end
  482     when :dlist
  483       # dlist has different structure than other blocks
  484       if any_context || context_selector != :section # optimization
  485         # NOTE the list item of a dlist can be nil, so we have to check
  486         @blocks.flatten.each {|b| b.find_by_internal selector, result, &block if b }
  487       end
  488     when :table
  489       if selector[:traverse_documents]
  490         rows.head.each {|r| r.each {|c| c.find_by_internal selector, result, &block } }
  491         selector = selector.merge context: :document if context_selector == :inner_document
  492         (rows.body + rows.foot).each do |r|
  493           r.each do |c|
  494             c.find_by_internal selector, result, &block
  495             c.inner_document.find_by_internal selector, result, &block if c.style == :asciidoc
  496           end
  497         end
  498       else
  499         (rows.head + rows.body + rows.foot).each {|r| r.each {|c| c.find_by_internal selector, result, &block } }
  500       end
  501     else
  502       @blocks.each do |b|
  503         next if (context_selector == :section && b.context != :section) # optimization
  504         b.find_by_internal selector, result, &block
  505       end
  506     end
  507     result
  508   end
  509 end
  510 end