asciidoctor 1.5.5 → 1.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of asciidoctor might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +216 -1
- data/CONTRIBUTING.adoc +2 -2
- data/Gemfile +20 -1
- data/LICENSE.adoc +1 -1
- data/README-fr.adoc +4 -3
- data/README-jp.adoc +11 -10
- data/README-zh_CN.adoc +4 -3
- data/README.adoc +17 -202
- data/Rakefile +41 -25
- data/asciidoctor.gemspec +9 -10
- data/data/locale/attributes.adoc +216 -34
- data/data/stylesheets/asciidoctor-default.css +23 -16
- data/features/step_definitions.rb +15 -19
- data/features/xref.feature +584 -20
- data/lib/asciidoctor.rb +292 -278
- data/lib/asciidoctor/abstract_block.rb +155 -94
- data/lib/asciidoctor/abstract_node.rb +108 -94
- data/lib/asciidoctor/attribute_list.rb +30 -22
- data/lib/asciidoctor/block.rb +7 -7
- data/lib/asciidoctor/cli/invoker.rb +47 -34
- data/lib/asciidoctor/cli/options.rb +22 -11
- data/lib/asciidoctor/converter.rb +3 -3
- data/lib/asciidoctor/converter/base.rb +2 -2
- data/lib/asciidoctor/converter/composite.rb +1 -1
- data/lib/asciidoctor/converter/docbook45.rb +2 -2
- data/lib/asciidoctor/converter/docbook5.rb +132 -87
- data/lib/asciidoctor/converter/factory.rb +0 -1
- data/lib/asciidoctor/converter/html5.rb +116 -98
- data/lib/asciidoctor/converter/manpage.rb +51 -52
- data/lib/asciidoctor/converter/template.rb +47 -36
- data/lib/asciidoctor/core_ext.rb +8 -2
- data/lib/asciidoctor/core_ext/1.8.7/hash/key.rb +4 -0
- data/lib/asciidoctor/core_ext/1.8.7/io/binread.rb +6 -0
- data/lib/asciidoctor/core_ext/1.8.7/io/write.rb +5 -0
- data/lib/asciidoctor/core_ext/1.8.7/string/chr.rb +1 -1
- data/lib/asciidoctor/core_ext/1.8.7/string/{limit.rb → limit_bytesize.rb} +7 -6
- data/lib/asciidoctor/core_ext/1.8.7/symbol/empty.rb +6 -0
- data/lib/asciidoctor/core_ext/1.8.7/symbol/length.rb +1 -1
- data/lib/asciidoctor/core_ext/nil_or_empty.rb +5 -5
- data/lib/asciidoctor/core_ext/regexp/is_match.rb +3 -0
- data/lib/asciidoctor/core_ext/string/{limit.rb → limit_bytesize.rb} +2 -2
- data/lib/asciidoctor/document.rb +216 -213
- data/lib/asciidoctor/extensions.rb +318 -185
- data/lib/asciidoctor/helpers.rb +35 -35
- data/lib/asciidoctor/inline.rb +32 -1
- data/lib/asciidoctor/list.rb +22 -6
- data/lib/asciidoctor/parser.rb +1008 -1038
- data/lib/asciidoctor/path_resolver.rb +46 -50
- data/lib/asciidoctor/reader.rb +275 -251
- data/lib/asciidoctor/section.rb +86 -58
- data/lib/asciidoctor/stylesheets.rb +6 -6
- data/lib/asciidoctor/substitutors.rb +567 -649
- data/lib/asciidoctor/table.rb +163 -108
- data/lib/asciidoctor/version.rb +1 -1
- data/man/asciidoctor.1 +18 -16
- data/man/asciidoctor.adoc +15 -13
- data/test/attributes_test.rb +138 -22
- data/test/blocks_test.rb +377 -97
- data/test/converter_test.rb +13 -0
- data/test/document_test.rb +244 -34
- data/test/extensions_test.rb +409 -42
- data/test/fixtures/asciidoc_index.txt +521 -0
- data/test/fixtures/basic-docinfo-footer.html +6 -0
- data/test/fixtures/basic-docinfo-footer.xml +8 -0
- data/test/fixtures/basic-docinfo.html +1 -0
- data/test/fixtures/basic-docinfo.xml +4 -0
- data/test/fixtures/basic.asciidoc +5 -0
- data/test/fixtures/chapter-a.adoc +3 -0
- data/test/fixtures/child-include.adoc +5 -0
- data/test/fixtures/circle.svg +9 -0
- data/test/fixtures/custom-backends/erb/html5/block_paragraph.html.erb +6 -0
- data/test/fixtures/custom-backends/haml/docbook45/block_paragraph.xml.haml +6 -0
- data/test/fixtures/custom-backends/haml/html5-tweaks/block_paragraph.html.haml +1 -0
- data/test/fixtures/custom-backends/haml/html5/block_paragraph.html.haml +3 -0
- data/test/fixtures/custom-backends/haml/html5/block_sidebar.html.haml +5 -0
- data/test/fixtures/custom-backends/slim/docbook45/block_paragraph.xml.slim +6 -0
- data/test/fixtures/custom-backends/slim/html5/block_paragraph.html.slim +3 -0
- data/test/fixtures/custom-backends/slim/html5/block_sidebar.html.slim +5 -0
- data/test/fixtures/custom-docinfodir/basic-docinfo.html +1 -0
- data/test/fixtures/custom-docinfodir/docinfo.html +1 -0
- data/test/fixtures/docinfo-footer.html +1 -0
- data/test/fixtures/docinfo-footer.xml +9 -0
- data/test/fixtures/docinfo.html +1 -0
- data/test/fixtures/docinfo.xml +3 -0
- data/test/fixtures/dot.gif +0 -0
- data/test/fixtures/encoding.asciidoc +13 -0
- data/test/fixtures/grandchild-include.adoc +3 -0
- data/test/fixtures/hello-asciidoctor.pdf +69 -0
- data/test/fixtures/include-file.asciidoc +24 -0
- data/test/fixtures/include-file.ml +3 -0
- data/test/fixtures/include-file.xml +5 -0
- data/test/fixtures/master.adoc +5 -0
- data/test/fixtures/mismatched-end-tag.adoc +7 -0
- data/test/fixtures/parent-include-restricted.adoc +5 -0
- data/test/fixtures/parent-include.adoc +5 -0
- data/test/fixtures/sample.asciidoc +26 -0
- data/test/fixtures/stylesheets/custom.css +3 -0
- data/test/fixtures/subs-docinfo.html +2 -0
- data/test/fixtures/subs.adoc +7 -0
- data/test/fixtures/tagged-class-enclosed.rb +26 -0
- data/test/fixtures/tagged-class.rb +23 -0
- data/test/fixtures/tip.gif +0 -0
- data/test/invoker_test.rb +82 -4
- data/test/links_test.rb +312 -37
- data/test/lists_test.rb +204 -25
- data/test/manpage_test.rb +191 -4
- data/test/options_test.rb +18 -1
- data/test/paragraphs_test.rb +32 -7
- data/test/parser_test.rb +150 -30
- data/test/paths_test.rb +47 -13
- data/test/preamble_test.rb +1 -1
- data/test/reader_test.rb +366 -126
- data/test/sections_test.rb +203 -56
- data/test/substitutions_test.rb +339 -131
- data/test/tables_test.rb +315 -15
- data/test/test_helper.rb +400 -0
- data/test/text_test.rb +5 -5
- metadata +110 -22
| @@ -2,27 +2,28 @@ if RUBY_ENGINE_JRUBY | |
| 2 2 | 
             
              class String
         | 
| 3 3 | 
             
                # Safely truncate the string to the specified number of bytes.
         | 
| 4 4 | 
             
                # If a multibyte char gets split, the dangling fragment is removed.
         | 
| 5 | 
            -
                def  | 
| 5 | 
            +
                def limit_bytesize size
         | 
| 6 6 | 
             
                  return self unless size < bytesize
         | 
