class TartanCloth

Constants

VERSION

Attributes

title[RW]

Public Class Methods

new( markdown_file, title = nil ) click to toggle source
# File lib/tartancloth.rb, line 12
def initialize( markdown_file, title = nil )
  @markdown_file = markdown_file
  @title = title
end

Public Instance Methods

body_html() click to toggle source

Build TOC and return body content (including TOC). Returned HTML does NOT include doc headers, footer, or stylesheet.

returns HTML that forms the body of the document

# File lib/tartancloth.rb, line 53
def body_html
  bc = BlueCloth::new( File::read( @markdown_file ), header_labels: true )
  body = bc.to_html

  body = build_toc( body )
end
to_html() click to toggle source

Convert a markdown source file to HTML. If a header element with text TOC exists within the markdown document, a Table of Contents will be generated and inserted at that location.

The TOC will only contain header (h1-h6) elements from the location of the TOC header to the end of the document

# File lib/tartancloth.rb, line 24
def to_html
  html = ""

  # Add a well formed HTML5 header.
  html << html_header(title)

  # Add the body content.
  html << body_html()

  # Add the document closing tags.
  html << html_footer()
end
to_html_file( html_file ) click to toggle source

The same as to_html() but writes the HTML to a file.

html_file - path to file

# File lib/tartancloth.rb, line 41
def to_html_file( html_file )

  File.open( html_file, 'w') do |f|
    f << to_html()
  end
end

Private Instance Methods

build_toc( html_content ) click to toggle source

Build a TOC based on headers located within HTML content. If a header element with text TOC exists within the markdown document, a Table of Contents will be generated and inserted at that location.

The TOC will only contain header (h1-h6) elements from the location of the TOC header to the end of the document

# File lib/tartancloth.rb, line 69
def build_toc( html_content )
  # Generate Nokogiri elements from HTML
  doc = Nokogiri::HTML::DocumentFragment.parse( html_content )

  # Make sure all header anchors are unique.
  make_header_anchors_unique(doc)

  # Find the TOC header
  toc = find_toc_header(doc)

  # Just return what was passed to us if there's no TOC.
  return html_content if toc.nil?

  # Get all headers in the document, starting from the TOC header.
  headers = get_headers(doc, toc)

  # Build the link info for the TOC.
  toc_links = []
  headers.each do |element|
    toc_links << link_hash(element)
  end

  # Convert link info to markdown.
  toc_md = toc_to_markdown(toc_links)

  # Convert the TOC markdown to HTML
  bc = BlueCloth::new( toc_md, pseudoprotocols: true )
  toc_content = bc.to_html

  # Convert the TOC HTML to Nokogiri elements.
  toc_html = Nokogiri::HTML::DocumentFragment.parse(toc_content)

  # Add toc class to the <ul> element
  toc_html.css('ul').add_class('toc')

  # Insert the TOC content before the toc element
  toc.before(toc_html.children)

  # Remove the TOC header placeholder element.
  toc.remove

  # Return the HTML
  doc.to_html
end
css() click to toggle source

Create some stylish CSS

returns - html style element

# File lib/tartancloth.rb, line 282
  def css()
    styles = <<CSS
<style media="screen" type="text/css">
<!--
  body {
    font-family: Arial, sans-serif;
  }

  .content {
    margin: 0 auto;
    min-height: 100%;
    padding: 0 0 100px;
    width: 980px;
    border: 1px solid #ccc;
    border-radius: 5px;
  }

  .rendered-content {
    padding: 10px;
  }

  /* Fancy HR styles based on http://css-tricks.com/examples/hrs/ */
  hr {
    border: 0;
    height: 1px;
    background-image: -webkit-linear-gradient(left, rgba(200,200,200,1), rgba(200,200,200,0.5), rgba(200,200,200,0));
    background-image:    -moz-linear-gradient(left, rgba(200,200,200,1), rgba(200,200,200,0.5), rgba(200,200,200,0));
    background-image:     -ms-linear-gradient(left, rgba(200,200,200,1), rgba(200,200,200,0.5), rgba(200,200,200,0));
    background-image:      -o-linear-gradient(left, rgba(200,200,200,1), rgba(200,200,200,0.5), rgba(200,200,200,0));
  }

  h1 {
    font-size: 24px;
    font-weight: normal;
    line-height: 1.25;
  }

  h2 {
    font-size: 20px;
    font-weight: normal;
    line-height: 1.5;
  }

  h3 {
    font-size: 16px;
    font-weight: bold;
    line-height: 1.5625;
  }

  h4 {
    font-size: 14px;
    font-weight: bold;
    line-height: 1.5;
  }

  h5 {
    font-size: 12px;
    font-weight: bold;
    line-height: 1.66;
    text-transform: uppercase;
  }

  h6 {
    font-size: 12px;
    font-style: italic;
    font-weight: bold;
    line-height: 1.66;
    text-transform: uppercase;
  }

  pre  {
    margin-left: 2em;
    display: block;
    background: #f5f5f5;
    font-family: monospace;
    border: 1px solid #ccc;
    border-radius: 2px;
    padding: 1px 3px;
  }

  code {
    background: #f5f5f5;
    font-family: monospace;
    border: 1px solid #ccc;
    border-radius: 2px;
    padding: 1px 3px;
  }

  pre, code {
    font-size: 12px;
    line-height: 1.4;
  }

  pre code {
    border: 0;
    padding: 0;
  }

  ul.toc li {
    list-style: none;
    font-size: 14px;
  }

  ul.toc li span.h3toc {
    margin-left: 20px;
  }

  ul.toc li span.h4toc {
    margin-left: 40px;
  }

  ul.toc li span.h5toc {
    margin-left: 60px;
  }

  ul.toc li span.h6toc {
    margin-left: 80px;
  }
