Class: Kramdown::Converter::Rfc2629

Inherits:
Base
  • Object
show all
Includes:
Utils::Html
Defined in:
lib/kramdown-rfc2629.rb

Overview

Converts a Kramdown::Document to HTML.

Constant Summary collapse

INDENTATION =

Defines the amount of indentation used when nesting XML tags.

2
STYLES =
{ul: 'symbols', ol: 'numbers', dl: 'hanging'}
HTML_TAGS_WITH_BODY =
['div', 'script']
ALIGNMENTS =
{ default: :left, left: :left, right: :right, center: :center}
COLS_ALIGN =
{ "l" => :left, "c" => :center, "r" => :right}
REFCACHEDIR =
".refcache"
XML_RESOURCE_ORG_MAP =
{
  "RFC" => "bibxml", "I-D" => "bibxml3", "W3C" => "bibxml4", "3GPP" => "bibxml5",
  "ANSI" => "bibxml2",
  "CCITT" => "bibxml2",
  "FIPS" => "bibxml2",
  "IANA" => "bibxml2",
  "IEEE" => "bibxml2",
  "ISO" => "bibxml2",
  "ITU" => "bibxml2",
  "NIST" => "bibxml2",
  "OASIS" => "bibxml2",
  "PKCS" => "bibxml2",
}
XML_RESOURCE_ORG_HOST =

XML_RESOURCE_ORG_HOST = ENV || “xml.resource.org”

ENV["XML_RESOURCE_ORG_HOST"] || "xml2rfc.tools.ietf.org"
XML_RESOURCE_ORG_PREFIX =
ENV["XML_RESOURCE_ORG_PREFIX"] ||
"http://#{XML_RESOURCE_ORG_HOST}/public/rfc"
EMPH =
{ em: "emph", strong: "strong"}
TYPOGRAPHIC_SYMS =
{
  :mdash => [::Kramdown::Utils::Entities.entity('mdash')],
  :ndash => [::Kramdown::Utils::Entities.entity('ndash')],
  :hellip => [::Kramdown::Utils::Entities.entity('hellip')],
  :laquo_space => [::Kramdown::Utils::Entities.entity('laquo'), ::Kramdown::Utils::Entities.entity('nbsp')],
  :raquo_space => [::Kramdown::Utils::Entities.entity('nbsp'), ::Kramdown::Utils::Entities.entity('raquo')],
  :laquo => [::Kramdown::Utils::Entities.entity('laquo')],
  :raquo => [::Kramdown::Utils::Entities.entity('raquo')]
}

Instance Method Summary collapse

Constructor Details

#initialize(*doc) ⇒ Rfc2629

Initialize the XML converter with the given Kramdown document doc.



88
89
90
91
92
# File 'lib/kramdown-rfc2629.rb', line 88

def initialize(*doc)
  super
  @sec_level = 1
  @in_dt = 0
end

Instance Method Details

#convert(el, indent = -INDENTATION,, opts = {}) ⇒ Object



94
95
96
97
98
99
# File 'lib/kramdown-rfc2629.rb', line 94

def convert(el, indent = -INDENTATION, opts = {})
  if el.children[-1].type == :raw
    raw = convert1(el.children.pop, indent, opts)
  end
  "#{convert1(el, indent, opts)}#{end_sections(1, indent)}#{raw}"
end

#convert1(el, indent, opts = {}) ⇒ Object



101
102
103
104
# File 'lib/kramdown-rfc2629.rb', line 101

def convert1(el, indent, opts = {})
  el.rfc2629_fix
  send("convert_#{el.type}", el, indent, opts)
end

#convert_a(el, indent, opts) ⇒ Object



334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/kramdown-rfc2629.rb', line 334

def convert_a(el, indent, opts)
  do_obfuscation = el.attr['href'] =~ /^mailto:/
  if do_obfuscation
    el = el.deep_clone
    href = obfuscate(el.attr['href'].sub(/^mailto:/, ''))
    mailto = obfuscate('mailto')
    el.attr['href'] = "#{mailto}:#{href}"
  end
  res = inner(el, indent, opts)
  res = obfuscate(res) if do_obfuscation
  "<eref#{el_html_attributes(el)}>#{res}</eref>"
end

#convert_abbreviation(el, indent, opts) ⇒ Object

XXX: This is wrong



468
469
470
471
472
# File 'lib/kramdown-rfc2629.rb', line 468

def convert_abbreviation(el, indent, opts) # XXX: This is wrong
  title = @doc.parse_infos[:abbrev_defs][el.value]
  title = nil if title.empty?
  "<abbr#{title ? " title=\"#{title}\"" : ''}>#{el.value}</abbr>"
