lib/kramdown/parser/kramdown/html.rb



# -*- coding: utf-8 -*-
#
#--
# Copyright (C) 2009-2014 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown which is licensed under the MIT.
#++
#

require 'kramdown/parser/html'

module Kramdown
  module Parser
    class Kramdown

      include Kramdown::Parser::Html::Parser

      # Mapping of markdown attribute value to content model. I.e. :raw when "0", :default when "1"
      # (use default content model for the HTML element), :span when "span", :block when block and
      # for everything else +nil+ is returned.
      HTML_MARKDOWN_ATTR_MAP = {"0" => :raw, "1" => :default, "span" => :span, "block" => :block}

      TRAILING_WHITESPACE = /[ \t]*\n/

      def handle_kramdown_html_tag(el, closed, handle_body)
        el.options[:ial] = @block_ial if @block_ial

        content_model = if @tree.type != :html_element || @tree.options[:content_model] != :raw
                          (@options[:parse_block_html] ? HTML_CONTENT_MODEL[el.value] : :raw)
                        else
                          :raw
                        end
        if val = HTML_MARKDOWN_ATTR_MAP[el.attr.delete('markdown')]
          content_model = (val == :default ? HTML_CONTENT_MODEL[el.value] : val)
        end

        @src.scan(TRAILING_WHITESPACE) if content_model == :block
        el.options[:content_model] = content_model
        el.options[:is_closed] = closed

        if !closed && handle_body
          if content_model == :block
            if !parse_blocks(el)
              warning("Found no end tag for '#{el.value}' (line #{el.options[:location]}) - auto-closing it")
            end
          elsif content_model == :span
            curpos = @src.pos
            if @src.scan_until(/(?=<\/#{el.value}\s*>)/mi)
              add_text(extract_string(curpos...@src.pos, @src), el)
              @src.scan(HTML_TAG_CLOSE_RE)
            else
              add_text(@src.rest, el)
              @src.terminate
              warning("Found no end tag for '#{el.value}' (line #{el.options[:location]}) - auto-closing it")
            end
          else
            parse_raw_html(el, &method(:handle_kramdown_html_tag))
          end
          @src.scan(TRAILING_WHITESPACE) unless (@tree.type == :html_element && @tree.options[:content_model] == :raw)
        end
      end


      HTML_BLOCK_START = /^#{OPT_SPACE}<(#{REXML::Parsers::BaseParser::UNAME_STR}|\?|!--|\/)/

      # Parse the HTML at the current position as block-level HTML.
      def parse_block_html
        line = @src.current_line_number
        if result = @src.scan(HTML_COMMENT_RE)
          @tree.children << Element.new(:xml_comment, result, nil, :category => :block, :location => line)
          @src.scan(TRAILING_WHITESPACE)
          true
        elsif result = @src.scan(HTML_INSTRUCTION_RE)
          @tree.children << Element.new(:xml_pi, result, nil, :category => :block, :location => line)
          @src.scan(TRAILING_WHITESPACE)
          true
        else
          if result = @src.check(/^#{OPT_SPACE}#{HTML_TAG_RE}/) && !HTML_SPAN_ELEMENTS.include?(@src[1].downcase)
            @src.pos += @src.matched_size
            handle_html_start_tag(line, &method(:handle_kramdown_html_tag))
            Kramdown::Parser::Html::ElementConverter.convert(@root, @tree.children.last) if @options[:html_to_native]
            true
          elsif result = @src.check(/^#{OPT_SPACE}#{HTML_TAG_CLOSE_RE}/) && !HTML_SPAN_ELEMENTS.include?(@src[1].downcase)
            name = @src[1].downcase

            if @tree.type == :html_element && @tree.value == name
              @src.pos += @src.matched_size
              throw :stop_block_parsing, :found
            else
              false
            end
          else
            false
          end
        end
      end
      define_parser(:block_html, HTML_BLOCK_START)


      HTML_SPAN_START = /<(#{REXML::Parsers::BaseParser::UNAME_STR}|\?|!--|\/)/

      # Parse the HTML at the current position as span-level HTML.
      def parse_span_html
        line = @src.current_line_number
        if result = @src.scan(HTML_COMMENT_RE)
          @tree.children << Element.new(:xml_comment, result, nil, :category => :span, :location => line)
        elsif result = @src.scan(HTML_INSTRUCTION_RE)
          @tree.children << Element.new(:xml_pi, result, nil, :category => :span, :location => line)
        elsif result = @src.scan(HTML_TAG_CLOSE_RE)
          warning("Found invalidly used HTML closing tag for '#{@src[1]}' on line #{line}")
          add_text(result)
        elsif result = @src.scan(HTML_TAG_RE)
          tag_name = @src[1].downcase
          if HTML_BLOCK_ELEMENTS.include?(tag_name)
            warning("Found block HTML tag '#{tag_name}' in span-level text on line #{line}")
            add_text(result)
            return
          end

          attrs = Utils::OrderedHash.new
          @src[2].scan(HTML_ATTRIBUTE_RE).each {|name,sep,val| attrs[name.downcase] = (val || '').gsub(/\n+/, ' ')}

          do_parsing = (HTML_CONTENT_MODEL[tag_name] == :raw || @tree.options[:content_model] == :raw ? false : @options[:parse_span_html])
          if val = HTML_MARKDOWN_ATTR_MAP[attrs.delete('markdown')]
            if val == :block
              warning("Cannot use block-level parsing in span-level HTML tag (line #{line}) - using default mode")
            elsif val == :span
              do_parsing = true
            elsif val == :default
              do_parsing = HTML_CONTENT_MODEL[tag_name] != :raw
            elsif val == :raw
              do_parsing = false
            end
          end

          el = Element.new(:html_element, tag_name, attrs, :category => :span, :location => line,
                           :content_model => (do_parsing ? :span : :raw), :is_closed => !!@src[4])
          @tree.children << el
          stop_re = /<\/#{Regexp.escape(tag_name)}\s*>/i
          if !@src[4] && HTML_ELEMENTS_WITHOUT_BODY.include?(el.value)
            warning("The HTML tag '#{el.value}' on line #{line} cannot have any content - auto-closing it")
          elsif !@src[4]
            if parse_spans(el, stop_re, (do_parsing ? nil : [:span_html]))
              @src.scan(stop_re)
            else
              warning("Found no end tag for '#{el.value}' (line #{line}) - auto-closing it")
              add_text(@src.rest, el)
              @src.terminate
            end
          end
          Kramdown::Parser::Html::ElementConverter.convert(@root, el) if @options[:html_to_native]
        else
          add_text(@src.getch)
        end
      end
      define_parser(:span_html, HTML_SPAN_START, '<')

    end
  end
end