-->
</style>
CSS
  end
find_toc_header(doc) click to toggle source

return the TOC header element or nil

doc - Nokogiri DocumentFragment

# File lib/tartancloth.rb, line 134
def find_toc_header(doc)
  return nil unless doc

  doc.children.each do |element|
    return element if is_toc_header(element)
  end

  return nil
end
get_anchor_for_header(element) click to toggle source

Return the previous element which should be an anchor

# File lib/tartancloth.rb, line 194
def get_anchor_for_header(element)
  # The previous element should be a simple anchor.
  # Get the actual link value from the anchor.
  anchor = element.previous_element
  anchor = nil unless anchor.name == 'a'
  anchor
end
get_headers(doc, starting_element = nil) click to toggle source

Create an array of all header (h1-h6) elements in an HTML document starting from a specific element

starting_element - element to start parsing from, if starting element is

nil, all headers will be collected.

returns - array of Nokogiri elements

# File lib/tartancloth.rb, line 163
def get_headers(doc, starting_element = nil)
  headers = []
  capture = (starting_element.nil? ? true : false)

  doc.children.each do |element|
    unless capture
      capture = true if element == starting_element
      next
    end # unless

    headers << element if is_header_element(element)
  end

  headers
end
html_header(title) click to toggle source

Create an HTML5 header

returns - HTML header and body open tags

# File lib/tartancloth.rb, line 262
  def html_header(title)
    styles = css()
    header = <<HTML_HEADER
<!DOCTYPE html>
<html>
  <head><title>#{title}</title></head>

  #{styles}

  <body>
    <div class="content">
      <div class="rendered-content">

HTML_HEADER
  end
is_header_element(element) click to toggle source

returns true if the element is a header (h1-h6) element

# File lib/tartancloth.rb, line 146
def is_header_element(element)
  %w(h1 h2 h3 h4 h5 h6).include? element.name
end
is_toc_header(element) click to toggle source

returns true when a header (h1-h6) element contains the text: TOC

# File lib/tartancloth.rb, line 152
def is_toc_header(element)
  return (is_header_element(element) && element.text == 'TOC')
end
make_header_anchors_unique(doc) click to toggle source

Identical headers will have identical anchors. Modify them so each anchor is unique.

# File lib/tartancloth.rb, line 226
def make_header_anchors_unique(doc)
  headers = get_headers(doc)

  # Get anchors for each header.
  anchors = []
  headers.each do |h|
    anchors << get_anchor_for_header(h)
  end

  anchor_collection = {}
  anchors.each do |a|
    # Get the link
    link = get_link(a)

    # Get the current link count, will be nil if it's the first time.
    link_count = anchor_collection[link]

    if link_count.nil?
      # First time we've seen this link
      link_count = 0

      # Store it in the collection
      anchor_collection[link] = link_count
    else
      # Link already exists, modify it (add .#)
      set_link(a, "#{link}.#{link_count}")

      # Update the count for the next time we find this link
      anchor_collection[link] = link_count + 1
    end # if
  end
end
toc_to_markdown(toc_links) click to toggle source

Convert an array of link hashes to markdown

toc_links - hash of links returns - markdown content

# File lib/tartancloth.rb, line 119
def toc_to_markdown(toc_links)
  markdown = "## Table of Contents\n\n"
  toc_links.each do |link_data|
    text = link_data[:text]
    link = link_data[:link]
    klass = link_data[:klass]
    markdown << "+ [[#{text}](##{link})](class:#{klass})\n"
  end
  markdown << "\n"
end