end

#convert_blank(el, indent, opts) ⇒ Object



118
119
120
# File 'lib/kramdown-rfc2629.rb', line 118

def convert_blank(el, indent, opts)
  "\n"
end

#convert_blockquote(el, indent, opts) ⇒ Object



170
171
172
# File 'lib/kramdown-rfc2629.rb', line 170

def convert_blockquote(el, indent, opts)
  "#{' '*indent}<t><list style='empty'#{el_html_attributes(el)}>\n#{inner(el, indent, opts)}#{' '*indent}</list></t>\n"
end

#convert_br(el, indent, opts) ⇒ Object



330
331
332
# File 'lib/kramdown-rfc2629.rb', line 330

def convert_br(el, indent, opts)
  "<vspace />"
end

#convert_codeblock(el, indent, opts) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/kramdown-rfc2629.rb', line 138

def convert_codeblock(el, indent, opts)
  # el.attr['anchor'] ||= saner_generate_id(el.value) -- no longer in 1.0.6
  result = el.value
  blockclass = el.attr.delete('class')
  if blockclass == 'language-tbreak'
    result = result.lines.map {|line| [line.chomp, 0]}
    spaceind = 0
    result.each_with_index {|pair, index|
      if pair[0] == ''
        result[spaceind][1] += 1
        pair[0] = nil unless index == spaceind
      else
        spaceind = index
      end
    }
    # $stderr.puts(result.inspect)
    result = result.map {|line, space|
      "<![CDATA[#{line.gsub(/^\s+/) {|s| "\u00A0" * s.size}}]]><vspace blankLines=\"#{space}\"/>" if line
    }.compact.join("\n")
    "#{' '*indent}<t>#{result}</t>\n"
  else
    if blockclass
      $stderr.puts "*** Unimplemented block class: #{blockclass}"
    end
    # compensate for XML2RFC idiosyncracy by insisting on a blank line
    unless el.attr.delete('tight')
      result[0,0] = "\n" unless result[0,1] == "\n"
    end
    "#{' '*indent}<figure#{el_html_attributes(el)}><artwork><![CDATA[#{result}#{result =~ /\n\Z/ ? '' : "\n"}]]></artwork></figure>\n"
  end
end

#convert_codespan(el, indent, opts) ⇒ Object



416
417
418
419
# File 'lib/kramdown-rfc2629.rb', line 416

def convert_codespan(el, indent, opts)
  attrstring = el_html_attributes_with(el, {"style" => 'verb'})
  "<spanx#{attrstring}>#{escape_html(el.value)}</spanx>"
end

#convert_comment(el, indent, opts) ⇒ Object



321
322
323
324
325
326
327
328
# File 'lib/kramdown-rfc2629.rb', line 321

def convert_comment(el, indent, opts)
## Don't actually output all those comments into the XML:
#        if el.options[:category] == :block
#          "#{' '*indent}<!-- #{el.value} -->\n"
#        else
#          "<!-- #{el.value} -->"
#        end
end

#convert_dd(el, indent, opts) ⇒ Object



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/kramdown-rfc2629.rb', line 229

def convert_dd(el, indent, opts)
  output = ' '*indent
  if @in_dt == 1
    @in_dt = 0
  else
    output << "<t#{el_html_attributes(el)}>"
  end
  res = inner(el, indent+INDENTATION, opts.merge(unpacked: true))
#        if el.children.empty? || el.children.first.options[:category] != :block
    output << res << (res =~ /\n\Z/ ? ' '*indent : '')
#        else                    FIXME: The latter case is needed for more complex cases
#          output << "\n" << res << ' '*indent
#        end
  output << "</t>\n"
end

#convert_dt(el, indent, opts) ⇒ Object

SERIOUSLY BAD HACK:



245
246
247
248
249
250
251
# File 'lib/kramdown-rfc2629.rb', line 245

def convert_dt(el, indent, opts) # SERIOUSLY BAD HACK:
  close = "#{' '*indent}</t>\n" * @in_dt
  @in_dt = 1
  vspace = opts[:vspace]
  vspaceel = "<vspace blankLines='#{vspace}'/>" if vspace
  "#{close}#{' '*indent}<t#{el_html_attributes(el)} hangText='#{inner(el, indent, opts)}'>#{vspaceel}\n"
end

#convert_em(el, indent, opts) ⇒ Object Also known as: convert_strong



432
433
434
435
# File 'lib/kramdown-rfc2629.rb', line 432

