"Fossies" - the Fresh Open Source Software Archive

Member "asciidoctor-2.0.10/lib/asciidoctor/abstract_node.rb" (1 Jun 2019, 23304 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_node.rb": 2.0.8_vs_2.0.9.

    1 # frozen_string_literal: true
    2 module Asciidoctor
    3 # Public: An abstract base class that provides state and methods for managing a
    4 # node of AsciiDoc content. The state and methods on this class are common to
    5 # all content segments in an AsciiDoc document.
    6 class AbstractNode
    7   include Substitutors, Logging
    8 
    9   # Public: Get the Hash of attributes for this node
   10   attr_reader :attributes
   11 
   12   # Public: Get the Symbol context for this node
   13   attr_reader :context
   14 
   15   # Public: Get the Asciidoctor::Document to which this node belongs
   16   attr_reader :document
   17 
   18   # Public: Get/Set the String id of this node
   19   attr_accessor :id
   20 
   21   # Public: Get the String name of this node
   22   attr_reader :node_name
   23 
   24   # Public: Get the AbstractBlock parent element of this node
   25   attr_reader :parent
   26 
   27   def initialize parent, context, opts = {}
   28     # document is a special case, should refer to itself
   29     if context == :document
   30       @document = self
   31     elsif parent
   32       @document = (@parent = parent).document
   33     end
   34     @node_name = (@context = context).to_s
   35     # NOTE the value of the :attributes option may be nil on an Inline node
   36     @attributes = (attrs = opts[:attributes]) ? attrs.merge : {}
   37     @passthroughs = []
   38   end
   39 
   40   # Public: Returns whether this {AbstractNode} is an instance of {Block}
   41   #
   42   # Returns [Boolean]
   43   def block?
   44     # :nocov:
   45     raise ::NotImplementedError
   46     # :nocov:
   47   end
   48 
   49   # Public: Returns whether this {AbstractNode} is an instance of {Inline}
   50   #
   51   # Returns [Boolean]
   52   def inline?
   53     # :nocov:
   54     raise ::NotImplementedError
   55     # :nocov:
   56   end
   57 
   58   # Public: Get the Asciidoctor::Converter instance being used to convert the
   59   # current Asciidoctor::Document.
   60   def converter
   61     @document.converter
   62   end
   63 
   64   # Public: Associate this Block with a new parent Block
   65   #
   66   # parent - The Block to set as the parent of this Block
   67   #
   68   # Returns the new parent Block associated with this Block
   69   def parent= parent
   70     @parent, @document = parent, parent.document
   71   end
   72 
   73   # Public: Get the value of the specified attribute. If the attribute is not found on this node, fallback_name is set,
   74   # and this node is not the Document node, get the value of the specified attribute from the Document node.
   75   #
   76   # Look for the specified attribute in the attributes on this node and return the value of the attribute, if found.
   77   # Otherwise, if fallback_name is set (default: same as name) and this node is not the Document node, look for that
   78   # attribute on the Document node and return its value, if found. Otherwise, return the default value (default: nil).
   79   #
   80   # name          - The String or Symbol name of the attribute to resolve.
   81   # default_value - The Object value to return if the attribute is not found (default: nil).
   82   # fallback_name - The String or Symbol of the attribute to resolve on the Document if the attribute is not found on
   83   #                 this node (default: same as name).
   84   #
   85   # Returns the [Object] value (typically a String) of the attribute or default_value if the attribute is not found.
   86   def attr name, default_value = nil, fallback_name = nil
   87     @attributes[name.to_s] || (fallback_name && @parent && @document.attributes[(fallback_name == true ? name : fallback_name).to_s] || default_value)
   88   end
   89 
   90   # Public: Check if the specified attribute is defined using the same logic as {#attr}, optionally performing a
   91   # comparison with the expected value if specified.
   92   #
   93   # Look for the specified attribute in the attributes on this node. If not found, fallback_name is specified (default:
   94   # same as name), and this node is not the Document node, look for that attribute on the Document node. In either case,
   95   # if the attribute is found, and the comparison value is truthy, return whether the two values match. Otherwise,
   96   # return whether the attribute was found.
   97   #
   98   # name           - The String or Symbol name of the attribute to resolve.
   99   # expected_value - The expected Object value of the attribute (default: nil).
  100   # fallback_name  - The String or Symbol of the attribute to resolve on the Document if the attribute is not found on
  101   #                  this node (default: same as name).
  102   #
  103   # Returns a [Boolean] indicating whether the attribute exists and, if a truthy comparison value is specified, whether
  104   # the value of the attribute matches the comparison value.
  105   def attr? name, expected_value = nil, fallback_name = nil
  106     if expected_value
  107       expected_value == (@attributes[name.to_s] || (fallback_name && @parent ? @document.attributes[(fallback_name == true ? name : fallback_name).to_s] : nil))
  108     else
  109       (@attributes.key? name.to_s) || (fallback_name && @parent ? (@document.attributes.key? (fallback_name == true ? name : fallback_name).to_s) : false)
  110     end
  111   end
  112 
  113   # Public: Assign the value to the attribute name for the current node.
  114   #
  115   # name      - The String attribute name to assign
  116   # value     - The Object value to assign to the attribute (default: '')
  117   # overwrite - A Boolean indicating whether to assign the attribute
  118   #             if currently present in the attributes Hash (default: true)
  119   #
  120   # Returns a [Boolean] indicating whether the assignment was performed
  121   def set_attr name, value = '', overwrite = true
  122     if overwrite == false && (@attributes.key? name)
  123       false
  124     else
  125       @attributes[name] = value
  126       true
  127     end
  128   end
  129 
  130   # Public: Remove the attribute from the current node.
  131   #
  132   # name      - The String attribute name to remove
  133   #
  134   # Returns the previous [String] value, or nil if the attribute was not present.
  135   def remove_attr name
  136     @attributes.delete name
  137   end
  138 
  139   # Public: A convenience method to check if the specified option attribute is
  140   # enabled on the current node.
  141   #
  142   # Check if the option is enabled. This method simply checks to see if the
  143   # <name>-option attribute is defined on the current node.
  144   #
  145   # name    - the String or Symbol name of the option
  146   #
  147   # return a Boolean indicating whether the option has been specified
  148   def option? name
  149     @attributes[%(#{name}-option)] ? true : false
  150   end
  151 
  152   # Public: Set the specified option on this node.
  153   #
  154   # This method sets the specified option on this node by setting the <name>-option attribute.
  155   #
  156   # name - the String name of the option
  157   #
  158   # Returns Nothing
  159   def set_option name
  160     @attributes[%(#{name}-option)] = ''
  161     nil
  162   end
  163 
  164   # Public: Retrieve the Set of option names that are enabled on this node
  165   #
  166   # Returns a [Set] of option names
  167   def enabled_options
  168     ::Set.new.tap {|accum| @attributes.each_key {|k| accum << (k.slice 0, k.length - 7) if k.to_s.end_with? '-option' } }
  169   end
  170 
  171   # Public: Update the attributes of this node with the new values in
  172   # the attributes argument.
  173   #
  174   # If an attribute already exists with the same key, it's value will
  175   # be overwritten.
  176   #
  177   # new_attributes - A Hash of additional attributes to assign to this node.
  178   #
  179   # Returns the updated attributes [Hash] on this node.
  180   def update_attributes new_attributes
  181     @attributes.update new_attributes
  182   end
  183 
  184   # Public: Retrieves the space-separated String role for this node.
  185   #
  186   # Returns the role as a space-separated [String].
  187   def role
  188     @attributes['role']
  189   end
  190 
  191   # Public: Retrieves the String role names for this node as an Array.
  192   #
  193   # Returns the role names as a String [Array], which is empty if the role attribute is absent on this node.
  194   def roles
  195     (val = @attributes['role']) ? val.split : []
  196   end
  197 
  198   # Public: Checks if the role attribute is set on this node and, if an expected value is given, whether the
  199   # space-separated role matches that value.
  200   #
  201   # expected_value - The expected String value of the role (optional, default: nil)
  202   #
  203   # Returns a [Boolean] indicating whether the role attribute is set on this node and, if an expected value is given,
  204   # whether the space-separated role matches that value.
  205   def role? expected_value = nil
  206     expected_value ? expected_value == @attributes['role'] : (@attributes.key? 'role')
  207   end
  208 
  209   # Public: Checks if the specified role is present in the list of roles for this node.
  210   #
  211   # name - The String name of the role to find.
  212   #
  213   # Returns a [Boolean] indicating whether this node has the specified role.
  214   def has_role? name
  215     # NOTE center + include? is faster than split + include?
  216     (val = @attributes['role']) ? (%( #{val} ).include? %( #{name} )) : false
  217   end
  218 
  219   # Public: Adds the given role directly to this node.
  220   #
  221   # Returns a [Boolean] indicating whether the role was added.
  222   def add_role name
  223     if (val = @attributes['role'])
  224       # NOTE center + include? is faster than split + include?
  225       if %( #{val} ).include? %( #{name} )
  226         false
  227       else
  228         @attributes['role'] = %(#{val} #{name})
  229         true
  230       end
  231     else
  232       @attributes['role'] = name
  233       true
  234     end
  235   end
  236 
  237   # Public: Removes the given role directly from this node.
  238   #
  239   # Returns a [Boolean] indicating whether the role was removed.
  240   def remove_role name
  241     if (val = @attributes['role']) && ((val = val.split).delete name)
  242       if val.empty?
  243         @attributes.delete 'role'
  244       else
  245         @attributes['role'] = val.join ' '
  246       end
  247       true
  248     else
  249       false
  250     end
  251   end
  252 
  253   # Public: A convenience method that returns the value of the reftext attribute with substitutions applied.
  254   def reftext
  255     (val = @attributes['reftext']) ? (apply_reftext_subs val) : nil
  256   end
  257 
  258   # Public: A convenience method that checks if the reftext attribute is defined.
  259   def reftext?
  260     @attributes.key? 'reftext'
  261   end
  262 
  263   # Public: Construct a reference or data URI to an icon image for the
  264   # specified icon name.
  265   #
  266   # If the 'icon' attribute is set on this block, the name is ignored and the
  267   # value of this attribute is used as the target image path. Otherwise,
  268   # construct a target image path by concatenating the value of the 'iconsdir'
  269   # attribute, the icon name, and the value of the 'icontype' attribute
  270   # (defaulting to 'png').
  271   #
  272   # The target image path is then passed through the #image_uri() method. If
  273   # the 'data-uri' attribute is set on the document, the image will be
  274   # safely converted to a data URI.
  275   #
  276   # The return value of this method can be safely used in an image tag.
  277   #
  278   # name - The String name of the icon
  279   #
  280   # Returns A String reference or data URI for an icon image
  281   def icon_uri name
  282     if attr? 'icon'
  283       icon = attr 'icon'
  284       # QUESTION should we be adding the extension if the icon is an absolute URI?
  285       icon = %(#{icon}.#{@document.attr 'icontype', 'png'}) unless Helpers.extname? icon
  286     else
  287       icon = %(#{name}.#{@document.attr 'icontype', 'png'})
  288     end
  289     image_uri icon, 'iconsdir'
  290   end
  291 
  292   # Public: Construct a URI reference or data URI to the target image.
  293   #
  294   # If the target image is a URI reference, then leave it untouched.
  295   #
  296   # The target image is resolved relative to the directory retrieved from the
  297   # specified attribute key, if provided.
  298   #
  299   # If the 'data-uri' attribute is set on the document, and the safe mode level
  300   # is less than SafeMode::SECURE, the image will be safely converted to a data URI
  301   # by reading it from the same directory. If neither of these conditions
  302   # are satisfied, a relative path (i.e., URL) will be returned.
  303   #
  304   # The return value of this method can be safely used in an image tag.
  305   #
  306   # target_image - A String path to the target image
  307   # asset_dir_key - The String attribute key used to lookup the directory where
  308   #                the image is located (default: 'imagesdir')
  309   #
  310   # Returns A String reference or data URI for the target image
  311   def image_uri(target_image, asset_dir_key = 'imagesdir')
  312     if (doc = @document).safe < SafeMode::SECURE && (doc.attr? 'data-uri')
  313       if ((Helpers.uriish? target_image) && (target_image = Helpers.encode_spaces_in_uri target_image)) ||
  314           (asset_dir_key && (images_base = doc.attr asset_dir_key) && (Helpers.uriish? images_base) &&
  315           (target_image = normalize_web_path target_image, images_base, false))
  316         (doc.attr? 'allow-uri-read') ? (generate_data_uri_from_uri target_image, (doc.attr? 'cache-uri')) : target_image
  317       else
  318         generate_data_uri target_image, asset_dir_key
  319       end
  320     else
  321       normalize_web_path target_image, (asset_dir_key ? (doc.attr asset_dir_key) : nil)
  322     end
  323   end
  324 
  325   # Public: Construct a URI reference to the target media.
  326   #
  327   # If the target media is a URI reference, then leave it untouched.
  328   #
  329   # The target media is resolved relative to the directory retrieved from the
  330   # specified attribute key, if provided.
  331   #
  332   # The return value can be safely used in a media tag (img, audio, video).
  333   #
  334   # target        - A String reference to the target media
  335   # asset_dir_key - The String attribute key used to lookup the directory where
  336   #                 the media is located (default: 'imagesdir')
  337   #
  338   # Returns A String reference for the target media
  339   def media_uri(target, asset_dir_key = 'imagesdir')
  340     normalize_web_path target, (asset_dir_key ? @document.attr(asset_dir_key) : nil)
  341   end
  342 
  343   # Public: Generate a data URI that can be used to embed an image in the output document
  344   #
  345   # First, and foremost, the target image path is cleaned if the document safe mode level
  346   # is set to at least SafeMode::SAFE (a condition which is true by default) to prevent access
  347   # to ancestor paths in the filesystem. The image data is then read and converted to
  348   # Base64. Finally, a data URI is built which can be used in an image tag.
  349   #
  350   # target_image - A String path to the target image
  351   # asset_dir_key - The String attribute key used to lookup the directory where
  352   #                the image is located (default: nil)
  353   #
  354   # Returns A String data URI containing the content of the target image
  355   def generate_data_uri(target_image, asset_dir_key = nil)
  356     if (ext = Helpers.extname target_image, nil)
  357       mimetype = ext == '.svg' ? 'image/svg+xml' : %(image/#{ext.slice 1, ext.length})
  358     else
  359       mimetype = 'application/octet-stream'
  360     end
  361 
  362     if asset_dir_key
  363       image_path = normalize_system_path(target_image, @document.attr(asset_dir_key), nil, target_name: 'image')
  364     else
  365       image_path = normalize_system_path(target_image)
  366     end
  367 
  368     if ::File.readable? image_path
  369       # NOTE base64 is autoloaded by reference to ::Base64
  370       %(data:#{mimetype};base64,#{::Base64.strict_encode64 ::File.binread image_path})
  371     else
  372       logger.warn %(image to embed not found or not readable: #{image_path})
  373       %(data:#{mimetype};base64,)
  374       # uncomment to return 1 pixel white dot instead
  375       #'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='
  376     end
  377   end
  378 
  379   # Public: Read the image data from the specified URI and generate a data URI
  380   #
  381   # The image data is read from the URI and converted to Base64. A data URI is
  382   # constructed from the content_type header and Base64 data and returned,
  383   # which can then be used in an image tag.
  384   #
  385   # image_uri  - The URI from which to read the image data. Can be http://, https:// or ftp://
  386   # cache_uri  - A Boolean to control caching. When true, the open-uri-cached library
  387   #              is used to cache the image for subsequent reads. (default: false)
  388   #
  389   # Returns A data URI string built from Base64 encoded data read from the URI
  390   # and the mime type specified in the Content Type header.
  391   def generate_data_uri_from_uri image_uri, cache_uri = false
  392     if cache_uri
  393       # caching requires the open-uri-cached gem to be installed
  394       # processing will be automatically aborted if these libraries can't be opened
  395       Helpers.require_library 'open-uri/cached', 'open-uri-cached'
  396     elsif !RUBY_ENGINE_OPAL
  397       # autoload open-uri
  398       ::OpenURI
  399     end
  400 
  401     begin
  402       mimetype, bindata = ::OpenURI.open_uri(image_uri, URI_READ_MODE) {|f| [f.content_type, f.read] }
  403       # NOTE base64 is autoloaded by reference to ::Base64
  404       %(data:#{mimetype};base64,#{::Base64.strict_encode64 bindata})
  405     rescue
  406       logger.warn %(could not retrieve image data from URI: #{image_uri})
  407       image_uri
  408       # uncomment to return empty data (however, mimetype needs to be resolved)
  409       #%(data:#{mimetype}:base64,)
  410       # uncomment to return 1 pixel white dot instead
  411       #'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='
  412     end
  413   end
  414 
  415   # Public: Normalize the asset file or directory to a concrete and rinsed path
  416   #
  417   # Delegates to normalize_system_path, with the start path set to the value of
  418   # the base_dir instance variable on the Document object.
  419   def normalize_asset_path(asset_ref, asset_name = 'path', autocorrect = true)
  420     normalize_system_path(asset_ref, @document.base_dir, nil, target_name: asset_name, recover: autocorrect)
  421   end
  422 
  423   # Public: Resolve and normalize a secure path from the target and start paths
  424   # using the PathResolver.
  425   #
  426   # See {PathResolver#system_path} for details.
  427   #
  428   # The most important functionality in this method is to prevent resolving a
  429   # path outside of the jail (which defaults to the directory of the source
  430   # file, stored in the base_dir instance variable on Document) if the document
  431   # safe level is set to SafeMode::SAFE or greater (a condition which is true
  432   # by default).
  433   #
  434   # target - the String target path
  435   # start  - the String start (i.e., parent) path
  436   # jail   - the String jail path to confine the resolved path
  437   # opts   - an optional Hash of options to control processing (default: {}):
  438   #          * :recover is used to control whether the processor should
  439   #            automatically recover when an illegal path is encountered
  440   #          * :target_name is used in messages to refer to the path being resolved
  441   #
  442   # raises a SecurityError if a jail is specified and the resolved path is
  443   # outside the jail.
  444   #
  445   # Returns the [String] path resolved from the start and target paths, with any
  446   # parent references resolved and self references removed. If a jail is provided,
  447   # this path will be guaranteed to be contained within the jail.
  448   def normalize_system_path target, start = nil, jail = nil, opts = {}
  449     if (doc = @document).safe < SafeMode::SAFE
  450       if start
  451         start = ::File.join doc.base_dir, start unless doc.path_resolver.root? start
  452       else
  453         start = doc.base_dir
  454       end
  455     else
  456       start = doc.base_dir unless start
  457       jail = doc.base_dir unless jail
  458     end
  459     doc.path_resolver.system_path target, start, jail, opts
  460   end
  461 
  462   # Public: Normalize the web path using the PathResolver.
  463   #
  464   # See {PathResolver#web_path} for details about path resolution and encoding.
  465   #
  466   # target              - the String target path
  467   # start               - the String start (i.e, parent) path (optional, default: nil)
  468   # preserve_uri_target - a Boolean indicating whether target should be preserved if contains a URI (default: true)
  469   #
  470   # Returns the resolved [String] path
  471   def normalize_web_path(target, start = nil, preserve_uri_target = true)
  472     if preserve_uri_target && (Helpers.uriish? target)
  473       Helpers.encode_spaces_in_uri target
  474     else
  475       @document.path_resolver.web_path target, start
  476     end
  477   end
  478 
  479   # Public: Read the contents of the file at the specified path.
  480   # This method assumes that the path is safe to read. It checks
  481   # that the file is readable before attempting to read it.
  482   #
  483   # path - the String path from which to read the contents
  484   # opts - a Hash of options to control processing (default: {})
  485   #        * :warn_on_failure a Boolean that controls whether a warning
  486   #          is issued if the file cannot be read (default: false)
  487   #        * :normalize a Boolean that controls whether the lines
  488   #          are normalized and coerced to UTF-8 (default: false)
  489   #
  490   # Returns the [String] content of the file at the specified path, or nil
  491   # if the file does not exist.
  492   def read_asset path, opts = {}
  493     # remap opts for backwards compatibility
  494     opts = { warn_on_failure: (opts != false) } unless ::Hash === opts
  495     if ::File.readable? path
  496       # QUESTION should we chomp content if normalize is false?
  497       opts[:normalize] ? ((Helpers.prepare_source_string ::File.read path, mode: FILE_READ_MODE).join LF) : (::File.read path, mode: FILE_READ_MODE)
  498     elsif opts[:warn_on_failure]
  499       logger.warn %(#{(attr 'docfile') || '<stdin>'}: #{opts[:label] || 'file'} does not exist or cannot be read: #{path})
  500       nil
  501     end
  502   end
  503 
  504   # Public: Resolve the URI or system path to the specified target, then read and return its contents
  505   #
  506   # The URI or system path of the target is first resolved. If the resolved path is a URI, read the
  507   # contents from the URI if the allow-uri-read attribute is set, enabling caching if the cache-uri
  508   # attribute is also set. If the resolved path is not a URI, read the contents of the file from the
  509   # file system. If the normalize option is set, the data will be normalized.
  510   #
  511   # target - The URI or local path from which to read the data.
  512   # opts   - a Hash of options to control processing (default: {})
  513   #          * :label the String label of the target to use in warning messages (default: 'asset')
  514   #          * :normalize a Boolean that indicates whether the data should be normalized (default: false)
  515   #          * :start the String relative base path to use when resolving the target (default: nil)
  516   #          * :warn_on_failure a Boolean that indicates whether warnings are issued if the target cannot be read (default: true)
  517   # Returns the contents of the resolved target or nil if the resolved target cannot be read
  518   # --
  519   # TODO refactor other methods in this class to use this method were possible (repurposing if necessary)
  520   def read_contents target, opts = {}
  521     doc = @document
  522     if (Helpers.uriish? target) || ((start = opts[:start]) && (Helpers.uriish? start) &&
  523         (target = doc.path_resolver.web_path target, start))
  524       if doc.attr? 'allow-uri-read'
  525         Helpers.require_library 'open-uri/cached', 'open-uri-cached' if doc.attr? 'cache-uri'
  526         begin
  527           if opts[:normalize]
  528             (Helpers.prepare_source_string ::OpenURI.open_uri(target, URI_READ_MODE) {|f| f.read }).join LF
  529           else
  530             ::OpenURI.open_uri(target, URI_READ_MODE) {|f| f.read }
  531           end
  532         rescue
  533           logger.warn %(could not retrieve contents of #{opts[:label] || 'asset'} at URI: #{target}) if opts.fetch :warn_on_failure, true
  534           return
  535         end
  536       else
  537         logger.warn %(cannot retrieve contents of #{opts[:label] || 'asset'} at URI: #{target} (allow-uri-read attribute not enabled)) if opts.fetch :warn_on_failure, true
  538         return
  539       end
  540     else
  541       target = normalize_system_path target, opts[:start], nil, target_name: (opts[:label] || 'asset')
  542       read_asset target, normalize: opts[:normalize], warn_on_failure: (opts.fetch :warn_on_failure, true), label: opts[:label]
  543     end
  544   end
  545 
  546   # Deprecated: Check whether the specified String is a URI by
  547   # matching it against the Asciidoctor::UriSniffRx regex.
  548   #
  549   # In use by Asciidoctor PDF
  550   #
  551   # @deprecated Use Helpers.uriish? instead
  552   def is_uri? str
  553     Helpers.uriish? str
  554   end
  555 end
  556 end