| 7 7 | 
             
                  result = (unpack %(a#{size}))[0]
         | 
| 8 8 | 
             
                  begin
         | 
| 9 9 | 
             
                    result.unpack 'U*'
         | 
| 10 | 
            -
                  rescue ArgumentError
         | 
| 10 | 
            +
                  rescue ::ArgumentError
         | 
| 11 11 | 
             
                    result.chop!
         | 
| 12 12 | 
             
                    retry
         | 
| 13 13 | 
             
                  end
         | 
| 14 14 | 
             
                  result
         | 
| 15 | 
            -
                end unless method_defined? : | 
| 15 | 
            +
                end unless method_defined? :limit_bytesize
         | 
| 16 16 | 
             
              end
         | 
| 17 17 | 
             
            else
         | 
| 18 18 | 
             
              class String
         | 
| 19 | 
            +
                ValidTrailingCharRx = /.$/u
         | 
| 19 20 | 
             
                # Safely truncate the string to the specified number of bytes.
         | 
| 20 21 | 
             
                # If a multibyte char gets split, the dangling fragment is removed.
         | 
| 21 | 
            -
                def  | 
| 22 | 
            +
                def limit_bytesize size
         | 
| 22 23 | 
             
                  return self unless size < bytesize
         | 
| 23 24 | 
             
                  result = (unpack %(a#{size}))[0]
         | 
| 24 | 
            -
                  result.chop! until result.empty? ||  | 
| 25 | 
            +
                  result.chop! until result.empty? || (ValidTrailingCharRx.match? result)
         | 
| 25 26 | 
             
                  result
         | 
| 26 | 
            -
                end unless method_defined? : | 
| 27 | 
            +
                end unless method_defined? :limit_bytesize
         | 
| 27 28 | 
             
              end
         | 
| 28 29 | 
             
            end
         | 
| @@ -3,21 +3,21 @@ | |
| 3 3 | 
             
            # String, Array, Hash, and Numeric.
         | 
| 4 4 |  | 
| 5 5 | 
             
            class NilClass
         | 
| 6 | 
            -
              alias  | 
| 6 | 
            +
              alias nil_or_empty? nil? unless method_defined? :nil_or_empty?
         | 
| 7 7 | 
             
            end
         | 
| 8 8 |  | 
| 9 9 | 
             
            class String
         | 
| 10 | 
            -
              alias  | 
| 10 | 
            +
              alias nil_or_empty? empty? unless method_defined? :nil_or_empty?
         | 
| 11 11 | 
             
            end
         | 
| 12 12 |  | 
| 13 13 | 
             
            class Array
         | 
| 14 | 
            -
              alias  | 
| 14 | 
            +
              alias nil_or_empty? empty? unless method_defined? :nil_or_empty?
         | 
| 15 15 | 
             
            end
         | 
| 16 16 |  | 
| 17 17 | 
             
            class Hash
         | 
| 18 | 
            -
              alias  | 
| 18 | 
            +
              alias nil_or_empty? empty? unless method_defined? :nil_or_empty?
         | 
| 19 19 | 
             
            end
         | 
| 20 20 |  | 
| 21 21 | 
             
            class Numeric
         | 
| 22 | 
            -
              alias  | 
| 22 | 
            +
              alias nil_or_empty? nil? unless method_defined? :nil_or_empty?
         | 
| 23 23 | 
             
            end
         | 
| @@ -1,10 +1,10 @@ | |
| 1 1 | 
             
            class String
         | 
| 2 2 | 
             
              # Safely truncate the string to the specified number of bytes.
         | 
| 3 3 | 
             
              # If a multibyte char gets split, the dangling fragment is removed.
         | 
| 4 | 
            -
              def  | 
| 4 | 
            +
              def limit_bytesize size
         | 
| 5 5 | 
             
                return self unless size < bytesize
         | 
| 6 6 | 
             
                # NOTE JRuby 1.7 & Rubinius fail to detect invalid encoding unless encoding is forced; impact is marginal.
         | 
| 7 7 | 
             
                size -= 1 until ((result = byteslice 0, size).force_encoding ::Encoding::UTF_8).valid_encoding?
         | 
| 8 8 | 
             
                result
         | 
| 9 | 
            -
              end unless method_defined? : | 
| 9 | 
            +
              end unless method_defined? :limit_bytesize
         | 
| 10 10 | 
             
            end
         | 
    
        data/lib/asciidoctor/document.rb
    CHANGED
    
    | @@ -32,20 +32,21 @@ class Document < AbstractBlock | |
| 32 32 |  | 
| 33 33 | 
             
                def save_to block_attributes
         | 
| 34 34 | 
             
                  (block_attributes[:attribute_entries] ||= []) << self
         | 
| 35 | 
            +
                  self
         | 
| 35 36 | 
             
                end
         | 
| 36 37 | 
             
              end
         | 
| 37 38 |  | 
| 38 39 | 
             
              # Public Parsed and stores a partitioned title (i.e., title & subtitle).
         | 
| 39 40 | 
             
              class Title
         | 
| 40 41 | 
             
                attr_reader :main
         | 
| 41 | 
            -
                alias  | 
| 42 | 
            +
                alias title main
         | 
| 42 43 | 
             
                attr_reader :subtitle
         | 
| 43 44 | 
             
                attr_reader :combined
         | 
| 44 45 |  | 
| 45 46 | 
             
                def initialize val, opts = {}
         | 
| 46 47 | 
             
                  # TODO separate sanitization by type (:cdata for HTML/XML, :plain_text for non-SGML, false for none)
         | 
| 47 48 | 
             
                  if (@sanitized = opts[:sanitize]) && val.include?('<')
         | 
| 48 | 
            -
                    val = val.gsub(XmlSanitizeRx, ''). | 
| 49 | 
            +
                    val = val.gsub(XmlSanitizeRx, '').squeeze(' ').strip
         | 
| 49 50 | 
             
                  end
         | 
| 50 51 | 
             
                  if (sep = opts[:separator] || ':').empty? || !val.include?(sep = %(#{sep} ))
         | 
| 51 52 | 
             
                    @main = val
         | 
| @@ -61,7 +62,7 @@ class Document < AbstractBlock | |
| 61 62 | 
             
                end
         | 
| 62 63 |  | 
| 63 64 | 
             
                def subtitle?
         | 
| 64 | 
            -
                   | 
| 65 | 
            +
                  @subtitle ? true : false
         | 
| 65 66 | 
             
                end
         | 
| 66 67 |  | 
| 67 68 | 
             
                def to_s
         | 
| @@ -78,7 +79,7 @@ class Document < AbstractBlock | |
| 78 79 | 
             
              #
         | 
| 79 80 | 
             
              # A value of 1 (SAFE) closely parallels safe mode in AsciiDoc. In particular,
         | 
| 80 81 | 
             
              # it prevents access to files which reside outside of the parent directory
         | 
| 81 | 
            -
              # of the source file and disables any macro other than the include  | 
| 82 | 
            +
              # of the source file and disables any macro other than the include directive.
         | 
| 82 83 | 
             
              #
         | 
| 83 84 | 
             
              # A value of 10 (SERVER) disallows the document from setting attributes that
         | 
| 84 85 | 
             
              # would affect the conversion of the document, in addition to all the security
         | 
| @@ -90,7 +91,7 @@ class Document < AbstractBlock | |
| 90 91 | 
             
              # A value of 20 (SECURE) disallows the document from attempting to read files
         | 
| 91 92 | 
             
              # from the file system and including the contents of them into the document,
         | 
| 92 93 | 
             
              # in addition to all the security features of SafeMode::SECURE. In
         | 
| 93 | 
            -
              # particular, it disallows use of the include::[]  | 
| 94 | 
            +
              # particular, it disallows use of the include::[] directive and the embedding of
         | 
| 94 95 | 
             
              # binary content (data uri), stylesheets and JavaScripts referenced by the
         | 
| 95 96 | 
             
              # document. (Asciidoctor and trusted extensions may still be allowed to embed
         | 
| 96 97 | 
             
              # trusted content into the document).
         | 
| @@ -115,11 +116,20 @@ class Document < AbstractBlock | |
| 115 116 | 
             
              #
         | 
| 116 117 | 
             
              attr_reader :compat_mode
         | 
| 117 118 |  | 
| 118 | 
            -
              # Public: Get the  | 
| 119 | 
            -
              attr_reader : | 
| 119 | 
            +
              # Public: Get the cached value of the backend attribute for this document
         | 
| 120 | 
            +
              attr_reader :backend
         | 
| 121 | 
            +
             | 
| 122 | 
            +
              # Public: Get the cached value of the doctype attribute for this document
         | 
| 123 | 
            +
              attr_reader :doctype
         | 
| 124 | 
            +
             | 
| 125 | 
            +
              # Public: Get or set the Boolean flag that indicates whether source map information should be tracked by the parser
         | 
| 126 | 
            +
              attr_accessor :sourcemap
         | 
| 120 127 |  | 
| 121 | 
            -
              # Public: Get the  | 
| 122 | 
            -
              attr_reader : | 
| 128 | 
            +
              # Public: Get the document catalog Hash
         | 
| 129 | 
            +
              attr_reader :catalog
         | 
| 130 | 
            +
             | 
| 131 | 
            +
              # Public: Alias catalog property as references for backwards compatiblity
         | 
| 132 | 
            +
              alias references catalog
         | 
| 123 133 |  | 
| 124 134 | 
             
              # Public: Get the Hash of document counters
         | 
| 125 135 | 
             
              attr_reader :counters
         | 
| @@ -151,7 +161,7 @@ class Document < AbstractBlock | |
| 151 161 | 
             
              # Public: Get the Converter associated with this document
         | 
| 152 162 | 
             
              attr_reader :converter
         | 
| 153 163 |  | 
| 154 | 
            -
              # Public: Get the  | 
| 164 | 
            +
              # Public: Get the activated Extensions::Registry associated with this document.
         | 
| 155 165 | 
             
              attr_reader :extensions
         | 
| 156 166 |  | 
| 157 167 | 
             
              # Public: Initialize a {Document} object.
         | 
| @@ -173,32 +183,30 @@ class Document < AbstractBlock | |
| 173 183 | 
             
                if (parent_doc = options.delete :parent)
         | 
| 174 184 | 
             
                  @parent_document = parent_doc
         | 
| 175 185 | 
             
                  options[:base_dir] ||= parent_doc.base_dir
         | 
| 176 | 
            -
                  @ | 
| 177 | 
            -
                     | 
| 178 | 
            -
                      accum[:footnotes] = []
         | 
| 179 | 
            -
                    else
         | 
| 180 | 
            -
                      accum[key] = ref
         | 
| 181 | 
            -
                    end
         | 
| 186 | 
            +
                  @catalog = parent_doc.catalog.inject({}) do |accum, (key, table)|
         | 
| 187 | 
            +
                    accum[key] = (key == :footnotes ? [] : table)
         | 
| 182 188 | 
             
                    accum
         | 
| 183 189 | 
             
                  end
         | 
| 184 190 | 
             
                  @callouts = parent_doc.callouts
         | 
| 185 191 | 
             
                  # QUESTION should we support setting attribute in parent document from nested document?
         | 
| 186 192 | 
             
                  # NOTE we must dup or else all the assignments to the overrides clobbers the real attributes
         | 
| 187 | 
            -
                  attr_overrides = parent_doc.attributes.dup
         | 
| 188 | 
            -
                   | 
| 189 | 
            -
             | 
| 190 | 
            -
                   | 
| 191 | 
            -
                   | 
| 193 | 
            +
                  @attribute_overrides = attr_overrides = parent_doc.attributes.dup
         | 
| 194 | 
            +
                  parent_doctype = attr_overrides.delete 'doctype'
         | 
| 195 | 
            +
                  attr_overrides.delete 'compat-mode'
         | 
| 196 | 
            +
                  attr_overrides.delete 'toc'
         | 
| 197 | 
            +
                  attr_overrides.delete 'toc-placement'
         | 
| 198 | 
            +
                  attr_overrides.delete 'toc-position'
         | 
| 192 199 | 
             
                  @safe = parent_doc.safe
         | 
| 193 | 
            -
                  @compat_mode = parent_doc.compat_mode
         | 
| 200 | 
            +
                  @attributes['compat-mode'] = '' if (@compat_mode = parent_doc.compat_mode)
         | 
| 194 201 | 
             
                  @sourcemap = parent_doc.sourcemap
         | 
| 195 202 | 
             
                  @converter = parent_doc.converter
         | 
| 196 203 | 
             
                  initialize_extensions = false
         | 
| 197 204 | 
             
                  @extensions = parent_doc.extensions
         | 
| 198 205 | 
             
                else
         | 
| 199 206 | 
             
                  @parent_document = nil
         | 
| 200 | 
            -
                  @ | 
| 207 | 
            +
                  @catalog = {
         | 
| 201 208 | 
             
                    :ids => {},
         | 
| 209 | 
            +
                    :refs => {},
         | 
| 202 210 | 
             
                    :footnotes => [],
         | 
| 203 211 | 
             
                    :links => [],
         | 
| 204 212 | 
             
                    :images => [],
         | 
| @@ -231,7 +239,7 @@ class Document < AbstractBlock | |
| 231 239 | 
             
                  else
         | 
| 232 240 | 
             
                    # NOTE: not using infix rescue for performance reasons, see https://github.com/jruby/jruby/issues/1816
         | 
| 233 241 | 
             
                    begin
         | 
| 234 | 
            -
                      @safe = SafeMode. | 
| 242 | 
            +
                      @safe = SafeMode.value_for_name safe_mode.to_s
         | 
| 235 243 | 
             
                    rescue
         | 
| 236 244 | 
             
                      @safe = SafeMode::SECURE
         | 
| 237 245 | 
             
                    end
         | 
| @@ -247,19 +255,25 @@ class Document < AbstractBlock | |
| 247 255 | 
             
                @header = nil
         | 
| 248 256 | 
             
                @counters = {}
         | 
| 249 257 | 
             
                @attributes_modified = ::Set.new
         | 
| 250 | 
            -
                @options = options
         | 
| 251 258 | 
             
                @docinfo_processor_extensions = {}
         | 
| 252 259 | 
             
                header_footer = (options[:header_footer] ||= false)
         | 
| 253 | 
            -
                options.freeze
         | 
| 260 | 
            +
                (@options = options).freeze
         | 
| 254 261 |  | 
| 255 262 | 
             
                attrs = @attributes
         | 
| 256 263 | 
             
                #attrs['encoding'] = 'UTF-8'
         | 
| 257 264 | 
             
                attrs['sectids'] = ''
         | 
| 258 | 
            -
                attrs['notitle'] = '' unless header_footer
         | 
| 259 265 | 
             
                attrs['toc-placement'] = 'auto'
         | 
| 266 | 
            +
                if header_footer
         | 
| 267 | 
            +
                  attrs['copycss'] = ''
         | 
| 268 | 
            +
                  # sync embedded attribute with :header_footer option value
         | 
| 269 | 
            +
                  attr_overrides['embedded'] = nil
         | 
| 270 | 
            +
                else
         | 
| 271 | 
            +
                  attrs['notitle'] = ''
         | 
| 272 | 
            +
                  # sync embedded attribute with :header_footer option value
         | 
| 273 | 
            +
                  attr_overrides['embedded'] = ''
         | 
| 274 | 
            +
                end
         | 
| 260 275 | 
             
                attrs['stylesheet'] = ''
         | 
| 261 276 | 
             
                attrs['webfonts'] = ''
         | 
| 262 | 
            -
                attrs['copycss'] = '' if header_footer
         | 
| 263 277 | 
             
                attrs['prewrap'] = ''
         | 
| 264 278 | 
             
                attrs['attribute-undefined'] = Compliance.attribute_undefined
         | 
| 265 279 | 
             
                attrs['attribute-missing'] = Compliance.attribute_missing
         | 
| @@ -272,7 +286,6 @@ class Document < AbstractBlock | |
| 272 286 | 
             
                attrs['note-caption'] = 'Note'
         | 
| 273 287 | 
             
                attrs['tip-caption'] = 'Tip'
         | 
| 274 288 | 
             
                attrs['warning-caption'] = 'Warning'
         | 
| 275 | 
            -
                attrs['appendix-caption'] = 'Appendix'
         | 
| 276 289 | 
             
                attrs['example-caption'] = 'Example'
         | 
| 277 290 | 
             
                attrs['figure-caption'] = 'Figure'
         | 
| 278 291 | 
             
                #attrs['listing-caption'] = 'Listing'
         | 
| @@ -280,6 +293,10 @@ class Document < AbstractBlock | |
| 280 293 | 
             
                attrs['toc-title'] = 'Table of Contents'
         | 
| 281 294 | 
             
                #attrs['preface-title'] = 'Preface'
         | 
| 282 295 | 
             
                attrs['manname-title'] = 'NAME'
         | 
| 296 | 
            +
                attrs['section-refsig'] = 'Section'
         | 
| 297 | 
            +
                #attrs['part-refsig'] = 'Part'
         | 
| 298 | 
            +
                attrs['chapter-refsig'] = 'Chapter'
         | 
| 299 | 
            +
                attrs['appendix-caption'] = attrs['appendix-refsig'] = 'Appendix'
         | 
| 283 300 | 
             
                attrs['untitled-label'] = 'Untitled'
         | 
| 284 301 | 
             
                attrs['version-label'] = 'Version'
         | 
| 285 302 | 
             
                attrs['last-update-label'] = 'Last updated'
         | 
| @@ -287,14 +304,10 @@ class Document < AbstractBlock | |
| 287 304 | 
             
                attr_overrides['asciidoctor'] = ''
         | 
| 288 305 | 
             
                attr_overrides['asciidoctor-version'] = VERSION
         | 
| 289 306 |  | 
| 290 | 
            -
                 | 
| 291 | 
            -
                attr_overrides['safe-mode-name'] = safe_mode_name
         | 
| 307 | 
            +
                attr_overrides['safe-mode-name'] = (safe_mode_name = SafeMode.name_for_value @safe)
         | 
| 292 308 | 
             
                attr_overrides["safe-mode-#{safe_mode_name}"] = ''
         | 
| 293 309 | 
             
                attr_overrides['safe-mode-level'] = @safe
         | 
| 294 310 |  | 
| 295 | 
            -
                # sync the embedded attribute w/ the value of options...do not allow override
         | 
| 296 | 
            -
                attr_overrides['embedded'] = header_footer ? nil : ''
         | 
| 297 | 
            -
             | 
| 298 311 | 
             
                # the only way to set the max-include-depth attribute is via the API; default to 64 like AsciiDoc Python
         | 
| 299 312 | 
             
                attr_overrides['max-include-depth'] ||= 64
         | 
| 300 313 |  | 
| @@ -353,7 +366,7 @@ class Document < AbstractBlock | |
| 353 366 | 
             
                end
         | 
| 354 367 |  | 
| 355 368 | 
             
                # the only way to set the max-attribute-value-size attribute is via the API; disabled by default
         | 
| 356 | 
            -
                @max_attribute_value_size = ( | 
| 369 | 
            +
                @max_attribute_value_size = (size = (attr_overrides['max-attribute-value-size'] ||= nil)) ? size.to_i.abs : nil
         | 
| 357 370 |  | 
| 358 371 | 
             
                attr_overrides.delete_if do |key, val|
         | 
| 359 372 | 
             
                  verdict = false
         | 
| @@ -373,8 +386,11 @@ class Document < AbstractBlock | |
| 373 386 | 
             
                end
         | 
| 374 387 |  | 
| 375 388 | 
             
                if parent_doc
         | 
| 376 | 
            -
                   | 
| 377 | 
            -
                   | 
| 389 | 
            +
                  @backend = attrs['backend']
         | 
| 390 | 
            +
                  # reset doctype unless it matches the default value
         | 
| 391 | 
            +
                  unless (@doctype = attrs['doctype'] = parent_doctype) == DEFAULT_DOCTYPE
         | 
| 392 | 
            +
                    update_doctype_attributes DEFAULT_DOCTYPE
         | 
| 393 | 
            +
                  end
         | 
| 378 394 |  | 
| 379 395 | 
             
                  # don't need to do the extra processing within our own document
         | 
| 380 396 | 
             
                  # FIXME line info isn't reported correctly within include files in nested document
         | 
| @@ -389,10 +405,11 @@ class Document < AbstractBlock | |
| 389 405 | 
             
                  @parsed = true
         | 
| 390 406 | 
             
                else
         | 
| 391 407 | 
             
                  # setup default backend and doctype
         | 
| 408 | 
            +
                  @backend = nil
         | 
| 392 409 | 
             
                  if (attrs['backend'] ||= DEFAULT_BACKEND) == 'manpage'
         | 
| 393 | 
            -
                    attrs['doctype'] = attr_overrides['doctype'] = 'manpage'
         | 
| 410 | 
            +
                    @doctype = attrs['doctype'] = attr_overrides['doctype'] = 'manpage'
         | 
| 394 411 | 
             
                  else
         | 
| 395 | 
            -
                    attrs['doctype'] ||= DEFAULT_DOCTYPE
         | 
| 412 | 
            +
                    @doctype = (attrs['doctype'] ||= DEFAULT_DOCTYPE)
         | 
| 396 413 | 
             
                  end
         | 
| 397 414 | 
             
                  update_backend_attributes attrs['backend'], true
         | 
| 398 415 |  | 
| @@ -402,20 +419,25 @@ class Document < AbstractBlock | |
| 402 419 | 
             
                  # dynamic intrinstic attribute values
         | 
| 403 420 |  | 
| 404 421 | 
             
                  # See https://reproducible-builds.org/specs/source-date-epoch/
         | 
| 405 | 
            -
                   | 
| 406 | 
            -
                   | 
| 407 | 
            -
                   | 
| 408 | 
            -
                     | 
| 409 | 
            -
             | 
| 410 | 
            -
                     | 
| 411 | 
            -
             | 
| 412 | 
            -
                    end
         | 
| 422 | 
            +
                  # NOTE Opal can't call key? on ENV
         | 
| 423 | 
            +
                  now = ::ENV['SOURCE_DATE_EPOCH'] ? ::Time.at(Integer ::ENV['SOURCE_DATE_EPOCH']).utc : ::Time.now
         | 
| 424 | 
            +
                  if (localdate = attrs['localdate'])
         | 
| 425 | 
            +
                    localyear = (attrs['localyear'] ||= ((localdate.index '-') == 4 ? (localdate.slice 0, 4) : nil))
         | 
| 426 | 
            +
                  else
         | 
| 427 | 
            +
                    localdate = attrs['localdate'] = (now.strftime '%Y-%m-%d')
         | 
| 428 | 
            +
                    localyear = (attrs['localyear'] ||= now.year.to_s)
         | 
| 413 429 | 
             
                  end
         | 
| 430 | 
            +
                  localtime = (attrs['localtime'] ||= begin
         | 
| 431 | 
            +
                      now.strftime '%H:%M:%S %Z'
         | 
| 432 | 
            +
                    rescue # Asciidoctor.js fails if timezone string has characters outside basic Latin (see asciidoctor.js#23)
         | 
| 433 | 
            +
                      now.strftime '%H:%M:%S %z'
         | 
| 434 | 
            +
                    end)
         | 
| 414 435 | 
             
                  attrs['localdatetime'] ||= %(#{localdate} #{localtime})
         | 
| 415 436 |  | 
| 416 437 | 
             
                  # docdate, doctime and docdatetime should default to
         | 
| 417 438 | 
             
                  # localdate, localtime and localdatetime if not otherwise set
         | 
| 418 439 | 
             
                  attrs['docdate'] ||= localdate
         | 
| 440 | 
            +
                  attrs['docyear'] ||= localyear
         | 
| 419 441 | 
             
                  attrs['doctime'] ||= localtime
         | 
| 420 442 | 
             
                  attrs['docdatetime'] ||= %(#{localdate} #{localtime})
         | 
| 421 443 |  | 
| @@ -424,22 +446,21 @@ class Document < AbstractBlock | |
| 424 446 | 
             
                  attrs['iconsdir'] ||= ::File.join(attrs.fetch('imagesdir', './images'), 'icons')
         | 
| 425 447 |  | 
| 426 448 | 
             
                  if initialize_extensions
         | 
| 427 | 
            -
                    if ( | 
| 428 | 
            -
                       | 
| 429 | 
            -
             | 
| 430 | 
            -
             | 
| 431 | 
            -
             | 
| 432 | 
            -
                        registry = Extensions::Registry.new
         | 
| 449 | 
            +
                    if (ext_registry = options[:extension_registry])
         | 
| 450 | 
            +
                      # QUESTION should we warn the value type of the option is not a registry or boolean?
         | 
| 451 | 
            +
                      unless Extensions::Registry === ext_registry || (::RUBY_ENGINE_JRUBY &&
         | 
| 452 | 
            +
                          ::AsciidoctorJ::Extensions::ExtensionRegistry === ext_registry)
         | 
| 453 | 
            +
                        ext_registry = Extensions::Registry.new
         | 
| 433 454 | 
             
                      end
         | 
| 434 455 | 
             
                    elsif ::Proc === (ext_block = options[:extensions])
         | 
| 435 | 
            -
                       | 
| 456 | 
            +
                      ext_registry = Extensions.create(&ext_block)
         | 
| 436 457 | 
             
                    else
         | 
| 437 | 
            -
                       | 
| 458 | 
            +
                      ext_registry = Extensions::Registry.new
         | 
| 438 459 | 
             
                    end
         | 
| 439 | 
            -
                    @extensions =  | 
| 460 | 
            +
                    @extensions = ext_registry.activate self
         | 
| 440 461 | 
             
                  end
         | 
| 441 462 |  | 
| 442 | 
            -
                  @reader = PreprocessorReader.new self, data, Reader::Cursor.new | 
| 463 | 
            +
                  @reader = PreprocessorReader.new self, data, (Reader::Cursor.new attrs['docfile'], @base_dir), :normalize => true
         | 
| 443 464 | 
             
                end
         | 
| 444 465 | 
             
              end
         | 
| 445 466 |  | 
| @@ -460,7 +481,9 @@ class Document < AbstractBlock | |
| 460 481 | 
             
                else
         | 
| 461 482 | 
             
                  doc = self
         | 
| 462 483 | 
             
                  # create reader if data is provided (used when data is not known at the time the Document object is created)
         | 
| 463 | 
            -
                   | 
| 484 | 
            +
                  if data
         | 
| 485 | 
            +
                    @reader = PreprocessorReader.new doc, data, (Reader::Cursor.new @attributes['docfile'], @base_dir), :normalize => true
         | 
| 486 | 
            +
                  end
         | 
| 464 487 |  | 
| 465 488 | 
             
                  if (exts = @parent_document ? nil : @extensions) && exts.preprocessors?
         | 
| 466 489 | 
             
                    exts.preprocessors.each do |ext|
         | 
| @@ -469,13 +492,13 @@ class Document < AbstractBlock | |
| 469 492 | 
             
                  end
         | 
| 470 493 |  | 
| 471 494 | 
             
                  # Now parse the lines in the reader into blocks
         | 
| 472 | 
            -
                  Parser.parse @reader, doc, :header_only =>  | 
| 495 | 
            +
                  Parser.parse @reader, doc, :header_only => @options[:parse_header_only]
         | 
| 473 496 |  | 
| 474 497 | 
             
                  # should we call sort of post-parse function?
         | 
| 475 498 | 
             
                  restore_attributes
         | 
| 476 499 |  | 
| 477 | 
            -
                  if exts && exts. | 
| 478 | 
            -
                    exts. | 
| 500 | 
            +
                  if exts && exts.tree_processors?
         | 
| 501 | 
            +
                    exts.tree_processors.each do |ext|
         | 
| 479 502 | 
             
                      if (result = ext.process_method[doc]) && Document === result && result != doc
         | 
| 480 503 | 
             
                        doc = result
         | 
| 481 504 | 
             
                      end
         | 
| @@ -493,19 +516,15 @@ class Document < AbstractBlock | |
| 493 516 | 
             
              # seed  - the initial value as a String or Integer
         | 
| 494 517 | 
             
              #
         | 
| 495 518 | 
             
              # returns the next number in the sequence for the specified counter
         | 
| 496 | 
            -
              def counter | 
| 497 | 
            -
                 | 
| 498 | 
            -
             | 
| 519 | 
            +
              def counter name, seed = nil
         | 
| 520 | 
            +
                return @parent_document.counter name, seed if @parent_document
         | 
| 521 | 
            +
                if (attr_seed = !(attr_val = @attributes[name]).nil_or_empty?) && (@counters.key? name)
         | 
| 522 | 
            +
                  @attributes[name] = @counters[name] = (nextval attr_val)
         | 
| 523 | 
            +
                elsif seed
         | 
| 524 | 
            +
                  @attributes[name] = @counters[name] = (seed == seed.to_i.to_s ? seed.to_i : seed)
         | 
| 499 525 | 
             
                else
         | 
| 500 | 
            -
                   | 
| 501 | 
            -
                    seed = nextval(attr_is_seed ? attr_val : 0)
         | 
| 502 | 
            -
                  elsif seed.to_i.to_s == seed
         | 
| 503 | 
            -
                    seed = seed.to_i
         | 
| 504 | 
            -
                  end
         | 
| 505 | 
            -
                  @counters[name] = seed
         | 
| 526 | 
            +
                  @attributes[name] = @counters[name] = nextval(attr_seed ? attr_val : 0)
         | 
| 506 527 | 
             
                end
         | 
| 507 | 
            -
             | 
| 508 | 
            -
                (@attributes[name] = @counters[name])
         | 
| 509 528 | 
             
              end
         | 
| 510 529 |  | 
| 511 530 | 
             
              # Public: Increment the specified counter and store it in the block's attributes
         | 
| @@ -514,11 +533,11 @@ class Document < AbstractBlock | |
| 514 533 | 
             
              # block        - the Block on which to save the counter
         | 
| 515 534 | 
             
              #
         | 
| 516 535 | 
             
              # returns the next number in the sequence for the specified counter
         | 
| 517 | 
            -
              def  | 
| 518 | 
            -
                 | 
| 519 | 
            -
                AttributeEntry.new(counter_name, val).save_to(block.attributes)
         | 
| 520 | 
            -
                val
         | 
| 536 | 
            +
              def increment_and_store_counter counter_name, block
         | 
| 537 | 
            +
                ((AttributeEntry.new counter_name, (counter counter_name)).save_to block.attributes).value
         | 
| 521 538 | 
             
              end
         | 
| 539 | 
            +
              # Deprecated: Map old counter_increment method to increment_counter for backwards compatibility
         | 
| 540 | 
            +
              alias counter_increment increment_and_store_counter
         | 
| 522 541 |  | 
| 523 542 | 
             
              # Internal: Get the next value in the sequence.
         | 
| 524 543 | 
             
              #
         | 
| @@ -540,44 +559,42 @@ class Document < AbstractBlock | |
| 540 559 | 
             
                end
         | 
| 541 560 | 
             
              end
         | 
| 542 561 |  | 
| 543 | 
            -
              def register | 
| 562 | 
            +
              def register type, value
         | 
| 544 563 | 
             
                case type
         | 
| 545 | 
            -
                when :ids
         | 
| 546 | 
            -
                  id, reftext =  | 
| 547 | 
            -
                   | 
| 548 | 
            -
             | 
| 549 | 
            -
             | 
| 550 | 
            -
                   | 
| 551 | 
            -
                    @ | 
| 564 | 
            +
                when :ids # deprecated
         | 
| 565 | 
            +
                  id, reftext = value
         | 
| 566 | 
            +
                  @catalog[:ids][id] ||= reftext || ('[' + id + ']')
         | 
| 567 | 
            +
                when :refs
         | 
| 568 | 
            +
                  id, ref, reftext = value
         | 
| 569 | 
            +
                  unless (refs = @catalog[:refs]).key? id
         | 
| 570 | 
            +
                    @catalog[:ids][id] = reftext || ('[' + id + ']')
         | 
| 571 | 
            +
                    refs[id] = ref
         | 
| 552 572 | 
             
                  end
         | 
| 553 573 | 
             
                when :footnotes, :indexterms
         | 
| 554 | 
            -
                  @ | 
| 574 | 
            +
                  @catalog[type] << value
         | 
| 555 575 | 
             
                else
         | 
| 556 | 
            -
                  if @options[:catalog_assets]
         | 
| 557 | 
            -
                    @references[type] << value
         | 
| 558 | 
            -
                  end
         | 
| 576 | 
            +
                  @catalog[type] << value if @options[:catalog_assets]
         | 
| 559 577 | 
             
                end
         | 
| 560 578 | 
             
              end
         | 
| 561 579 |  | 
| 562 580 | 
             
              def footnotes?
         | 
| 563 | 
            -
                 | 
| 581 | 
            +
                @catalog[:footnotes].empty? ? false : true
         | 
| 564 582 | 
             
              end
         | 
| 565 583 |  | 
| 566 584 | 
             
              def footnotes
         | 
| 567 | 
            -
                @ | 
| 585 | 
            +
                @catalog[:footnotes]
         | 
| 568 586 | 
             
              end
         | 
| 569 587 |  | 
| 570 588 | 
             
              def nested?
         | 
| 571 | 
            -
                 | 
| 589 | 
            +
                @parent_document ? true : false
         | 
| 572 590 | 
             
              end
         | 
| 573 591 |  | 
| 574 592 | 
             
              def embedded?
         | 
| 575 | 
            -
                # QUESTION should this be !@options[:header_footer] ?
         | 
| 576 593 | 
             
                @attributes.key? 'embedded'
         | 
| 577 594 | 
             
              end
         | 
| 578 595 |  | 
| 579 596 | 
             
              def extensions?
         | 
| 580 | 
            -
                 | 
| 597 | 
            +
                @extensions ? true : false
         | 
| 581 598 | 
             
              end
         | 
| 582 599 |  | 
| 583 600 | 
             
              # Make the raw source for the Document available.
         | 
| @@ -590,14 +607,6 @@ class Document < AbstractBlock | |
| 590 607 | 
             
                @reader.source_lines if @reader
         | 
| 591 608 | 
             
              end
         | 
| 592 609 |  | 
| 593 | 
            -
              def doctype
         | 
| 594 | 
            -
                @doctype ||= @attributes['doctype']
         | 
| 595 | 
            -
              end
         | 
| 596 | 
            -
             | 
| 597 | 
            -
              def backend
         | 
| 598 | 
            -
                @backend ||= @attributes['backend']
         | 
| 599 | 
            -
              end
         | 
| 600 | 
            -
             | 
| 601 610 | 
             
              def basebackend? base
         | 
| 602 611 | 
             
                @attributes['basebackend'] == base
         | 
| 603 612 | 
             
              end
         | 
| @@ -607,9 +616,11 @@ class Document < AbstractBlock | |
| 607 616 | 
             
                @attributes['title']
         | 
| 608 617 | 
             
              end
         | 
| 609 618 |  | 
| 610 | 
            -
              def title= | 
| 611 | 
            -
                 | 
| 612 | 
            -
             | 
| 619 | 
            +
              def title= title
         | 
| 620 | 
            +
                unless (sect = @header)
         | 
| 621 | 
            +
                  (sect = (@header = Section.new self, 0, false)).sectname = 'header'
         | 
| 622 | 
            +
                end
         | 
| 623 | 
            +
                sect.title = title
         | 
| 613 624 | 
             
              end
         | 
| 614 625 |  | 
| 615 626 | 
             
              # Public: Resolves the primary title for the document
         | 
| @@ -634,7 +645,7 @@ class Document < AbstractBlock | |
| 634 645 | 
             
              def doctitle opts = {}
         | 
| 635 646 | 
             
                if !(val = @attributes['title'].nil_or_empty?)
         | 
| 636 647 | 
             
                  val = title
         | 
| 637 | 
            -
                elsif (sect = first_section) | 
| 648 | 
            +
                elsif (sect = first_section)
         | 
| 638 649 | 
             
                  val = sect.title
         | 
| 639 650 | 
             
                elsif opts[:use_fallback] && (val = @attributes['untitled-label'])
         | 
| 640 651 | 
             
                  # use val set in condition
         | 
| @@ -645,12 +656,12 @@ class Document < AbstractBlock | |
| 645 656 | 
             
                if (separator = opts[:partition])
         | 
| 646 657 | 
             
                  Title.new val, opts.merge({ :separator => (separator == true ? @attributes['title-separator'] : separator) })
         | 
| 647 658 | 
             
                elsif opts[:sanitize] && val.include?('<')
         | 
| 648 | 
            -
                  val.gsub(XmlSanitizeRx, ''). | 
| 659 | 
            +
                  val.gsub(XmlSanitizeRx, '').squeeze(' ').strip
         | 
| 649 660 | 
             
                else
         | 
| 650 661 | 
             
                  val
         | 
| 651 662 | 
             
                end
         | 
| 652 663 | 
             
              end
         | 
| 653 | 
            -
              alias  | 
| 664 | 
            +
              alias name doctitle
         | 
| 654 665 |  | 
| 655 666 | 
             
              # Public: Convenience method to retrieve the document attribute 'author'
         | 
| 656 667 | 
             
              #
         | 
| @@ -678,15 +689,14 @@ class Document < AbstractBlock | |
| 678 689 | 
             
                @attributes.key? 'nofooter'
         | 
| 679 690 | 
             
              end
         | 
| 680 691 |  | 
| 681 | 
            -
              # QUESTION move to AbstractBlock?
         | 
| 682 692 | 
             
              def first_section
         | 
| 683 | 
            -
                 | 
| 693 | 
            +
                @header || @blocks.find {|e| e.context == :section }
         | 
| 684 694 | 
             
              end
         | 
| 685 695 |  | 
| 686 696 | 
             
              def has_header?
         | 
| 687 697 | 
             
                @header ? true : false
         | 
| 688 698 | 
             
              end
         | 
| 689 | 
            -
              alias  | 
| 699 | 
            +
              alias header? has_header?
         | 
| 690 700 |  | 
| 691 701 | 
             
              # Public: Append a content Block to this Document.
         | 
| 692 702 | 
             
              #
         | 
| @@ -696,14 +706,14 @@ class Document < AbstractBlock | |
| 696 706 | 
             
              #
         | 
| 697 707 | 
             
              # Returns The parent Block
         | 
| 698 708 | 
             
              def << block
         | 
| 699 | 
            -
                 | 
| 709 | 
            +
                enumerate_section block if block.context == :section
         | 
| 700 710 | 
             
                super
         | 
| 701 711 | 
             
              end
         | 
| 702 712 |  | 
| 703 713 | 
             
              # Internal: called after the header has been parsed and before the content
         | 
| 704 714 | 
             
              # will be parsed.
         | 
| 705 715 | 
             
              #--
         | 
| 706 | 
            -
              # QUESTION should we invoke the  | 
| 716 | 
            +
              # QUESTION should we invoke the TreeProcessors here, passing in a phase?
         | 
| 707 717 | 
             
              # QUESTION is finalize_header the right name?
         | 
| 708 718 | 
             
              def finalize_header unrooted_attributes, header_valid = true
         | 
| 709 719 | 
             
                clear_playback_attributes unrooted_attributes
         | 
| @@ -769,11 +779,8 @@ class Document < AbstractBlock | |
| 769 779 | 
             
                  attrs['toc-class'] ||= default_toc_class if default_toc_class
         | 
| 770 780 | 
             
                end
         | 
| 771 781 |  | 
| 772 | 
            -
                if attrs.key? 'compat-mode'
         | 
| 773 | 
            -
                  attrs['source-language'] = attrs['language'] if attrs. | 
| 774 | 
            -
                  @compat_mode = true
         | 
| 775 | 
            -
                else
         | 
| 776 | 
            -
                  @compat_mode = false
         | 
| 782 | 
            +
                if (@compat_mode = attrs.key? 'compat-mode')
         | 
| 783 | 
            +
                  attrs['source-language'] = attrs['language'] if attrs.key? 'language'
         | 
| 777 784 | 
             
                end
         | 
| 778 785 |  | 
| 779 786 | 
             
                # NOTE pin the outfilesuffix after the header is parsed
         | 
| @@ -825,32 +832,32 @@ class Document < AbstractBlock | |
| 825 832 | 
             
              #
         | 
| 826 833 | 
             
              # If the attribute is locked, false is returned. Otherwise, the value is
         | 
| 827 834 | 
             
              # assigned to the attribute name after first performing attribute
         | 
| 828 | 
            -
              # substitutions on the value. If the attribute name is 'backend' | 
| 829 | 
            -
              # value of backend-related attributes are updated.
         | 
| 835 | 
            +
              # substitutions on the value. If the attribute name is 'backend' or
         | 
| 836 | 
            +
              # 'doctype', then the value of backend-related attributes are updated.
         | 
| 830 837 | 
             
              #
         | 
| 831 838 | 
             
              # name  - the String attribute name
         | 
| 832 | 
            -
              # value - the String attribute value
         | 
| 839 | 
            +
              # value - the String attribute value; must not be nil (default: '')
         | 
| 833 840 | 
             
              #
         | 
| 834 | 
            -
              #  | 
| 835 | 
            -
              def set_attribute | 
| 836 | 
            -
                if attribute_locked? | 
| 841 | 
            +
              # Returns the resolved value if the attribute was set or false if it was not because it's locked.
         | 
| 842 | 
            +
              def set_attribute name, value = ''
         | 
| 843 | 
            +
                if attribute_locked? name
         | 
| 837 844 | 
             
                  false
         | 
| 838 845 | 
             
                else
         | 
| 839 846 | 
             
                  if @max_attribute_value_size
         | 
| 840 | 
            -
                    resolved_value = (apply_attribute_value_subs value). | 
| 847 | 
            +
                    resolved_value = (apply_attribute_value_subs value).limit_bytesize @max_attribute_value_size
         | 
| 841 848 | 
             
                  else
         | 
| 842 849 | 
             
                    resolved_value = apply_attribute_value_subs value
         | 
| 843 850 | 
             
                  end
         | 
| 844 851 | 
             
                  case name
         | 
| 845 852 | 
             
                  when 'backend'
         | 
| 846 | 
            -
                    update_backend_attributes resolved_value,  | 
| 853 | 
            +
                    update_backend_attributes resolved_value, (@attributes_modified.delete? 'htmlsyntax')
         | 
| 847 854 | 
             
                  when 'doctype'
         | 
| 848 855 | 
             
                    update_doctype_attributes resolved_value
         | 
| 849 856 | 
             
                  else
         | 
| 850 857 | 
             
                    @attributes[name] = resolved_value
         | 
| 851 858 | 
             
                  end
         | 
| 852 859 | 
             
                  @attributes_modified << name
         | 
| 853 | 
            -
                   | 
| 860 | 
            +
                  resolved_value
         | 
| 854 861 | 
             
                end
         | 
| 855 862 | 
             
              end
         | 
| 856 863 |  | 
| @@ -890,61 +897,60 @@ class Document < AbstractBlock | |
| 890 897 | 
             
              # value - The String attribute value on which to perform substitutions
         | 
| 891 898 | 
             
              #
         | 
| 892 899 | 
             
              # Returns The String value with substitutions performed
         | 
| 893 | 
            -
              def apply_attribute_value_subs | 
| 894 | 
            -
                if  | 
| 895 | 
            -
                   | 
| 896 | 
            -
                    subs = resolve_pass_subs m[1]
         | 
| 897 | 
            -
                    subs.empty? ? m[2] : (apply_subs m[2], subs)
         | 
| 898 | 
            -
                  else
         | 
| 899 | 
            -
                    m[2]
         | 
| 900 | 
            -
                  end
         | 
| 900 | 
            +
              def apply_attribute_value_subs value
         | 
| 901 | 
            +
                if AttributeEntryPassMacroRx =~ value
         | 
| 902 | 
            +
                  $1 ? (apply_subs $2, (resolve_pass_subs $1)) : $2
         | 
| 901 903 | 
             
                else
         | 
| 902 904 | 
             
                  apply_header_subs value
         | 
| 903 905 | 
             
                end
         | 
| 904 906 | 
             
              end
         | 
| 905 907 |  | 
| 906 | 
            -
              # Public: Update the backend attributes to reflect a change in the  | 
| 908 | 
            +
              # Public: Update the backend attributes to reflect a change in the active backend.
         | 
| 907 909 | 
             
              #
         | 
| 908 910 | 
             
              # This method also handles updating the related doctype attributes if the
         | 
| 909 911 | 
             
              # doctype attribute is assigned at the time this method is called.
         | 
| 910 | 
            -
               | 
| 911 | 
            -
             | 
| 912 | 
            -
             | 
| 913 | 
            -
             | 
| 914 | 
            -
                  current_basebackend = attrs['basebackend']
         | 
| 915 | 
            -
                  current_doctype = attrs['doctype']
         | 
| 912 | 
            +
              #
         | 
| 913 | 
            +
              # Returns the resolved String backend if updated, nothing otherwise.
         | 
| 914 | 
            +
              def update_backend_attributes new_backend, force = nil
         | 
| 915 | 
            +
                if force || (new_backend && new_backend != @backend)
         | 
| 916 | 
            +
                  current_backend, current_basebackend, current_doctype = @backend, (attrs = @attributes)['basebackend'], @doctype
         | 
| 916 917 | 
             
                  if new_backend.start_with? 'xhtml'
         | 
| 917 918 | 
             
                    attrs['htmlsyntax'] = 'xml'
         | 
| 918 919 | 
             
                    new_backend = new_backend[1..-1]
         | 
| 919 920 | 
             
                  elsif new_backend.start_with? 'html'
         | 
| 920 921 | 
             
                    attrs['htmlsyntax'] = 'html' unless attrs['htmlsyntax'] == 'xml'
         | 
| 921 922 | 
             
                  end
         | 
| 922 | 
            -
                  if ( | 
| 923 | 
            -
                    new_backend =  | 
| 923 | 
            +
                  if (resolved_backend = BACKEND_ALIASES[new_backend])
         | 
| 924 | 
            +
                    new_backend = resolved_backend
         | 
| 924 925 | 
             
                  end
         | 
| 925 | 
            -
                  if  | 
| 926 | 
            -
                     | 
| 927 | 
            -
             | 
| 926 | 
            +
                  if current_doctype
         | 
| 927 | 
            +
                    if current_backend
         | 
| 928 | 
            +
                      attrs.delete %(backend-#{current_backend})
         | 
| 928 929 | 
             
                      attrs.delete %(backend-#{current_backend}-doctype-#{current_doctype})
         | 
| 929 930 | 
             
                    end
         | 
| 930 | 
            -
                  end
         | 
| 931 | 
            -
                  if current_doctype
         | 
| 932 | 
            -
                    attrs[%(doctype-#{current_doctype})] = ''
         | 
| 933 931 | 
             
                    attrs[%(backend-#{new_backend}-doctype-#{current_doctype})] = ''
         | 
| 932 | 
            +
                    attrs[%(doctype-#{current_doctype})] = ''
         | 
| 933 | 
            +
                  elsif current_backend
         | 
| 934 | 
            +
                    attrs.delete %(backend-#{current_backend})
         | 
| 934 935 | 
             
                  end
         | 
| 935 | 
            -
                  attrs['backend'] = new_backend
         | 
| 936 936 | 
             
                  attrs[%(backend-#{new_backend})] = ''
         | 
| 937 | 
            +
                  @backend = attrs['backend'] = new_backend
         | 
| 937 938 | 
             
                  # (re)initialize converter
         | 
| 938 939 | 
             
                  if Converter::BackendInfo === (@converter = create_converter)
         | 
| 939 940 | 
             
                    new_basebackend = @converter.basebackend
         | 
| 940 941 | 
             
                    attrs['outfilesuffix'] = @converter.outfilesuffix unless attribute_locked? 'outfilesuffix'
         | 
| 941 942 | 
             
                    new_filetype = @converter.filetype
         | 
| 942 | 
            -
                   | 
| 943 | 
            +
                  elsif @converter
         | 
| 943 944 | 
             
                    new_basebackend = new_backend.sub TrailingDigitsRx, ''
         | 
| 944 | 
            -
                     | 
| 945 | 
            -
             | 
| 946 | 
            -
                     | 
| 945 | 
            +
                    if (new_outfilesuffix = DEFAULT_EXTENSIONS[new_basebackend])
         | 
| 946 | 
            +
                      new_filetype = new_outfilesuffix[1..-1]
         | 
| 947 | 
            +
                    else
         | 
| 948 | 
            +
                      new_outfilesuffix, new_basebackend, new_filetype = '.html', 'html', 'html'
         | 
| 949 | 
            +
                    end
         | 
| 947 950 | 
             
                    attrs['outfilesuffix'] = new_outfilesuffix unless attribute_locked? 'outfilesuffix'
         | 
| 951 | 
            +
                  else
         | 
| 952 | 
            +
                    # NOTE ideally we shouldn't need the converter before the converter phase, but we do
         | 
| 953 | 
            +
                    raise ::NotImplementedError, %(asciidoctor: FAILED: missing converter for backend '#{new_backend}'. Processing aborted.)
         | 
| 948 954 | 
             
                  end
         | 
| 949 955 | 
             
                  if (current_filetype = attrs['filetype'])
         | 
| 950 956 | 
             
                    attrs.delete %(filetype-#{current_filetype})
         | 
| @@ -957,38 +963,44 @@ class Document < AbstractBlock | |
| 957 963 | 
             
                    attrs.delete 'pagewidth'
         | 
| 958 964 | 
             
                  end
         | 
| 959 965 | 
             
                  if new_basebackend != current_basebackend
         | 
| 960 | 
            -
                    if  | 
| 961 | 
            -
                       | 
| 962 | 
            -
             | 
| 966 | 
            +
                    if current_doctype
         | 
| 967 | 
            +
                      if current_basebackend
         | 
| 968 | 
            +
                        attrs.delete %(basebackend-#{current_basebackend})
         | 
| 963 969 | 
             
                        attrs.delete %(basebackend-#{current_basebackend}-doctype-#{current_doctype})
         | 
| 964 970 | 
             
                      end
         | 
| 971 | 
            +
                      attrs[%(basebackend-#{new_basebackend}-doctype-#{current_doctype})] = ''
         | 
| 972 | 
            +
                    elsif current_basebackend
         | 
| 973 | 
            +
                      attrs.delete %(basebackend-#{current_basebackend})
         | 
| 965 974 | 
             
                    end
         | 
| 966 | 
            -
                    attrs['basebackend'] = new_basebackend
         | 
| 967 975 | 
             
                    attrs[%(basebackend-#{new_basebackend})] = ''
         | 
| 968 | 
            -
                    attrs[ | 
| 976 | 
            +
                    attrs['basebackend'] = new_basebackend
         | 
| 969 977 | 
             
                  end
         | 
| 970 | 
            -
                   | 
| 971 | 
            -
                  @backend = nil
         | 
| 978 | 
            +
                  return new_backend
         | 
| 972 979 | 
             
                end
         | 
| 973 980 | 
             
              end
         | 
| 974 981 |  | 
| 982 | 
            +
              # TODO document me
         | 
| 983 | 
            +
              #
         | 
| 984 | 
            +
              # Returns the String doctype if updated, nothing otherwise.
         | 
| 975 985 | 
             
              def update_doctype_attributes new_doctype
         | 
| 976 | 
            -
                if new_doctype && new_doctype != @ | 
| 977 | 
            -
                  attrs = @attributes
         | 
| 978 | 
            -
                  current_doctype = attrs['doctype']
         | 
| 979 | 
            -
                  current_backend = attrs['backend']
         | 
| 980 | 
            -
                  current_basebackend = attrs['basebackend']
         | 
| 986 | 
            +
                if new_doctype && new_doctype != @doctype
         | 
| 987 | 
            +
                  current_backend, current_basebackend, current_doctype = @backend, (attrs = @attributes)['basebackend'], @doctype
         | 
| 981 988 | 
             
                  if current_doctype
         | 
| 982 989 | 
             
                    attrs.delete %(doctype-#{current_doctype})
         | 
| 983 | 
            -
                     | 
| 984 | 
            -
             | 
| 990 | 
            +
                    if current_backend
         | 
| 991 | 
            +
                      attrs.delete %(backend-#{current_backend}-doctype-#{current_doctype})
         | 
| 992 | 
            +
                      attrs[%(backend-#{current_backend}-doctype-#{new_doctype})] = ''
         | 
| 993 | 
            +
                    end
         | 
| 994 | 
            +
                    if current_basebackend
         | 
| 995 | 
            +
                      attrs.delete %(basebackend-#{current_basebackend}-doctype-#{current_doctype})
         | 
| 996 | 
            +
                      attrs[%(basebackend-#{current_basebackend}-doctype-#{new_doctype})] = ''
         | 
| 997 | 
            +
                    end
         | 
| 998 | 
            +
                  else
         | 
| 999 | 
            +
                    attrs[%(backend-#{current_backend}-doctype-#{new_doctype})] = '' if current_backend
         | 
| 1000 | 
            +
                    attrs[%(basebackend-#{current_basebackend}-doctype-#{new_doctype})] = '' if current_basebackend
         | 
| 985 1001 | 
             
                  end
         | 
| 986 | 
            -
                  attrs['doctype'] = new_doctype
         | 
| 987 1002 | 
             
                  attrs[%(doctype-#{new_doctype})] = ''
         | 
| 988 | 
            -
                   | 
| 989 | 
            -
                  attrs[%(basebackend-#{current_basebackend}-doctype-#{new_doctype})] = '' if current_basebackend
         | 
| 990 | 
            -
                  # clear cached doctype value
         | 
| 991 | 
            -
                  @doctype = nil
         | 
| 1003 | 
            +
                  return @doctype = attrs['doctype'] = new_doctype
         | 
| 992 1004 | 
             
                end
         | 
| 993 1005 | 
             
              end
         | 
| 994 1006 |  | 
| @@ -1033,17 +1045,15 @@ class Document < AbstractBlock | |
| 1033 1045 | 
             
                  @attributes.delete 'outdir' unless (@attributes['outdir'] = opts['outdir'])
         | 
| 1034 1046 | 
             
                end
         | 
| 1035 1047 |  | 
| 1036 | 
            -
                # QUESTION should we add  | 
| 1037 | 
            -
                unless @converter
         | 
| 1038 | 
            -
                  fail %(asciidoctor: FAILED: missing converter for backend '#{backend}'. Processing aborted.)
         | 
| 1039 | 
            -
                end
         | 
| 1048 | 
            +
                # QUESTION should we add extensions that execute before conversion begins?
         | 
| 1040 1049 |  | 
| 1041 1050 | 
             
                if doctype == 'inline'
         | 
| 1042 | 
            -
                   | 
| 1043 | 
            -
             | 
| 1044 | 
            -
             | 
| 1045 | 
            -
             | 
| 1046 | 
            -
             | 
| 1051 | 
            +
                  if (block = @blocks[0])
         | 
| 1052 | 
            +
                    if block.content_model == :compound || block.content_model == :empty
         | 
| 1053 | 
            +
                      warn %(asciidoctor: WARNING: no inline candidate; use the inline doctype to convert a single paragragh, verbatim, or raw block)
         | 
| 1054 | 
            +
                    else
         | 
| 1055 | 
            +
                      output = block.content
         | 
| 1056 | 
            +
                    end
         | 
| 1047 1057 | 
             
                  end
         | 
| 1048 1058 | 
             
                else
         | 
| 1049 1059 | 
             
                  transform = ((opts.key? :header_footer) ? opts[:header_footer] : @options[:header_footer]) ? 'document' : 'embedded'
         | 
| @@ -1062,7 +1072,7 @@ class Document < AbstractBlock | |
| 1062 1072 | 
             
              end
         | 
| 1063 1073 |  | 
| 1064 1074 | 
             
              # Alias render to convert to maintain backwards compatibility
         | 
| 1065 | 
            -
              alias  | 
| 1075 | 
            +
              alias render convert
         | 
| 1066 1076 |  | 
| 1067 1077 | 
             
              # Public: Write the output to the specified file
         | 
| 1068 1078 | 
             
              #
         | 
| @@ -1076,10 +1086,10 @@ class Document < AbstractBlock | |
| 1076 1086 | 
             
                    unless output.nil_or_empty?
         | 
| 1077 1087 | 
             
                      target.write output.chomp
         | 
| 1078 1088 | 
             
                      # ensure there's a trailing endline
         | 
| 1079 | 
            -
                      target.write  | 
| 1089 | 
            +
                      target.write LF
         | 
| 1080 1090 | 
             
                    end
         | 
| 1081 1091 | 
             
                  else
         | 
| 1082 | 
            -
                    :: | 
| 1092 | 
            +
                    ::IO.write target, output
         | 
| 1083 1093 | 
             
                  end
         | 
| 1084 1094 | 
             
                  nil
         | 
| 1085 1095 | 
             
                end
         | 
| @@ -1126,11 +1136,9 @@ class Document < AbstractBlock | |
| 1126 1136 | 
             
                if safe >= SafeMode::SECURE
         | 
| 1127 1137 | 
             
                  ''
         | 
| 1128 1138 | 
             
                else
         | 
| 1129 | 
            -
                   | 
| 1139 | 
            +
                  content = []
         | 
| 1140 | 
            +
                  qualifier = %(-#{location}) unless location == :head
         | 
| 1130 1141 | 
             
                  suffix = @outfilesuffix unless suffix
         | 
| 1131 | 
            -
                  docinfodir = @attributes['docinfodir']
         | 
| 1132 | 
            -
             | 
| 1133 | 
            -
                  content = nil
         | 
| 1134 1142 |  | 
| 1135 1143 | 
             
                  if (docinfo = @attributes['docinfo']).nil_or_empty?
         | 
| 1136 1144 | 
             
                    if @attributes.key? 'docinfo2'
         | 
| @@ -1141,51 +1149,46 @@ class Document < AbstractBlock | |
| 1141 1149 | 
             
                      docinfo = docinfo ? ['private'] : nil
         | 
| 1142 1150 | 
             
                    end
         | 
| 1143 1151 | 
             
                  else
         | 
| 1144 | 
            -
                    docinfo = docinfo.split(',').map | 
| 1152 | 
            +
                    docinfo = docinfo.split(',').map {|it| it.strip }
         | 
| 1145 1153 | 
             
                  end
         | 
| 1146 1154 |  | 
| 1147 1155 | 
             
                  if docinfo
         | 
| 1148 | 
            -
                     | 
| 1156 | 
            +
                    docinfo_file, docinfo_dir, docinfo_subs = %(docinfo#{qualifier}#{suffix}), @attributes['docinfodir'], resolve_docinfo_subs
         | 
| 1149 1157 | 
             
                    unless (docinfo & ['shared', %(shared-#{location})]).empty?
         | 
| 1150 | 
            -
                      docinfo_path = normalize_system_path | 
| 1158 | 
            +
                      docinfo_path = normalize_system_path docinfo_file, docinfo_dir
         | 
| 1151 1159 | 
             
                      # NOTE normalizing the lines is essential if we're performing substitutions
         | 
| 1152 | 
            -
                      if ( | 
| 1153 | 
            -
                         | 
| 1154 | 
            -
                          content = (docinfosubs == :attributes) ? sub_attributes(content) : apply_subs(content, docinfosubs)
         | 
| 1155 | 
            -
                        end
         | 
| 1160 | 
            +
                      if (shd_content = (read_asset docinfo_path, :normalize => true))
         | 
| 1161 | 
            +
                        content << (apply_subs shd_content, docinfo_subs)
         | 
| 1156 1162 | 
             
                      end
         | 
| 1157 1163 | 
             
                    end
         | 
| 1158 1164 |  | 
| 1159 1165 | 
             
                    unless @attributes['docname'].nil_or_empty? || (docinfo & ['private', %(private-#{location})]).empty?
         | 
| 1160 | 
            -
                      docinfo_path = normalize_system_path | 
| 1166 | 
            +
                      docinfo_path = normalize_system_path %(#{@attributes['docname']}-#{docinfo_file}), docinfo_dir
         | 
| 1161 1167 | 
             
                      # NOTE normalizing the lines is essential if we're performing substitutions
         | 
| 1162 | 
            -
                      if ( | 
| 1163 | 
            -
                         | 
| 1164 | 
            -
                          content2 = (docinfosubs == :attributes) ? sub_attributes(content2) : apply_subs(content2, docinfosubs)
         | 
| 1165 | 
            -
                        end
         | 
| 1166 | 
            -
                        content = content ? %(#{content}#{EOL}#{content2}) : content2
         | 
| 1168 | 
            +
                      if (pvt_content = (read_asset docinfo_path, :normalize => true))
         | 
| 1169 | 
            +
                        content << (apply_subs pvt_content, docinfo_subs)
         | 
| 1167 1170 | 
             
                      end
         | 
| 1168 1171 | 
             
                    end
         | 
| 1169 1172 | 
             
                  end
         | 
| 1170 1173 |  | 
| 1171 1174 | 
             
                  # TODO allow document to control whether extension docinfo is contributed
         | 
| 1172 | 
            -
                  if @extensions && docinfo_processors? | 
| 1173 | 
            -
                     | 
| 1174 | 
            -
                    content = content ? %(#{content}#{EOL}#{contentx}) : contentx
         | 
| 1175 | 
            +
                  if @extensions && (docinfo_processors? location)
         | 
| 1176 | 
            +
                    content += @docinfo_processor_extensions[location].map {|ext| ext.process_method[self] }.compact
         | 
| 1175 1177 | 
             
                  end
         | 
| 1176 1178 |  | 
| 1177 | 
            -
                   | 
| 1178 | 
            -
                  %(#{content})
         | 
| 1179 | 
            +
                  content * LF
         | 
| 1179 1180 | 
             
                end
         | 
| 1180 1181 | 
             
              end
         | 
| 1181 1182 |  | 
| 1183 | 
            +
              # Internal: Resolve the list of comma-delimited subs to apply to docinfo files.
         | 
| 1184 | 
            +
              #
         | 
| 1185 | 
            +
              # Resolve the list of substitutions from the value of the docinfosubs
         | 
| 1186 | 
            +
              # document attribute, if specified. Otherwise, return an Array containing
         | 
| 1187 | 
            +
              # the Symbol :attributes.
         | 
| 1188 | 
            +
              #
         | 
| 1189 | 
            +
              # Returns an [Array] of substitution [Symbol]s
         | 
| 1182 1190 | 
             
              def resolve_docinfo_subs
         | 
| 1183 | 
            -
                 | 
| 1184 | 
            -
                  subs = resolve_subs @attributes['docinfosubs'], :block, nil, 'docinfo'
         | 
| 1185 | 
            -
                  subs.empty? ? nil : subs
         | 
| 1186 | 
            -
                else
         | 
| 1187 | 
            -
                  :attributes
         | 
| 1188 | 
            -
                end
         | 
| 1191 | 
            +
                (@attributes.key? 'docinfosubs') ? (resolve_subs @attributes['docinfosubs'], :block, nil, 'docinfo') : [:attributes]
         | 
| 1189 1192 | 
             
              end
         | 
| 1190 1193 |  | 
| 1191 1194 | 
             
              def docinfo_processors?(location = :head)
         |