def convert_em(el, indent, opts)
  attrstring = el_html_attributes_with(el, {"style" => EMPH[el.type]})
  "<spanx#{attrstring}>#{inner(el, indent, opts)}</spanx>"
end

#convert_entity(el, indent, opts) ⇒ Object



438
439
440
# File 'lib/kramdown-rfc2629.rb', line 438

def convert_entity(el, indent, opts)
  entity_to_str(el.value)
end

#convert_footnote(el, indent, opts) ⇒ Object

XXX: This is wrong.



421
422
423
# File 'lib/kramdown-rfc2629.rb', line 421

def convert_footnote(el, indent, opts) # XXX: This is wrong.
  "<xref target='#{escape_html(el.value)}'#{el_html_attributes(el)}/>"
end

#convert_header(el, indent, opts) ⇒ Object



187
188
189
190
191
192
193
194
195
196
# File 'lib/kramdown-rfc2629.rb', line 187

def convert_header(el, indent, opts)
  # todo: handle appendix tags
  el = el.deep_clone
  options = @doc ? @doc.options : @options # XXX: 0.11 vs. 0.12
  if options[:auto_ids] && !el.attr['anchor']
    el.attr['anchor'] = saner_generate_id(el.options[:raw_text])
  end
  el.attr['title'] = inner(el, indent, opts)
  "#{end_sections(el.options[:level], indent)}#{' '*indent}<section#{@sec_level += 1; el_html_attributes(el)}>\n"
end

#convert_hr(el, indent, opts) ⇒ Object

misuse for page break



198
199
200
# File 'lib/kramdown-rfc2629.rb', line 198

def convert_hr(el, indent, opts) # misuse for page break
  "#{' '*indent}<t><vspace blankLines='999' /></t>\n"
end

#convert_html_element(el, indent, opts) ⇒ Object



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/kramdown-rfc2629.rb', line 255

def convert_html_element(el, indent, opts)
  res = inner(el, indent, opts)
  if el.options[:category] == :span
    "<#{el.value}#{el_html_attributes(el)}" << (!res.empty? ? ">#{res}</#{el.value}>" : " />")
  else
    output = ''
    output << ' '*indent if !el.options[:parent_is_raw]
    output << "<#{el.value}#{el_html_attributes(el)}"
    if !res.empty? && el.options[:parse_type] != :block
      output << ">#{res}</#{el.value}>"
    elsif !res.empty?
      output << ">\n#{res}"  << ' '*indent << "</#{el.value}>"
    elsif HTML_TAGS_WITH_BODY.include?(el.value)
      output << "></#{el.value}>"
    else
      output << " />"
    end
    output << "\n" if el.options[:outer_element] || !el.options[:parent_is_raw]
    output
  end
end

#convert_img(el, indent, opts) ⇒ Object

misuse the tag!



392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# File 'lib/kramdown-rfc2629.rb', line 392

