global

def xml_from_sections(input)

def xml_from_sections(input)

  unless ENV["KRAMDOWN_NO_SOURCE"]
    require 'kramdown-rfc/gzip-clone'
    require 'base64'
    compressed_input = Gzip.compress_m0(input)
    $source = Base64.encode64(compressed_input)
  end

  sections = input.scan(RE_SECTION)
  # resulting in an array; each section is [section-label, nomarkdown-flag, section-text]
  line = 1                      # skip "---"
  sections.each do |section|
    section << line
    line += 1 + section[2].lines.count
  end
  # warn "#{line-1} lines"

  # the first section is a YAML with front matter parameters (don't put a label here)
  # We put back the "---" plus gratuitous blank lines to hack the line number in errors
  yaml_in = input[/---\s*/] << sections.shift[2]
  ps = KramdownRFC::ParameterSet.new(yaml_load(yaml_in, [Date], [], true))

  if v = ps[:v]
    warn "*** unsupported RFCXML version #{v}" if v != 3
    if $options.v2
      warn "*** command line --v2 wins over document's 'v: #{v}'"
    else
      $options.v3 = true
      $options.v = 3
      ps.default!(:stand_alone, true)
      ps.default!(:ipr, "trust200902")
      ps.default!(:pi,  {"toc" => true, "sortrefs" => true, "symrefs" => true})
    end
  end

  if r = ENV["KRAMDOWN_RFC_DOCREV"]
    warn "** building document revision -#{r}"
    unless n = ps.has(:docname) and n.sub!(/-latest\z/, "-#{r}")
      warn "** -d#{r}: docname #{n.inspect} doesn't have a '-latest' suffix"
    end
  end

  if o = ps[:'autolink-iref-cleanup']
    $options.autolink_iref_cleanup = o
  end
  if o = ps[:'svg-id-cleanup']
    $options.svg_id_cleanup = o
  end

  coding_override = ps.has(:coding)
  smart_quotes = ps[:smart_quotes] || ps[:"smart-quotes"]
  typographic_symbols = ps[:typographic_symbols]
  header_kramdown_options = ps[:kramdown_options]

  kramdown_options = process_kramdown_options(coding_override,
                                              smart_quotes, typographic_symbols,
                                              header_kramdown_options)

  # all the other sections are put in a Hash, possibly concatenated from parts there
  sechash = Hash.new{ |h,k| h[k] = ""}
  snames = []                   # a stack of section names
  sections.each do |sname, nmdflag, text, line|
    # warn [:SNAME, sname, nmdflag, text[0..10]].inspect
    nmdin, nmdout = {
      "-" => ["", ""],          # stay in nomarkdown
      "" => NMDTAGS, # pop out temporarily
    }[nmdflag || ""]
    if sname
      snames << sname           # "--- label" -> push label (now current)
    else
      snames.pop                # just "---" -> pop label (previous now current)
    end
    sechash[snames.last] << "#{nmdin}<?line #{line}?>\n#{text}#{nmdout}"
  end

  ref_replacements = { }
  anchor_to_bibref = { }

  displayref = {}

  [:ref, :normative, :informative].each do |sn|
    if refs = ps.has(sn)
      warn "*** bad section #{sn}: #{refs.inspect}" unless refs.respond_to? :each
      refs.each do |k, v|
        if v.respond_to? :to_str
          if bibtagsys(v)       # enable "foo: RFC4711" as a custom anchor definition
            anchor_to_bibref[k] = v.to_str
          end
          ref_replacements[v.to_str] = k
        end
        if Hash === v
          if aliasname = v.delete("-")
            ref_replacements[aliasname] = k
          end
          if bibref = v.delete("=")
            anchor_to_bibref[k] = bibref
          end
          if dr = v.delete("display")
            displayref[k.gsub("/", "_")] = dr
          end
        end
      end
    end
  end
  open_refs = ps[:ref] || { }       # consumed

  norm_ref = { }

  # convenience replacement of {{-coap}} with {{I-D.ietf-core-coap}}
  # collect normative/informative tagging {{!RFC2119}} {{?RFC4711}}
  sechash.each do |k, v|
    next if k == "fluff"
    v.gsub!(/{{(#{
      spacify_re(XSR_PREFIX)
    })?([\w.\/_\-]+@)?(?:([?!])(-)?|(-))([\w._\-]+)(?:=([\w.\/_\-]+))?(#{
      XREF_TXT_SUFFIX
    })?(#{
      spacify_re(XSR_SUFFIX)
    })?}}/) do |match|
      xsr_prefix = $1
      subref = $2
      norminform = $3
      replacing = $4 || $5
      word = $6
      bibref = $7
      xrt_suffix = $8
      xsr_suffix = $9
      if replacing
        if new = ref_replacements[word]
          word = new
        else
          warn "*** no alias replacement for {{-#{word}}}"
          word = "-#{word}"
        end
      end       # now, word is the anchor
      if bibref
        if old = anchor_to_bibref[word]
          if bibref != old
            warn "*** conflicting definitions for xref #{word}: #{old} != #{bibref}"
          end
        else
          anchor_to_bibref[word] = bibref
        end
      end

      # things can be normative in one place and informative in another -> normative
      # collect norm/inform above and assign it by priority here
      if norminform
        norm_ref[word] ||= norminform == '!' # one normative ref is enough
      end
      "{{#{xsr_prefix}#{subref}#{word}#{xrt_suffix}#{xsr_suffix}}}"
    end
  end

  [:normative, :informative].each do |k|
    ps.rest[k.to_s] ||= { }
  end

  norm_ref.each do |k, v|
    # could check bibtagsys here: needed if open_refs is nil or string
    target = ps.has(v ? :normative : :informative)
    warn "*** overwriting #{k}" if target.has_key?(k)
    target[k] = open_refs[k] # add reference to normative/informative
  end
  # note that unused items from ref are considered OK, therefore no check for that here

  # also should allow norm/inform check of other references
  # {{?coap}} vs. {{!coap}} vs. {{-coap}} (undecided)
  # or {{?-coap}} vs. {{!-coap}} vs. {{-coap}} (undecided)
  # could require all references to be decided by a global flag
  overlap = [:normative, :informative].map { |s| (ps.has(s) || { }).keys }.reduce(:&)
  unless overlap.empty?
    warn "*** #{overlap.join(', ')}: both normative and informative"
  end

  stand_alone = ps[:stand_alone]

  [:normative, :informative].each do |sn|
    if refs = ps[sn]
      refs.each do |k, v|
        href = ::Kramdown::Parser::RFC2629Kramdown.idref_cleanup(k)
        kramdown_options[:link_defs][k] = ["##{href}", nil]   # allow [RFC2119] in addition to {{RFC2119}}

        bibref = anchor_to_bibref[k] || k
        bts, url = bibtagsys(bibref, k, stand_alone)
        ann = v.delete("annotation") || v.delete("ann") if Hash === v
        if bts && (!v || v == {} || v.respond_to?(:to_str))
          if stand_alone
            a = %{{: anchor="#{k}"}}
            a[-1...-1] = %{ ann="#{escape_html(ann, :attribute)}"} if ann
            sechash[sn.to_s] << %{\n#{NMDTAGS[0]}\n![:include:](#{bts})#{a}\n#{NMDTAGS[1]}\n}
          else
            warn "*** please use standalone mode for adding annotations to references" if ann
            bts.gsub!('/', '_')
            (ps.rest["bibxml"] ||= []) << [bts, url]
            sechash[sn.to_s] << %{&#{bts};\n} # ???
          end
        else
          unless v && Hash === v
            warn "*** don't know how to expand ref #{k}"
            next
          end
          if bts && !v.delete("override")
            warn "*** warning: explicit settings completely override canned bibxml in reference #{k}"
          end
          v["ann"] = ann if ann
          sechash[sn.to_s] << KramdownRFC::ref_to_xml(href, v)
        end
      end
    end
  end

  erbfilename = File.expand_path '../../../data/kramdown-rfc2629.erb', __FILE__
  erbfile = File.read(erbfilename, coding: "UTF-8")
  erb = ERB.trim_new(erbfile, '-')
  # remove redundant nomarkdown pop outs/pop ins as they confuse kramdown
  input = erb.result(binding).gsub(%r"{::nomarkdown}\s*{:/nomarkdown}"m, "")
  ps.warn_if_leftovers
  sechash.delete("fluff")       # fluff is a "commented out" section
  if !sechash.empty?            # any sections unused by the ERb file?
    warn "*** sections left #{sechash.keys.inspect}!"
  end

  [input, kramdown_options, coding_override]
end