Comparing version 0.3.0 to 0.4.0
@@ -0,1 +1,24 @@ | ||
<a name"0.4.0"></a> | ||
## 0.4.0 (2015-10-03) | ||
#### Bug Fixes | ||
* **lint:** fix jscsrc ([a534e0a0](https://github.com/posthtml/posthtml/commit/a534e0a0)) | ||
* **posthtml:** | ||
* extend new object with api methods on each plugin call ([82e096ea](https://github.com/posthtml/posthtml/commit/82e096ea)) | ||
* code style fix ([d1b3484d](https://github.com/posthtml/posthtml/commit/d1b3484d)) | ||
* code style fix ([26e6d7e3](https://github.com/posthtml/posthtml/commit/26e6d7e3)) | ||
#### Features | ||
* **api:** handle array matchers ([335b5aac](https://github.com/posthtml/posthtml/commit/335b5aac)) | ||
* **docs:** | ||
* write array matchers example in jsdocs/readme ([a14b7675](https://github.com/posthtml/posthtml/commit/a14b7675)) | ||
* add logo to readme ([78740c34](https://github.com/posthtml/posthtml/commit/78740c34)) | ||
* **lint:** upd jscs ([cef42d5d](https://github.com/posthtml/posthtml/commit/cef42d5d)) | ||
* **posthtml:** implement truly sync and async modes, and tests for them ([337243f5](https://github.com/posthtml/posthtml/commit/337243f5)) | ||
<a name"0.3.0"></a> | ||
@@ -2,0 +25,0 @@ ## 0.3.0 (2015-09-25) |
@@ -20,7 +20,6 @@ 'use strict'; | ||
* }); | ||
* return tree; | ||
* } | ||
*/ | ||
walk: function walk(cb) { | ||
return traverse(this, function (node) { | ||
traverse(this, function (node) { | ||
return cb(node); | ||
@@ -30,4 +29,4 @@ }); | ||
/** | ||
* match precondition object for of his search in nodes of tree | ||
* @param {Object} precondition Object for search | ||
* match expression for of his search in nodes of tree | ||
* @param {*} expression Object/String... for search. Array is an enumeration expressions | ||
* @param {Function} cb Callbak function | ||
@@ -43,11 +42,25 @@ * @return {Function} Node in callback | ||
* }); | ||
* return tree; | ||
* | ||
* // Array matchers | ||
* tree.match([{ tag: 'b' }, { tag: 'strong' }], function(node) { | ||
* var style = 'font-weight: bold;'; | ||
* node.tag = 'span'; | ||
* node.attrs ? ( | ||
* node.attrs.style ? ( | ||
* node.attrs.style += style | ||
* ) : node.attrs.style = style; | ||
* ) : node.attrs = { style: style }; | ||
* return node | ||
* }); | ||
* } | ||
*/ | ||
match: function match(precondition, cb) { | ||
return this.walk(function (node) { | ||
if (compare(precondition, node)) { | ||
return cb(node); | ||
match: function match(expression, cb) { | ||
Array.isArray(expression) ? this.walk(function (node) { | ||
for (var i = 0, len = expression.length; i < len; i++) { | ||
if (compare(expression[i], node)) return cb(node); | ||
} | ||
return node; | ||
}) : this.walk(function (node) { | ||
if (compare(expression, node)) return cb(node); | ||
return node; | ||
}); | ||
@@ -59,3 +72,3 @@ }, | ||
* @param {Function} cb Callbak function | ||
* @return {Function} Node in callback | ||
* @return {Function} Node in callback | ||
* | ||
@@ -68,8 +81,7 @@ * Examples use: | ||
* }); | ||
* return tree; | ||
* } | ||
*/ | ||
matchClass: function matchClass(className, cb) { | ||
return this.match({ attrs: { 'class': true } }, function (node) { | ||
var classes = node.attrs['class'].split(' ') || []; | ||
this.match({ attrs: { 'class': true } }, function (node) { | ||
var classes = node.attrs['class'].split(' '); | ||
if (classes.includes(className)) { | ||
@@ -76,0 +88,0 @@ return cb(node); |
@@ -51,4 +51,2 @@ /*jshint -W082 */ | ||
if (isEmpty(buf)) buf = { content: '' }; | ||
bufArray.push(buf); | ||
@@ -95,3 +93,3 @@ }, | ||
toHtml: function toHtml(tree) { | ||
var options = arguments[1] === undefined ? {} : arguments[1]; | ||
var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; | ||
@@ -104,3 +102,3 @@ /** | ||
* default: `<br>` | ||
* slash: `<br/>` | ||
* slash: `<br />` | ||
* tag: `<br></br>` | ||
@@ -144,3 +142,3 @@ * | ||
} | ||
if (typeof node.tag === 'boolean' && !node.tag) return node.content || ''; | ||
if (typeof node.tag === 'boolean' && !node.tag) return node.content; | ||
var tag = node.tag || 'div'; | ||
@@ -147,0 +145,0 @@ if (singleTags[tag]) { |
@@ -15,8 +15,12 @@ 'use strict'; | ||
var Posthtml = (function () { | ||
function Posthtml() { | ||
var plugins = arguments[0] === undefined ? [] : arguments[0]; | ||
exports['default'] = function (plugins) { | ||
return new PostHTML(plugins); | ||
}; | ||
_classCallCheck(this, Posthtml); | ||
var PostHTML = (function () { | ||
function PostHTML() { | ||
var plugins = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0]; | ||
_classCallCheck(this, PostHTML); | ||
this.plugins = plugins; | ||
@@ -26,8 +30,15 @@ } | ||
/** | ||
* Checks the argument to be a Promise (or thenable) object. | ||
* | ||
* @param {*} p - Target object to test | ||
* @returns {Boolean} | ||
*/ | ||
/** | ||
* Parse html to json tree | ||
* @param {String} html htmltree | ||
* @return {String} json jsontree | ||
* @param {String} html htmltree | ||
* @returns {PostHTMLTree} json jsontree | ||
*/ | ||
Posthtml.prototype.parse = function parse(html) { | ||
PostHTML.parse = function parse(html) { | ||
return _parserJs.toTree(html); | ||
@@ -37,7 +48,9 @@ }; | ||
/** | ||
* use plugins | ||
* @param {function} plugin posthtml().use(plugin()); | ||
* Use plugin | ||
* | ||
* @param {Function} plugin - PostHTML plugin to register | ||
* @returns {PostHTML} | ||
*/ | ||
Posthtml.prototype.use = function use(plugin) { | ||
PostHTML.prototype.use = function use(plugin) { | ||
this.plugins.push(plugin); | ||
@@ -48,43 +61,124 @@ return this; | ||
/** | ||
* @param {String} tree html/json tree | ||
* @param {Object} options Options obj | ||
* @return {Object} result | ||
* @param {String|PostHTMLTree} tree - html/json tree | ||
* @param {?Object} options - Options object | ||
* @param {?Boolean} options.skipParse - to prevent parsing incoming tree | ||
* @param {?Boolean} options.sync - to run plugins syncronously, will throw if there are async plugins | ||
* @returns {Promise<{html: String, tree: PostHTMLTree}>|{html: String, tree: PostHTMLTree}} - result | ||
*/ | ||
Posthtml.prototype.process = function process(tree) { | ||
PostHTML.prototype.process = function process(tree) { | ||
var _this = this; | ||
var options = arguments[1] === undefined ? {} : arguments[1]; | ||
var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; | ||
return new Promise(function (resolve) { | ||
tree = options.skipParse ? tree : PostHTML.parse(tree); | ||
tree.options = options; | ||
tree = options.skipParse ? tree : _this.parse(tree); | ||
tree.options = options; | ||
// sync mode | ||
if (options.sync === true) { | ||
this.plugins.forEach(function (plugin) { | ||
apiExtend(tree); | ||
for (var key in _apiJs2['default']) { | ||
tree[key] = _apiJs2['default'][key]; | ||
} | ||
_this.plugins.forEach(function (plugin) { | ||
var result = plugin(tree); | ||
if (result) { | ||
tree = result; | ||
var result = undefined; | ||
if (plugin.length === 2 || isPromise(result = plugin(tree))) { | ||
throw new Error('Can’t process synchronously because of async plugin: ' + plugin.name); | ||
} | ||
// return the previous tree unless result is filled | ||
tree = result || tree; | ||
}); | ||
resolve({ | ||
return { | ||
html: _parserJs.toHtml(tree), | ||
tree: tree | ||
}; | ||
} | ||
// async mode | ||
var i = 0, | ||
next = function next(res, cb) { | ||
// all plugins called | ||
if (_this.plugins.length <= i) { | ||
cb(null, res); | ||
return; | ||
} | ||
// little helper to go to the next iteration | ||
var _next = function _next(pluginResult) { | ||
return next(pluginResult || res, cb); | ||
}; | ||
// (re)extend the object | ||
apiExtend(res); | ||
// call next | ||
var plugin = _this.plugins[i++]; | ||
if (plugin.length === 2) { | ||
plugin(res, function (err, pluginResult) { | ||
if (err) return cb(err); | ||
_next(pluginResult); | ||
}); | ||
return; | ||
} | ||
// sync and promised plugins | ||
var err = null; | ||
var pluginResult = tryCatch(function () { | ||
return plugin(res); | ||
}, function (e) { | ||
return err = e; | ||
}); | ||
if (err) { | ||
cb(err); | ||
return; | ||
} | ||
if (isPromise(pluginResult)) { | ||
pluginResult.then(_next)['catch'](cb); | ||
return; | ||
} | ||
_next(pluginResult); | ||
}; | ||
return new Promise(function (resolve, reject) { | ||
next(tree, function (err, tree) { | ||
if (err) return reject(err); | ||
resolve({ | ||
html: _parserJs.toHtml(tree), | ||
tree: tree | ||
}); | ||
}); | ||
}); | ||
}; | ||
return Posthtml; | ||
return PostHTML; | ||
})(); | ||
exports['default'] = function (plugins) { | ||
return new Posthtml(plugins); | ||
}; | ||
exports.PostHTML = PostHTML; | ||
function isPromise(p) { | ||
return p instanceof Promise || typeof p === 'object' && p.then && p['catch']; | ||
} | ||
module.exports = exports['default']; | ||
/** | ||
* Simple tryCatch helper | ||
* | ||
* @param {Function} tryFn - try block | ||
* @param {Function} catchFn - catch block | ||
* @returns {?*} - result if exists | ||
*/ | ||
function tryCatch(tryFn, catchFn) { | ||
var res = undefined; | ||
try { | ||
res = tryFn(); | ||
} catch (e) { | ||
catchFn(e); | ||
} | ||
return res; | ||
} | ||
function apiExtend(tree) { | ||
tree.match = _apiJs2['default'].match; | ||
tree.matchClass = _apiJs2['default'].matchClass; | ||
tree.walk = _apiJs2['default'].walk; | ||
} |
{ | ||
"name": "posthtml", | ||
"version": "0.3.0", | ||
"description": "HTML/XML post processor", | ||
"version": "0.4.0", | ||
"description": "HTML/XML processor", | ||
"keywords": [ | ||
@@ -33,2 +33,4 @@ "html", | ||
"chai": "^3.0.0", | ||
"chai-as-promised": "^5.1.0", | ||
"chai-subset": "^1.1.0", | ||
"conventional-changelog": "0.0.17", | ||
@@ -39,3 +41,3 @@ "gulp": "^3.9.0", | ||
"isparta": "^3.0.3", | ||
"jscs": "^2.0.0", | ||
"jscs": "^2.1.1", | ||
"jshint": "^2.8.0", | ||
@@ -53,3 +55,3 @@ "mocha": "^2.2.5", | ||
"coverage": "babel-node node_modules/.bin/isparta cover --report text --report html --report lcov node_modules/.bin/_mocha", | ||
"lint": "jshint . && jscs .", | ||
"lint": "jshint . && jscs -v .", | ||
"release-patch": "mversion patch", | ||
@@ -56,0 +58,0 @@ "release-minor": "mversion minor", |
257
README.md
# PostHTML | ||
[![npm version](https://badge.fury.io/js/posthtml.svg)](http://badge.fury.io/js/posthtml) | ||
[![Build Status](https://travis-ci.org/posthtml/posthtml.svg?branch=master)](https://travis-ci.org/posthtml/posthtml) | ||
[![Build Status](https://travis-ci.org/posthtml/posthtml.svg?branch=master)](https://travis-ci.org/posthtml/posthtml?branch=master) | ||
[![Coverage Status](https://coveralls.io/repos/posthtml/posthtml/badge.svg?branch=master)](https://coveralls.io/r/posthtml/posthtml?branch=master) | ||
<img align="right" width="220" height="200" title="PostHTML logo" src="http://posthtml.github.io/posthtml/logo.svg"> | ||
PostHTML is a tool for transforming HTML/XML with JS plugins. PostHTML itself is very small. It includes only a HTML parser, a HTML node tree API and a node tree stringifier. | ||
@@ -65,2 +67,3 @@ | ||
#### Install [gulp-posthtml](https://www.npmjs.com/package/gulp-posthtml) | ||
``` | ||
@@ -71,13 +74,148 @@ npm install --save-dev gulp-posthtml | ||
```javascript | ||
gulp.task('html', function () { | ||
gulp.task('html', function() { | ||
var posthtml = require('gulp-posthtml'); | ||
return gulp.src('src/**/*.html') | ||
.pipe( posthtml([ require('posthtml-custom-elements')() ]/*, options */) ) | ||
.pipe( gulp.dest('build/') ); | ||
.pipe(posthtml([ require('posthtml-custom-elements')() ]/*, options */)) | ||
.pipe(gulp.dest('build/')); | ||
}); | ||
``` | ||
## Options | ||
## PostHTML JSON tree example | ||
#### `singleTags` | ||
__input HTML__ | ||
```html | ||
<a class="animals" href="#"> | ||
<span class="animals__cat" style="background: url(cat.png)">Cat</span> | ||
</a> | ||
``` | ||
__Tree in PostHTML (PostHTMLTree)__ | ||
```javascript | ||
[{ | ||
tag: 'a', | ||
attrs: { | ||
class: 'animals', | ||
href: '#' | ||
}, | ||
content: [{ | ||
tag: 'span', | ||
attrs: { | ||
class: 'animals__cat', | ||
style: 'background: url(cat.png)' | ||
}, | ||
content: ['Cat'] | ||
}] | ||
}] | ||
``` | ||
## Create PostHTML plugin | ||
This is a simple function with a single argument | ||
### Synchronous plugin example | ||
```javascript | ||
module.exports = function pluginName(tree) { | ||
// do something for tree | ||
tree.match({ tag: 'img' }, function(node) { | ||
node = Object.assign(node, { attrs: { class: 'img-wrapped' } }}); | ||
return { | ||
tag: 'span', | ||
attrs: { class: 'img-wrapper' }, | ||
content: node | ||
} | ||
}); | ||
}; | ||
``` | ||
### Classic asynchronous plugin example | ||
```javascript | ||
var request = request('request'); | ||
module.exports = function pluginName(tree, cb) { | ||
var tasks = 0; | ||
tree.match({ tag: 'a' }, function(node) { | ||
// skip local anchors | ||
if (!/^(https?:)?\/\//.test(node.attrs.href)) { | ||
return node; | ||
} | ||
request.head(node.attrs.href, function (err, resp) { | ||
if (err) return done(); | ||
if (resp.statusCode >= 400) { | ||
node.attrs.class += ' ' + 'Erroric'; | ||
} | ||
if (resp.headers.contentType) { | ||
node.attrs.class += ' content-type_' + resp.headers.contentType; | ||
} | ||
done(); | ||
}); | ||
tasks += 1; | ||
return node; | ||
}); | ||
function done() { | ||
tasks -= 1; | ||
if (!tasks) cb(null, tree); | ||
} | ||
}; | ||
``` | ||
### Promised asynchronous plugin example | ||
```javascript | ||
import { PostHTML } from 'posthtml'; | ||
import request from 'request'; | ||
export default tree => { | ||
return new Promise(resolve => { | ||
tree.match({ tag: 'user-info' }, (node) => { | ||
request(`/api/user-info?${node.attrs.dataUserId}`, (err, resp, body) { | ||
if (!err && body) node.content = PostHTML.parse(body); | ||
resolve(tree); | ||
}); | ||
}); | ||
}); | ||
}; | ||
``` | ||
## class PostHTML | ||
### #parse ({String} html): {PostHTMLTree} | ||
Parses HTML string into a PostHTMLTree object. | ||
#### Example | ||
```javascript | ||
import { PostHTML } from 'posthtml'; | ||
PostHTML.parse('<div></div>'); // [{ tag: 'div' }] | ||
``` | ||
### .use ({Function} plugin): {PostHTML} | ||
Adds a plugin into the flow. | ||
### Example | ||
```javascript | ||
var posthtml = require('posthtml'); | ||
var ph = posthtml() | ||
.use(function(tree) { | ||
return { tag: 'div', content: tree }; | ||
}); | ||
``` | ||
### .process ({String|PostHTMLTree} html, {Object} options): {{tree: PostHTMLTree, html: String}} | ||
Applies all plugins to the incoming `html` object. | ||
Returns (eventually) an Object with modified html and/or tree. | ||
#### Example | ||
```javascript | ||
var ph = posthtml() | ||
.process('<div></div>'/*, { options }*/); | ||
``` | ||
#### Options | ||
##### `singleTags` | ||
Array tags for extend default list single tags | ||
@@ -99,3 +237,3 @@ | ||
#### `closingSingleTag` | ||
##### `closingSingleTag` | ||
Option to specify version closing single tags. | ||
@@ -115,3 +253,3 @@ Accepts values: `default`, `slash`, `tag`. | ||
```html | ||
<singletag/> | ||
<singletag /> | ||
``` | ||
@@ -126,59 +264,37 @@ | ||
## PostHTML JSON tree example | ||
##### `skipParse` | ||
Skips input html parsing process. | ||
__input HTML__ | ||
```html | ||
<a class="animals" href="#"> | ||
<span class="animals__cat" style="background: url(cat.png)">Cat</span> | ||
</a> | ||
``` | ||
__Default__: `null` | ||
__Tree in PostHTML__ | ||
```js | ||
[{ | ||
tag: 'a', | ||
attrs: { | ||
class: 'animals', | ||
href: '#' | ||
}, | ||
content: [{ | ||
tag: 'span', | ||
attrs: { | ||
class: 'animals__cat', | ||
style: 'background: url(cat.png)' | ||
}, | ||
content: ['Cat'] | ||
}] | ||
}] | ||
```javascript | ||
posthtml() | ||
.use(function(tree) { tree.tag = 'section'; }) | ||
.process({ tag: 'div' }, { skipParse: true }) | ||
.then(function (result) { | ||
result.tree; // { tag: 'section' } | ||
result.html; // <section></section> | ||
}); | ||
``` | ||
## Create PostHTML plugin | ||
##### `sync` | ||
Try to run plugins synchronously. Throws if some plugins are async. | ||
This is a simple function with a single argument | ||
__Default__: `null` | ||
### Example plugin | ||
```javascript | ||
module.exports = function (tree) { | ||
posthtml() | ||
.use(function(tree) { tree.tag = 'section'; }) | ||
.process('<div>foo</div>', { sync: true }) | ||
.html; // <section>foo</section> | ||
``` | ||
// do something for tree | ||
tree.match({ tag: 'img' }, function(node) { | ||
node = Object.assign(node, { attrs: { class: 'img-wrapped' } }}); | ||
return { | ||
tag: 'span', | ||
attrs: { class: 'img-wrapper' }, | ||
content: node | ||
} | ||
}); | ||
return tree; | ||
} | ||
``` | ||
## class API | ||
## API | ||
### Walk | ||
### .walk ({function(PostHTMLNode): PostHTMLNode}) | ||
Walk for all nodes in tree, run callback. | ||
#### Example use | ||
#### Example | ||
```javascript | ||
@@ -195,11 +311,30 @@ tree.walk(function(node) { | ||
### Match | ||
### .match ({Object|String}, {function(PostHTMLNode): PostHTMLNode|String}) | ||
Find subtree in tree, run callback. | ||
#### Example use | ||
#### Example | ||
```javascript | ||
tree.match({ tag: 'custom-tag' }, function(node) { | ||
// do something for node | ||
var tag = node.tag; | ||
node = Object.assign(node, { tag: 'div', attrs: { class: tag } }}); | ||
return Object.assign(node, { | ||
tag: 'div', | ||
attrs: { class: node.tag } | ||
}); | ||
}); | ||
``` | ||
Support Array matchers | ||
#### Example | ||
```javascript | ||
tree.match([{ tag: 'b' }, { tag: 'strong' }], function(node) { | ||
var style = 'font-weight: bold;'; | ||
node.tag = 'span'; | ||
node.attrs ? ( | ||
node.attrs.style ? ( | ||
node.attrs.style += style | ||
) : node.attrs.style = style; | ||
) : node.attrs = { style: style }; | ||
return node | ||
@@ -209,6 +344,7 @@ }); | ||
### matchClass | ||
### .matchClass ({String}, {function(PostHTMLNode): PostHTMLNode}) | ||
For each found of class run callback | ||
#### Example use | ||
#### Example | ||
```javascript | ||
@@ -226,8 +362,11 @@ tree.matchClass('class-for-delete', function(node) { | ||
- [posthtml-custom-elements](https://npmjs.com/package/posthtml-custom-elements) — Use custom elements now | ||
- [posthtml-style-to-file](https://npmjs.com/package/posthtml-style-to-file) — Save HTML style nodes and attributes to CSS file | ||
- [posthtml-doctype](https://npmjs.com/package/posthtml-doctype) — Extend html tags doctype | ||
- [posthtml-to-svg-tags](https://github.com/theprotein/posthtml-to-svg-tags) — Convert html tags to svg equals | ||
- [posthtml-extend-attrs](https://github.com/theprotein/posthtml-extend-attrs) — Extend html tags attributes with custom data and attributes | ||
- [posthtml-modular-css](https://github.com/admdh/posthtml-modular-css) — Makes css modular | ||
## Ideas for plugins | ||
- [retext](https://github.com/wooorm/retext) — Extensible system for analysing and manipulating natural language | ||
- [posthtml-include](https://github.com/posthtml/posthtml-include) — Include html file | ||
@@ -234,0 +373,0 @@ - [posthtml-imports](https://github.com/posthtml/posthtml-imports) — Support W3C HTML imports |
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
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
30908
433
372
13