def convert_img(el, indent, opts) # misuse the tag!
  if a = el.attr
    alt = a.delete('alt').strip
    alt = '' if alt == '!' # work around re-wrap uglyness
    if anchor = a.delete('src')
      a['target'] = anchor
    end
  end
  if alt == ":include:"   # Really bad misuse of tag...
    to_insert = ""
    anchor.scan(/(W3C|3GPP|[A-Z-]+)[.]?([A-Za-z0-9.-]+)/) do |t, n|
      fn = "reference.#{t}.#{n}.xml"
      sub = XML_RESOURCE_ORG_MAP[t]
      puts "Huh: ${fn}" unless sub
      url = "#{XML_RESOURCE_ORG_PREFIX}/#{sub}/#{fn}"
      to_insert = get_and_cache_resource(url)
    end
    to_insert.gsub(/<\?xml version=["']1.0["'] encoding=["']UTF-8["']\?>/, '').
      gsub(/(anchor=["'])([0-9])/) { "#{$1}_#{$2}"} # can't start an ID with a number
  else
    "<xref#{el_html_attributes(el)}>#{alt}</xref>"
  end
end

#convert_li(el, indent, opts) ⇒ Object



216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/kramdown-rfc2629.rb', line 216

def convert_li(el, indent, opts)
  res_a = inner_a(el, indent, opts)
  if el.children.empty? || el.children.first.options[:category] == :span
    res = res_a.join('')
  else                    # merge multiple <t> elements
    res = res_a.select { |x|
      x.strip != ''
    }.map { |x|
      x.sub(/\A\s*<t>(.*)<\/t>\s*\Z/m) { $1}
    }.join("#{' '*indent}<vspace blankLines='1'/>\n").gsub(%r{(</list>)\s*<vspace blankLines='1'/>}) { $1 }.gsub(%r{<vspace blankLines='1'/>\s*(<list)}) { $1 }
  end
  "#{' '*indent}<t#{el_html_attributes(el)}>#{res}#{(res =~ /\n\Z/ ? ' '*indent : '')}</t>\n"
end

#convert_math(el, indent, opts) ⇒ Object

XXX: This is wrong



459
460
461
462
463
464
465
466
# File 'lib/kramdown-rfc2629.rb', line 459

def convert_math(el, indent, opts) # XXX: This is wrong
  el = el.deep_clone
  el.attr['class'] ||= ''
  el.attr['class'] += (el.attr['class'].empty? ? '' : ' ') + 'math'
  type = 'span'
  type = 'div' if el.options[:category] == :block
  "<#{type}#{el_html_attributes(el)}>#{escape_html(el.value, :text)}</#{type}>#{type == 'div' ? "\n" : ''}"
end

#convert_p(el, indent, opts) ⇒ Object



126
127
128
129
130
131
132
# File 'lib/kramdown-rfc2629.rb', line 126

def convert_p(el, indent, opts)
  if (el.children.size == 1 && el.children[0].type == :img) || opts[:unpacked]
    inner(el, indent, opts) # Part of the bad reference hack
  else
    "#{' '*indent}<t#{el_html_attributes(el)}>#{inner(el, indent, opts)}</t>\n"
  end
end

#convert_raw(el, indent, opts) ⇒ Object



425
426
427
428
# File 'lib/kramdown-rfc2629.rb', line 425

def convert_raw(el, indent, opts)
  end_sections(1, indent) +
  el.value + (el.options[:category] == :block ? "\n" : '')
end

#convert_root(el, indent, opts) ⇒ Object



474
475
476
# File 'lib/kramdown-rfc2629.rb', line 474

def convert_root(el, indent, opts)
  result = inner(el, indent, opts)
end

#convert_smart_quote(el, indent, opts) ⇒ Object



455
456
457
# File 'lib/kramdown-rfc2629.rb', line 455

def convert_smart_quote(el, indent, opts)
  entity_to_str(::Kramdown::Utils::Entities.entity(el.value.to_s))
end

#convert_table(el, indent, opts) ⇒ Object

This only works for tables with headers



290
291
292
293
294
# File 'lib/kramdown-rfc2629.rb', line 290

def convert_table(el, indent, opts) # This only works for tables with headers
  alignment = el.options[:alignment].map { |al| ALIGNMENTS[al]}
  cols = (el.attr.delete("cols") || "").split(' ')
  "#{' '*indent}<texttable#{el_html_attributes(el)}>\n#{inner(el, indent, opts.merge(table_alignment: alignment, table_cols: cols))}#{' '*indent}</texttable>\n"
end

#convert_td(el, indent, opts) ⇒ Object Also known as: convert_th



303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/kramdown-rfc2629.rb', line 303

def convert_td(el, indent, opts)
  if alignment = opts[:table_alignment]
    alignment = alignment.shift
    if cols = opts[:table_cols].shift
      md = cols.match(/(\d*)(.*)/)
      widthopt = "width='#{md[1]}' " if md[1].to_i != 0
      alignment = COLS_ALIGN[md[2]] || :left
    end
  end
  res = inner(el, indent, opts)
  if alignment
    "#{' '*indent}<ttcol #{widthopt}align='#{alignment}'#{el_html_attributes(el)}>#{res.empty? ? "&#160;" : res}</ttcol>\n"
    else
    "#{' '*indent}<c#{el_html_attributes(el)}>#{res.empty? ? "&#160;" : res}</c>\n"
  end
end

#convert_text(el, indent, opts) ⇒ Object



122
123
124
# File 'lib/kramdown-rfc2629.rb', line 122

def convert_text(el, indent, opts)
  escape_html(el.value, :text)
end

#convert_thead(el, indent, opts) ⇒ Object Also known as: convert_tbody, convert_tfoot, convert_tr



296
297
298
# File 'lib/kramdown-rfc2629.rb', line 296

def convert_thead(el, indent, opts)
  inner(el, indent, opts)
end

#convert_typographic_sym(el, indent, opts) ⇒ Object



451
452
453
# File 'lib/kramdown-rfc2629.rb', line 451

def convert_typographic_sym(el, indent, opts)
  TYPOGRAPHIC_SYMS[el.value].map {|e| entity_to_str(e)}.join('')
end

#convert_ul(el, indent, opts) ⇒ Object Also known as: convert_ol, convert_dl



204
205
206
207
208
209
210
211
212
# File 'lib/kramdown-rfc2629.rb', line 204

def convert_ul(el, indent, opts)
  opts = opts.merge(vspace: el.attr.delete('vspace'))
  attrstring = el_html_attributes_with(el, {"style" => STYLES[el.type]})
  if opts[:unpacked]
    "#{' '*indent}<list#{attrstring}>\n#{inner(el, indent, opts)}#{' '*indent}</list>\n"
    else
    "#{' '*indent}<t><list#{attrstring}>\n#{inner(el, indent, opts)}#{' '*indent}</list></t>\n"
  end
end

#convert_xml_comment(el, indent, opts) ⇒ Object Also known as: convert_xml_pi, convert_html_doctype



277
278
279
280
281
282
283
# File 'lib/kramdown-rfc2629.rb', line 277

def convert_xml_comment(el, indent, opts)
  if el.options[:category] == :block && !el.options[:parent_is_raw]
    ' '*indent + el.value + "\n"
  else
    el.value
  end
end

#convert_xref(el, indent, opts) ⇒ Object



347
348
349
350
351
352
353
354
# File 'lib/kramdown-rfc2629.rb', line 347

def convert_xref(el, indent, opts)
  target = el.attr['target']
  if target[0] == "&"
    "#{target};"
  else
    "<xref#{el_html_attributes(el)}/>"
  end
end

#el_html_attributes(el) ⇒ Object



75
76
77
# File 'lib/kramdown-rfc2629.rb', line 75

def el_html_attributes(el)
  html_attributes(el.attr)
end

#el_html_attributes_with(el, defattr) ⇒ Object



78
79
80
# File 'lib/kramdown-rfc2629.rb', line 78

def el_html_attributes_with(el, defattr)
  html_attributes(defattr.merge(el.attr))
end

#end_sections(to_level, indent) ⇒ Object



174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/kramdown-rfc2629.rb', line 174

def end_sections(to_level, indent)
  if indent < 0
    indent = 0
  end
  if @sec_level >= to_level
    delta = (@sec_level - to_level)
    @sec_level = to_level
    "#{' '*indent}</section>\n" * delta
  else
    $stderr.puts "Incorrect section nesting: Need to start with 1"
  end
end

#get_and_cache_resource(url, tn = Time.now, tvalid = 7200) ⇒ Object



357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# File 'lib/kramdown-rfc2629.rb', line 357

def get_and_cache_resource(url, tn = Time.now, tvalid = 7200)
  Dir.mkdir(REFCACHEDIR) unless Dir.exists?(REFCACHEDIR)
  fn = "#{REFCACHEDIR}/#{File.basename(url)}"
  f = File.stat(fn) rescue nil
  if !f || tn - f.ctime >= tvalid
    $stderr.puts "#{fn}: #{f && tn-f.ctime}"
    `cd #{REFCACHEDIR}; wget -t 3 -T 20 -Nnv "#{url}"` # ignore errors if offline (hack)
    begin
      File.utime nil, nil, fn
    rescue Errno::ENOENT
      warn "Can't fetch #{url} -- is wget in path?"
    end
  end
  File.read(fn)
end

#inner(el, indent, opts) ⇒ Object



114
115
116
# File 'lib/kramdown-rfc2629.rb', line 114

def inner(el, indent, opts)
  inner_a(el, indent, opts).join('')
end

#inner_a(el, indent, opts) ⇒ Object



106
107
108
109
110
111
112
# File 'lib/kramdown-rfc2629.rb', line 106

def inner_a(el, indent, opts)
  indent += INDENTATION
  el.children.map do |inner_el|
    inner_el.rfc2629_fix
    send("convert_#{inner_el.type}", inner_el, indent, opts)
  end
end

#obfuscate(text) ⇒ Object

Helper method for obfuscating the text by using XML entities.



479
480
481
482
483
484
485
486
# File 'lib/kramdown-rfc2629.rb', line 479

def obfuscate(text)
  result = ""
  text.each_byte do |b|
    result += (b > 128 ? b.chr : "&#%03d;" % b)
  end
  result.force_encoding(text.encoding) if RUBY_VERSION >= '1.9'
  result
end

#saner_generate_id(value) ⇒ Object



134
135
136
# File 'lib/kramdown-rfc2629.rb', line 134

def saner_generate_id(value)
  generate_id(value).gsub(/-+/, '-')
end