Comparing version 0.0.8 to 0.1.0
377
htmlbook.js
@@ -1,247 +0,200 @@ | ||
!(function () { | ||
function HTMLBook(source) { | ||
this.source = source; | ||
var sys = require('sys'), | ||
fs = require('fs'), | ||
util = require('util'), | ||
_ = require('underscore'), | ||
htmlparser = require('htmlparser2'), | ||
html = require('html'), | ||
marked = require('marked'), | ||
schema = require('./schema'), | ||
elements = schema["xs:schema"]["xs:element"], | ||
complex = schema["xs:schema"]["xs:complexType"], | ||
S = require('string'); | ||
// Detect if module is being run from the browser or a Node environment and | ||
// set up a standard logging platform for both as well as requiring | ||
// dependencies properly. | ||
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { | ||
var sys = require('sys'); | ||
this.log = function (message) { | ||
sys.puts(message); | ||
} | ||
// If this is | ||
this.$ = require('jquery'); | ||
this.marked = require('marked'); | ||
} | ||
else { | ||
this.$ = $; | ||
this.marked = marked; | ||
this.log = function (message) { | ||
console.log(message); | ||
} | ||
} | ||
marked.setOptions({gfm: true}); | ||
this.marked.setOptions({ | ||
gfm: true | ||
}); | ||
} | ||
var markdown_headers = ['h1','h2','h3','h4','h5','h6'], | ||
htmlbook_headers = ['h1','h1','h2','h3','h4','h5'], | ||
heirarchy = ['chapter', 'sect1', 'sect2', 'sect3', 'sect4', 'sect5'], | ||
void_elements = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr']; | ||
var hp = HTMLBook.prototype = { | ||
parse: function () { | ||
var body = this.$('<body>').append(this.source), | ||
children = body.children(), | ||
args = Array.prototype.slice.call(arguments), | ||
options = { | ||
debug: true, | ||
fragment: true, | ||
level: htmlbook_spec.chapter, | ||
sourceFormat: 'html' | ||
}, | ||
logs = []; | ||
(function () { | ||
var helpers = { | ||
existy: function (x) { | ||
return x != null; | ||
}, | ||
// Converts an integer to 2 space indentation. | ||
indentation: function (n) { | ||
return _.times(n, function () { | ||
return " " | ||
}).join("") | ||
}, | ||
get_language: function (str) { | ||
if (!this.existy(str)) return ""; | ||
match = str.match(/lang\-(\S*)/); | ||
// parse arguments and merge with options | ||
if (args.length == 1 && typeof args[0] === 'object') { | ||
options = this.$.extend(options, args[0]); | ||
if (typeof htmlbook_spec[options.level] !== 'undefined') { | ||
options.level = htmlbook_spec[options.level]; | ||
} else { | ||
logs.push("please specify a valid htmlbook level. defaulting to chapter"); | ||
options.level = htmlbook_spec.chapter; | ||
} | ||
if (options.debug === false) { | ||
this.log = function (message) { | ||
return message; | ||
} | ||
} | ||
if (options.sourceFormat === 'markdown') { | ||
children = this.$('<body>').append(this.marked(this.source)).children(); | ||
} | ||
} | ||
if (options.fragment) { | ||
return this.parse_html(children, options.level); | ||
if (this.existy(match)) { | ||
return match[1] | ||
} else { | ||
return skeleton.header + this.parse_html(children, options.level) + skeleton.footer; | ||
return ""; | ||
} | ||
}, | ||
attribs_to_string: function (obj) { | ||
if (!this.existy(obj)) | ||
return "" | ||
// parse_html - recursive part of this library, takes an amount of html, | ||
// looks at top level header, and looks for a sub-section. if there's a | ||
// subsection, call this again. | ||
// content: jQuery collection where the first element is a header. | ||
parse_html: function (content, htmlbooklevel) { | ||
if (!(htmlbooklevel != null)) { | ||
return content; | ||
} | ||
return _.reduce(_.pairs(obj), function (memo, v) { | ||
if (this.existy(v[1]) && v[1].length > 0) | ||
return memo + " " + v[0] + "='" + v[1]+ "'" | ||
else | ||
return memo | ||
}, "", this); | ||
} | ||
} | ||
this.log("\n>>> Making section " + htmlbooklevel.name); | ||
// initialize variables | ||
var section; | ||
var next_section = ''; | ||
var normalize_headings = function (arr) { | ||
return _.map(arr, function (n, i) { | ||
if (i == 0) | ||
return n | ||
else if (n > arr[i-1]+1) | ||
return arr[i-1] + 1 | ||
else | ||
return n | ||
}); | ||
} | ||
// Take the input, wrap it in a div and then get the children. In this way | ||
// if 'content' is a string it will be converted into a jQuery object and | ||
// if it's already a jQuery object everything is fine. | ||
var wrap = this.$('<div>').html(content); | ||
var children = wrap.children(); | ||
var concat_times = function (n, str, connector) { | ||
return _.times(n, function () { | ||
return str | ||
}).join(connector) | ||
} | ||
if (children.length === 0) { return ''; } | ||
var close_sections = function (o, c) { | ||
return concat_times(o - c, "</section>", "\n"); | ||
} | ||
// Do some analysis on the first element. HTMLBook is pretty strict as to | ||
// what the first element should be: either a heading or a div must start | ||
// a new section. If the element is neither of those, assume we have inner | ||
// section content. | ||
var first_element = this.deconstruct_heading(children.first(), htmlbooklevel); | ||
function HTMLBook (input, opts) { | ||
this.input = input; | ||
this.depth = 0; | ||
this.options = {}; | ||
} | ||
// are there more headings? | ||
if (first_element != false) { | ||
this.log("found a heading as the first element") | ||
var more_headings = this.find_headings(content, first_element.tag_name); | ||
var arr = this.wrap_in_section(htmlbooklevel, children, first_element, more_headings); | ||
section = arr[0]; | ||
next_section = arr[1]; | ||
} else { | ||
var rest = content.splice(1); | ||
var first = content; | ||
var section_content; | ||
this.log('no more headings in this branch.'); | ||
section_content = this.$('<div>').html(first).html() + this.parse_html(rest, htmlbooklevel); | ||
HTMLBook.prototype.header_content = function () { | ||
if (!helpers.existy(this.title)) throw new Error("No title provided.") | ||
section = this.$('<div>').html(section_content).html(); | ||
} | ||
return '<?xml version="1.0" encoding="utf-8"?>\n\n<!DOCTYPE html>\n\n<html xmlns="http://www.w3.org/1999/xhtml" lang="en">\n<head>\n<title>' + this.title + '</title>\n</head>\n<body data-type="book">\n<h1>' + this.title + '</h1>\n'; | ||
} | ||
return this.$('<div>').html(section).html() + next_section; | ||
}, | ||
HTMLBook.prototype.footer_content = function () { | ||
return "\n</body>\n</html>"; | ||
} | ||
wrap_in_section: function (htmlbooklevel, children, first_element, more_headings) { | ||
var subheading, next_heading_index, nested_element_count, section_content, next_section = '' | ||
// Parse the html input and pass off to the traverse callback | ||
HTMLBook.prototype.parse = function (opts) { | ||
this.first_sect = true; | ||
this.options.parse = {"complete_html": false} | ||
if (helpers.existy(opts) && typeof opts === "object") { | ||
this.title = opts.title; | ||
this.options = _.extend(this.options, opts); | ||
this.options.parse = _.extend(this.options.parse, opts) | ||
} | ||
if (more_headings == 'samelevel') { | ||
subheading = htmlbook_spec[htmlbooklevel.child]; | ||
next_heading_index = this.$('<div>').html(children).find(first_element.tag_name+':gt(0)').first().index(); | ||
nested_element_count = next_heading_index - 1; | ||
section_content = this.parse_html(children.splice(1, nested_element_count), subheading); | ||
next_section = this.parse_html(children.splice(1), htmlbooklevel); | ||
} else if (more_headings == 'subheadings') { | ||
subheading = htmlbook_spec[htmlbooklevel.child]; | ||
this.log('sub headings'); | ||
section_content = this.parse_html(children.splice(1), subheading); | ||
} else { | ||
// There are no other headings, so the work is done. | ||
this.log("no more headings in the document."); | ||
section_content = children.splice(1); | ||
if (!helpers.existy(this.input)) throw new Error("No content"); | ||
this.closings = 0; | ||
this.openings = 0; | ||
this.first_heading = true; | ||
this.first_sect1 = false; | ||
var handler = new htmlparser.DomHandler(function (error, dom) { | ||
if (error) { | ||
console.log('error dog');process.exit(1) | ||
} | ||
}); | ||
var parser = new htmlparser.Parser(handler); | ||
parser.write(marked(this.input)); | ||
parser.end(); | ||
section = this.$('<section data-type="' + htmlbooklevel.name + '">'); | ||
section.append(first_element.html).append(section_content); | ||
if (this.options.parse.complete_html === true) { | ||
return this.header_content() + this.traverse(handler.dom) + close_sections(this.openings, this.closings) + this.footer_content(); | ||
} | ||
else { | ||
return this.traverse(handler.dom) + close_sections(this.openings, this.closings); | ||
} | ||
} | ||
return [section, next_section]; | ||
}, | ||
// Construct an opening tag with the specified attributes. | ||
var open_tag = function (node) { | ||
return "<" + node.name + helpers.attribs_to_string(node.attribs) + ">" | ||
} | ||
find_headings: function (content, parent_tag_name) { | ||
var content = this.$(content).clone().splice(1); | ||
var wrap = this.$('<div>').html(content); | ||
var heading_index = parseInt(parent_tag_name.substr(1)); | ||
var subheadings = this.$(wrap).find('h' + (heading_index+1)); | ||
var void_tag = function (node) { | ||
if (node.name === "input") { | ||
return "<div><" + node.name + helpers.attribs_to_string(node.attribs) + "/></div>"; | ||
} else { | ||
return "<" + node.name + helpers.attribs_to_string(node.attribs) + "/>" | ||
} | ||
} | ||
if (heading_index == 6) { | ||
return false; | ||
} else if (this.$(wrap).find(parent_tag_name).length > 0) { | ||
return 'samelevel'; | ||
} else if (subheadings.length > 0) { | ||
return 'subheadings'; | ||
} else { | ||
return false; | ||
} | ||
}, | ||
var close_tag = function (node) { | ||
return "</" + node.name + ">" | ||
} | ||
deconstruct_heading: function (heading, htmlbooklevel) { | ||
var tag_name = heading.prop('tagName'); | ||
if (tag_name.match(/H[1-6]/) == null) { | ||
return false; | ||
} else { | ||
return { | ||
'tag_name': tag_name, | ||
'heading': htmlbooklevel.heading, | ||
'level': htmlbooklevel.name, | ||
'content': heading.html(), | ||
'html': '<' + htmlbooklevel.heading + '>' + heading.html() + '</' + htmlbooklevel.heading + '>' | ||
} | ||
} | ||
HTMLBook.prototype.section_start = function (name) { | ||
var current_section = heirarchy[this.depth]; | ||
var header_depth = _.indexOf(markdown_headers, name); | ||
var new_section = heirarchy[header_depth]; | ||
var closings = 0; | ||
if (this.depth === header_depth) { | ||
closings = (this.first_sect === true) ? 0 : 1; | ||
this.first_sect = false; | ||
} else if (this.depth < header_depth) { | ||
this.depth += 1; | ||
} else if (this.depth > header_depth) { | ||
closings = this.depth - header_depth + 1; | ||
this.depth = header_depth; | ||
} | ||
} | ||
function Export(str) { | ||
return new HTMLBook(str); | ||
}; | ||
this.closings += closings | ||
var skeleton = { | ||
'header': '<!DOCTYPE html><html xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.w3.org/1999/xhtml ../schema/htmlbook.xsd" xmlns="http://www.w3.org/1999/xhtml"><head><title>HTMLBook Sample</title><meta name="HTMLBook Sample" content="text/html; charset=utf-8" /></head><body data-type="book" class="book" id="htmlbook">', | ||
'footer': '</body></html>' | ||
return _.times(closings, function() {return "</section>\n"}).join("") + "<section data-type='" + heirarchy[this.depth] + "'>\n"; | ||
} | ||
var htmlbook_spec = { | ||
'book': { | ||
name: 'book', | ||
parent: false, | ||
child: 'chapter', | ||
heading: 'h1', | ||
level: 0 | ||
}, | ||
'chapter': { | ||
name: 'chapter', | ||
parent: 'book', | ||
child: 'sect1', | ||
heading: 'h1', | ||
level: 1 | ||
}, | ||
'sect1': { | ||
name: 'sect1', | ||
parent: 'chapter', | ||
child: 'sect2', | ||
heading: 'h1', | ||
level: 2 | ||
}, | ||
'sect2': { | ||
name: 'sect2', | ||
parent: 'sect1', | ||
child: 'sect3', | ||
heading: 'h2', | ||
level: 3 | ||
}, | ||
'sect3': { | ||
name: 'sect3', | ||
parent:'sect2', | ||
heading: 'h3', | ||
level: 4 | ||
}, | ||
'sect4': { | ||
name: 'sect4', | ||
parent:'sect3', | ||
child: false, | ||
heading: 'h4', | ||
level: 5 | ||
} | ||
HTMLBook.prototype.wrap_in_section = function (node, callback) { | ||
return this.section_start(node.name) + "<" + htmlbook_headers[this.depth] + ">" + callback(node.children) + "</" + htmlbook_headers[this.depth] + ">\n" | ||
} | ||
// EXPORTS from string.js | ||
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { | ||
module.exports = Export; | ||
HTMLBook.prototype.traverse = function (dom_tree, htmlbook_tracker) { | ||
output = "" | ||
} else { | ||
_.forEach(dom_tree, function (node, i) { | ||
// When the node is a text type, it has no children, just return it. | ||
if (node.type === "text") { | ||
output += node.data | ||
// Markdown doesn't convert pre blocks the way we would like, so let's | ||
// step in and make it all work. | ||
} else if (node.name === "pre" && node.children.length === 1 && node.children[0].name === "code") { | ||
// Attempt to distinguish between "code" <pre> blocks and other <pre>s | ||
var code = node.children[0] | ||
node.attribs["data-code-language"] = helpers.get_language(code.attribs["class"]) | ||
node.attribs["data-type"] = "programlisting"; | ||
output += open_tag(node) + this.traverse(code.children) + close_tag(node); | ||
// Check to see if this node is a header, which should signal the start // of a new section. | ||
} else if (_.contains(markdown_headers, node.name)) { | ||
this.openings += 1; | ||
output += this.wrap_in_section(node, this.traverse) | ||
// If a node has children then it has a starting and closing tag. | ||
} else if (node.children.length !== 0) { | ||
output += open_tag(node) + this.traverse(node.children) + close_tag(node); | ||
// Void elements | ||
} else if (_.contains(void_elements, node.name)) { | ||
output += void_tag(node) | ||
// Everything else, typically empty elements with no children. | ||
} else { | ||
output += open_tag(node) + close_tag(node); | ||
} | ||
}, this); | ||
if(typeof define === "function" && define.amd) { | ||
define([], function() { | ||
return Export; | ||
}); | ||
} else { | ||
window.HTMLBook = Export; | ||
} | ||
return S(output).decodeHTMLEntities().s; | ||
} | ||
module.exports = function (str) {return new HTMLBook(str)}; | ||
}).call(this); |
{ | ||
"name": "htmlbook", | ||
"version": "0.0.8", | ||
"description": "HTML to HTMLBook parser", | ||
"version": "0.1.0", | ||
"description": "Markdown to HTMLBook converter", | ||
"main": "./htmlbook.js", | ||
@@ -16,11 +16,17 @@ "bin": { | ||
}, | ||
"keywords": ["htmlbook"], | ||
"keywords": ["htmlbook", "markdown"], | ||
"author": "O'Reilly Media", | ||
"license": "MIT", | ||
"dependencies": { | ||
"jquery": "1.8.3", | ||
"marked": "0.2.9", | ||
"optparse": "1.0.4", | ||
"html": "0.0.7" | ||
"html": "0.0.7", | ||
"underscore": "1.5.2", | ||
"htmlparser2": "", | ||
"string": "1.6.0" | ||
}, | ||
"devDependencies": { | ||
"xml2js": "0.2.8", | ||
"jasmine-node": "1.11.0" | ||
} | ||
} |
# htmlbook.js | ||
Parses HTML into [HTMLBook](https://github.com/oreillymedia/htmlbook). | ||
Converts Markdown to [HTMLBook](https://github.com/oreillymedia/htmlbook). | ||
## Setup | ||
## Installation | ||
### Browser | ||
Install with npm: `npm install -g htmlbook` | ||
Add the htmlbook.js script to a page with jQuery (tested with jQuery 1.10.2) and [Marked](https://github.com/chjj/marked). | ||
## Usage | ||
### Node.js | ||
Install with npm: `npm install -g htmlbook` | ||
Within Node, **htmlbook.js** works on strings. | ||
## Usage | ||
```javascript | ||
var htmlbook = require('htmlbook'); | ||
var htmlbook_output = htmlbook("source content").parse(); | ||
``` | ||
### Browser & Node.js | ||
To use **htmlbook.js** with a file, in Node.js do the following: | ||
For both the browser and Node.js, usage is the same | ||
```javascript | ||
var fs = require('fs'); | ||
var htmlbook = require('htmlbook'); | ||
var htmlbook_output; | ||
fs.readFile('path/to/file.md', 'utf-8', function (error, data) { | ||
if (error) | ||
return error; | ||
htmlbook_output = htmlbook(data).parse(); | ||
}) | ||
``` | ||
var input = "HTML TEXT OR JQUERY OBJECT" | ||
var output = HTMLBook(input).parse(opts); | ||
``` | ||
### Command Line | ||
Installing this package with npm and the `-g` flag will install an executable. | ||
``` | ||
```bash | ||
$ htmlbook -s SOURCE_FILE -o OUTPUT_FILE | ||
@@ -36,9 +44,22 @@ ``` | ||
### Opts | ||
## Options | ||
Below is a list of available options, default value is emphasized. | ||
- sourceFormat: _html_ or markdown | ||
- fragment: _false_ or true | ||
- level: _chapter_ or book | ||
- debug: boolean -- default for command line is 'false' | ||
- debug: boolean -- default for command line is 'false' | ||
## Testing | ||
There are two suites of tests for this repository. The first is `spec/htmlbook_spec.js` and tests the package for expected output. Run this test with the following: | ||
```bash | ||
$ jasmine-node spec/htmlbook_spec.js | ||
``` | ||
The second test suite checks to be sure that the file being tested in `htmlbook_spec` are in fact being output to valid HTMLBook. It's no use writing tests that pass unless they pass on valid output. These tests are separated because the validation takes longer. | ||
```bash | ||
$ jasmine-node spec/validation_spec.js | ||
``` |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
463915
30
11224
64
6
2
2
4
2
+ Addedhtmlparser2@
+ Addedstring@1.6.0
+ Addedunderscore@1.5.2
+ Addeddom-serializer@2.0.0(transitive)
+ Addeddomelementtype@2.3.0(transitive)
+ Addeddomhandler@5.0.3(transitive)
+ Addeddomutils@3.1.0(transitive)
+ Addedentities@4.5.0(transitive)
+ Addedhtmlparser2@9.1.0(transitive)
+ Addedstring@1.6.0(transitive)
+ Addedunderscore@1.5.2(transitive)
- Removedjquery@1.8.3
- Removedajv@6.12.6(transitive)
- Removedasn1@0.2.6(transitive)
- Removedassert-plus@1.0.0(transitive)
- Removedasynckit@0.4.0(transitive)
- Removedaws-sign2@0.7.0(transitive)
- Removedaws4@1.13.2(transitive)
- Removedbcrypt-pbkdf@1.0.2(transitive)
- Removedbindings@1.5.0(transitive)
- Removedcaseless@0.12.0(transitive)
- Removedcombined-stream@1.0.8(transitive)
- Removedcontextify@0.1.15(transitive)
- Removedcore-util-is@1.0.2(transitive)
- Removedcssom@0.2.5(transitive)
- Removedcssstyle@4.1.0(transitive)
- Removeddashdash@1.14.1(transitive)
- Removeddelayed-stream@1.0.0(transitive)
- Removedecc-jsbn@0.1.2(transitive)
- Removedextend@3.0.2(transitive)
- Removedextsprintf@1.3.0(transitive)
- Removedfast-deep-equal@3.1.3(transitive)
- Removedfast-json-stable-stringify@2.1.0(transitive)
- Removedfile-uri-to-path@1.0.0(transitive)
- Removedforever-agent@0.6.1(transitive)
- Removedform-data@2.3.3(transitive)
- Removedgetpass@0.1.7(transitive)
- Removedhar-schema@2.0.0(transitive)
- Removedhar-validator@5.1.5(transitive)
- Removedhtmlparser@1.7.6(transitive)
- Removedhttp-signature@1.2.0(transitive)
- Removedis-typedarray@1.0.0(transitive)
- Removedisstream@0.1.2(transitive)
- Removedjquery@1.8.3(transitive)
- Removedjsbn@0.1.1(transitive)
- Removedjsdom@0.2.19(transitive)
- Removedjson-schema@0.4.0(transitive)
- Removedjson-schema-traverse@0.4.1(transitive)
- Removedjson-stringify-safe@5.0.1(transitive)
- Removedjsprim@1.4.2(transitive)
- Removedlocation@0.0.1(transitive)
- Removedmime-db@1.52.0(transitive)
- Removedmime-types@2.1.35(transitive)
- Removednan@2.22.0(transitive)
- Removednavigator@1.0.1(transitive)
- Removedoauth-sign@0.9.0(transitive)
- Removedperformance-now@2.1.0(transitive)
- Removedpsl@1.15.0(transitive)
- Removedpunycode@2.3.1(transitive)
- Removedqs@6.5.3(transitive)
- Removedrequest@2.88.2(transitive)
- Removedrrweb-cssom@0.7.1(transitive)
- Removedsafe-buffer@5.2.1(transitive)
- Removedsafer-buffer@2.1.2(transitive)
- Removedsshpk@1.18.0(transitive)
- Removedtough-cookie@2.5.0(transitive)
- Removedtunnel-agent@0.6.0(transitive)
- Removedtweetnacl@0.14.5(transitive)
- Removeduri-js@4.4.1(transitive)
- Removeduuid@3.4.0(transitive)
- Removedverror@1.10.0(transitive)
- Removedxmlhttprequest@1.4.2(transitive)