diff options
Diffstat (limited to 'lib/page')
-rw-r--r-- | lib/page/html.js | 57 | ||||
-rw-r--r-- | lib/page/index.js | 121 |
2 files changed, 178 insertions, 0 deletions
diff --git a/lib/page/html.js b/lib/page/html.js new file mode 100644 index 0000000..f828d11 --- /dev/null +++ b/lib/page/html.js @@ -0,0 +1,57 @@ +var _ = require('lodash'); +var cheerio = require('cheerio'); +var domSerializer = require('dom-serializer'); +var slug = require('github-slugid'); + +var Promise = require('../utils/promise'); + +// Render a cheerio DOM as html +function renderDOM($, dom, options) { + if (!dom && $._root && $._root.children) { + dom = $._root.children; + } + options = options|| dom.options || $._options; + return domSerializer(dom, options); +} + +function HTMLPipeline(htmlString, opts) { + _.bindAll(this); + + this.opts = _.defaults(opts || {}, { + convertImages: true + }); + + this.$ = cheerio.load(htmlString, { + // We should parse html without trying to normalize too much + xmlMode: false, + + // SVG need some attributes to use uppercases + lowerCaseAttributeNames: false, + lowerCaseTags: false + }); +} + +// Add ID to headings +HTMLPipeline.prototype.addHeadingIDs = function() { + var that = this; + + this.$('h1,h2,h3,h4,h5,h6').each(function() { + // Already has an ID? + if (that.$(this).attr('id')) return; + + that.$(this).attr('id', slug(that.$(this).text())); + }); +}; + +// Write content to the pipeline +HTMLPipeline.prototype.output = function() { + var that = this; + + return Promise() + .then(this.addHeadingIDs) + .then(function() { + return renderDOM(that.$); + }); +}; + +module.exports = HTMLPipeline; diff --git a/lib/page/index.js b/lib/page/index.js new file mode 100644 index 0000000..8f8819c --- /dev/null +++ b/lib/page/index.js @@ -0,0 +1,121 @@ +var _ = require('lodash'); +var path = require('path'); +var parsers = require('gitbook-parsers'); + +var error = require('../utils/error'); +var Promise = require('../utils/promise'); +var HTMLPipeline = require('./html'); + +/* +A page represent a parsable file in the book (Markdown, Asciidoc, etc) +*/ + +function Page(book, filename) { + if (!(this instanceof Page)) return new Page(book, filename); + var extension; + _.bindAll(this); + + this.book = book; + this.log = this.book.log; + + // Current content + this.content = ''; + + // Relative path to the page + this.path = filename; + + // Absolute path to the page + this.rawPath = this.book.resolve(filename); + + // Last modification date + this.mtime = 0; + + // Can we parse it? + extension = path.extname(this.path); + this.parser = parsers.get(extension); + if (!this.parser) throw error.ParsingError(new Error('Can\'t parse file "'+this.path+'"')); + + this.type = this.parser.name; +} + +// Return the filename of the page with another extension +// "README.md" -> "README.html" +Page.prototype.withExtension = function(ext) { + return path.join( + path.dirname(this.path), + path.basename(this.path, path.extname(this.path)) + ext + ); +}; + +// Update content of the page +Page.prototype.update = function(content) { + this.content = content; +}; + +// Read the page as a string +Page.prototype.read = function() { + var that = this; + + return this.book.statFile(this.path) + .then(function(stat) { + that.mtime = stat.mtime; + return that.book.readFile(that.path); + }) + .then(this.update); +}; + +// Parse the page and return its content +Page.prototype.parse = function(opts) { + var that = this; + + opts = _.defaults(opts || {}, { + + }); + + + this.log.debug.ln('start parsing file', this.path); + + return this.read() + + // Pre-process page with parser + .then(function() { + return that.parser.page.prepare(that.content) + .then(that.update); + }) + + // Render template + .then(function() { + return that.book.template.renderString(that.content, { + file: { + path: that.path, + mtime: that.mtime + } + }, { + file: that.path + }) + .then(that.update); + }) + + // Render markup using the parser + .then(function() { + return that.parser.page(that.content) + .then(that.update); + }) + + // Normalize HTML output + .then(function() { + return Promise.map(that.content.sections, function(section) { + var pipeline = new HTMLPipeline(section.content, opts); + + return pipeline.output() + .then(function(content) { + return { + content: content + }; + }); + }); + }); +}; + + +module.exports = Page; |