From 0ac5f17cda030d59de73e1d7e4efec00abc91a94 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 20 Jun 2015 10:59:02 -0700 Subject: [PATCH 001/227] Removed unused definition --- lib/processing-instructions.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/processing-instructions.js b/lib/processing-instructions.js index bc629c6..7855be3 100644 --- a/lib/processing-instructions.js +++ b/lib/processing-instructions.js @@ -14,10 +14,6 @@ var ProcessingInstructions = function(React) { defaultProcessingInstructionsWithInlineStyle: [{ shouldProcessNode: ShouldProcessNodeDefinitions.shouldProcessEveryNode, processNode: processNodeDefinitions.processNodeWithInlineStyle - }], - defaultProcessingInstructionsWithDataAttribtues: [{ - shouldProcessNode: ShouldProcessNodeDefinitions.shouldProcessEveryNode, - processNode: processNodeDefinitions.processNodeWithDataAttributes }] }; }; From f6839eff85ca8f5b0ae65281f734d270308e8d75 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 24 Apr 2016 15:40:07 +0200 Subject: [PATCH 002/227] Use Ramda and underscore.string.fp instead of lodash --- lib/parser.js | 24 +++++++++++------------- lib/process-node-definitions.js | 15 ++++++++------- package.json | 3 ++- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/parser.js b/lib/parser.js index 49dd124..43ed81c 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -1,6 +1,6 @@ 'use strict'; -var _ = require('lodash'); +var R = require('ramda'); var htmlParser = require('htmlparser2'); var ProcessingInstructions = require('./processing-instructions'); var IsValidNodeDefinitions = require('./is-valid-node-definitions'); @@ -15,19 +15,18 @@ var Html2React = function(React) { var traverseDom = function(node, isValidNode, processingInstructions) { if (isValidNode(node)) { - var children = []; - _.each(node.children, function(child) { - children.push(traverseDom(child, isValidNode, processingInstructions)); - }); - _.compact(children); // Remove invalid nodes - for (var index = 0; index < processingInstructions.length; index++) { - var processingInstruction = processingInstructions[index]; - if (processingInstruction.shouldProcessNode(node)) { - return processingInstruction.processNode(node, children); - } + var children = R.reject((x) => {return x == null}, R.map(function (child) { + return traverseDom(child, isValidNode, processingInstructions); + }, node.children)); + var processingInstruction = R.find((processingInstruction) => { + return processingInstruction.shouldProcessNode(node); + }, processingInstructions); + if (processingInstruction != null) { + return processingInstruction.processNode(node, children); + } else { + return false; } } - return false; }; var parseWithInstructions = function(html, isValidNode, processingInstructions) { @@ -55,4 +54,3 @@ var Html2React = function(React) { }; module.exports = Html2React; - diff --git a/lib/process-node-definitions.js b/lib/process-node-definitions.js index d037ad7..3d4dac7 100644 --- a/lib/process-node-definitions.js +++ b/lib/process-node-definitions.js @@ -1,17 +1,18 @@ 'use strict'; -var _ = require('lodash'); +var R = require('ramda'); +var S = require('underscore.string.fp'); var ent = require('ent'); function createStyleJsonFromString(styleString) { - if (_.isNull(styleString) || _.isUndefined(styleString) || styleString === '') { + if (S.isBlank(styleString)) { return {}; } var styles = styleString.split(';'); var singleStyle, key, value, jsonStyles = {}; for (var i = 0; i < styles.length; i++) { singleStyle = styles[i].split(':'); - key = _.camelCase(singleStyle[0]); + key = S.camelize(singleStyle[0]); value = singleStyle[1]; if (key.length > 0 && value.length > 0) { jsonStyles[key] = value; @@ -33,11 +34,11 @@ var ProcessNodeDefinitions = function(React) { } var elementProps = { - key: ++index + key: ++index, }; // Process attributes if (node.attribs) { - _.each(node.attribs, function(value, key) { + R.each(function (value, key) { switch (key || '') { case 'style': elementProps.style = createStyleJsonFromString(node.attribs.style); @@ -49,14 +50,14 @@ var ProcessNodeDefinitions = function(React) { elementProps[key] = value; break; } - }); + }, node.attribs); } return React.createElement(node.name, elementProps, node.data, children); } return { - processDefaultNode: processDefaultNode + processDefaultNode: processDefaultNode, }; }; diff --git a/package.json b/package.json index 57e81e3..a3b57e6 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,8 @@ "dependencies": { "ent": "^2.2.0", "htmlparser2": "^3.8.3", - "lodash": "^3.9.3" + "ramda": "^0.21.0", + "underscore.string.fp": "^1.0.4" }, "devDependencies": { "blanket": "1.1.7", From 355677862cd83705df108821c89ca625bb2659ae Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 24 Apr 2016 15:46:28 +0200 Subject: [PATCH 003/227] Fix Ramda usage --- lib/parser.js | 4 ++-- lib/process-node-definitions.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/parser.js b/lib/parser.js index 43ed81c..43b6a6c 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -17,7 +17,7 @@ var Html2React = function(React) { if (isValidNode(node)) { var children = R.reject((x) => {return x == null}, R.map(function (child) { return traverseDom(child, isValidNode, processingInstructions); - }, node.children)); + }, node.children || [])); var processingInstruction = R.find((processingInstruction) => { return processingInstruction.shouldProcessNode(node); }, processingInstructions); @@ -49,7 +49,7 @@ var Html2React = function(React) { return { parse: parse, - parseWithInstructions: parseWithInstructions + parseWithInstructions: parseWithInstructions, }; }; diff --git a/lib/process-node-definitions.js b/lib/process-node-definitions.js index 3d4dac7..20a7d3e 100644 --- a/lib/process-node-definitions.js +++ b/lib/process-node-definitions.js @@ -38,7 +38,7 @@ var ProcessNodeDefinitions = function(React) { }; // Process attributes if (node.attribs) { - R.each(function (value, key) { + R.forEach(function (value, key) { switch (key || '') { case 'style': elementProps.style = createStyleJsonFromString(node.attribs.style); From 47ba0733f05902e2a7a925ac97e6f99a00b375e9 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 24 Apr 2016 15:48:35 +0200 Subject: [PATCH 004/227] Style fixes --- test/html-to-react-tests.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index ac6c6d6..89d0715 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -1,6 +1,6 @@ 'use strict'; -var assert = require("assert"); +var assert = require('assert'); var React = require('react'); var ReactDOMServer = require('react-dom/server') @@ -154,8 +154,8 @@ describe('Html2React', function() { shouldProcessNode: function(node) { return node.name && node.name !== 'p'; }, - processNode: processNodeDefinitions.processDefaultNode - }]; + processNode: processNodeDefinitions.processDefaultNode, + },]; var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, processingInstructions); // With only 1

element, nothing is rendered @@ -174,8 +174,8 @@ describe('Html2React', function() { shouldProcessNode: function(node) { return node.type === 'text' || node.name !== 'p'; }, - processNode: processNodeDefinitions.processDefaultNode - }]; + processNode: processNodeDefinitions.processDefaultNode, + },]; var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, processingInstructions); var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); assert.equal(reactHtml, htmlExpected); @@ -197,14 +197,14 @@ describe('Html2React', function() { }, processNode: function(node, children) { return node.data.toUpperCase(); - } + }, }, { // Anything else shouldProcessNode: function(node) { return true; }, - processNode: processNodeDefinitions.processDefaultNode - }]; + processNode: processNodeDefinitions.processDefaultNode, + },]; var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, processingInstructions); var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); assert.equal(reactHtml, htmlExpected); From 9827c537b4afc075d770707dba0b4b487d989a05 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 24 Apr 2016 15:53:21 +0200 Subject: [PATCH 005/227] Handle void element tags --- lib/process-node-definitions.js | 12 +++++++++++- lib/processing-instructions.js | 5 ++--- test/html-to-react-tests.js | 19 +++++++++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/lib/process-node-definitions.js b/lib/process-node-definitions.js index 20a7d3e..ad85d41 100644 --- a/lib/process-node-definitions.js +++ b/lib/process-node-definitions.js @@ -4,6 +4,12 @@ var R = require('ramda'); var S = require('underscore.string.fp'); var ent = require('ent'); +// https://github.com/facebook/react/blob/0.14-stable/src/renderers/dom/shared/ReactDOMComponent.js#L457 +var voidElementTags = [ + 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', + 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr', +]; + function createStyleJsonFromString(styleString) { if (S.isBlank(styleString)) { return {}; @@ -53,7 +59,11 @@ var ProcessNodeDefinitions = function(React) { }, node.attribs); } - return React.createElement(node.name, elementProps, node.data, children); + if (R.contains(node.name, voidElementTags)) { + return React.createElement(node.name, elementProps) + } else { + return React.createElement(node.name, elementProps, node.data, children); + } } return { diff --git a/lib/processing-instructions.js b/lib/processing-instructions.js index 8f3f97a..9617b6e 100644 --- a/lib/processing-instructions.js +++ b/lib/processing-instructions.js @@ -9,10 +9,9 @@ var ProcessingInstructions = function(React) { return { defaultProcessingInstructions: [{ shouldProcessNode: ShouldProcessNodeDefinitions.shouldProcessEveryNode, - processNode: processNodeDefinitions.processDefaultNode - }] + processNode: processNodeDefinitions.processDefaultNode, + },], }; }; module.exports = ProcessingInstructions; - diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index 89d0715..603e17b 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -102,6 +102,25 @@ describe('Html2React', function() { assert.equal(reactHtml, htmlExpected); }); + it('should parse br elements without warnings', function() { + var htmlInput = '

Line one
Line two
Line three

'; + var htmlExpected = '

Line one
Line two
Line three

'; + + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + + assert.equal(reactHtml, htmlExpected); + }); + + it('should parse src elements with all attributes but without warnings', function() { + var htmlInput = '

'; + + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + + assert.equal(reactHtml, htmlInput); + }); + it('should decode character entities in text nodes', function () { var htmlInput = '
1 < 2
'; From 94b1f826c9a851a6a32d8b9fb9bc57402c522563 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 24 Apr 2016 16:00:00 +0200 Subject: [PATCH 006/227] Add menuitem among void element tags --- lib/process-node-definitions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/process-node-definitions.js b/lib/process-node-definitions.js index ad85d41..7d9115c 100644 --- a/lib/process-node-definitions.js +++ b/lib/process-node-definitions.js @@ -6,8 +6,8 @@ var ent = require('ent'); // https://github.com/facebook/react/blob/0.14-stable/src/renderers/dom/shared/ReactDOMComponent.js#L457 var voidElementTags = [ - 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', - 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr', + 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', + 'source', 'track', 'wbr', 'menuitem', ]; function createStyleJsonFromString(styleString) { From c239d97e2588ddcf26f52fd1fe0f06c9de33a624 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 24 Apr 2016 16:05:41 +0200 Subject: [PATCH 007/227] Add tests --- index.js | 3 +-- lib/process-node-definitions.js | 2 +- test/html-to-react-tests.js | 26 ++++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index ec3fa91..7cbad93 100644 --- a/index.js +++ b/index.js @@ -9,6 +9,5 @@ module.exports = { Parser: parser, ProcessingInstructions: processingInstructions, IsValidNodeDefinitions: isValidNodeDefinitions, - ProcessNodeDefinitions: processNodeDefinitions + ProcessNodeDefinitions: processNodeDefinitions, }; - diff --git a/lib/process-node-definitions.js b/lib/process-node-definitions.js index 7d9115c..226c7f5 100644 --- a/lib/process-node-definitions.js +++ b/lib/process-node-definitions.js @@ -4,7 +4,7 @@ var R = require('ramda'); var S = require('underscore.string.fp'); var ent = require('ent'); -// https://github.com/facebook/react/blob/0.14-stable/src/renderers/dom/shared/ReactDOMComponent.js#L457 +// https://github.com/facebook/react/blob/15.0-stable/src/renderers/dom/shared/ReactDOMComponent.js#L457 var voidElementTags = [ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr', 'menuitem', diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index 603e17b..a80dff1 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -102,6 +102,20 @@ describe('Html2React', function() { assert.equal(reactHtml, htmlExpected); }); + it('should generate keys for sequence items', function () { + var htmlInput = '
  • Item 1
  • Item 2
  • <
'; + + var reactComponent = parser.parse(htmlInput); + + var children = _.filter(_.flatten(reactComponent.props.children), function (c) { + return _.has(c, 'key'); + }); + var keys = _.map(children, function (child) { + return child.key; + }); + assert.deepStrictEqual(keys, ['0', '1', ]); + }) + it('should parse br elements without warnings', function() { var htmlInput = '

Line one
Line two
Line three

'; var htmlExpected = '

Line one
Line two
Line three

'; @@ -228,6 +242,18 @@ describe('Html2React', function() { var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); assert.equal(reactHtml, htmlExpected); }); + + it('should return false in case of invalid node', function() { + var htmlInput = '

'; + var processingInstructions = [{ + shouldProcessNode: function(node) { return true; }, + processNode: processNodeDefinitions.processDefaultNode, + }, ]; + var reactComponent = parser.parseWithInstructions(htmlInput, + function () { return false }, processingInstructions); + + assert.equal(reactComponent, false); + }); }); }); }); From ba32e687f7001bd7f3b0930d68e256855ec82aff Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 24 Apr 2016 16:12:41 +0200 Subject: [PATCH 008/227] Create element keys per sequence --- lib/parser.js | 15 ++++++++------- lib/process-node-definitions.js | 6 ++---- test/html-to-react-tests.js | 10 +++++----- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/parser.js b/lib/parser.js index 43b6a6c..38820e6 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -13,16 +13,17 @@ var Html2React = function(React) { return handler.dom; }; - var traverseDom = function(node, isValidNode, processingInstructions) { + var traverseDom = function(node, isValidNode, processingInstructions, index) { if (isValidNode(node)) { - var children = R.reject((x) => {return x == null}, R.map(function (child) { - return traverseDom(child, isValidNode, processingInstructions); - }, node.children || [])); - var processingInstruction = R.find((processingInstruction) => { + var processingInstruction = R.find(function (processingInstruction) { return processingInstruction.shouldProcessNode(node); }, processingInstructions); if (processingInstruction != null) { - return processingInstruction.processNode(node, children); + var children = R.reject((x) => {return x == null}, + R.addIndex(R.map)(function (child, i) { + return traverseDom(child, isValidNode, processingInstructions, i); + }, node.children || [])); + return processingInstruction.processNode(node, children, index); } else { return false; } @@ -37,7 +38,7 @@ var Html2React = function(React) { 'The HTML provided contains ' + domTree.length + ' root elements. You can fix that by simply wrapping your HTML ' + 'in a
element.'); } - return traverseDom(domTree[0], isValidNode, processingInstructions); + return traverseDom(domTree[0], isValidNode, processingInstructions, 0); }; var parse = function(html) { diff --git a/lib/process-node-definitions.js b/lib/process-node-definitions.js index 226c7f5..de1d7e2 100644 --- a/lib/process-node-definitions.js +++ b/lib/process-node-definitions.js @@ -28,9 +28,7 @@ function createStyleJsonFromString(styleString) { } var ProcessNodeDefinitions = function(React) { - var index = 0; - - function processDefaultNode(node, children) { + function processDefaultNode(node, children, index) { if (node.type === 'text') { return ent.decode(node.data); } else if (node.type === 'comment') { @@ -40,7 +38,7 @@ var ProcessNodeDefinitions = function(React) { } var elementProps = { - key: ++index, + key: index, }; // Process attributes if (node.attribs) { diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index a80dff1..5a75799 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -107,12 +107,12 @@ describe('Html2React', function() { var reactComponent = parser.parse(htmlInput); - var children = _.filter(_.flatten(reactComponent.props.children), function (c) { - return _.has(c, 'key'); - }); - var keys = _.map(children, function (child) { + var children = R.filter(function (c) { + return R.has('key', c); + }, R.flatten(reactComponent.props.children)); + var keys = R.map(function (child) { return child.key; - }); + }, children); assert.deepStrictEqual(keys, ['0', '1', ]); }) From 0b74e9acf7e39e276c3242c139e10943772f8163 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Tue, 26 Apr 2016 15:35:21 +0200 Subject: [PATCH 009/227] Revert "Merge branch 'master' of https://github.com/mikenikles/html-to-react into child-keys" This reverts commit c853878a1d82fae3e2d5be37cdec26f1ba8cd6b2, reversing changes made to c2060d79880398dc0c2cbfb3ae8552a7fe99526a. --- lib/parser.js | 29 +++++++++++++++-------------- lib/process-node-definitions.js | 9 +++------ package.json | 1 - test/html-to-react-tests.js | 12 ++++++++++++ 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/lib/parser.js b/lib/parser.js index 49dd124..70e2086 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -13,21 +13,23 @@ var Html2React = function(React) { return handler.dom; }; - var traverseDom = function(node, isValidNode, processingInstructions) { + var traverseDom = function(node, isValidNode, processingInstructions, index) { if (isValidNode(node)) { - var children = []; - _.each(node.children, function(child) { - children.push(traverseDom(child, isValidNode, processingInstructions)); + var processingInstruction = _.find(processingInstructions || [], + function (processingInstruction) { + return processingInstruction.shouldProcessNode(node); }); - _.compact(children); // Remove invalid nodes - for (var index = 0; index < processingInstructions.length; index++) { - var processingInstruction = processingInstructions[index]; - if (processingInstruction.shouldProcessNode(node)) { - return processingInstruction.processNode(node, children); - } + if (processingInstruction != null) { + var children = _.compact(_.map(node.children, function (child, i) { + return traverseDom(child, isValidNode, processingInstructions, i); + })); + return processingInstruction.processNode(node, children, index); + } else { + return false; } + } else { + return false; } - return false; }; var parseWithInstructions = function(html, isValidNode, processingInstructions) { @@ -38,7 +40,7 @@ var Html2React = function(React) { 'The HTML provided contains ' + domTree.length + ' root elements. You can fix that by simply wrapping your HTML ' + 'in a
element.'); } - return traverseDom(domTree[0], isValidNode, processingInstructions); + return traverseDom(domTree[0], isValidNode, processingInstructions, 0); }; var parse = function(html) { @@ -50,9 +52,8 @@ var Html2React = function(React) { return { parse: parse, - parseWithInstructions: parseWithInstructions + parseWithInstructions: parseWithInstructions, }; }; module.exports = Html2React; - diff --git a/lib/process-node-definitions.js b/lib/process-node-definitions.js index ac6c014..34488af 100644 --- a/lib/process-node-definitions.js +++ b/lib/process-node-definitions.js @@ -1,7 +1,6 @@ 'use strict'; var _ = require('lodash'); -var ent = require('ent'); // https://github.com/facebook/react/blob/0.14-stable/src/renderers/dom/shared/ReactDOMComponent.js#L457 var voidElementTags = [ @@ -27,11 +26,9 @@ function createStyleJsonFromString(styleString) { } var ProcessNodeDefinitions = function(React) { - var index = 0; - - function processDefaultNode(node, children) { + function processDefaultNode(node, children, index) { if (node.type === 'text') { - return ent.decode(node.data); + return node.data; } else if (node.type === 'comment') { // FIXME: The following doesn't work as the generated HTML results in "<!-- This is a comment -->" //return ''; @@ -39,7 +36,7 @@ var ProcessNodeDefinitions = function(React) { } var elementProps = { - key: ++index + key: index, }; // Process attributes if (node.attribs) { diff --git a/package.json b/package.json index f04e593..abf018d 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,6 @@ } }, "dependencies": { - "ent": "^2.2.0", "htmlparser2": "^3.8.3", "lodash": "^3.9.3" }, diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index 3507cf0..c41497b 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -254,6 +254,18 @@ describe('Html2React', function() { var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); assert.equal(reactHtml, htmlExpected); }); + + it('should return false in case of invalid node', function() { + var htmlInput = '

'; + var processingInstructions = [{ + shouldProcessNode: function(node) { return true; }, + processNode: processNodeDefinitions.processDefaultNode, + }, ]; + var reactComponent = parser.parseWithInstructions(htmlInput, + function () { return false }, processingInstructions); + + assert.equal(reactComponent, false); + }); }); }); }); From 4f15f0caf07d382b780594aa05c783ae4539c977 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Tue, 26 Apr 2016 15:35:21 +0200 Subject: [PATCH 010/227] Create element keys per sequence --- lib/parser.js | 29 +++++++++++++++-------------- lib/process-node-definitions.js | 8 +++----- test/html-to-react-tests.js | 12 ++++++++++++ 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/lib/parser.js b/lib/parser.js index 49dd124..70e2086 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -13,21 +13,23 @@ var Html2React = function(React) { return handler.dom; }; - var traverseDom = function(node, isValidNode, processingInstructions) { + var traverseDom = function(node, isValidNode, processingInstructions, index) { if (isValidNode(node)) { - var children = []; - _.each(node.children, function(child) { - children.push(traverseDom(child, isValidNode, processingInstructions)); + var processingInstruction = _.find(processingInstructions || [], + function (processingInstruction) { + return processingInstruction.shouldProcessNode(node); }); - _.compact(children); // Remove invalid nodes - for (var index = 0; index < processingInstructions.length; index++) { - var processingInstruction = processingInstructions[index]; - if (processingInstruction.shouldProcessNode(node)) { - return processingInstruction.processNode(node, children); - } + if (processingInstruction != null) { + var children = _.compact(_.map(node.children, function (child, i) { + return traverseDom(child, isValidNode, processingInstructions, i); + })); + return processingInstruction.processNode(node, children, index); + } else { + return false; } + } else { + return false; } - return false; }; var parseWithInstructions = function(html, isValidNode, processingInstructions) { @@ -38,7 +40,7 @@ var Html2React = function(React) { 'The HTML provided contains ' + domTree.length + ' root elements. You can fix that by simply wrapping your HTML ' + 'in a
element.'); } - return traverseDom(domTree[0], isValidNode, processingInstructions); + return traverseDom(domTree[0], isValidNode, processingInstructions, 0); }; var parse = function(html) { @@ -50,9 +52,8 @@ var Html2React = function(React) { return { parse: parse, - parseWithInstructions: parseWithInstructions + parseWithInstructions: parseWithInstructions, }; }; module.exports = Html2React; - diff --git a/lib/process-node-definitions.js b/lib/process-node-definitions.js index ac6c014..5aac867 100644 --- a/lib/process-node-definitions.js +++ b/lib/process-node-definitions.js @@ -6,7 +6,7 @@ var ent = require('ent'); // https://github.com/facebook/react/blob/0.14-stable/src/renderers/dom/shared/ReactDOMComponent.js#L457 var voidElementTags = [ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', - 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr', 'textarea' + 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr', 'textarea', ]; function createStyleJsonFromString(styleString) { @@ -27,9 +27,7 @@ function createStyleJsonFromString(styleString) { } var ProcessNodeDefinitions = function(React) { - var index = 0; - - function processDefaultNode(node, children) { + function processDefaultNode(node, children, index) { if (node.type === 'text') { return ent.decode(node.data); } else if (node.type === 'comment') { @@ -39,7 +37,7 @@ var ProcessNodeDefinitions = function(React) { } var elementProps = { - key: ++index + key: index, }; // Process attributes if (node.attribs) { diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index 991061c..a400953 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -239,6 +239,18 @@ describe('Html2React', function() { var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); assert.equal(reactHtml, htmlExpected); }); + + it('should return false in case of invalid node', function() { + var htmlInput = '

'; + var processingInstructions = [{ + shouldProcessNode: function(node) { return true; }, + processNode: processNodeDefinitions.processDefaultNode, + }, ]; + var reactComponent = parser.parseWithInstructions(htmlInput, + function () { return false }, processingInstructions); + + assert.equal(reactComponent, false); + }); }); }); }); From aa6d18d840505a46bb7aacd9a64dea03c1b636bf Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Wed, 27 Apr 2016 20:40:22 +0200 Subject: [PATCH 011/227] Remove lodash stuff --- package.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/package.json b/package.json index 75936b9..681081c 100644 --- a/package.json +++ b/package.json @@ -38,10 +38,6 @@ "htmlparser2": "^3.8.3", "ramda": "^0.21.0", "underscore.string.fp": "^1.0.4" - "lodash.find": "^4.3.0", - "lodash.foreach": "^4.2.0", - "lodash.includes": "^4.1.2", - "lodash.map": "^4.3.0" }, "devDependencies": { "coveralls": "2.11.9", From 3259cfdc8d9f39dc5622c90c410ffe82c2e9fc00 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Mon, 2 May 2016 23:25:22 +0200 Subject: [PATCH 012/227] Stylistics --- lib/parser.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/parser.js b/lib/parser.js index 65d6830..86504ea 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -1,5 +1,4 @@ 'use strict'; - var R = require('ramda'); var htmlParser = require('htmlparser2'); var ProcessingInstructions = require('./processing-instructions'); @@ -34,9 +33,11 @@ var Html2React = function(React, options) { var domTree = parseHtmlToTree(html); // TODO: Deal with HTML that contains more than one root level node if (domTree && domTree.length !== 1) { - throw new Error('html-to-react currently only supports HTML with one single root element. ' + - 'The HTML provided contains ' + domTree.length + ' root elements. You can fix that by simply wrapping your HTML ' + - 'in a
element.'); + throw new Error( + 'html-to-react currently only supports HTML with one single root element. ' + + 'The HTML provided contains ' + domTree.length + + ' root elements. You can fix that by simply wrapping your HTML ' + + 'in a
element.'); } return traverseDom(domTree[0], isValidNode, processingInstructions, 0); }; From 4be39721a68d86eb3beb367635cda7f8097849f2 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Mon, 2 May 2016 23:44:03 +0200 Subject: [PATCH 013/227] Upgrade React --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index fa4a3c2..e23745b 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "istanbul": "0.4.3", "mocha": "2.4.5", "mocha-lcov-reporter": "1.2.0", - "react": "^0.14.7", - "react-dom": "^0.14.7" + "react": "^15.0", + "react-dom": "^15.0" } } From d965b0650d454b7ba56454b5c8450c916a877656 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Mon, 2 May 2016 23:46:56 +0200 Subject: [PATCH 014/227] Don't create null children --- lib/process-node-definitions.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/process-node-definitions.js b/lib/process-node-definitions.js index de1d7e2..28ee06e 100644 --- a/lib/process-node-definitions.js +++ b/lib/process-node-definitions.js @@ -7,7 +7,7 @@ var ent = require('ent'); // https://github.com/facebook/react/blob/15.0-stable/src/renderers/dom/shared/ReactDOMComponent.js#L457 var voidElementTags = [ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', - 'source', 'track', 'wbr', 'menuitem', + 'source', 'track', 'wbr', 'menuitem', 'textarea', ]; function createStyleJsonFromString(styleString) { @@ -41,26 +41,31 @@ var ProcessNodeDefinitions = function(React) { key: index, }; // Process attributes - if (node.attribs) { - R.forEach(function (value, key) { + if (!R.isEmpty(node.attribs)) { + elementProps = R.merge(elementProps, R.fromPairs(R.map(function (keyAndValue) { + var key = keyAndValue[0]; + var value = keyAndValue[1]; switch (key || '') { case 'style': - elementProps.style = createStyleJsonFromString(node.attribs.style); + value = createStyleJsonFromString(node.attribs.style); break; case 'class': - elementProps.className = value; + key = 'className'; break; default: - elementProps[key] = value; break; } - }, node.attribs); + + return [key, value,]; + }, R.toPairs(node.attribs)))); } if (R.contains(node.name, voidElementTags)) { return React.createElement(node.name, elementProps) } else { - return React.createElement(node.name, elementProps, node.data, children); + var allChildren = node.data != null ? R.concat([node.data,], children) : children; + return React.createElement.apply(null, R.concat([node.name, elementProps,], + allChildren)); } } From 65ef3a890d62243474b926ec3e7fffd21716d723 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Mon, 2 May 2016 23:50:59 +0200 Subject: [PATCH 015/227] Return false for invalid nodes --- lib/parser.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/parser.js b/lib/parser.js index 86504ea..87aa8ae 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -26,6 +26,8 @@ var Html2React = function(React, options) { } else { return false; } + } else { + return false; } }; From 374a6ee00ab5d0fa07068f53a03d561b44e3353c Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Tue, 3 May 2016 10:41:44 +0200 Subject: [PATCH 016/227] Don't add null or nested children to elements --- lib/process-node-definitions.js | 9 ++++++--- package.json | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/process-node-definitions.js b/lib/process-node-definitions.js index df2df0b..5cf722b 100644 --- a/lib/process-node-definitions.js +++ b/lib/process-node-definitions.js @@ -3,6 +3,7 @@ var camelCase = require('lodash.camelcase'); var forEach = require('lodash.foreach'); var includes = require('lodash.includes'); +var concat = require('lodash.concat'); var ent = require('ent'); // https://github.com/facebook/react/blob/0.14-stable/src/renderers/dom/shared/ReactDOMComponent.js#L457 @@ -61,9 +62,11 @@ var ProcessNodeDefinitions = function(React) { if (includes(voidElementTags, node.name)) { return React.createElement(node.name, elementProps) } else { - return React.createElement(node.name, elementProps, node.data, children); - } - + var allChildren = node.data != null ? concat([node.data,], children) : children; + return React.createElement.apply( + this, concat([node.name, elementProps,], allChildren) + ); + } } return { diff --git a/package.json b/package.json index d1ccfdd..2be5257 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "htmlparser2": "^3.8.3", "lodash.camelcase": "^4.1.0", "lodash.compact": "^3.0.1", + "lodash.concat": "^4.3.0", "lodash.find": "^4.3.0", "lodash.foreach": "^4.2.0", "lodash.includes": "^4.1.2", From db26b3d502ec2db8608aac3c5695e3f6f36c2a8f Mon Sep 17 00:00:00 2001 From: Alex Gilleran Date: Mon, 6 Jun 2016 12:39:53 +1000 Subject: [PATCH 017/227] Attribute mapping. --- lib/camel-case-attribute-names.js | 62 +++++++++++++++++++++++++++++++ lib/process-node-definitions.js | 23 ++++++------ 2 files changed, 73 insertions(+), 12 deletions(-) create mode 100644 lib/camel-case-attribute-names.js diff --git a/lib/camel-case-attribute-names.js b/lib/camel-case-attribute-names.js new file mode 100644 index 0000000..cac03c8 --- /dev/null +++ b/lib/camel-case-attribute-names.js @@ -0,0 +1,62 @@ +var HTML_ATTRIBUTES = [ + 'accept', 'acceptCharset', 'accessKey', 'action', 'allowFullScreen', 'allowTransparency', 'alt', + 'async', 'autoComplete', 'autoFocus', 'autoPlay', 'capture', 'cellPadding', 'cellSpacing', 'challenge', + 'charSet', 'checked', 'cite', 'classID', 'className', 'colSpan', 'cols', 'content', 'contentEditable', + 'contextMenu', 'controls', 'coords', 'crossOrigin', 'data', 'dateTime', 'default', 'defer', 'dir', + 'disabled', 'download', 'draggable', 'encType', 'form', 'formAction', 'formEncType', 'formMethod', + 'formNoValidate', 'formTarget', 'frameBorder', 'headers', 'height', 'hidden', 'high', 'href', 'hrefLang', + 'htmlFor', 'httpEquiv', 'icon', 'id', 'inputMode', 'integrity', 'is', 'keyParams', 'keyType', 'kind', 'label', + 'lang', 'list', 'loop', 'low', 'manifest', 'marginHeight', 'marginWidth', 'max', 'maxLength', 'media', + 'mediaGroup', 'method', 'min', 'minLength', 'multiple', 'muted', 'name', 'noValidate', 'nonce', 'open', + 'optimum', 'pattern', 'placeholder', 'poster', 'preload', 'profile', 'radioGroup', 'readOnly', 'rel', + 'required', 'reversed', 'role', 'rowSpan', 'rows', 'sandbox', 'scope', 'scoped', 'scrolling', 'seamless', + 'selected', 'shape', 'size', 'sizes', 'span', 'spellCheck', 'src', 'srcDoc', 'srcLang', 'srcSet', 'start', 'step', + 'style', 'summary', 'tabIndex', 'target', 'title', 'type', 'useMap', 'value', 'width', 'wmode', 'wrap' +]; + +var NON_STANDARD_ATTRIBUTES = [ + 'autoCapitalize', 'autoCorrect', 'color', 'itemProp', 'itemScope', 'itemType', 'itemRef', 'itemID', 'security', + 'unselectable', 'results', 'autoSave' +]; + +var SVG_ATTRIBUTES = [ + 'accentHeight', 'accumulate', 'additive', 'alignmentBaseline', 'allowReorder', 'alphabetic', 'amplitude', + 'arabicForm', 'ascent', 'attributeName', 'attributeType', 'autoReverse', 'azimuth', 'baseFrequency', 'baseProfile', + 'baselineShift', 'bbox', 'begin', 'bias', 'by', 'calcMode', 'capHeight', 'clip', 'clipPath', 'clipPathUnits', + 'clipRule', 'colorInterpolation', 'colorInterpolationFilters', 'colorProfile', 'colorRendering', 'contentScriptType', + 'contentStyleType', 'cursor', 'cx', 'cy', 'd', 'decelerate', 'descent', 'diffuseConstant', 'direction', 'display', + 'divisor', 'dominantBaseline', 'dur', 'dx', 'dy', 'edgeMode', 'elevation', 'enableBackground', 'end', 'exponent', + 'externalResourcesRequired', 'fill', 'fillOpacity', 'fillRule', 'filter', 'filterRes', 'filterUnits', 'floodColor', + 'floodOpacity', 'focusable', 'fontFamily', 'fontSize', 'fontSizeAdjust', 'fontStretch', 'fontStyle', 'fontVariant', + 'fontWeight', 'format', 'from', 'fx', 'fy', 'g1', 'g2', 'glyphName', 'glyphOrientationHorizontal', + 'glyphOrientationVertical', 'glyphRef', 'gradientTransform', 'gradientUnits', 'hanging', 'horizAdvX', 'horizOriginX', + 'ideographic', 'imageRendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kernelMatrix', + 'kernelUnitLength', 'kerning', 'keyPoints', 'keySplines', 'keyTimes', 'lengthAdjust', 'letterSpacing', 'lightingColor', + 'limitingConeAngle', 'local', 'markerEnd', 'markerHeight', 'markerMid', 'markerStart', 'markerUnits', 'markerWidth', + 'mask', 'maskContentUnits', 'maskUnits', 'mathematical', 'mode', 'numOctaves', 'offset', 'opacity', 'operator', 'order', + 'orient', 'orientation', 'origin', 'overflow', 'overlinePosition', 'overlineThickness', 'paintOrder', 'panose1', + 'pathLength', 'patternContentUnits', 'patternTransform', 'patternUnits', 'pointerEvents', 'points', 'pointsAtX', + 'pointsAtY', 'pointsAtZ', 'preserveAlpha', 'preserveAspectRatio', 'primitiveUnits', 'r', 'radius', 'refX', 'refY', + 'renderingIntent', 'repeatCount', 'repeatDur', 'requiredExtensions', 'requiredFeatures', 'restart', 'result', 'rotate', + 'rx', 'ry', 'scale', 'seed', 'shapeRendering', 'slope', 'spacing', 'specularConstant', 'specularExponent', 'speed', + 'spreadMethod', 'startOffset', 'stdDeviation', 'stemh', 'stemv', 'stitchTiles', 'stopColor', 'stopOpacity', + 'strikethroughPosition', 'strikethroughThickness', 'string', 'stroke', 'strokeDasharray', 'strokeDashoffset', + 'strokeLinecap', 'strokeLinejoin', 'strokeMiterlimit', 'strokeOpacity', 'strokeWidth', 'surfaceScale', + 'systemLanguage', 'tableValues', 'targetX', 'targetY', 'textAnchor', 'textDecoration', 'textLength', 'textRendering', + 'to', 'transform', 'u1', 'u2', 'underlinePosition', 'underlineThickness', 'unicode', 'unicodeBidi', 'unicodeRange', + 'unitsPerEm', 'vAlphabetic', 'vHanging', 'vIdeographic', 'vMathematical', 'values', 'vectorEffect', 'version', + 'vertAdvY', 'vertOriginX', 'vertOriginY', 'viewBox', 'viewTarget', 'visibility', 'widths', 'wordSpacing', + 'writingMode', 'x', 'x1', 'x2', 'xChannelSelector', 'xHeight', 'xlinkActuate', 'xlinkArcrole', 'xlinkHref', + 'xlinkRole', 'xlinkShow', 'xlinkTitle', 'xlinkType', 'xmlBase', 'xmlLang', 'xmlSpace', 'y', 'y1', 'y2', + 'yChannelSelector', 'z', 'zoomAndPan' +]; + +var camelCaseMap = HTML_ATTRIBUTES + .concat(NON_STANDARD_ATTRIBUTES) + .concat(SVG_ATTRIBUTES) + .reduce(function(soFar, attr) { + soFar[attr.toLowerCase()] = attr; + return soFar; + }, {}); + +module.exports = camelCaseMap; diff --git a/lib/process-node-definitions.js b/lib/process-node-definitions.js index df2df0b..702bd13 100644 --- a/lib/process-node-definitions.js +++ b/lib/process-node-definitions.js @@ -4,6 +4,7 @@ var camelCase = require('lodash.camelcase'); var forEach = require('lodash.foreach'); var includes = require('lodash.includes'); var ent = require('ent'); +var camelCaseAttrMap = require('./camel-case-attribute-names'); // https://github.com/facebook/react/blob/0.14-stable/src/renderers/dom/shared/ReactDOMComponent.js#L457 var voidElementTags = [ @@ -44,16 +45,14 @@ var ProcessNodeDefinitions = function(React) { // Process attributes if (node.attribs) { forEach(node.attribs, function(value, key) { - switch (key || '') { - case 'style': - elementProps.style = createStyleJsonFromString(node.attribs.style); - break; - case 'class': - elementProps.className = value; - break; - default: - elementProps[key] = value; - break; + if (key === 'style') { + elementProps.style = createStyleJsonFromString(node.attribs.style); + } else if (key === 'class') { + elementProps.className = value + } else if (camelCaseAttrMap[key]) { + elementProps[camelCaseAttrMap[key]] = value; + } else { + elementProps[key] = value; } }); } @@ -61,8 +60,8 @@ var ProcessNodeDefinitions = function(React) { if (includes(voidElementTags, node.name)) { return React.createElement(node.name, elementProps) } else { - return React.createElement(node.name, elementProps, node.data, children); - } + return React.createElement(node.name, elementProps, node.data, children); + } } From 435812842c0f0b17b0eadafa8f7fc4cc99389374 Mon Sep 17 00:00:00 2001 From: Alex Gilleran Date: Mon, 6 Jun 2016 15:47:48 +1000 Subject: [PATCH 018/227] Added tests. --- lib/camel-case-attribute-names.js | 10 +++++++++- test/html-to-react-tests.js | 9 +++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/camel-case-attribute-names.js b/lib/camel-case-attribute-names.js index cac03c8..e427e14 100644 --- a/lib/camel-case-attribute-names.js +++ b/lib/camel-case-attribute-names.js @@ -1,3 +1,6 @@ +// These are all sourced from https://facebook.github.io/react/docs/tags-and-attributes.html - all attributes regardless +// of whether they have a different case to their HTML equivalents are listed to reduce the chance of human error +// and make it easier to just copy-paste the new list if it changes. var HTML_ATTRIBUTES = [ 'accept', 'acceptCharset', 'accessKey', 'action', 'allowFullScreen', 'allowTransparency', 'alt', 'async', 'autoComplete', 'autoFocus', 'autoPlay', 'capture', 'cellPadding', 'cellSpacing', 'challenge', @@ -55,7 +58,12 @@ var camelCaseMap = HTML_ATTRIBUTES .concat(NON_STANDARD_ATTRIBUTES) .concat(SVG_ATTRIBUTES) .reduce(function(soFar, attr) { - soFar[attr.toLowerCase()] = attr; + var lower = attr.toLowerCase(); + + if (lower !== attr) { + soFar[lower] = attr; + } + return soFar; }, {}); diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index 8cebfd2..7a77a8e 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -76,6 +76,15 @@ describe('Html2React', function() { assert.equal(reactHtml, htmlInput); }); + it('should return a valid HTML string with a react camelCase attribute', function() { + var htmlInput = '
'; + + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + + assert.equal(reactHtml, htmlInput); + }); + // FIXME: See lib/process-node-definitions.js -> processDefaultNode() it.skip('should return a valid HTML string with comments', function() { var htmlInput = '
'; From 6698cd5c769bf09c262772379b5be2a24b09adb0 Mon Sep 17 00:00:00 2001 From: Jason English Date: Tue, 28 Jun 2016 20:18:58 -0700 Subject: [PATCH 019/227] Replace element children --- README.md | 77 +++++++++++++++++++++++++++++++++ lib/parser.js | 10 +++++ lib/process-node-definitions.js | 40 +---------------- lib/utils.js | 51 ++++++++++++++++++++++ test/html-to-react-tests.js | 30 +++++++++++++ 5 files changed, 170 insertions(+), 38 deletions(-) create mode 100644 lib/utils.js diff --git a/README.md b/README.md index 6d0103c..ace61bd 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,83 @@ var reactHtml = React.renderToStaticMarkup(reactComponent); assert.equal(reactHtml, htmlExpected); ``` +### Replace the children of an element + +There may be a situation where you want to replace the children of an element with a React component. This is +beneficial if you want to: +- a) preserve the containing element +- b) not rely on any child node to insert your React component + +#### Example + +Below is a simple template that could get loaded via ajax into your application + +##### Before +``` +
+
+
+

Sample Heading

+

Sample Text

+
+
+
+``` + +##### After + +You may want to extract the inner html from the `data-container` attribute, store it and then pass it as a +prop to your injected `RichTextEditor`. + +``` +
+
+
+ Sample heading

Sample Text

"} /> +
+
+
+``` + +#### Setup + +In your instructions object, you must specify `replaceChildren: true`. + +```javascript +var React = require('react'); +var HtmlToReact = new require('html-to-react'); + +var htmlInput = '

Text

Text

'; +var htmlExpected = '

Heading

'; + +var isValidNode = function() { + return true; +}; + +// Order matters. Instructions are processed in the order they're defined +var processingInstructions = [{ + // This is REQUIRED, it tells the parser that we want to insert our React component as a child + replaceChildren: true, + shouldProcessNode: function(node) { + return node.attribs && node.attribs['data-test'] === 'foo'; + }, + processNode: function(node, children, index) { + return React.createElement('h1', { key: index }, 'Heading'); + } +}, +{ + // Anything else + shouldProcessNode: function(node) { + return true; + }, + processNode: processNodeDefinitions.processDefaultNode +}]; + +var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, processingInstructions); +var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); +assert.equal(reactHtml, htmlExpected); +``` + ## Tests & Coverage `$ npm run test-locally` diff --git a/lib/parser.js b/lib/parser.js index bed57a7..983de9b 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -6,6 +6,7 @@ var map = require('lodash.map'); var htmlParser = require('htmlparser2'); var ProcessingInstructions = require('./processing-instructions'); var IsValidNodeDefinitions = require('./is-valid-node-definitions'); +var utils = require('./utils'); var Html2React = function(React, options) { var parseHtmlToTree = function(html) { @@ -25,6 +26,15 @@ var Html2React = function(React, options) { var children = compact(map(node.children, function (child, i) { return traverseDom(child, isValidNode, processingInstructions, i); })); + + if (processingInstruction.replaceChildren) { + var elementProps = utils.createElementProps(node, index); + + return React.createElement(node.name, elementProps, node.data, [ + processingInstruction.processNode(node, children, index) + ]); + } + return processingInstruction.processNode(node, children, index); } else { return false; diff --git a/lib/process-node-definitions.js b/lib/process-node-definitions.js index df2df0b..751ede5 100644 --- a/lib/process-node-definitions.js +++ b/lib/process-node-definitions.js @@ -1,9 +1,8 @@ 'use strict'; -var camelCase = require('lodash.camelcase'); -var forEach = require('lodash.foreach'); var includes = require('lodash.includes'); var ent = require('ent'); +var utils = require('./utils'); // https://github.com/facebook/react/blob/0.14-stable/src/renderers/dom/shared/ReactDOMComponent.js#L457 var voidElementTags = [ @@ -11,23 +10,6 @@ var voidElementTags = [ 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr', 'textarea', ]; -function createStyleJsonFromString(styleString) { - if (!styleString) { - return {}; - } - var styles = styleString.split(';'); - var singleStyle, key, value, jsonStyles = {}; - for (var i = 0; i < styles.length; i++) { - singleStyle = styles[i].split(':'); - key = camelCase(singleStyle[0]); - value = singleStyle[1]; - if (key.length > 0 && value.length > 0) { - jsonStyles[key] = value; - } - } - return jsonStyles; -} - var ProcessNodeDefinitions = function(React) { function processDefaultNode(node, children, index) { if (node.type === 'text') { @@ -38,25 +20,7 @@ var ProcessNodeDefinitions = function(React) { return false; } - var elementProps = { - key: index, - }; - // Process attributes - if (node.attribs) { - forEach(node.attribs, function(value, key) { - switch (key || '') { - case 'style': - elementProps.style = createStyleJsonFromString(node.attribs.style); - break; - case 'class': - elementProps.className = value; - break; - default: - elementProps[key] = value; - break; - } - }); - } + var elementProps = utils.createElementProps(node, index); if (includes(voidElementTags, node.name)) { return React.createElement(node.name, elementProps) diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..1305a59 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,51 @@ +'use strict'; + +var camelCase = require('lodash.camelcase'); +var forEach = require('lodash.foreach'); +var includes = require('lodash.includes'); + +function createStyleJsonFromString(styleString) { + if (!styleString) { + return {}; + } + var styles = styleString.split(';'); + var singleStyle, key, value, jsonStyles = {}; + for (var i = 0; i < styles.length; i++) { + singleStyle = styles[i].split(':'); + key = camelCase(singleStyle[0]); + value = singleStyle[1]; + if (key.length > 0 && value.length > 0) { + jsonStyles[key] = value; + } + } + return jsonStyles; +} + +function createElementProps(node, index) { + var elementProps = { + key: index + }; + + // Process attributes + if (node.attribs) { + forEach(node.attribs, function(value, key) { + switch (key || '') { + case 'style': + elementProps.style = createStyleJsonFromString(node.attribs.style); + break; + case 'class': + elementProps.className = value; + break; + default: + elementProps[key] = value; + break; + } + }); + } + + return elementProps; +} + +module.exports = { + createElementProps: createElementProps +}; diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index 8cebfd2..4a67f8c 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -212,6 +212,36 @@ describe('Html2React', function() { assert.equal(reactHtml, htmlExpected); }); + it('should replace the children of an element', function() { + var htmlInput = '

Text

Text

'; + var htmlExpected = '

Heading

'; + + var isValidNode = function() { + return true; + }; + + var processingInstructions = [{ + replaceChildren: true, + shouldProcessNode: function(node) { + return node.attribs && node.attribs['data-test'] === 'foo'; + }, + processNode: function(node, children, index) { + return React.createElement('h1', { key: index }, 'Heading'); + } + }, + { + // Anything else + shouldProcessNode: function(node) { + return true; + }, + processNode: processNodeDefinitions.processDefaultNode + }]; + + var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, processingInstructions); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + assert.equal(reactHtml, htmlExpected); + }); + it('should return capitalized content for all

elements', function() { var htmlInput = '

Title

Paragraph

Another title

'; var htmlExpected = '

TITLE

Paragraph

ANOTHER TITLE

'; From 5f20d4083cc44da68d32268f2406a65b7b6a09ed Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Mon, 15 Aug 2016 15:53:59 +0200 Subject: [PATCH 020/227] Use filter instead of reduce --- lib/camel-case-attribute-names.js | 107 ++++++++++++++++-------------- 1 file changed, 56 insertions(+), 51 deletions(-) diff --git a/lib/camel-case-attribute-names.js b/lib/camel-case-attribute-names.js index e427e14..09bd0a4 100644 --- a/lib/camel-case-attribute-names.js +++ b/lib/camel-case-attribute-names.js @@ -1,70 +1,75 @@ // These are all sourced from https://facebook.github.io/react/docs/tags-and-attributes.html - all attributes regardless // of whether they have a different case to their HTML equivalents are listed to reduce the chance of human error // and make it easier to just copy-paste the new list if it changes. +'use strict'; var HTML_ATTRIBUTES = [ - 'accept', 'acceptCharset', 'accessKey', 'action', 'allowFullScreen', 'allowTransparency', 'alt', - 'async', 'autoComplete', 'autoFocus', 'autoPlay', 'capture', 'cellPadding', 'cellSpacing', 'challenge', - 'charSet', 'checked', 'cite', 'classID', 'className', 'colSpan', 'cols', 'content', 'contentEditable', - 'contextMenu', 'controls', 'coords', 'crossOrigin', 'data', 'dateTime', 'default', 'defer', 'dir', - 'disabled', 'download', 'draggable', 'encType', 'form', 'formAction', 'formEncType', 'formMethod', - 'formNoValidate', 'formTarget', 'frameBorder', 'headers', 'height', 'hidden', 'high', 'href', 'hrefLang', - 'htmlFor', 'httpEquiv', 'icon', 'id', 'inputMode', 'integrity', 'is', 'keyParams', 'keyType', 'kind', 'label', - 'lang', 'list', 'loop', 'low', 'manifest', 'marginHeight', 'marginWidth', 'max', 'maxLength', 'media', - 'mediaGroup', 'method', 'min', 'minLength', 'multiple', 'muted', 'name', 'noValidate', 'nonce', 'open', - 'optimum', 'pattern', 'placeholder', 'poster', 'preload', 'profile', 'radioGroup', 'readOnly', 'rel', - 'required', 'reversed', 'role', 'rowSpan', 'rows', 'sandbox', 'scope', 'scoped', 'scrolling', 'seamless', - 'selected', 'shape', 'size', 'sizes', 'span', 'spellCheck', 'src', 'srcDoc', 'srcLang', 'srcSet', 'start', 'step', - 'style', 'summary', 'tabIndex', 'target', 'title', 'type', 'useMap', 'value', 'width', 'wmode', 'wrap' + 'accept', 'acceptCharset', 'accessKey', 'action', 'allowFullScreen', 'allowTransparency', + 'alt', 'async', 'autoComplete', 'autoFocus', 'autoPlay', 'capture', 'cellPadding', + 'cellSpacing', 'challenge', 'charSet', 'checked', 'cite', 'classID', 'className', + 'colSpan', 'cols', 'content', 'contentEditable', 'contextMenu', 'controls', 'coords', + 'crossOrigin', 'data', 'dateTime', 'default', 'defer', 'dir', 'disabled', 'download', + 'draggable', 'encType', 'form', 'formAction', 'formEncType', 'formMethod', + 'formNoValidate', 'formTarget', 'frameBorder', 'headers', 'height', 'hidden', 'high', 'href', 'hrefLang', 'htmlFor', 'httpEquiv', 'icon', 'id', 'inputMode', 'integrity', 'is', 'keyParams', 'keyType', 'kind', 'label', 'lang', 'list', 'loop', 'low', 'manifest', 'marginHeight', + 'marginWidth', 'max', 'maxLength', 'media', 'mediaGroup', 'method', 'min', 'minLength', + 'multiple', 'muted', 'name', 'noValidate', 'nonce', 'open', 'optimum', 'pattern', + 'placeholder', 'poster', 'preload', 'profile', 'radioGroup', 'readOnly', 'rel', + 'required', 'reversed', 'role', 'rowSpan', 'rows', 'sandbox', 'scope', 'scoped', 'scrolling', + 'seamless', 'selected', 'shape', 'size', 'sizes', 'span', 'spellCheck', 'src', 'srcDoc', + 'srcLang', 'srcSet', 'start', 'step', 'style', 'summary', 'tabIndex', 'target', 'title', + 'type', 'useMap', 'value', 'width', 'wmode', 'wrap', ]; var NON_STANDARD_ATTRIBUTES = [ - 'autoCapitalize', 'autoCorrect', 'color', 'itemProp', 'itemScope', 'itemType', 'itemRef', 'itemID', 'security', - 'unselectable', 'results', 'autoSave' + 'autoCapitalize', 'autoCorrect', 'color', 'itemProp', 'itemScope', 'itemType', 'itemRef', + 'itemID', 'security', 'unselectable', 'results', 'autoSave', ]; var SVG_ATTRIBUTES = [ - 'accentHeight', 'accumulate', 'additive', 'alignmentBaseline', 'allowReorder', 'alphabetic', 'amplitude', - 'arabicForm', 'ascent', 'attributeName', 'attributeType', 'autoReverse', 'azimuth', 'baseFrequency', 'baseProfile', - 'baselineShift', 'bbox', 'begin', 'bias', 'by', 'calcMode', 'capHeight', 'clip', 'clipPath', 'clipPathUnits', - 'clipRule', 'colorInterpolation', 'colorInterpolationFilters', 'colorProfile', 'colorRendering', 'contentScriptType', - 'contentStyleType', 'cursor', 'cx', 'cy', 'd', 'decelerate', 'descent', 'diffuseConstant', 'direction', 'display', - 'divisor', 'dominantBaseline', 'dur', 'dx', 'dy', 'edgeMode', 'elevation', 'enableBackground', 'end', 'exponent', - 'externalResourcesRequired', 'fill', 'fillOpacity', 'fillRule', 'filter', 'filterRes', 'filterUnits', 'floodColor', - 'floodOpacity', 'focusable', 'fontFamily', 'fontSize', 'fontSizeAdjust', 'fontStretch', 'fontStyle', 'fontVariant', - 'fontWeight', 'format', 'from', 'fx', 'fy', 'g1', 'g2', 'glyphName', 'glyphOrientationHorizontal', - 'glyphOrientationVertical', 'glyphRef', 'gradientTransform', 'gradientUnits', 'hanging', 'horizAdvX', 'horizOriginX', - 'ideographic', 'imageRendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kernelMatrix', - 'kernelUnitLength', 'kerning', 'keyPoints', 'keySplines', 'keyTimes', 'lengthAdjust', 'letterSpacing', 'lightingColor', - 'limitingConeAngle', 'local', 'markerEnd', 'markerHeight', 'markerMid', 'markerStart', 'markerUnits', 'markerWidth', - 'mask', 'maskContentUnits', 'maskUnits', 'mathematical', 'mode', 'numOctaves', 'offset', 'opacity', 'operator', 'order', - 'orient', 'orientation', 'origin', 'overflow', 'overlinePosition', 'overlineThickness', 'paintOrder', 'panose1', - 'pathLength', 'patternContentUnits', 'patternTransform', 'patternUnits', 'pointerEvents', 'points', 'pointsAtX', - 'pointsAtY', 'pointsAtZ', 'preserveAlpha', 'preserveAspectRatio', 'primitiveUnits', 'r', 'radius', 'refX', 'refY', - 'renderingIntent', 'repeatCount', 'repeatDur', 'requiredExtensions', 'requiredFeatures', 'restart', 'result', 'rotate', - 'rx', 'ry', 'scale', 'seed', 'shapeRendering', 'slope', 'spacing', 'specularConstant', 'specularExponent', 'speed', - 'spreadMethod', 'startOffset', 'stdDeviation', 'stemh', 'stemv', 'stitchTiles', 'stopColor', 'stopOpacity', - 'strikethroughPosition', 'strikethroughThickness', 'string', 'stroke', 'strokeDasharray', 'strokeDashoffset', - 'strokeLinecap', 'strokeLinejoin', 'strokeMiterlimit', 'strokeOpacity', 'strokeWidth', 'surfaceScale', - 'systemLanguage', 'tableValues', 'targetX', 'targetY', 'textAnchor', 'textDecoration', 'textLength', 'textRendering', - 'to', 'transform', 'u1', 'u2', 'underlinePosition', 'underlineThickness', 'unicode', 'unicodeBidi', 'unicodeRange', - 'unitsPerEm', 'vAlphabetic', 'vHanging', 'vIdeographic', 'vMathematical', 'values', 'vectorEffect', 'version', - 'vertAdvY', 'vertOriginX', 'vertOriginY', 'viewBox', 'viewTarget', 'visibility', 'widths', 'wordSpacing', - 'writingMode', 'x', 'x1', 'x2', 'xChannelSelector', 'xHeight', 'xlinkActuate', 'xlinkArcrole', 'xlinkHref', - 'xlinkRole', 'xlinkShow', 'xlinkTitle', 'xlinkType', 'xmlBase', 'xmlLang', 'xmlSpace', 'y', 'y1', 'y2', - 'yChannelSelector', 'z', 'zoomAndPan' + 'accentHeight', 'accumulate', 'additive', 'alignmentBaseline', 'allowReorder', 'alphabetic', + 'amplitude', 'arabicForm', 'ascent', 'attributeName', 'attributeType', 'autoReverse', + 'azimuth', 'baseFrequency', 'baseProfile', 'baselineShift', 'bbox', 'begin', 'bias', 'by', + 'calcMode', 'capHeight', 'clip', 'clipPath', 'clipPathUnits', 'clipRule', 'colorInterpolation', + 'colorInterpolationFilters', 'colorProfile', 'colorRendering', 'contentScriptType', + 'contentStyleType', 'cursor', 'cx', 'cy', 'd', 'decelerate', 'descent', 'diffuseConstant', + 'direction', 'display', 'divisor', 'dominantBaseline', 'dur', 'dx', 'dy', 'edgeMode', + 'elevation', 'enableBackground', 'end', 'exponent', 'externalResourcesRequired', 'fill', + 'fillOpacity', 'fillRule', 'filter', 'filterRes', 'filterUnits', 'floodColor', 'floodOpacity', + 'focusable', 'fontFamily', 'fontSize', 'fontSizeAdjust', 'fontStretch', 'fontStyle', + 'fontVariant', 'fontWeight', 'format', 'from', 'fx', 'fy', 'g1', 'g2', 'glyphName', + 'glyphOrientationHorizontal', 'glyphOrientationVertical', 'glyphRef', 'gradientTransform', + 'gradientUnits', 'hanging', 'horizAdvX', 'horizOriginX', 'ideographic', 'imageRendering', + 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kernelMatrix', 'kernelUnitLength', + 'kerning', 'keyPoints', 'keySplines', 'keyTimes', 'lengthAdjust', 'letterSpacing', + 'lightingColor', 'limitingConeAngle', 'local', 'markerEnd', 'markerHeight', 'markerMid', + 'markerStart', 'markerUnits', 'markerWidth', 'mask', 'maskContentUnits', 'maskUnits', + 'mathematical', 'mode', 'numOctaves', 'offset', 'opacity', 'operator', 'order', + 'orient', 'orientation', 'origin', 'overflow', 'overlinePosition', 'overlineThickness', + 'paintOrder', 'panose1', 'pathLength', 'patternContentUnits', 'patternTransform', + 'patternUnits', 'pointerEvents', 'points', 'pointsAtX', 'pointsAtY', 'pointsAtZ', + 'preserveAlpha', 'preserveAspectRatio', 'primitiveUnits', 'r', 'radius', 'refX', 'refY', + 'renderingIntent', 'repeatCount', 'repeatDur', 'requiredExtensions', 'requiredFeatures', + 'restart', 'result', 'rotate', 'rx', 'ry', 'scale', 'seed', 'shapeRendering', 'slope', + 'spacing', 'specularConstant', 'specularExponent', 'speed', 'spreadMethod', 'startOffset', + 'stdDeviation', 'stemh', 'stemv', 'stitchTiles', 'stopColor', 'stopOpacity', + 'strikethroughPosition', 'strikethroughThickness', 'string', 'stroke', 'strokeDasharray', + 'strokeDashoffset', 'strokeLinecap', 'strokeLinejoin', 'strokeMiterlimit', + 'strokeOpacity', 'strokeWidth', 'surfaceScale', 'systemLanguage', 'tableValues', 'targetX', + 'targetY', 'textAnchor', 'textDecoration', 'textLength', 'textRendering', 'to', 'transform', + 'u1', 'u2', 'underlinePosition', 'underlineThickness', 'unicode', 'unicodeBidi', + 'unicodeRange', 'unitsPerEm', 'vAlphabetic', 'vHanging', 'vIdeographic', 'vMathematical', + 'values', 'vectorEffect', 'version', 'vertAdvY', 'vertOriginX', 'vertOriginY', 'viewBox', + 'viewTarget', 'visibility', 'widths', 'wordSpacing', 'writingMode', 'x', 'x1', 'x2', + 'xChannelSelector', 'xHeight', 'xlinkActuate', 'xlinkArcrole', 'xlinkHref', 'xlinkRole', + 'xlinkShow', 'xlinkTitle', 'xlinkType', 'xmlBase', 'xmlLang', 'xmlSpace', 'y', 'y1', 'y2', + 'yChannelSelector', 'z', 'zoomAndPan', ]; var camelCaseMap = HTML_ATTRIBUTES .concat(NON_STANDARD_ATTRIBUTES) .concat(SVG_ATTRIBUTES) - .reduce(function(soFar, attr) { + .filter(function (attr) { var lower = attr.toLowerCase(); - - if (lower !== attr) { - soFar[lower] = attr; - } - - return soFar; + return lower !== attr; }, {}); module.exports = camelCaseMap; From 4c6d599c98e27bead766c294762bea2e0362cfd7 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Mon, 15 Aug 2016 15:54:06 +0200 Subject: [PATCH 021/227] Use camelCase attr converter --- lib/process-node-definitions.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/process-node-definitions.js b/lib/process-node-definitions.js index 07628e3..13d1645 100644 --- a/lib/process-node-definitions.js +++ b/lib/process-node-definitions.js @@ -46,15 +46,12 @@ var ProcessNodeDefinitions = function(React) { elementProps = R.merge(elementProps, R.fromPairs(R.map(function (keyAndValue) { var key = keyAndValue[0]; var value = keyAndValue[1]; - switch (key || '') { - case 'style': - value = createStyleJsonFromString(node.attribs.style); - break; - case 'class': - key = 'className'; - break; - default: - break; + if (key === 'style') { + value = createStyleJsonFromString(node.attribs.style); + } else if (key === 'class') { + key = 'className'; + } else if (camelCaseAttrMap[key]) { + key = camelCaseAttrMap[key] = value; } return [key, value,]; From b1fcfa0aad2b416ced1538b9856ad26f63d7e2ea Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Mon, 15 Aug 2016 16:01:40 +0200 Subject: [PATCH 022/227] Fix test command --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6d0103c..4bd6578 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,6 @@ assert.equal(reactHtml, htmlExpected); ## Tests & Coverage -`$ npm run test-locally` +`$ npm run test` `$ npm run test-html-coverage` From d29d48e2cfdba01010b52f9c4cf26e89ad774583 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Mon, 15 Aug 2016 16:01:47 +0200 Subject: [PATCH 023/227] Update dependencies --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index e23745b..fffb923 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "html-to-react", - "version": "1.0.0", + "version": "1.0.1", "description": "A lightweight library that converts raw HTML to a React DOM structure.", "main": "index.js", "scripts": { @@ -36,13 +36,13 @@ "dependencies": { "ent": "^2.2.0", "htmlparser2": "^3.8.3", - "ramda": "^0.21.0", + "ramda": "^0.22.0", "underscore.string.fp": "^1.0.4" }, "devDependencies": { - "coveralls": "2.11.9", - "istanbul": "0.4.3", - "mocha": "2.4.5", + "coveralls": "^2.11.12", + "istanbul": "^0.4.4", + "mocha": "^3.0", "mocha-lcov-reporter": "1.2.0", "react": "^15.0", "react-dom": "^15.0" From 5083c5c79ec65b8f0640e8689239a6b797a01e63 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Mon, 15 Aug 2016 16:38:36 +0200 Subject: [PATCH 024/227] Fix attribute name mapping --- lib/camel-case-attribute-names.js | 7 +++++-- lib/process-node-definitions.js | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/camel-case-attribute-names.js b/lib/camel-case-attribute-names.js index 09bd0a4..d98ec5f 100644 --- a/lib/camel-case-attribute-names.js +++ b/lib/camel-case-attribute-names.js @@ -67,9 +67,12 @@ var SVG_ATTRIBUTES = [ var camelCaseMap = HTML_ATTRIBUTES .concat(NON_STANDARD_ATTRIBUTES) .concat(SVG_ATTRIBUTES) - .filter(function (attr) { + .reduce(function (soFar, attr) { var lower = attr.toLowerCase(); - return lower !== attr; + if (lower !== attr) { + soFar[lower] = attr; + } + return soFar; }, {}); module.exports = camelCaseMap; diff --git a/lib/process-node-definitions.js b/lib/process-node-definitions.js index 13d1645..074d9ce 100644 --- a/lib/process-node-definitions.js +++ b/lib/process-node-definitions.js @@ -51,7 +51,7 @@ var ProcessNodeDefinitions = function(React) { } else if (key === 'class') { key = 'className'; } else if (camelCaseAttrMap[key]) { - key = camelCaseAttrMap[key] = value; + key = camelCaseAttrMap[key]; } return [key, value,]; From fab3a9407e6b7a5524c79bc77afb542ec962ae8e Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 18 Sep 2016 10:32:38 +0100 Subject: [PATCH 025/227] Add eslint config --- .eslintrc.yaml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .eslintrc.yaml diff --git a/.eslintrc.yaml b/.eslintrc.yaml new file mode 100644 index 0000000..8ad815a --- /dev/null +++ b/.eslintrc.yaml @@ -0,0 +1,20 @@ +--- +env: + browser: true + es6: true + commonjs: true +globals: +rules: + strict: + - 2 + - "global" + comma-dangle: + - 2 + - "always" + quotes: + - 2 + - "single" + - + allowTemplateLiterals: true + new-cap: 0 + camelcase: 0 From 0fd58781135c272198363cd9a932dbf2f3f7d5c9 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 18 Sep 2016 10:57:16 +0100 Subject: [PATCH 026/227] Add test --- test/html-to-react-tests.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index 8cebfd2..0c13e7c 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -141,6 +141,14 @@ describe('Html2React', function() { assert.equal(reactHtml, htmlInput); }); + + it('should not generate children for childless elements', function () { + var htmlInput = '
'; + + var reactComponent = parser.parse(htmlInput); + + assert.strictEqual((reactComponent.props.children || []).length, 0); + }); }); describe('parse invalid HTML', function() { @@ -255,6 +263,23 @@ describe('Html2React', function() { assert.deepStrictEqual(keys, ['0', '1', ]); }); + it('should not generate children when there are none', function () { + var htmlInput = ''; + var htmlExpected = ''; + + var isValidNode = function() { + return true; + }; + + var processingInstructions = [{ + shouldProcessNode: function(node) { return true; }, + processNode: processNodeDefinitions.processDefaultNode + }]; + var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, processingInstructions); + var reactHtml = React.renderToStaticMarkup(reactComponent); + assert.equal(reactHtml, htmlExpected); + }); + it('should return false in case of invalid node', function() { var htmlInput = '

'; var processingInstructions = [{ From b2a53789a0b140196f6dccdd49359825cc2d3fce Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 18 Sep 2016 10:58:11 +0100 Subject: [PATCH 027/227] Remove extra test --- test/html-to-react-tests.js | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index 0c13e7c..d66aba0 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -263,23 +263,6 @@ describe('Html2React', function() { assert.deepStrictEqual(keys, ['0', '1', ]); }); - it('should not generate children when there are none', function () { - var htmlInput = ''; - var htmlExpected = ''; - - var isValidNode = function() { - return true; - }; - - var processingInstructions = [{ - shouldProcessNode: function(node) { return true; }, - processNode: processNodeDefinitions.processDefaultNode - }]; - var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, processingInstructions); - var reactHtml = React.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, htmlExpected); - }); - it('should return false in case of invalid node', function() { var htmlInput = '

'; var processingInstructions = [{ From 5423ede17d8225c291719d8c5b3ac70473923c67 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 18 Sep 2016 12:34:11 +0100 Subject: [PATCH 028/227] Test with Node v4.5.0 --- circle.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 circle.yml diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..3331fbb --- /dev/null +++ b/circle.yml @@ -0,0 +1,3 @@ +machine: + node: + version: 4.5.0 From 19b0ba3de61e86865cd5eeb7a964bc0e1a56f8b4 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 18 Sep 2016 13:34:10 +0100 Subject: [PATCH 029/227] Move to own repository --- README.md | 7 ++++++- package.json | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4bd6578..502f638 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,9 @@ -# html-to-react [![Build Status](https://travis-ci.org/mikenikles/html-to-react.svg?branch=master)](https://travis-ci.org/mikenikles/html-to-react) [![npm version](https://badge.fury.io/js/html-to-react.svg)](http://badge.fury.io/js/html-to-react) [![Dependency Status](https://david-dm.org/mikenikles/html-to-react.svg)](https://david-dm.org/mikenikles/html-to-react) [![Coverage Status](https://coveralls.io/repos/mikenikles/html-to-react/badge.svg?branch=master)](https://coveralls.io/r/mikenikles/html-to-react?branch=master) +# html-to-react +[![Build Status](https://travis-ci.org/aknuds1/html-to-react.svg?branch=master)](https://travis-ci.org/aknuds1/html-to-react) +[![npm version](https://badge.fury.io/js/html-to-react.svg)](http://badge.fury.io/js/html-to-react) +[![Dependency Status](https://david-dm.org/aknuds1/html-to-react.svg)](https://david-dm.org/aknuds1/html-to-react) +[![Coverage Status](https://coveralls.io/repos/aknuds1/html-to-react/badge.svg?branch=master)](https://coveralls.io/r/aknuds1/html-to-react?branch=master) + A lightweight library that converts raw HTML to a React DOM structure. ## Why? diff --git a/package.json b/package.json index fffb923..ed88c04 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/mikenikles/html-to-react.git" + "url": "https://github.com/aknuds1/html-to-react.git" }, "keywords": [ "react", @@ -21,7 +21,7 @@ "bugs": { "url": "https://github.com/mikenikles/html-to-react/issues" }, - "homepage": "https://github.com/mikenikles/html-to-react", + "homepage": "https://github.com/aknuds1/html-to-react", "config": { "blanket": { "pattern": [ From 350413e8bc41ae20e75e7c00884543c644e847ce Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 18 Sep 2016 13:36:24 +0100 Subject: [PATCH 030/227] Remove Circle CI config --- circle.yml | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 circle.yml diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 3331fbb..0000000 --- a/circle.yml +++ /dev/null @@ -1,3 +0,0 @@ -machine: - node: - version: 4.5.0 From 683187af188f9a8d4682d88a4a6ffeb2f36be256 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 18 Sep 2016 13:36:56 +0100 Subject: [PATCH 031/227] Add author --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ed88c04..4330238 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "react-component", "html" ], - "author": "Mike Nikles", + "author": "Arve Knudsen, Mike Nikles", "license": "MIT", "bugs": { "url": "https://github.com/mikenikles/html-to-react/issues" From 44e26e4384eea0d9e97e7160cf9c22ca2a071709 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 18 Sep 2016 13:51:38 +0100 Subject: [PATCH 032/227] Configure eslint --- .eslintrc.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 8ad815a..73c8013 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -1,10 +1,10 @@ --- env: browser: true - es6: true commonjs: true globals: rules: + semi: 2 strict: - 2 - "global" From 633051d7896c947741907ebaa8b0a6e4e199707e Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 18 Sep 2016 13:52:20 +0100 Subject: [PATCH 033/227] Use modularized Ramda --- lib/parser.js | 11 +++++++---- lib/process-node-definitions.js | 22 ++++++++++++++-------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/lib/parser.js b/lib/parser.js index 87aa8ae..7df054c 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -1,5 +1,8 @@ 'use strict'; -var R = require('ramda'); +var find = require('ramda/src/find'); +var reject = require('ramda/src/reject'); +var addIndex = require('ramda/src/addIndex'); +var map = require('ramda/src/map'); var htmlParser = require('htmlparser2'); var ProcessingInstructions = require('./processing-instructions'); var IsValidNodeDefinitions = require('./is-valid-node-definitions'); @@ -14,12 +17,12 @@ var Html2React = function(React, options) { var traverseDom = function(node, isValidNode, processingInstructions, index) { if (isValidNode(node)) { - var processingInstruction = R.find(function (processingInstruction) { + var processingInstruction = find(function (processingInstruction) { return processingInstruction.shouldProcessNode(node); }, processingInstructions); if (processingInstruction != null) { - var children = R.reject((x) => {return x == null}, - R.addIndex(R.map)(function (child, i) { + var children = reject((x) => {return x == null;}, + addIndex(map)(function (child, i) { return traverseDom(child, isValidNode, processingInstructions, i); }, node.children || [])); return processingInstruction.processNode(node, children, index); diff --git a/lib/process-node-definitions.js b/lib/process-node-definitions.js index 4b922e1..f24b191 100644 --- a/lib/process-node-definitions.js +++ b/lib/process-node-definitions.js @@ -1,6 +1,12 @@ 'use strict'; -var R = require('ramda'); +var isEmpty = require('ramda/src/isEmpty'); +var merge = require('ramda/src/merge'); +var fromPairs = require('ramda/src/fromPairs'); +var map = require('ramda/src/map'); +var toPairs = require('ramda/src/toPairs'); +var contains = require('ramda/src/contains'); +var concat = require('ramda/src/concat'); var S = require('underscore.string.fp'); var ent = require('ent'); var camelCaseAttrMap = require('./camel-case-attribute-names'); @@ -42,8 +48,8 @@ var ProcessNodeDefinitions = function(React) { key: index, }; // Process attributes - if (!R.isEmpty(node.attribs)) { - elementProps = R.merge(elementProps, R.fromPairs(R.map(function (keyAndValue) { + if (!isEmpty(node.attribs)) { + elementProps = merge(elementProps, fromPairs(map(function (keyAndValue) { var key = keyAndValue[0]; var value = keyAndValue[1]; if (key === 'style') { @@ -61,14 +67,14 @@ var ProcessNodeDefinitions = function(React) { } return [key, value,]; - }, R.toPairs(node.attribs)))); + }, toPairs(node.attribs)))); } - if (R.contains(node.name, voidElementTags)) { - return React.createElement(node.name, elementProps) + if (contains(node.name, voidElementTags)) { + return React.createElement(node.name, elementProps); } else { - var allChildren = node.data != null ? R.concat([node.data,], children) : children; - return React.createElement.apply(null, R.concat([node.name, elementProps,], + var allChildren = node.data != null ? concat([node.data,], children) : children; + return React.createElement.apply(null, concat([node.name, elementProps,], allChildren)); } } From 4d85858f2834c1fe0665221291b3c3d29878c82c Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 18 Sep 2016 14:09:43 +0100 Subject: [PATCH 034/227] Import React as a module, make it a peer dependency --- lib/parser.js | 4 ++-- lib/process-node-definitions.js | 3 ++- lib/processing-instructions.js | 4 ++-- package.json | 4 +++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/parser.js b/lib/parser.js index 7df054c..8af5d6d 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -7,7 +7,7 @@ var htmlParser = require('htmlparser2'); var ProcessingInstructions = require('./processing-instructions'); var IsValidNodeDefinitions = require('./is-valid-node-definitions'); -var Html2React = function(React, options) { +var Html2React = function(options) { var parseHtmlToTree = function(html) { var handler = new htmlParser.DomHandler(); var parser = new htmlParser.Parser(handler, options); @@ -48,7 +48,7 @@ var Html2React = function(React, options) { }; var parse = function(html) { - var processingInstructions = new ProcessingInstructions(React); + var processingInstructions = new ProcessingInstructions(); return parseWithInstructions(html, IsValidNodeDefinitions.alwaysValid, processingInstructions.defaultProcessingInstructions); diff --git a/lib/process-node-definitions.js b/lib/process-node-definitions.js index f24b191..98eaa45 100644 --- a/lib/process-node-definitions.js +++ b/lib/process-node-definitions.js @@ -10,6 +10,7 @@ var concat = require('ramda/src/concat'); var S = require('underscore.string.fp'); var ent = require('ent'); var camelCaseAttrMap = require('./camel-case-attribute-names'); +var React = require('react'); // https://github.com/facebook/react/blob/15.0-stable/src/renderers/dom/shared/ReactDOMComponent.js#L457 var voidElementTags = [ @@ -34,7 +35,7 @@ function createStyleJsonFromString(styleString) { return jsonStyles; } -var ProcessNodeDefinitions = function(React) { +var ProcessNodeDefinitions = function() { function processDefaultNode(node, children, index) { if (node.type === 'text') { return ent.decode(node.data); diff --git a/lib/processing-instructions.js b/lib/processing-instructions.js index 9617b6e..89da930 100644 --- a/lib/processing-instructions.js +++ b/lib/processing-instructions.js @@ -3,8 +3,8 @@ var ShouldProcessNodeDefinitions = require('./should-process-node-definitions'); var ProcessNodeDefinitions = require('./process-node-definitions'); -var ProcessingInstructions = function(React) { - var processNodeDefinitions = new ProcessNodeDefinitions(React); +var ProcessingInstructions = function() { + var processNodeDefinitions = new ProcessNodeDefinitions(); return { defaultProcessingInstructions: [{ diff --git a/package.json b/package.json index 4330238..e0adf11 100644 --- a/package.json +++ b/package.json @@ -39,12 +39,14 @@ "ramda": "^0.22.0", "underscore.string.fp": "^1.0.4" }, + "peerDependencies": { + "react": "^15.0" + }, "devDependencies": { "coveralls": "^2.11.12", "istanbul": "^0.4.4", "mocha": "^3.0", "mocha-lcov-reporter": "1.2.0", - "react": "^15.0", "react-dom": "^15.0" } } From 79c9e2b5b45d0d17650c82d34ebe66658e3339db Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 18 Sep 2016 14:13:55 +0100 Subject: [PATCH 035/227] Make React explicit dev dep --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index e0adf11..44cbe5f 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "istanbul": "^0.4.4", "mocha": "^3.0", "mocha-lcov-reporter": "1.2.0", + "react": "^15.0", "react-dom": "^15.0" } } From 1de405dc977de28d2dcc111ac52b9993c69ebaa9 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 18 Sep 2016 14:27:13 +0100 Subject: [PATCH 036/227] Add editorconfig --- .editorconfig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.editorconfig b/.editorconfig index 2384269..7250816 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,10 +1,8 @@ root = true -[*] +[*.js] indent_style = space end_of_line = lf insert_final_newline = true max_line_length = 100 - -[*.js] indent_size = 4 From 866f54a45a773a37bdfa4a910862d7974f92237f Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 18 Sep 2016 14:27:22 +0100 Subject: [PATCH 037/227] Stylistics --- test/html-to-react-tests.js | 40 ++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index 8489e9f..286bad0 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -2,7 +2,7 @@ var assert = require('assert'); var React = require('react'); -var ReactDOMServer = require('react-dom/server') +var ReactDOMServer = require('react-dom/server'); var R = require('ramda'); var Parser = require('../index').Parser; @@ -121,7 +121,7 @@ describe('Html2React', function() { var reactComponent = parser.parse(htmlInput); assert.strictEqual((reactComponent.props.children || []).length, 0); - }); + }); it('should parse void elements with all attributes and no warnings', function() { var htmlInput = '

'; @@ -148,11 +148,11 @@ describe('Html2React', function() { var reactComponent = parser.parse(htmlInput); var children = R.filter(function (c) { - return R.has('key', c); + return R.has('key', c); }, R.flatten(reactComponent.props.children)); var keys = R.map(function (child) { - return child.key; - }, children); + return child.key; + }, children); assert.deepStrictEqual(keys, ['0', '1', ]); }); @@ -194,7 +194,7 @@ describe('Html2React', function() { it('should fill in the key name with boolean attribute', function() { var htmlInput = ''; - var htmlExpected = '' + var htmlExpected = ''; var reactComponent = parser.parse(htmlInput); var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); @@ -212,7 +212,8 @@ describe('Html2React', function() { }, Error); }); - it('should throw an error with a specific message when parsing multiple root elements', function() { + it('should throw an error with a specific message when parsing multiple root elements', + function() { var htmlInput = '
'; assert.throws(function() { @@ -247,13 +248,15 @@ describe('Html2React', function() { }, processNode: processNodeDefinitions.processDefaultNode, },]; - var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, processingInstructions); + var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, + processingInstructions); // With only 1

element, nothing is rendered assert.equal(reactComponent, false); }); - it('should return a single

element within a div of

and

as siblings', function() { + it('should return a single

element within a div of

and

as siblings', + function() { var htmlInput = '

Title

Paragraph

'; var htmlExpected = '

Title

'; @@ -267,14 +270,17 @@ describe('Html2React', function() { }, processNode: processNodeDefinitions.processDefaultNode, },]; - var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, processingInstructions); + var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, + processingInstructions); var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); assert.equal(reactHtml, htmlExpected); }); it('should return capitalized content for all

elements', function() { - var htmlInput = '

Title

Paragraph

Another title

'; - var htmlExpected = '

TITLE

Paragraph

ANOTHER TITLE

'; + var htmlInput = '

Title

Paragraph

' + + '

Another title

'; + var htmlExpected = '

TITLE

Paragraph

' + + '

ANOTHER TITLE

'; var isValidNode = function() { return true; @@ -284,7 +290,8 @@ describe('Html2React', function() { { // Custom

processing shouldProcessNode: function(node) { - return node.parent && node.parent.name && node.parent.name === 'h1'; + return node.parent && node.parent.name && + node.parent.name === 'h1'; }, processNode: function(node, children) { return node.data.toUpperCase(); @@ -295,8 +302,9 @@ describe('Html2React', function() { return true; }, processNode: processNodeDefinitions.processDefaultNode, - },]; - var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, processingInstructions); + },]; + var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, + processingInstructions); var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); assert.equal(reactHtml, htmlExpected); }); @@ -308,7 +316,7 @@ describe('Html2React', function() { processNode: processNodeDefinitions.processDefaultNode, }, ]; var reactComponent = parser.parseWithInstructions(htmlInput, - function () { return false }, processingInstructions); + function () { return false; }, processingInstructions); assert.equal(reactComponent, false); }); From 13cf0630be32f2abd667ef2e30e6e7483396d34d Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 18 Sep 2016 14:29:14 +0100 Subject: [PATCH 038/227] Stylistics --- lib/camel-case-attribute-names.js | 11 +++++++---- lib/is-valid-node-definitions.js | 2 +- lib/process-node-definitions.js | 5 +++-- lib/should-process-node-definitions.js | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/camel-case-attribute-names.js b/lib/camel-case-attribute-names.js index d98ec5f..488801b 100644 --- a/lib/camel-case-attribute-names.js +++ b/lib/camel-case-attribute-names.js @@ -1,6 +1,7 @@ -// These are all sourced from https://facebook.github.io/react/docs/tags-and-attributes.html - all attributes regardless -// of whether they have a different case to their HTML equivalents are listed to reduce the chance of human error -// and make it easier to just copy-paste the new list if it changes. +// These are all sourced from https://facebook.github.io/react/docs/tags-and-attributes.html - +// all attributes regardless of whether they have a different case to their HTML equivalents are +// listed to reduce the chance of human error and make it easier to just copy-paste the new list +// if it changes. 'use strict'; var HTML_ATTRIBUTES = [ 'accept', 'acceptCharset', 'accessKey', 'action', 'allowFullScreen', 'allowTransparency', @@ -9,7 +10,9 @@ var HTML_ATTRIBUTES = [ 'colSpan', 'cols', 'content', 'contentEditable', 'contextMenu', 'controls', 'coords', 'crossOrigin', 'data', 'dateTime', 'default', 'defer', 'dir', 'disabled', 'download', 'draggable', 'encType', 'form', 'formAction', 'formEncType', 'formMethod', - 'formNoValidate', 'formTarget', 'frameBorder', 'headers', 'height', 'hidden', 'high', 'href', 'hrefLang', 'htmlFor', 'httpEquiv', 'icon', 'id', 'inputMode', 'integrity', 'is', 'keyParams', 'keyType', 'kind', 'label', 'lang', 'list', 'loop', 'low', 'manifest', 'marginHeight', + 'formNoValidate', 'formTarget', 'frameBorder', 'headers', 'height', 'hidden', 'high', 'href', + 'hrefLang', 'htmlFor', 'httpEquiv', 'icon', 'id', 'inputMode', 'integrity', 'is', 'keyParams', + 'keyType', 'kind', 'label', 'lang', 'list', 'loop', 'low', 'manifest', 'marginHeight', 'marginWidth', 'max', 'maxLength', 'media', 'mediaGroup', 'method', 'min', 'minLength', 'multiple', 'muted', 'name', 'noValidate', 'nonce', 'open', 'optimum', 'pattern', 'placeholder', 'poster', 'preload', 'profile', 'radioGroup', 'readOnly', 'rel', diff --git a/lib/is-valid-node-definitions.js b/lib/is-valid-node-definitions.js index e7add9a..1ffb40d 100644 --- a/lib/is-valid-node-definitions.js +++ b/lib/is-valid-node-definitions.js @@ -5,5 +5,5 @@ function alwaysValid() { } module.exports = { - alwaysValid: alwaysValid + alwaysValid: alwaysValid, }; diff --git a/lib/process-node-definitions.js b/lib/process-node-definitions.js index 98eaa45..730adc0 100644 --- a/lib/process-node-definitions.js +++ b/lib/process-node-definitions.js @@ -40,8 +40,9 @@ var ProcessNodeDefinitions = function() { if (node.type === 'text') { return ent.decode(node.data); } else if (node.type === 'comment') { - // FIXME: The following doesn't work as the generated HTML results in "<!-- This is a comment -->" - //return ''; + // FIXME: The following doesn't work as the generated HTML results in + // "<!-- This is a comment -->" + // return ''; return false; } diff --git a/lib/should-process-node-definitions.js b/lib/should-process-node-definitions.js index 981dd26..7b30948 100644 --- a/lib/should-process-node-definitions.js +++ b/lib/should-process-node-definitions.js @@ -5,5 +5,5 @@ function shouldProcessEveryNode(node) { } module.exports = { - shouldProcessEveryNode: shouldProcessEveryNode + shouldProcessEveryNode: shouldProcessEveryNode, }; From 466910d284eb426504b70b624395ac82eda601fc Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 18 Sep 2016 14:42:39 +0100 Subject: [PATCH 039/227] Stylistics --- lib/parser.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/parser.js b/lib/parser.js index 8af5d6d..34d4a78 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -22,9 +22,9 @@ var Html2React = function(options) { }, processingInstructions); if (processingInstruction != null) { var children = reject((x) => {return x == null;}, - addIndex(map)(function (child, i) { - return traverseDom(child, isValidNode, processingInstructions, i); - }, node.children || [])); + addIndex(map)(function (child, i) { + return traverseDom(child, isValidNode, processingInstructions, i); + }, node.children || [])); return processingInstruction.processNode(node, children, index); } else { return false; From d7bdf738c60ffba569bb184b1251f244729fbdbf Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 18 Sep 2016 18:14:24 +0100 Subject: [PATCH 040/227] Make separate command for testing with coverage --- .travis.yml | 1 + package.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1f238a3..c4732c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,3 +3,4 @@ node_js: - 4 - 5 - 6 +script: npm run test-coverage diff --git a/package.json b/package.json index 44cbe5f..ab03bc5 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "A lightweight library that converts raw HTML to a React DOM structure.", "main": "index.js", "scripts": { - "test": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage", + "test": "./node_modules/mocha/bin/mocha", + "test-coverage": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage", "test-html-coverage": "istanbul cover ./node_modules/mocha/bin/_mocha; open coverage/lcov-report/index.html" }, "repository": { From 85dc6ead0a79f1a2d79f31e19e49bfc4c782196f Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Fri, 23 Sep 2016 14:10:25 +0200 Subject: [PATCH 041/227] Modularize usage of underscore.string.fp --- lib/process-node-definitions.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/process-node-definitions.js b/lib/process-node-definitions.js index 730adc0..0b76101 100644 --- a/lib/process-node-definitions.js +++ b/lib/process-node-definitions.js @@ -7,7 +7,8 @@ var map = require('ramda/src/map'); var toPairs = require('ramda/src/toPairs'); var contains = require('ramda/src/contains'); var concat = require('ramda/src/concat'); -var S = require('underscore.string.fp'); +var camelize = require('underscore.string.fp/camelize'); +var isBlank = require('underscore.string.fp/isBlank'); var ent = require('ent'); var camelCaseAttrMap = require('./camel-case-attribute-names'); var React = require('react'); @@ -19,14 +20,14 @@ var voidElementTags = [ ]; function createStyleJsonFromString(styleString) { - if (S.isBlank(styleString)) { + if (isBlank(styleString)) { return {}; } var styles = styleString.split(';'); var singleStyle, key, value, jsonStyles = {}; for (var i = 0; i < styles.length; i++) { singleStyle = styles[i].split(':'); - key = S.camelize(singleStyle[0]); + key = camelize(singleStyle[0]); value = singleStyle[1]; if (key.length > 0 && value.length > 0) { jsonStyles[key] = value; @@ -62,7 +63,7 @@ var ProcessNodeDefinitions = function() { key = camelCaseAttrMap[key]; } - if (S.isBlank(value)) { + if (isBlank(value)) { // Handle boolean attributes - if their value isn't explicitly supplied, // define it as the attribute name (e.g. disabled="disabled") value = key; From f5092351bcdb2d125b028822eb6d0b247100b0e2 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Fri, 23 Sep 2016 14:10:25 +0200 Subject: [PATCH 042/227] Modularize usage of underscore.string.fp --- lib/camel-case-attribute-names.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/camel-case-attribute-names.js b/lib/camel-case-attribute-names.js index 488801b..f158e83 100644 --- a/lib/camel-case-attribute-names.js +++ b/lib/camel-case-attribute-names.js @@ -1,7 +1,7 @@ // These are all sourced from https://facebook.github.io/react/docs/tags-and-attributes.html - // all attributes regardless of whether they have a different case to their HTML equivalents are -// listed to reduce the chance of human error and make it easier to just copy-paste the new list -// if it changes. +// listed to reduce the chance of human error and make it easier to just copy-paste the new list if +// it changes. 'use strict'; var HTML_ATTRIBUTES = [ 'accept', 'acceptCharset', 'accessKey', 'action', 'allowFullScreen', 'allowTransparency', @@ -9,17 +9,17 @@ var HTML_ATTRIBUTES = [ 'cellSpacing', 'challenge', 'charSet', 'checked', 'cite', 'classID', 'className', 'colSpan', 'cols', 'content', 'contentEditable', 'contextMenu', 'controls', 'coords', 'crossOrigin', 'data', 'dateTime', 'default', 'defer', 'dir', 'disabled', 'download', - 'draggable', 'encType', 'form', 'formAction', 'formEncType', 'formMethod', - 'formNoValidate', 'formTarget', 'frameBorder', 'headers', 'height', 'hidden', 'high', 'href', - 'hrefLang', 'htmlFor', 'httpEquiv', 'icon', 'id', 'inputMode', 'integrity', 'is', 'keyParams', - 'keyType', 'kind', 'label', 'lang', 'list', 'loop', 'low', 'manifest', 'marginHeight', - 'marginWidth', 'max', 'maxLength', 'media', 'mediaGroup', 'method', 'min', 'minLength', - 'multiple', 'muted', 'name', 'noValidate', 'nonce', 'open', 'optimum', 'pattern', - 'placeholder', 'poster', 'preload', 'profile', 'radioGroup', 'readOnly', 'rel', - 'required', 'reversed', 'role', 'rowSpan', 'rows', 'sandbox', 'scope', 'scoped', 'scrolling', - 'seamless', 'selected', 'shape', 'size', 'sizes', 'span', 'spellCheck', 'src', 'srcDoc', - 'srcLang', 'srcSet', 'start', 'step', 'style', 'summary', 'tabIndex', 'target', 'title', - 'type', 'useMap', 'value', 'width', 'wmode', 'wrap', + 'draggable', 'encType', 'form', 'formAction', 'formEncType', 'formMethod', 'formNoValidate', + 'formTarget', 'frameBorder', 'headers', 'height', 'hidden', 'high', 'href', 'hrefLang', + 'htmlFor', 'httpEquiv', 'icon', 'id', 'inputMode', 'integrity', 'is', 'keyParams', 'keyType', + 'kind', 'label', 'lang', 'list', 'loop', 'low', 'manifest', 'marginHeight', 'marginWidth', + 'max', 'maxLength', 'media', 'mediaGroup', 'method', 'min', 'minLength', 'multiple', 'muted', + 'name', 'noValidate', 'nonce', 'open', 'optimum', 'pattern', 'placeholder', 'poster', + 'preload', 'profile', 'radioGroup', 'readOnly', 'rel', 'required', 'reversed', 'role', + 'rowSpan', 'rows', 'sandbox', 'scope', 'scoped', 'scrolling', 'seamless', 'selected', + 'shape', 'size', 'sizes', 'span', 'spellCheck', 'src', 'srcDoc', 'srcLang', 'srcSet', 'start', + 'step', 'style', 'summary', 'tabIndex', 'target', 'title', 'type', 'useMap', 'value', 'width', + 'wmode', 'wrap', ]; var NON_STANDARD_ATTRIBUTES = [ From be538ef8ef69f773db9139c2f32cf62afd86d5d5 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 25 Sep 2016 20:53:58 +0200 Subject: [PATCH 043/227] Update package.json --- package.json | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 664793b..2a96404 100644 --- a/package.json +++ b/package.json @@ -1,27 +1,28 @@ { "name": "html-to-react", - "version": "1.0.0", + "version": "1.0.1", "description": "A lightweight library that converts raw HTML to a React DOM structure.", "main": "index.js", "scripts": { - "test": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage", + "test": "./node_modules/mocha/bin/mocha", + "test-coverage": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage", "test-html-coverage": "istanbul cover ./node_modules/mocha/bin/_mocha; open coverage/lcov-report/index.html" }, "repository": { "type": "git", - "url": "https://github.com/mikenikles/html-to-react.git" + "url": "https://github.com/aknuds1/html-to-react.git" }, "keywords": [ "react", "react-component", "html" ], - "author": "Mike Nikles", + "author": "Arve Knudsen, Mike Nikles", "license": "MIT", "bugs": { - "url": "https://github.com/mikenikles/html-to-react/issues" + "url": "https://github.com/aknuds1/html-to-react/issues" }, - "homepage": "https://github.com/mikenikles/html-to-react", + "homepage": "https://github.com/aknuds1/html-to-react", "config": { "blanket": { "pattern": [ @@ -44,12 +45,15 @@ "lodash.map": "^4.6.0", "lodash.merge": "^4.6.0" }, + "peerDependencies": { + "react": "^15.0" + }, "devDependencies": { - "coveralls": "2.11.9", - "istanbul": "0.4.3", + "coveralls": "^2.11", + "istanbul": "^0.4", "lodash": "^4.16.1", - "mocha": "2.4.5", - "mocha-lcov-reporter": "1.2.0", + "mocha": "^3.0", + "mocha-lcov-reporter": "^1.2.0", "react": "^15.0", "react-dom": "^15.0" } From dd67a4273787c0e16facd5ca8477d8bf6463cb1b Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 25 Sep 2016 20:57:31 +0200 Subject: [PATCH 044/227] Update .editorconfig --- .editorconfig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.editorconfig b/.editorconfig index 2384269..7250816 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,10 +1,8 @@ root = true -[*] +[*.js] indent_style = space end_of_line = lf insert_final_newline = true max_line_length = 100 - -[*.js] indent_size = 4 From 995c948034bc84ae807ff847b05aea628c23d92f Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 25 Sep 2016 20:58:42 +0200 Subject: [PATCH 045/227] Add .eslintrc.yaml, update .travis.yml --- .eslintrc.yaml | 20 ++++++++++++++++++++ .travis.yml | 1 + 2 files changed, 21 insertions(+) create mode 100644 .eslintrc.yaml diff --git a/.eslintrc.yaml b/.eslintrc.yaml new file mode 100644 index 0000000..73c8013 --- /dev/null +++ b/.eslintrc.yaml @@ -0,0 +1,20 @@ +--- +env: + browser: true + commonjs: true +globals: +rules: + semi: 2 + strict: + - 2 + - "global" + comma-dangle: + - 2 + - "always" + quotes: + - 2 + - "single" + - + allowTemplateLiterals: true + new-cap: 0 + camelcase: 0 diff --git a/.travis.yml b/.travis.yml index 1f238a3..c4732c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,3 +3,4 @@ node_js: - 4 - 5 - 6 +script: npm run test-coverage From ea60245b017958f3808f12b13debc8f4085079ee Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 25 Sep 2016 20:59:21 +0200 Subject: [PATCH 046/227] Update README --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6d0103c..502f638 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,9 @@ -# html-to-react [![Build Status](https://travis-ci.org/mikenikles/html-to-react.svg?branch=master)](https://travis-ci.org/mikenikles/html-to-react) [![npm version](https://badge.fury.io/js/html-to-react.svg)](http://badge.fury.io/js/html-to-react) [![Dependency Status](https://david-dm.org/mikenikles/html-to-react.svg)](https://david-dm.org/mikenikles/html-to-react) [![Coverage Status](https://coveralls.io/repos/mikenikles/html-to-react/badge.svg?branch=master)](https://coveralls.io/r/mikenikles/html-to-react?branch=master) +# html-to-react +[![Build Status](https://travis-ci.org/aknuds1/html-to-react.svg?branch=master)](https://travis-ci.org/aknuds1/html-to-react) +[![npm version](https://badge.fury.io/js/html-to-react.svg)](http://badge.fury.io/js/html-to-react) +[![Dependency Status](https://david-dm.org/aknuds1/html-to-react.svg)](https://david-dm.org/aknuds1/html-to-react) +[![Coverage Status](https://coveralls.io/repos/aknuds1/html-to-react/badge.svg?branch=master)](https://coveralls.io/r/aknuds1/html-to-react?branch=master) + A lightweight library that converts raw HTML to a React DOM structure. ## Why? @@ -93,6 +98,6 @@ assert.equal(reactHtml, htmlExpected); ## Tests & Coverage -`$ npm run test-locally` +`$ npm run test` `$ npm run test-html-coverage` From 3ec4e4a0d2739f897961bfb58fde633cb51ae39c Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 25 Sep 2016 21:00:45 +0200 Subject: [PATCH 047/227] Import react directly --- lib/is-valid-node-definitions.js | 2 +- lib/parser.js | 4 +- lib/process-node-definitions.js | 5 +- lib/processing-instructions.js | 4 +- lib/should-process-node-definitions.js | 2 +- package.json | 2 +- test/html-to-react-tests.js | 75 +++++++++++++++++--------- 7 files changed, 60 insertions(+), 34 deletions(-) diff --git a/lib/is-valid-node-definitions.js b/lib/is-valid-node-definitions.js index e7add9a..1ffb40d 100644 --- a/lib/is-valid-node-definitions.js +++ b/lib/is-valid-node-definitions.js @@ -5,5 +5,5 @@ function alwaysValid() { } module.exports = { - alwaysValid: alwaysValid + alwaysValid: alwaysValid, }; diff --git a/lib/parser.js b/lib/parser.js index 2f87714..eb4facc 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -5,7 +5,7 @@ var htmlParser = require('htmlparser2'); var ProcessingInstructions = require('./processing-instructions'); var IsValidNodeDefinitions = require('./is-valid-node-definitions'); -var Html2React = function(React, options) { +var Html2React = function(options) { var parseHtmlToTree = function(html) { var handler = new htmlParser.DomHandler(); var parser = new htmlParser.Parser(handler, options); @@ -48,7 +48,7 @@ var Html2React = function(React, options) { }; var parse = function(html) { - var processingInstructions = new ProcessingInstructions(React); + var processingInstructions = new ProcessingInstructions(); return parseWithInstructions(html, IsValidNodeDefinitions.alwaysValid, processingInstructions.defaultProcessingInstructions); diff --git a/lib/process-node-definitions.js b/lib/process-node-definitions.js index af94c77..dbb7a74 100644 --- a/lib/process-node-definitions.js +++ b/lib/process-node-definitions.js @@ -8,6 +8,7 @@ var includes = require('lodash.includes'); var merge = require('lodash.merge'); var ent = require('ent'); var camelCaseAttrMap = require('./camel-case-attribute-names'); +var React = require('react'); // https://github.com/facebook/react/blob/15.0-stable/src/renderers/dom/shared/ReactDOMComponent.js#L457 var voidElementTags = [ @@ -32,7 +33,7 @@ function createStyleJsonFromString(styleString) { return jsonStyles; } -var ProcessNodeDefinitions = function(React) { +var ProcessNodeDefinitions = function() { function processDefaultNode(node, children, index) { if (node.type === 'text') { return ent.decode(node.data); @@ -61,7 +62,7 @@ var ProcessNodeDefinitions = function(React) { } if (includes(voidElementTags, node.name)) { - return React.createElement(node.name, elementProps) + return React.createElement(node.name, elementProps); } else { var allChildren = node.data != null ? [node.data,].concat(children) : children; return React.createElement.apply( diff --git a/lib/processing-instructions.js b/lib/processing-instructions.js index 9617b6e..89da930 100644 --- a/lib/processing-instructions.js +++ b/lib/processing-instructions.js @@ -3,8 +3,8 @@ var ShouldProcessNodeDefinitions = require('./should-process-node-definitions'); var ProcessNodeDefinitions = require('./process-node-definitions'); -var ProcessingInstructions = function(React) { - var processNodeDefinitions = new ProcessNodeDefinitions(React); +var ProcessingInstructions = function() { + var processNodeDefinitions = new ProcessNodeDefinitions(); return { defaultProcessingInstructions: [{ diff --git a/lib/should-process-node-definitions.js b/lib/should-process-node-definitions.js index 981dd26..7b30948 100644 --- a/lib/should-process-node-definitions.js +++ b/lib/should-process-node-definitions.js @@ -5,5 +5,5 @@ function shouldProcessEveryNode(node) { } module.exports = { - shouldProcessEveryNode: shouldProcessEveryNode + shouldProcessEveryNode: shouldProcessEveryNode, }; diff --git a/package.json b/package.json index 2a96404..195c8c2 100644 --- a/package.json +++ b/package.json @@ -51,9 +51,9 @@ "devDependencies": { "coveralls": "^2.11", "istanbul": "^0.4", - "lodash": "^4.16.1", "mocha": "^3.0", "mocha-lcov-reporter": "^1.2.0", + "ramda": "^0.22.1", "react": "^15.0", "react-dom": "^15.0" } diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index 43e7e8e..10c0dba 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -2,8 +2,8 @@ var assert = require('assert'); var React = require('react'); -var ReactDOMServer = require('react-dom/server') -var _ = require('lodash'); +var ReactDOMServer = require('react-dom/server'); +var R = require('ramda'); var Parser = require('../index').Parser; var ProcessNodeDefinitions = require('../index').ProcessNodeDefinitions; @@ -121,7 +121,7 @@ describe('Html2React', function() { var reactComponent = parser.parse(htmlInput); assert.strictEqual((reactComponent.props.children || []).length, 0); - }); + }); it('should parse void elements with all attributes and no warnings', function() { var htmlInput = '

'; @@ -142,6 +142,39 @@ describe('Html2React', function() { assert.equal(reactHtml, htmlInput); }); + it('should generate keys for sequence items', function () { + var htmlInput = '
  • Item 1
  • Item 2
  • <
'; + + var reactComponent = parser.parse(htmlInput); + + var children = R.filter(function (c) { + return R.has('key', c); + }, R.flatten(reactComponent.props.children)); + var keys = R.map(function (child) { + return child.key; + }, children); + assert.deepStrictEqual(keys, ['0', '1', ]); + }); + + it('should parse br elements without warnings', function() { + var htmlInput = '

Line one
Line two
Line three

'; + var htmlExpected = '

Line one
Line two
Line three

'; + + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + + assert.equal(reactHtml, htmlExpected); + }); + + it('should parse src elements with all attributes but without warnings', function() { + var htmlInput = '

'; + + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + + assert.equal(reactHtml, htmlInput); + }); + it('should decode character entities in text nodes', function () { var htmlInput = '
1 < 2
'; @@ -161,7 +194,7 @@ describe('Html2React', function() { it('should fill in the key name with boolean attribute', function() { var htmlInput = ''; - var htmlExpected = '' + var htmlExpected = ''; var reactComponent = parser.parse(htmlInput); var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); @@ -179,7 +212,8 @@ describe('Html2React', function() { }, Error); }); - it('should throw an error with a specific message when parsing multiple root elements', function() { + it('should throw an error with a specific message when parsing multiple root elements', + function() { var htmlInput = '
'; assert.throws(function() { @@ -214,13 +248,15 @@ describe('Html2React', function() { }, processNode: processNodeDefinitions.processDefaultNode, },]; - var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, processingInstructions); + var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, + processingInstructions); // With only 1

element, nothing is rendered assert.equal(reactComponent, false); }); - it('should return a single

element within a div of

and

as siblings', function() { + it('should return a single

element within a div of

and

as siblings', + function() { var htmlInput = '

Title

Paragraph

'; var htmlExpected = '

Title

'; @@ -234,7 +270,8 @@ describe('Html2React', function() { }, processNode: processNodeDefinitions.processDefaultNode, },]; - var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, processingInstructions); + var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, + processingInstructions); var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); assert.equal(reactHtml, htmlExpected); }); @@ -262,26 +299,14 @@ describe('Html2React', function() { return true; }, processNode: processNodeDefinitions.processDefaultNode, - },]; - var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, processingInstructions); + }, + ]; + var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, + processingInstructions); var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); assert.equal(reactHtml, htmlExpected); }); - it('should generate keys for sequence items', function () { - var htmlInput = '
  • Item 1
  • Item 2
  • <
'; - - var reactComponent = parser.parse(htmlInput); - - var children = _.filter(_.flatten(reactComponent.props.children), function (c) { - return _.has(c, 'key'); - }); - var keys = _.map(children, function (child) { - return child.key; - }); - assert.deepStrictEqual(keys, ['0', '1', ]); - }); - it('should return false in case of invalid node', function() { var htmlInput = '

'; var processingInstructions = [{ @@ -289,7 +314,7 @@ describe('Html2React', function() { processNode: processNodeDefinitions.processDefaultNode, }, ]; var reactComponent = parser.parseWithInstructions(htmlInput, - function () { return false }, processingInstructions); + function () { return false; }, processingInstructions); assert.equal(reactComponent, false); }); From 5300bba9b627ba9e51713713d23f5f4e47183614 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 25 Sep 2016 21:24:49 +0200 Subject: [PATCH 048/227] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 502f638..e311bdb 100644 --- a/README.md +++ b/README.md @@ -99,5 +99,5 @@ assert.equal(reactHtml, htmlExpected); ## Tests & Coverage `$ npm run test` - +`$ npm run test-coverage` `$ npm run test-html-coverage` From ed1ebabc40b9daaa490f79e4d9a75d3da4221ab9 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 25 Sep 2016 21:27:54 +0200 Subject: [PATCH 049/227] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 195c8c2..4b76e39 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "html-to-react", - "version": "1.0.1", + "version": "1.1.0", "description": "A lightweight library that converts raw HTML to a React DOM structure.", "main": "index.js", "scripts": { From 4a1f7a74e37fbc629b949fa46df053849ce2c639 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 25 Sep 2016 21:30:46 +0200 Subject: [PATCH 050/227] Fix README --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e311bdb..7815aad 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,8 @@ assert.equal(reactHtml, htmlExpected); ## Tests & Coverage -`$ npm run test` -`$ npm run test-coverage` -`$ npm run test-html-coverage` +Test locally: `$ npm run test` + +Test with coverage: `$ npm run test-coverage` + +Test with coverage and open HTML report `$ npm run test-html-coverage` From 2d1fb0afa5dd71a908e3eeb412a78a28ffbd9c40 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 25 Sep 2016 21:32:06 +0200 Subject: [PATCH 051/227] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ca0d51a..f003d6b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "html-to-react", - "version": "1.1.0", + "version": "1.1.1", "description": "A lightweight library that converts raw HTML to a React DOM structure.", "main": "index.js", "scripts": { From 1a7e3f4ea10e4e5b2375ca64534f1d646bee458e Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 25 Sep 2016 21:33:17 +0200 Subject: [PATCH 052/227] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4b76e39..4751693 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "html-to-react", - "version": "1.1.0", + "version": "1.1.2", "description": "A lightweight library that converts raw HTML to a React DOM structure.", "main": "index.js", "scripts": { From 859415b869a4015d2b6cc04ace0afebccd0d99a2 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Fri, 30 Sep 2016 14:25:34 +0200 Subject: [PATCH 053/227] Fix test section in README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7815aad..86ab7d3 100644 --- a/README.md +++ b/README.md @@ -98,8 +98,8 @@ assert.equal(reactHtml, htmlExpected); ## Tests & Coverage -Test locally: `$ npm run test` +Test locally: `$ npm test` -Test with coverage: `$ npm run test-coverage` +Test with coverage and report coverage to Coveralls: `$ npm run test-coverage` -Test with coverage and open HTML report `$ npm run test-html-coverage` +Test with coverage and open HTML report: `$ npm run test-html-coverage` From 9a8fe03494f13b929a6c83dff5d3f1c7badc1254 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 2 Oct 2016 09:39:10 +0200 Subject: [PATCH 054/227] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 125d89c..727d0e1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "html-to-react", - "version": "1.1.2", + "version": "1.2.0", "description": "A lightweight library that converts raw HTML to a React DOM structure.", "main": "index.js", "scripts": { From dfc13b9717c1a3eb820fb827b367b8b8b99c96b8 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 2 Oct 2016 09:47:24 +0200 Subject: [PATCH 055/227] Update Changelog --- CHANGELOG.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f777d8..58a74e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Change Log +## [v1.2.0](https://github.com/aknuds1/html-to-react/tree/v1.2.0) + +[Full Changelog](https://github.com/aknuds1/html-to-react/compare/v1.1.2...v1.2.0) + +**Merged pull requests:** + +- Replace element children [\#2]https://github.com/aknuds1/html-to-react/pull/3) ([hirefrederick](HireFrederick)) + +## [v1.1.2](https://github.com/aknuds1/html-to-react/tree/v1.1.2) + +[Full Changelog](https://github.com/aknuds1/html-to-react/compare/v1.1.1...v1.1.2) + +## [v1.1.1](https://github.com/aknuds1/html-to-react/tree/v1.1.1) + +[Full Changelog](https://github.com/aknuds1/html-to-react/compare/v1.1.0...v1.1.1) + +## [v1.1.0](https://github.com/aknuds1/html-to-react/tree/v1.1.0) + +[Full Changelog](https://github.com/aknuds1/html-to-react/compare/v1.0.0...v1.1.0) + ## [v1.0.0](https://github.com/mikenikles/html-to-react/tree/v1.0.0) [Full Changelog](https://github.com/mikenikles/html-to-react/compare/v0.1.0...v1.0.0) @@ -55,4 +75,4 @@ ## [v0.0.2](https://github.com/mikenikles/html-to-react/tree/v0.0.2) (2015-06-20) -\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file +\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* From ff65a8dcf77a64afa85a7737e41e3980b4d3b655 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 2 Oct 2016 10:26:52 +0200 Subject: [PATCH 056/227] Use eslint when testing --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 727d0e1..ec29abd 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,9 @@ "description": "A lightweight library that converts raw HTML to a React DOM structure.", "main": "index.js", "scripts": { - "test": "./node_modules/mocha/bin/mocha", - "test-coverage": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage", - "test-html-coverage": "istanbul cover ./node_modules/mocha/bin/_mocha; open coverage/lcov-report/index.html" + "test": "./node_modules/.bin/eslint . && ./node_modules/mocha/bin/mocha", + "test-coverage": "./node_modules/.bin/eslint . && istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage", + "test-html-coverage": "./node_modules/.bin/eslint . && istanbul cover ./node_modules/mocha/bin/_mocha; open coverage/lcov-report/index.html" }, "repository": { "type": "git", From 3989dad52a0320bce9a00a749bf6dd466a1fcebc Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 2 Oct 2016 10:33:58 +0200 Subject: [PATCH 057/227] Install eslint --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index ec29abd..465fc6d 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ }, "devDependencies": { "coveralls": "^2.11", + "eslint": "^3.7.0", "istanbul": "^0.4", "mocha": "^3.0", "mocha-lcov-reporter": "^1.2.0", From 010bf9343013306d903c4b0038a22e5ddb8a33e3 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 2 Oct 2016 12:57:18 +0200 Subject: [PATCH 058/227] Revert to classical JS syntax --- lib/parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/parser.js b/lib/parser.js index 9a043ff..ddb3f69 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -22,7 +22,7 @@ var Html2React = function(options) { return processingInstruction.shouldProcessNode(node); }, processingInstructions); if (processingInstruction != null) { - var children = reject((x) => {return x == null;}, + var children = reject(function (x) {return x == null;}, addIndex(map)(function (child, i) { return traverseDom(child, isValidNode, processingInstructions, i); }, node.children || [])); From 4d16e369f944cbb96569aaa2ef49736d73604ea4 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 2 Oct 2016 13:06:30 +0200 Subject: [PATCH 059/227] Add downloads badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7546b4d..8502763 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![npm version](https://badge.fury.io/js/html-to-react.svg)](http://badge.fury.io/js/html-to-react) [![Dependency Status](https://david-dm.org/aknuds1/html-to-react.svg)](https://david-dm.org/aknuds1/html-to-react) [![Coverage Status](https://coveralls.io/repos/aknuds1/html-to-react/badge.svg?branch=master)](https://coveralls.io/r/aknuds1/html-to-react?branch=master) +[![npm](https://img.shields.io/npm/dm/html-to-react.svg?maxAge=2592000)](https://www.npmjs.com/package/html-to-react) A lightweight library that converts raw HTML to a React DOM structure. From 50d8fb8887a651b4ae040a5f61c0f31790ad8a03 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sun, 2 Oct 2016 13:14:58 +0200 Subject: [PATCH 060/227] Fix tests/documentation --- README.md | 11 ++++++----- lib/parser.js | 4 ++-- test/html-to-react-tests.js | 8 ++++---- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 8502763..b0534a9 100644 --- a/README.md +++ b/README.md @@ -47,10 +47,10 @@ The following example parses each node and its attributes and returns a tree of ```javascript var React = require('react'); -var HtmlToReact = new require('html-to-react'); +var HtmlToReactParser = require('html-to-react').Parser; var htmlInput = '

Title

A paragraph

'; -var htmlToReactParser = new HtmlToReact.Parser(React); +var htmlToReactParser = new HtmlToReactParser(); var reactComponent = htmlToReactParser.parse(htmlInput); var reactHtml = React.renderToStaticMarkup(reactComponent); @@ -64,7 +64,7 @@ If certain DOM nodes require specific processing, for example if you want to cap ```javascript var React = require('react'); -var HtmlToReact = new require('html-to-react'); +var HtmlToReactParser = require('html-to-react').Parser; var htmlInput = '

Title

Paragraph

Another title

'; var htmlExpected = '

TITLE

Paragraph

ANOTHER TITLE

'; @@ -91,7 +91,7 @@ var processingInstructions = [ }, processNode: processNodeDefinitions.processDefaultNode }]; -var htmlToReactParser = new HtmlToReact.Parser(React); +var htmlToReactParser = new HtmlToReactParser(); var reactComponent = htmlToReactParser.parseWithInstructions(htmlInput, isValidNode, processingInstructions); var reactHtml = React.renderToStaticMarkup(reactComponent); @@ -142,8 +142,9 @@ In your instructions object, you must specify `replaceChildren: true`. ```javascript var React = require('react'); -var HtmlToReact = new require('html-to-react'); +var HtmlToReactParser = require('html-to-react').Parser; +var htmlToReactParser = new HtmlToReactParser(); var htmlInput = '

Text

Text

'; var htmlExpected = '

Heading

'; diff --git a/lib/parser.js b/lib/parser.js index 0068fc4..95ac25e 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -6,7 +6,7 @@ var ProcessingInstructions = require('./processing-instructions'); var IsValidNodeDefinitions = require('./is-valid-node-definitions'); var utils = require('./utils'); -var Html2React = function(options) { +var Html2ReactParser = function(options) { var parseHtmlToTree = function(html) { var handler = new htmlParser.DomHandler(); var parser = new htmlParser.Parser(handler, options); @@ -69,4 +69,4 @@ var Html2React = function(options) { }; }; -module.exports = Html2React; +module.exports = Html2ReactParser; diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index 98faeff..683b2ba 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -5,11 +5,11 @@ var React = require('react'); var ReactDOMServer = require('react-dom/server'); var R = require('ramda'); -var Parser = require('../index').Parser; -var ProcessNodeDefinitions = require('../index').ProcessNodeDefinitions; +var Parser = require('..').Parser; +var ProcessNodeDefinitions = require('..').ProcessNodeDefinitions; describe('Html2React', function () { - var parser = new Parser(React); + var parser = new Parser(); describe('parse valid HTML', function () { it('should return a valid HTML string', function () { @@ -233,7 +233,7 @@ describe('Html2React', function () { }); describe('with custom processing instructions', function () { - var parser = new Parser(React); + var parser = new Parser(); var processNodeDefinitions = new ProcessNodeDefinitions(React); describe('parse valid HTML', function () { From 6eccd7c7a375fce77b611add31793ce82a18ceb3 Mon Sep 17 00:00:00 2001 From: Christian Stuff Date: Wed, 5 Oct 2016 11:29:41 +0200 Subject: [PATCH 061/227] Don't return false for invalid nodes. Instead return nothing. Adjusted tests. --- lib/parser.js | 4 ---- test/html-to-react-tests.js | 6 +++--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/parser.js b/lib/parser.js index 95ac25e..a72ad11 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -35,11 +35,7 @@ var Html2ReactParser = function(options) { } return processingInstruction.processNode(node, children, index); - } else { - return false; } - } else { - return false; } }; diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index 683b2ba..236ce6e 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -252,7 +252,7 @@ describe('Html2React', function () { processingInstructions); // With only 1

element, nothing is rendered - assert.equal(reactComponent, false); + assert.equal(reactComponent, undefined); }); it('should return a single

element within a div of

and

as siblings', @@ -340,7 +340,7 @@ describe('Html2React', function () { assert.equal(reactHtml, htmlExpected); }); - it('should return false in case of invalid node', function () { + it('should return undefined in case of invalid node', function () { var htmlInput = '

'; var processingInstructions = [{ shouldProcessNode: function (node) { return true; }, @@ -349,7 +349,7 @@ describe('Html2React', function () { var reactComponent = parser.parseWithInstructions(htmlInput, function () { return false; }, processingInstructions); - assert.equal(reactComponent, false); + assert.equal(reactComponent, undefined); }); }); }); From b66ff971191ffb3c5c08d59dae64504fd366bd83 Mon Sep 17 00:00:00 2001 From: Christian Stuff Date: Wed, 5 Oct 2016 14:40:49 +0200 Subject: [PATCH 062/227] Added test to check, that invalid nodes are not present in React structure. --- test/html-to-react-tests.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index 236ce6e..20ae3c1 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -351,6 +351,25 @@ describe('Html2React', function () { assert.equal(reactComponent, undefined); }); + + it('should generate only valid children', function () { + var htmlInput = '

'; + + var processingInstructions = [{ + shouldProcessNode: function (node) { return true; }, + processNode: processNodeDefinitions.processDefaultNode, + }, ]; + var reactComponent = parser.parseWithInstructions(htmlInput, + function (node) { + // skip whitespace text nodes to clean up children + if(node.type === 'text') { + return node.data.trim() !== ''; + } + return true; + }, processingInstructions); + + assert.equal(reactComponent.props.children.length, 2); + }); }); }); }); From 24fa2e4967e1fd7857e8266d202c9b5caf7a35b9 Mon Sep 17 00:00:00 2001 From: Christian Stuff Date: Wed, 5 Oct 2016 15:14:41 +0200 Subject: [PATCH 063/227] Added another test to prove, that whitespace text nodes are still handled correctly with default settings. --- test/html-to-react-tests.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index 20ae3c1..9573aae 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -370,6 +370,19 @@ describe('Html2React', function () { assert.equal(reactComponent.props.children.length, 2); }); + + it('should not affect unhandled whitespace', function () { + var htmlInput = '

'; + + var processingInstructions = [{ + shouldProcessNode: function (node) { return true; }, + processNode: processNodeDefinitions.processDefaultNode, + }, ]; + var reactComponent = parser.parseWithInstructions(htmlInput, + function () { return true; }, processingInstructions); + + assert.equal(reactComponent.props.children.length, 5); + }); }); }); }); From f015248622f382a0708bbed80a75cceaaf53247c Mon Sep 17 00:00:00 2001 From: Aaron Harvey Date: Wed, 5 Oct 2016 12:32:07 -0400 Subject: [PATCH 064/227] refactor prop creation --- lib/process-node-definitions.js | 41 +-------------------------------- lib/utils.js | 25 +++++++++----------- package.json | 6 +---- 3 files changed, 13 insertions(+), 59 deletions(-) diff --git a/lib/process-node-definitions.js b/lib/process-node-definitions.js index 170091d..3d9a71c 100644 --- a/lib/process-node-definitions.js +++ b/lib/process-node-definitions.js @@ -1,12 +1,5 @@ 'use strict'; -var isEmpty = require('lodash.isempty'); -var map = require('lodash.map'); -var fromPairs = require('lodash.frompairs'); -var camelCase = require('lodash.camelcase'); -var includes = require('lodash.includes'); -var merge = require('lodash.merge'); var ent = require('ent'); -var camelCaseAttrMap = require('./camel-case-attribute-names'); var utils = require('./utils'); // https://github.com/facebook/react/blob/15.0-stable/src/renderers/dom/shared/ReactDOMComponent.js#L457 @@ -15,23 +8,6 @@ var voidElementTags = [ 'source', 'track', 'wbr', 'menuitem', 'textarea', ]; -function createStyleJsonFromString(styleString) { - if (!styleString) { - return {}; - } - var styles = styleString.split(';'); - var singleStyle, key, value, jsonStyles = {}; - for (var i = 0; i < styles.length; i++) { - singleStyle = styles[i].split(':'); - key = camelCase(singleStyle[0]); - value = singleStyle[1]; - if (key.length > 0 && value.length > 0) { - jsonStyles[key] = value; - } - } - return jsonStyles; -} - var ProcessNodeDefinitions = function() { function processDefaultNode(node, children, index) { if (node.type === 'text') { @@ -44,22 +20,7 @@ var ProcessNodeDefinitions = function() { } var elementProps = utils.createElementProps(node, index); - // Process attributes - if (!isEmpty(node.attribs)) { - elementProps = merge(elementProps, fromPairs(map(node.attribs, function (value, key) { - if (key === 'style') { - value = createStyleJsonFromString(node.attribs.style); - } else if (key === 'class') { - key = 'className'; - } else if (camelCaseAttrMap[key]) { - key = camelCaseAttrMap[key]; - } - - return [key, value || key,]; - }))); - } - - if (includes(voidElementTags, node.name)) { + if (voidElementTags.indexOf(node.name) > -1) { return utils.createElement(node.name, elementProps); } else { return utils.createElement(node.name, elementProps, node.data, children); diff --git a/lib/utils.js b/lib/utils.js index 57ea8fc..25f1bb7 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,8 +1,8 @@ 'use strict'; var camelCase = require('lodash.camelcase'); -var forEach = require('lodash.foreach'); -var includes = require('lodash.includes'); +var reduce = require('lodash.reduce'); var React = require('react'); +var camelCaseAttrMap = require('./camel-case-attribute-names'); function createStyleJsonFromString(styleString) { if (!styleString) { @@ -26,19 +26,16 @@ function createElementProps(node, index) { key: index, }; if (node.attribs) { - forEach(node.attribs, function (value, key) { - switch (key || '') { - case 'style': - elementProps.style = createStyleJsonFromString(node.attribs.style); - break; - case 'class': - elementProps.className = value; - break; - default: - elementProps[key] = value; - break; + elementProps = reduce(node.attribs, function(result, value, key) { + key = camelCaseAttrMap[key] || key; + if (key === 'style') { + value = createStyleJsonFromString(node.attribs.style); + } else if (key === 'class') { + key = 'className'; } - }); + result[key] = value || key; + return result; + }, elementProps); } return elementProps; diff --git a/package.json b/package.json index 465fc6d..48d52ee 100644 --- a/package.json +++ b/package.json @@ -39,12 +39,8 @@ "htmlparser2": "^3.8.3", "lodash.camelcase": "^4.3.0", "lodash.find": "^4.6.0", - "lodash.foreach": "^4.5.0", - "lodash.frompairs": "^4.0.1", - "lodash.includes": "^4.3.0", - "lodash.isempty": "^4.4.0", "lodash.map": "^4.6.0", - "lodash.merge": "^4.6.0" + "lodash.reduce": "^4.6.0" }, "peerDependencies": { "react": "^15.0" From ac6a5e17a38bd04edb983097591788e453743a79 Mon Sep 17 00:00:00 2001 From: Aaron Harvey Date: Wed, 5 Oct 2016 12:57:42 -0400 Subject: [PATCH 065/227] Fix dashed attribute mapping --- lib/utils.js | 7 ++++++- test/html-to-react-tests.js | 8 ++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index 25f1bb7..632f281 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -4,6 +4,11 @@ var reduce = require('lodash.reduce'); var React = require('react'); var camelCaseAttrMap = require('./camel-case-attribute-names'); +function getCamelCaseAttr(key) { + key = key.replace(/-/, ''); + return camelCaseAttrMap[key]; +} + function createStyleJsonFromString(styleString) { if (!styleString) { return {}; @@ -27,7 +32,7 @@ function createElementProps(node, index) { }; if (node.attribs) { elementProps = reduce(node.attribs, function(result, value, key) { - key = camelCaseAttrMap[key] || key; + key = getCamelCaseAttr(key) || key; if (key === 'style') { value = createStyleJsonFromString(node.attribs.style); } else if (key === 'class') { diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index 683b2ba..895def4 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -85,6 +85,14 @@ describe('Html2React', function () { assert.equal(reactHtml, htmlInput); }); + it('should handle dashed attributes', function () { + var input = '
'; + var reactComponent = parser.parse(input); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + + assert.equal(reactHtml, input); + }); + // FIXME: See lib/process-node-definitions.js -> processDefaultNode() it.skip('should return a valid HTML string with comments', function () { var htmlInput = '
'; From fa1c3c2022ab381a3e9ae998232756010c121998 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Wed, 5 Oct 2016 22:18:43 +0200 Subject: [PATCH 066/227] Update .eslintrc --- .eslintrc.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 73c8013..fd13d01 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -18,3 +18,7 @@ rules: allowTemplateLiterals: true new-cap: 0 camelcase: 0 + keyword-spacing: + - error + - + after: true From d2c082094914c72e79d80c1cf2de8d545ffac818 Mon Sep 17 00:00:00 2001 From: Christian Stuff Date: Wed, 5 Oct 2016 22:40:10 +0200 Subject: [PATCH 067/227] Simplified test case. --- test/html-to-react-tests.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index 9573aae..f22b2e9 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -374,12 +374,7 @@ describe('Html2React', function () { it('should not affect unhandled whitespace', function () { var htmlInput = '

'; - var processingInstructions = [{ - shouldProcessNode: function (node) { return true; }, - processNode: processNodeDefinitions.processDefaultNode, - }, ]; - var reactComponent = parser.parseWithInstructions(htmlInput, - function () { return true; }, processingInstructions); + var reactComponent = parser.parse(htmlInput); assert.equal(reactComponent.props.children.length, 5); }); From 325c0c603370a32abfe40b93a69f153e3459f566 Mon Sep 17 00:00:00 2001 From: Christian Stuff Date: Wed, 5 Oct 2016 22:45:45 +0200 Subject: [PATCH 068/227] Fix linter issue. --- test/html-to-react-tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index f22b2e9..9979e30 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -362,7 +362,7 @@ describe('Html2React', function () { var reactComponent = parser.parseWithInstructions(htmlInput, function (node) { // skip whitespace text nodes to clean up children - if(node.type === 'text') { + if (node.type === 'text') { return node.data.trim() !== ''; } return true; From fd9f0c859d1009ea17dc26b15a9811b8833b876d Mon Sep 17 00:00:00 2001 From: Christian Stuff Date: Thu, 6 Oct 2016 07:44:23 +0200 Subject: [PATCH 069/227] Revert API to return false on top level. --- lib/parser.js | 3 ++- test/html-to-react-tests.js | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/parser.js b/lib/parser.js index a72ad11..ed7c5b6 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -24,7 +24,7 @@ var Html2ReactParser = function(options) { var children = map(node.children || [], function (child, i) { return traverseDom(child, isValidNode, processingInstructions, i); }).filter(function (x) { - return x != null; + return x != null && x !== false; }); if (processingInstruction.replaceChildren) { @@ -37,6 +37,7 @@ var Html2ReactParser = function(options) { return processingInstruction.processNode(node, children, index); } } + return false; }; var parseWithInstructions = function(html, isValidNode, processingInstructions) { diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index 9979e30..8d689b7 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -252,7 +252,7 @@ describe('Html2React', function () { processingInstructions); // With only 1

element, nothing is rendered - assert.equal(reactComponent, undefined); + assert.equal(reactComponent, false); }); it('should return a single

element within a div of

and

as siblings', @@ -340,7 +340,7 @@ describe('Html2React', function () { assert.equal(reactHtml, htmlExpected); }); - it('should return undefined in case of invalid node', function () { + it('should return false in case of invalid node', function () { var htmlInput = '

'; var processingInstructions = [{ shouldProcessNode: function (node) { return true; }, @@ -349,7 +349,7 @@ describe('Html2React', function () { var reactComponent = parser.parseWithInstructions(htmlInput, function () { return false; }, processingInstructions); - assert.equal(reactComponent, undefined); + assert.equal(reactComponent, false); }); it('should generate only valid children', function () { From b724a6f00d8a04134bbe5a8823d637e63b4ef859 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Thu, 6 Oct 2016 11:03:24 +0200 Subject: [PATCH 070/227] Simplify --- lib/utils.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 632f281..ba61f64 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -4,11 +4,6 @@ var reduce = require('lodash.reduce'); var React = require('react'); var camelCaseAttrMap = require('./camel-case-attribute-names'); -function getCamelCaseAttr(key) { - key = key.replace(/-/, ''); - return camelCaseAttrMap[key]; -} - function createStyleJsonFromString(styleString) { if (!styleString) { return {}; @@ -32,7 +27,7 @@ function createElementProps(node, index) { }; if (node.attribs) { elementProps = reduce(node.attribs, function(result, value, key) { - key = getCamelCaseAttr(key) || key; + key = camelCaseAttrMap[key.replace(/-/, '')] || key; if (key === 'style') { value = createStyleJsonFromString(node.attribs.style); } else if (key === 'class') { From a2bf720146687ea13e906a73f7a0a069a6d1de21 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Thu, 6 Oct 2016 11:05:55 +0200 Subject: [PATCH 071/227] Simplify --- lib/parser.js | 3 +-- lib/process-node-definitions.js | 5 ++--- lib/utils.js | 9 ++------- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/lib/parser.js b/lib/parser.js index ed7c5b6..6103853 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -28,8 +28,7 @@ var Html2ReactParser = function(options) { }); if (processingInstruction.replaceChildren) { - var elementProps = utils.createElementProps(node, index); - return utils.createElement(node.name, elementProps, node.data, [ + return utils.createElement(node, index, node.data, [ processingInstruction.processNode(node, children, index), ]); } diff --git a/lib/process-node-definitions.js b/lib/process-node-definitions.js index 3d9a71c..18dbc92 100644 --- a/lib/process-node-definitions.js +++ b/lib/process-node-definitions.js @@ -19,11 +19,10 @@ var ProcessNodeDefinitions = function() { return false; } - var elementProps = utils.createElementProps(node, index); if (voidElementTags.indexOf(node.name) > -1) { - return utils.createElement(node.name, elementProps); + return utils.createElement(node, index); } else { - return utils.createElement(node.name, elementProps, node.data, children); + return utils.createElement(node, index, node.data, children); } } diff --git a/lib/utils.js b/lib/utils.js index ba61f64..cf03e92 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -21,7 +21,7 @@ function createStyleJsonFromString(styleString) { return jsonStyles; } -function createElementProps(node, index) { +function createElement(node, index, data, children) { var elementProps = { key: index, }; @@ -38,18 +38,13 @@ function createElementProps(node, index) { }, elementProps); } - return elementProps; -} - -function createElement(name, elementProps, data, children) { children = children || []; var allChildren = data != null ? [data,].concat(children) : children; return React.createElement.apply( - null, [name, elementProps,].concat(allChildren) + null, [node.name, elementProps,].concat(allChildren) ); } module.exports = { - createElementProps: createElementProps, createElement: createElement, }; From 59c8d0a8cd6a15b4da0b7bc5b90f74b7554fc4bb Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sat, 15 Oct 2016 14:55:07 +0200 Subject: [PATCH 072/227] Change to 2 spaces of indentation --- .editorconfig | 2 +- index.js | 8 +- lib/camel-case-attribute-names.js | 128 ++++++++++++------------- lib/is-valid-node-definitions.js | 4 +- lib/parser.js | 104 ++++++++++---------- lib/process-node-definitions.js | 42 ++++---- lib/processing-instructions.js | 16 ++-- lib/should-process-node-definitions.js | 4 +- lib/utils.js | 72 +++++++------- 9 files changed, 190 insertions(+), 190 deletions(-) diff --git a/.editorconfig b/.editorconfig index 7250816..b7066e9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,4 +5,4 @@ indent_style = space end_of_line = lf insert_final_newline = true max_line_length = 100 -indent_size = 4 +indent_size = 2 diff --git a/index.js b/index.js index 7cbad93..3288c6d 100644 --- a/index.js +++ b/index.js @@ -6,8 +6,8 @@ var isValidNodeDefinitions = require('./lib/is-valid-node-definitions'); var processNodeDefinitions = require('./lib/process-node-definitions'); module.exports = { - Parser: parser, - ProcessingInstructions: processingInstructions, - IsValidNodeDefinitions: isValidNodeDefinitions, - ProcessNodeDefinitions: processNodeDefinitions, + Parser: parser, + ProcessingInstructions: processingInstructions, + IsValidNodeDefinitions: isValidNodeDefinitions, + ProcessNodeDefinitions: processNodeDefinitions, }; diff --git a/lib/camel-case-attribute-names.js b/lib/camel-case-attribute-names.js index f158e83..d9b5f2e 100644 --- a/lib/camel-case-attribute-names.js +++ b/lib/camel-case-attribute-names.js @@ -4,78 +4,78 @@ // it changes. 'use strict'; var HTML_ATTRIBUTES = [ - 'accept', 'acceptCharset', 'accessKey', 'action', 'allowFullScreen', 'allowTransparency', - 'alt', 'async', 'autoComplete', 'autoFocus', 'autoPlay', 'capture', 'cellPadding', - 'cellSpacing', 'challenge', 'charSet', 'checked', 'cite', 'classID', 'className', - 'colSpan', 'cols', 'content', 'contentEditable', 'contextMenu', 'controls', 'coords', - 'crossOrigin', 'data', 'dateTime', 'default', 'defer', 'dir', 'disabled', 'download', - 'draggable', 'encType', 'form', 'formAction', 'formEncType', 'formMethod', 'formNoValidate', - 'formTarget', 'frameBorder', 'headers', 'height', 'hidden', 'high', 'href', 'hrefLang', - 'htmlFor', 'httpEquiv', 'icon', 'id', 'inputMode', 'integrity', 'is', 'keyParams', 'keyType', - 'kind', 'label', 'lang', 'list', 'loop', 'low', 'manifest', 'marginHeight', 'marginWidth', - 'max', 'maxLength', 'media', 'mediaGroup', 'method', 'min', 'minLength', 'multiple', 'muted', - 'name', 'noValidate', 'nonce', 'open', 'optimum', 'pattern', 'placeholder', 'poster', - 'preload', 'profile', 'radioGroup', 'readOnly', 'rel', 'required', 'reversed', 'role', - 'rowSpan', 'rows', 'sandbox', 'scope', 'scoped', 'scrolling', 'seamless', 'selected', - 'shape', 'size', 'sizes', 'span', 'spellCheck', 'src', 'srcDoc', 'srcLang', 'srcSet', 'start', - 'step', 'style', 'summary', 'tabIndex', 'target', 'title', 'type', 'useMap', 'value', 'width', - 'wmode', 'wrap', + 'accept', 'acceptCharset', 'accessKey', 'action', 'allowFullScreen', 'allowTransparency', + 'alt', 'async', 'autoComplete', 'autoFocus', 'autoPlay', 'capture', 'cellPadding', + 'cellSpacing', 'challenge', 'charSet', 'checked', 'cite', 'classID', 'className', + 'colSpan', 'cols', 'content', 'contentEditable', 'contextMenu', 'controls', 'coords', + 'crossOrigin', 'data', 'dateTime', 'default', 'defer', 'dir', 'disabled', 'download', + 'draggable', 'encType', 'form', 'formAction', 'formEncType', 'formMethod', 'formNoValidate', + 'formTarget', 'frameBorder', 'headers', 'height', 'hidden', 'high', 'href', 'hrefLang', + 'htmlFor', 'httpEquiv', 'icon', 'id', 'inputMode', 'integrity', 'is', 'keyParams', 'keyType', + 'kind', 'label', 'lang', 'list', 'loop', 'low', 'manifest', 'marginHeight', 'marginWidth', + 'max', 'maxLength', 'media', 'mediaGroup', 'method', 'min', 'minLength', 'multiple', 'muted', + 'name', 'noValidate', 'nonce', 'open', 'optimum', 'pattern', 'placeholder', 'poster', + 'preload', 'profile', 'radioGroup', 'readOnly', 'rel', 'required', 'reversed', 'role', + 'rowSpan', 'rows', 'sandbox', 'scope', 'scoped', 'scrolling', 'seamless', 'selected', + 'shape', 'size', 'sizes', 'span', 'spellCheck', 'src', 'srcDoc', 'srcLang', 'srcSet', 'start', + 'step', 'style', 'summary', 'tabIndex', 'target', 'title', 'type', 'useMap', 'value', 'width', + 'wmode', 'wrap', ]; var NON_STANDARD_ATTRIBUTES = [ - 'autoCapitalize', 'autoCorrect', 'color', 'itemProp', 'itemScope', 'itemType', 'itemRef', - 'itemID', 'security', 'unselectable', 'results', 'autoSave', + 'autoCapitalize', 'autoCorrect', 'color', 'itemProp', 'itemScope', 'itemType', 'itemRef', + 'itemID', 'security', 'unselectable', 'results', 'autoSave', ]; var SVG_ATTRIBUTES = [ - 'accentHeight', 'accumulate', 'additive', 'alignmentBaseline', 'allowReorder', 'alphabetic', - 'amplitude', 'arabicForm', 'ascent', 'attributeName', 'attributeType', 'autoReverse', - 'azimuth', 'baseFrequency', 'baseProfile', 'baselineShift', 'bbox', 'begin', 'bias', 'by', - 'calcMode', 'capHeight', 'clip', 'clipPath', 'clipPathUnits', 'clipRule', 'colorInterpolation', - 'colorInterpolationFilters', 'colorProfile', 'colorRendering', 'contentScriptType', - 'contentStyleType', 'cursor', 'cx', 'cy', 'd', 'decelerate', 'descent', 'diffuseConstant', - 'direction', 'display', 'divisor', 'dominantBaseline', 'dur', 'dx', 'dy', 'edgeMode', - 'elevation', 'enableBackground', 'end', 'exponent', 'externalResourcesRequired', 'fill', - 'fillOpacity', 'fillRule', 'filter', 'filterRes', 'filterUnits', 'floodColor', 'floodOpacity', - 'focusable', 'fontFamily', 'fontSize', 'fontSizeAdjust', 'fontStretch', 'fontStyle', - 'fontVariant', 'fontWeight', 'format', 'from', 'fx', 'fy', 'g1', 'g2', 'glyphName', - 'glyphOrientationHorizontal', 'glyphOrientationVertical', 'glyphRef', 'gradientTransform', - 'gradientUnits', 'hanging', 'horizAdvX', 'horizOriginX', 'ideographic', 'imageRendering', - 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kernelMatrix', 'kernelUnitLength', - 'kerning', 'keyPoints', 'keySplines', 'keyTimes', 'lengthAdjust', 'letterSpacing', - 'lightingColor', 'limitingConeAngle', 'local', 'markerEnd', 'markerHeight', 'markerMid', - 'markerStart', 'markerUnits', 'markerWidth', 'mask', 'maskContentUnits', 'maskUnits', - 'mathematical', 'mode', 'numOctaves', 'offset', 'opacity', 'operator', 'order', - 'orient', 'orientation', 'origin', 'overflow', 'overlinePosition', 'overlineThickness', - 'paintOrder', 'panose1', 'pathLength', 'patternContentUnits', 'patternTransform', - 'patternUnits', 'pointerEvents', 'points', 'pointsAtX', 'pointsAtY', 'pointsAtZ', - 'preserveAlpha', 'preserveAspectRatio', 'primitiveUnits', 'r', 'radius', 'refX', 'refY', - 'renderingIntent', 'repeatCount', 'repeatDur', 'requiredExtensions', 'requiredFeatures', - 'restart', 'result', 'rotate', 'rx', 'ry', 'scale', 'seed', 'shapeRendering', 'slope', - 'spacing', 'specularConstant', 'specularExponent', 'speed', 'spreadMethod', 'startOffset', - 'stdDeviation', 'stemh', 'stemv', 'stitchTiles', 'stopColor', 'stopOpacity', - 'strikethroughPosition', 'strikethroughThickness', 'string', 'stroke', 'strokeDasharray', - 'strokeDashoffset', 'strokeLinecap', 'strokeLinejoin', 'strokeMiterlimit', - 'strokeOpacity', 'strokeWidth', 'surfaceScale', 'systemLanguage', 'tableValues', 'targetX', - 'targetY', 'textAnchor', 'textDecoration', 'textLength', 'textRendering', 'to', 'transform', - 'u1', 'u2', 'underlinePosition', 'underlineThickness', 'unicode', 'unicodeBidi', - 'unicodeRange', 'unitsPerEm', 'vAlphabetic', 'vHanging', 'vIdeographic', 'vMathematical', - 'values', 'vectorEffect', 'version', 'vertAdvY', 'vertOriginX', 'vertOriginY', 'viewBox', - 'viewTarget', 'visibility', 'widths', 'wordSpacing', 'writingMode', 'x', 'x1', 'x2', - 'xChannelSelector', 'xHeight', 'xlinkActuate', 'xlinkArcrole', 'xlinkHref', 'xlinkRole', - 'xlinkShow', 'xlinkTitle', 'xlinkType', 'xmlBase', 'xmlLang', 'xmlSpace', 'y', 'y1', 'y2', - 'yChannelSelector', 'z', 'zoomAndPan', + 'accentHeight', 'accumulate', 'additive', 'alignmentBaseline', 'allowReorder', 'alphabetic', + 'amplitude', 'arabicForm', 'ascent', 'attributeName', 'attributeType', 'autoReverse', + 'azimuth', 'baseFrequency', 'baseProfile', 'baselineShift', 'bbox', 'begin', 'bias', 'by', + 'calcMode', 'capHeight', 'clip', 'clipPath', 'clipPathUnits', 'clipRule', 'colorInterpolation', + 'colorInterpolationFilters', 'colorProfile', 'colorRendering', 'contentScriptType', + 'contentStyleType', 'cursor', 'cx', 'cy', 'd', 'decelerate', 'descent', 'diffuseConstant', + 'direction', 'display', 'divisor', 'dominantBaseline', 'dur', 'dx', 'dy', 'edgeMode', + 'elevation', 'enableBackground', 'end', 'exponent', 'externalResourcesRequired', 'fill', + 'fillOpacity', 'fillRule', 'filter', 'filterRes', 'filterUnits', 'floodColor', 'floodOpacity', + 'focusable', 'fontFamily', 'fontSize', 'fontSizeAdjust', 'fontStretch', 'fontStyle', + 'fontVariant', 'fontWeight', 'format', 'from', 'fx', 'fy', 'g1', 'g2', 'glyphName', + 'glyphOrientationHorizontal', 'glyphOrientationVertical', 'glyphRef', 'gradientTransform', + 'gradientUnits', 'hanging', 'horizAdvX', 'horizOriginX', 'ideographic', 'imageRendering', + 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kernelMatrix', 'kernelUnitLength', + 'kerning', 'keyPoints', 'keySplines', 'keyTimes', 'lengthAdjust', 'letterSpacing', + 'lightingColor', 'limitingConeAngle', 'local', 'markerEnd', 'markerHeight', 'markerMid', + 'markerStart', 'markerUnits', 'markerWidth', 'mask', 'maskContentUnits', 'maskUnits', + 'mathematical', 'mode', 'numOctaves', 'offset', 'opacity', 'operator', 'order', + 'orient', 'orientation', 'origin', 'overflow', 'overlinePosition', 'overlineThickness', + 'paintOrder', 'panose1', 'pathLength', 'patternContentUnits', 'patternTransform', + 'patternUnits', 'pointerEvents', 'points', 'pointsAtX', 'pointsAtY', 'pointsAtZ', + 'preserveAlpha', 'preserveAspectRatio', 'primitiveUnits', 'r', 'radius', 'refX', 'refY', + 'renderingIntent', 'repeatCount', 'repeatDur', 'requiredExtensions', 'requiredFeatures', + 'restart', 'result', 'rotate', 'rx', 'ry', 'scale', 'seed', 'shapeRendering', 'slope', + 'spacing', 'specularConstant', 'specularExponent', 'speed', 'spreadMethod', 'startOffset', + 'stdDeviation', 'stemh', 'stemv', 'stitchTiles', 'stopColor', 'stopOpacity', + 'strikethroughPosition', 'strikethroughThickness', 'string', 'stroke', 'strokeDasharray', + 'strokeDashoffset', 'strokeLinecap', 'strokeLinejoin', 'strokeMiterlimit', + 'strokeOpacity', 'strokeWidth', 'surfaceScale', 'systemLanguage', 'tableValues', 'targetX', + 'targetY', 'textAnchor', 'textDecoration', 'textLength', 'textRendering', 'to', 'transform', + 'u1', 'u2', 'underlinePosition', 'underlineThickness', 'unicode', 'unicodeBidi', + 'unicodeRange', 'unitsPerEm', 'vAlphabetic', 'vHanging', 'vIdeographic', 'vMathematical', + 'values', 'vectorEffect', 'version', 'vertAdvY', 'vertOriginX', 'vertOriginY', 'viewBox', + 'viewTarget', 'visibility', 'widths', 'wordSpacing', 'writingMode', 'x', 'x1', 'x2', + 'xChannelSelector', 'xHeight', 'xlinkActuate', 'xlinkArcrole', 'xlinkHref', 'xlinkRole', + 'xlinkShow', 'xlinkTitle', 'xlinkType', 'xmlBase', 'xmlLang', 'xmlSpace', 'y', 'y1', 'y2', + 'yChannelSelector', 'z', 'zoomAndPan', ]; var camelCaseMap = HTML_ATTRIBUTES - .concat(NON_STANDARD_ATTRIBUTES) - .concat(SVG_ATTRIBUTES) - .reduce(function (soFar, attr) { - var lower = attr.toLowerCase(); - if (lower !== attr) { - soFar[lower] = attr; - } - return soFar; - }, {}); + .concat(NON_STANDARD_ATTRIBUTES) + .concat(SVG_ATTRIBUTES) + .reduce(function (soFar, attr) { + var lower = attr.toLowerCase(); + if (lower !== attr) { + soFar[lower] = attr; + } + return soFar; + }, {}); module.exports = camelCaseMap; diff --git a/lib/is-valid-node-definitions.js b/lib/is-valid-node-definitions.js index 295c48f..0dadd42 100644 --- a/lib/is-valid-node-definitions.js +++ b/lib/is-valid-node-definitions.js @@ -1,8 +1,8 @@ 'use strict'; function alwaysValid() { - return true; + return true; } module.exports = { - alwaysValid: alwaysValid, + alwaysValid: alwaysValid, }; diff --git a/lib/parser.js b/lib/parser.js index e822b4f..35d2878 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -8,64 +8,64 @@ var ProcessingInstructions = require('./processing-instructions'); var IsValidNodeDefinitions = require('./is-valid-node-definitions'); var utils = require('./utils'); -var Html2ReactParser = function(options) { - var parseHtmlToTree = function(html) { - var handler = new htmlParser.DomHandler(); - var parser = new htmlParser.Parser(handler, options); - parser.parseComplete(html); - return handler.dom; - }; +function Html2ReactParser(options) { + function parseHtmlToTree(html) { + var handler = new htmlParser.DomHandler(); + var parser = new htmlParser.Parser(handler, options); + parser.parseComplete(html); + return handler.dom; + }; - var traverseDom = function(node, isValidNode, processingInstructions, index) { - if (isValidNode(node)) { - var processingInstruction = find(function (processingInstruction) { - return processingInstruction.shouldProcessNode(node); - }, processingInstructions); - if (processingInstruction != null) { - var children = reject(function (x) {return x == null || x === false;}, - addIndex(map)(function (child, i) { - return traverseDom(child, isValidNode, processingInstructions, i); - }, node.children || [])); + function traverseDom(node, isValidNode, processingInstructions, index) { + if (isValidNode(node)) { + var processingInstruction = find(function (processingInstruction) { + return processingInstruction.shouldProcessNode(node); + }, processingInstructions); + if (processingInstruction != null) { + var children = reject(function (x) {return x == null || x === false;}, + addIndex(map)(function (child, i) { + return traverseDom(child, isValidNode, processingInstructions, i); + }, node.children || [])); - if (processingInstruction.replaceChildren) { - return utils.createElement(node, index, node.data, [ - processingInstruction.processNode(node, children, index), - ]); - } - - return processingInstruction.processNode(node, children, index); - } else { - return false; - } - } else { - return false; + if (processingInstruction.replaceChildren) { + return utils.createElement(node, index, node.data, [ + processingInstruction.processNode(node, children, index), + ]); } - }; - var parseWithInstructions = function(html, isValidNode, processingInstructions) { - var domTree = parseHtmlToTree(html); - // TODO: Deal with HTML that contains more than one root level node - if (domTree && domTree.length !== 1) { - throw new Error( - 'html-to-react currently only supports HTML with one single root element. ' + - 'The HTML provided contains ' + domTree.length + - ' root elements. You can fix that by simply wrapping your HTML ' + - 'in a
element.'); - } - return traverseDom(domTree[0], isValidNode, processingInstructions, 0); - }; + return processingInstruction.processNode(node, children, index); + } else { + return false; + } + } else { + return false; + } + }; + + function parseWithInstructions(html, isValidNode, processingInstructions) { + var domTree = parseHtmlToTree(html); + // TODO: Deal with HTML that contains more than one root level node + if (domTree && domTree.length !== 1) { + throw new Error( + 'html-to-react currently only supports HTML with one single root element. ' + + 'The HTML provided contains ' + domTree.length + + ' root elements. You can fix that by simply wrapping your HTML ' + + 'in a
element.'); + } + return traverseDom(domTree[0], isValidNode, processingInstructions, 0); + }; - var parse = function(html) { - var processingInstructions = new ProcessingInstructions(); - return parseWithInstructions(html, - IsValidNodeDefinitions.alwaysValid, - processingInstructions.defaultProcessingInstructions); - }; + function parse(html) { + var processingInstructions = new ProcessingInstructions(); + return parseWithInstructions(html, + IsValidNodeDefinitions.alwaysValid, + processingInstructions.defaultProcessingInstructions); + }; - return { - parse: parse, - parseWithInstructions: parseWithInstructions, - }; + return { + parse: parse, + parseWithInstructions: parseWithInstructions, + }; }; module.exports = Html2ReactParser; diff --git a/lib/process-node-definitions.js b/lib/process-node-definitions.js index 38c5a70..bb59fda 100644 --- a/lib/process-node-definitions.js +++ b/lib/process-node-definitions.js @@ -4,31 +4,31 @@ var utils = require('./utils'); // https://github.com/facebook/react/blob/15.0-stable/src/renderers/dom/shared/ReactDOMComponent.js#L457 var voidElementTags = [ - 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', - 'source', 'track', 'wbr', 'menuitem', 'textarea', + 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', + 'source', 'track', 'wbr', 'menuitem', 'textarea', ]; -var ProcessNodeDefinitions = function() { - function processDefaultNode(node, children, index) { - if (node.type === 'text') { - return ent.decode(node.data); - } else if (node.type === 'comment') { - // FIXME: The following doesn't work as the generated HTML results in - // "<!-- This is a comment -->" - // return ''; - return false; - } +function ProcessNodeDefinitions() { + function processDefaultNode(node, children, index) { + if (node.type === 'text') { + return ent.decode(node.data); + } else if (node.type === 'comment') { + // FIXME: The following doesn't work as the generated HTML results in + // "<!-- This is a comment -->" + // return ''; + return false; + } - if (voidElementTags.indexOf(node.name) > -1) { - return utils.createElement(node, index); - } else { - return utils.createElement(node, index, node.data, children); - } + if (voidElementTags.indexOf(node.name) > -1) { + return utils.createElement(node, index); + } else { + return utils.createElement(node, index, node.data, children); } + } - return { - processDefaultNode: processDefaultNode, - }; -}; + return { + processDefaultNode: processDefaultNode, + }; +} module.exports = ProcessNodeDefinitions; diff --git a/lib/processing-instructions.js b/lib/processing-instructions.js index fc8a409..26fe8ce 100644 --- a/lib/processing-instructions.js +++ b/lib/processing-instructions.js @@ -2,15 +2,15 @@ var ShouldProcessNodeDefinitions = require('./should-process-node-definitions'); var ProcessNodeDefinitions = require('./process-node-definitions'); -var ProcessingInstructions = function() { - var processNodeDefinitions = new ProcessNodeDefinitions(); +function ProcessingInstructions() { + var processNodeDefinitions = new ProcessNodeDefinitions(); - return { - defaultProcessingInstructions: [{ - shouldProcessNode: ShouldProcessNodeDefinitions.shouldProcessEveryNode, - processNode: processNodeDefinitions.processDefaultNode, - },], - }; + return { + defaultProcessingInstructions: [{ + shouldProcessNode: ShouldProcessNodeDefinitions.shouldProcessEveryNode, + processNode: processNodeDefinitions.processDefaultNode, + },], + }; }; module.exports = ProcessingInstructions; diff --git a/lib/should-process-node-definitions.js b/lib/should-process-node-definitions.js index 624137e..1d28bd1 100644 --- a/lib/should-process-node-definitions.js +++ b/lib/should-process-node-definitions.js @@ -1,8 +1,8 @@ 'use strict'; function shouldProcessEveryNode(node) { - return true; + return true; } module.exports = { - shouldProcessEveryNode: shouldProcessEveryNode, + shouldProcessEveryNode: shouldProcessEveryNode, }; diff --git a/lib/utils.js b/lib/utils.js index 24ee3c5..445ea65 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -6,48 +6,48 @@ var React = require('react'); var camelCaseAttrMap = require('./camel-case-attribute-names'); function createStyleJsonFromString(styleString) { - if (!styleString) { - return {}; + if (!styleString) { + return {}; + } + var styles = styleString.split(';'); + var singleStyle, key, value, jsonStyles = {}; + for (var i = 0; i < styles.length; i++) { + singleStyle = styles[i].split(':'); + key = camelize(singleStyle[0]); + value = singleStyle[1]; + if (key.length > 0 && value.length > 0) { + jsonStyles[key] = value; } - var styles = styleString.split(';'); - var singleStyle, key, value, jsonStyles = {}; - for (var i = 0; i < styles.length; i++) { - singleStyle = styles[i].split(':'); - key = camelize(singleStyle[0]); - value = singleStyle[1]; - if (key.length > 0 && value.length > 0) { - jsonStyles[key] = value; - } - } - return jsonStyles; + } + return jsonStyles; } function createElement(node, index, data, children) { - var elementProps = { - key: index, - }; - if (node.attribs) { - elementProps = reduce(function(result, keyAndValue) { - var key = keyAndValue[0]; - var value = keyAndValue[1]; - key = camelCaseAttrMap[key.replace(/-/, '')] || key; - if (key === 'style') { - value = createStyleJsonFromString(value); - } else if (key === 'class') { - key = 'className'; - } - result[key] = value || key; - return result; - }, elementProps, toPairs(node.attribs)); - } + var elementProps = { + key: index, + }; + if (node.attribs) { + elementProps = reduce(function(result, keyAndValue) { + var key = keyAndValue[0]; + var value = keyAndValue[1]; + key = camelCaseAttrMap[key.replace(/-/, '')] || key; + if (key === 'style') { + value = createStyleJsonFromString(value); + } else if (key === 'class') { + key = 'className'; + } + result[key] = value || key; + return result; + }, elementProps, toPairs(node.attribs)); + } - children = children || []; - var allChildren = data != null ? [data,].concat(children) : children; - return React.createElement.apply( - null, [node.name, elementProps,].concat(allChildren) - ); + children = children || []; + var allChildren = data != null ? [data,].concat(children) : children; + return React.createElement.apply( + null, [node.name, elementProps,].concat(allChildren) + ); } module.exports = { - createElement: createElement, + createElement: createElement, }; From 8cf03c9c7fe712d32381bd6c50dcd0cbb275ec1f Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sat, 15 Oct 2016 15:14:29 +0200 Subject: [PATCH 073/227] Fix indentation --- test/html-to-react-tests.js | 621 ++++++++++++++++++------------------ 1 file changed, 310 insertions(+), 311 deletions(-) diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index ee92e41..c0a45bf 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -1,5 +1,4 @@ 'use strict'; - var assert = require('assert'); var React = require('react'); var ReactDOMServer = require('react-dom/server'); @@ -9,386 +8,386 @@ var Parser = require('..').Parser; var ProcessNodeDefinitions = require('..').ProcessNodeDefinitions; describe('Html2React', function () { - var parser = new Parser(); + var parser = new Parser(); - describe('parse valid HTML', function () { - it('should return a valid HTML string', function () { - var htmlInput = '

Does this work?

'; + describe('parse valid HTML', function () { + it('should return a valid HTML string', function () { + var htmlInput = '

Does this work?

'; - var reactComponent = parser.parse(htmlInput); - var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, htmlInput); - }); + assert.equal(reactHtml, htmlInput); + }); - it('should return a valid HTML string with nested elements', function () { - var htmlInput = '

Heading

'; + it('should return a valid HTML string with nested elements', function () { + var htmlInput = '

Heading

'; - var reactComponent = parser.parse(htmlInput); - var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, htmlInput); - }); + assert.equal(reactHtml, htmlInput); + }); - it('should return a valid HTML string with inline styles', function () { - var htmlInput = '
'; + it('should return a valid HTML string with inline styles', function () { + var htmlInput = '
'; - var reactComponent = parser.parse(htmlInput); - var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, htmlInput); - }); + assert.equal(reactHtml, htmlInput); + }); - it('should return a valid HTML string with empty inline styles', function () { - var htmlInput = '
'; - var htmlExpected = '
'; + it('should return a valid HTML string with empty inline styles', function () { + var htmlInput = '
'; + var htmlExpected = '
'; - var reactComponent = parser.parse(htmlInput); - var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, htmlExpected); - }); + assert.equal(reactHtml, htmlExpected); + }); - it('should return a valid HTML string with data attributes', function () { - var htmlInput = '
'; + it('should return a valid HTML string with data attributes', function () { + var htmlInput = '
'; - var reactComponent = parser.parse(htmlInput); - var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, htmlInput); - }); + assert.equal(reactHtml, htmlInput); + }); - it('should return a valid HTML string with aria attributes', function () { - var htmlInput = '
'; + it('should return a valid HTML string with aria attributes', function () { + var htmlInput = '
'; - var reactComponent = parser.parse(htmlInput); - var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, htmlInput); - }); + assert.equal(reactHtml, htmlInput); + }); - it('should return a valid HTML string with a class attribute', function () { - var htmlInput = '
'; + it('should return a valid HTML string with a class attribute', function () { + var htmlInput = '
'; - var reactComponent = parser.parse(htmlInput); - var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, htmlInput); - }); + assert.equal(reactHtml, htmlInput); + }); - it('should return a valid HTML string with a react camelCase attribute', function () { - var htmlInput = '
'; + it('should return a valid HTML string with a react camelCase attribute', function () { + var htmlInput = '
'; - var reactComponent = parser.parse(htmlInput); - var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, htmlInput); - }); + assert.equal(reactHtml, htmlInput); + }); - it('should handle dashed attributes', function () { - var input = '
'; - var reactComponent = parser.parse(input); - var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + it('should handle dashed attributes', function () { + var input = '
' + + '
'; + var reactComponent = parser.parse(input); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, input); - }); + assert.equal(reactHtml, input); + }); - // FIXME: See lib/process-node-definitions.js -> processDefaultNode() - it.skip('should return a valid HTML string with comments', function () { - var htmlInput = '
'; + // FIXME: See lib/process-node-definitions.js -> processDefaultNode() + it.skip('should return a valid HTML string with comments', function () { + var htmlInput = '
'; - var reactComponent = parser.parse(htmlInput); - var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, htmlInput); - }); + assert.equal(reactHtml, htmlInput); + }); - // FIXME: If / when React implements HTML comments, this test can be removed - it('should return a valid HTML string without comments', function () { - var htmlInput = '
'; - var htmlExpected = '
'; + // FIXME: If / when React implements HTML comments, this test can be removed + it('should return a valid HTML string without comments', function () { + var htmlInput = '
'; + var htmlExpected = '
'; - var reactComponent = parser.parse(htmlInput); - var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, htmlExpected); - }); + assert.equal(reactHtml, htmlExpected); + }); - it('should parse br elements without warnings', function () { - var htmlInput = '

Line one
Line two
Line three

'; - var htmlExpected = '

Line one
Line two
Line three

'; + it('should parse br elements without warnings', function () { + var htmlInput = '

Line one
Line two
Line three

'; + var htmlExpected = '

Line one
Line two
Line three

'; - var reactComponent = parser.parse(htmlInput); - var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, htmlExpected); - }); + assert.equal(reactHtml, htmlExpected); + }); - it('should not generate children for br tags', function () { - var htmlInput = '
'; + it('should not generate children for br tags', function () { + var htmlInput = '
'; - var reactComponent = parser.parse(htmlInput); - assert.strictEqual((reactComponent.props.children || []).length, 0); - }); + var reactComponent = parser.parse(htmlInput); + assert.strictEqual((reactComponent.props.children || []).length, 0); + }); - it('should parse void elements with all attributes and no warnings', function () { - var htmlInput = '

'; + it('should parse void elements with all attributes and no warnings', function () { + var htmlInput = '

'; - var reactComponent = parser.parse(htmlInput); - var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, htmlInput); - }); + assert.equal(reactHtml, htmlInput); + }); - // Covers issue #9 - it('should parse textarea elements', function () { - var htmlInput = ''; + // Covers issue #9 + it('should parse textarea elements', function () { + var htmlInput = ''; - var reactComponent = parser.parse(htmlInput); - var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, htmlInput); - }); + assert.equal(reactHtml, htmlInput); + }); - it('should generate keys for sequence items', function () { - var htmlInput = '
  • Item 1
  • Item 2
  • <
'; + it('should generate keys for sequence items', function () { + var htmlInput = '
  • Item 1
  • Item 2
  • <
'; - var reactComponent = parser.parse(htmlInput); + var reactComponent = parser.parse(htmlInput); - var children = R.filter(function (c) { - return R.has('key', c); - }, R.flatten(reactComponent.props.children)); - var keys = R.map(function (child) { - return child.key; - }, children); - assert.deepStrictEqual(keys, ['0', '1', ]); - }); + var children = R.filter(function (c) { + return R.has('key', c); + }, R.flatten(reactComponent.props.children)); + var keys = R.map(function (child) { + return child.key; + }, children); + assert.deepStrictEqual(keys, ['0', '1', ]); + }); - it('should parse br elements without warnings', function () { - var htmlInput = '

Line one
Line two
Line three

'; - var htmlExpected = '

Line one
Line two
Line three

'; + it('should parse br elements without warnings', function () { + var htmlInput = '

Line one
Line two
Line three

'; + var htmlExpected = '

Line one
Line two
Line three

'; - var reactComponent = parser.parse(htmlInput); - var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, htmlExpected); - }); + assert.equal(reactHtml, htmlExpected); + }); - it('should parse src elements with all attributes but without warnings', function () { - var htmlInput = '

'; + it('should parse src elements with all attributes but without warnings', function () { + var htmlInput = '

'; - var reactComponent = parser.parse(htmlInput); - var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, htmlInput); - }); + assert.equal(reactHtml, htmlInput); + }); - it('should decode character entities in text nodes', function () { - var htmlInput = '
1 < 2
'; + it('should decode character entities in text nodes', function () { + var htmlInput = '
1 < 2
'; - var reactComponent = parser.parse(htmlInput); - var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, htmlInput); - }); + assert.equal(reactHtml, htmlInput); + }); - it('should not generate children for childless elements', function () { - var htmlInput = '
'; + it('should not generate children for childless elements', function () { + var htmlInput = '
'; - var reactComponent = parser.parse(htmlInput); + var reactComponent = parser.parse(htmlInput); - assert.strictEqual((reactComponent.props.children || []).length, 0); - }); + assert.strictEqual((reactComponent.props.children || []).length, 0); + }); - it('should fill in the key name with boolean attribute', function () { - var htmlInput = ''; - var htmlExpected = ''; + it('should fill in the key name with boolean attribute', function () { + var htmlInput = ''; + var htmlExpected = ''; - var reactComponent = parser.parse(htmlInput); - var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, htmlExpected); - }); + assert.equal(reactHtml, htmlExpected); }); + }); - describe('parse invalid HTML', function () { - it('should throw an error when trying parsing multiple root elements', function () { - var htmlInput = '
'; + describe('parse invalid HTML', function () { + it('should throw an error when trying parsing multiple root elements', function () { + var htmlInput = '
'; - assert.throws(function () { - parser.parse(htmlInput); - }, Error); - }); + assert.throws(function () { + parser.parse(htmlInput); + }, Error); + }); - it('should throw an error with a specific message when parsing multiple root elements', - function () { - var htmlInput = '
'; + it('should throw an error with a specific message when parsing multiple root elements', + function () { + var htmlInput = '
'; - assert.throws(function () { - parser.parse(htmlInput); - }, /contains 3 root elements/); - }); + assert.throws(function () { + parser.parse(htmlInput); + }, /contains 3 root elements/); + }); - it('should fix missing closing tags', function () { - var htmlInput = '

'; - var htmlExpected = '

'; + it('should fix missing closing tags', function () { + var htmlInput = '

'; + var htmlExpected = '

'; - var reactComponent = parser.parse(htmlInput); - var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, htmlExpected); - }); + assert.equal(reactHtml, htmlExpected); }); + }); - describe('with custom processing instructions', function () { - var parser = new Parser(); - var processNodeDefinitions = new ProcessNodeDefinitions(React); - - describe('parse valid HTML', function () { - it('should return nothing with only a single

element', function () { - var htmlInput = '

Does this work?

'; - var isValidNode = function () { - return true; - }; - var processingInstructions = [{ - shouldProcessNode: function (node) { - return node.name && node.name !== 'p'; - }, - processNode: processNodeDefinitions.processDefaultNode, - },]; - var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, - processingInstructions); - - // With only 1

element, nothing is rendered - assert.equal(reactComponent, false); - }); - - it('should return a single

element within a div of

and

as siblings', - function () { - var htmlInput = '

Title

Paragraph

'; - var htmlExpected = '

Title

'; - - var isValidNode = function () { - return true; - }; - - var processingInstructions = [{ - shouldProcessNode: function (node) { - return node.type === 'text' || node.name !== 'p'; - }, - processNode: processNodeDefinitions.processDefaultNode, - },]; - var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, - processingInstructions); - var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, htmlExpected); - }); - - it('should replace the children of an element', function () { - var htmlInput = '

Text

Text

'; - var htmlExpected = '

Heading

'; - - var isValidNode = function () { - return true; - }; - - var processingInstructions = [ - { - replaceChildren: true, - shouldProcessNode: function (node) { - return node.attribs && node.attribs['data-test'] === 'foo'; - }, - processNode: function (node, children, index) { - return React.createElement('h1', {key: index,}, 'Heading'); - }, - }, - { - // Anything else - shouldProcessNode: function (node) { - return true; - }, - processNode: processNodeDefinitions.processDefaultNode, - }, - ]; - - var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, - processingInstructions); - var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, htmlExpected); - }); - - it('should return capitalized content for all

elements', function () { - var htmlInput = '

Title

Paragraph

' + - '

Another title

'; - var htmlExpected = '

TITLE

Paragraph

' + - '

ANOTHER TITLE

'; - - var isValidNode = function () { - return true; - }; - - var processingInstructions = [ - { - // Custom

processing - shouldProcessNode: function (node) { - return node.parent && node.parent.name && - node.parent.name === 'h1'; - }, - processNode: function (node, children) { - return node.data.toUpperCase(); - }, - }, { - // Anything else - shouldProcessNode: function (node) { - return true; - }, - processNode: processNodeDefinitions.processDefaultNode, - }, - ]; - var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, - processingInstructions); - var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert.equal(reactHtml, htmlExpected); - }); - - it('should return false in case of invalid node', function () { - var htmlInput = '

'; - var processingInstructions = [{ - shouldProcessNode: function (node) { return true; }, - processNode: processNodeDefinitions.processDefaultNode, - }, ]; - var reactComponent = parser.parseWithInstructions(htmlInput, - function () { return false; }, processingInstructions); - - assert.equal(reactComponent, false); - }); - - it('should generate only valid children', function () { - var htmlInput = '

'; - - var processingInstructions = [{ - shouldProcessNode: function (node) { return true; }, - processNode: processNodeDefinitions.processDefaultNode, - }, ]; - var reactComponent = parser.parseWithInstructions(htmlInput, - function (node) { - // skip whitespace text nodes to clean up children - if (node.type === 'text') { - return node.data.trim() !== ''; - } - return true; - }, processingInstructions); - - assert.equal(reactComponent.props.children.length, 2); - }); - - it('should not affect unhandled whitespace', function () { - var htmlInput = '

'; - - var reactComponent = parser.parse(htmlInput); - - assert.equal(reactComponent.props.children.length, 5); - }); - }); + describe('with custom processing instructions', function () { + var parser = new Parser(); + var processNodeDefinitions = new ProcessNodeDefinitions(React); + + describe('parse valid HTML', function () { + it('should return nothing with only a single

element', function () { + var htmlInput = '

Does this work?

'; + var isValidNode = function () { + return true; + }; + var processingInstructions = [{ + shouldProcessNode: function (node) { + return node.name && node.name !== 'p'; + }, + processNode: processNodeDefinitions.processDefaultNode, + },]; + var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, + processingInstructions); + + // With only 1

element, nothing is rendered + assert.equal(reactComponent, false); + }); + + it('should return a single

element within a div of

and

as siblings', + function () { + var htmlInput = '

Title

Paragraph

'; + var htmlExpected = '

Title

'; + + var isValidNode = function () { + return true; + }; + + var processingInstructions = [{ + shouldProcessNode: function (node) { + return node.type === 'text' || node.name !== 'p'; + }, + processNode: processNodeDefinitions.processDefaultNode, + },]; + var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, + processingInstructions); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + assert.equal(reactHtml, htmlExpected); + }); + + it('should replace the children of an element', function () { + var htmlInput = '

Text

Text

'; + var htmlExpected = '

Heading

'; + + var isValidNode = function () { + return true; + }; + + var processingInstructions = [ + { + replaceChildren: true, + shouldProcessNode: function (node) { + return node.attribs && node.attribs['data-test'] === 'foo'; + }, + processNode: function (node, children, index) { + return React.createElement('h1', {key: index,}, 'Heading'); + }, + }, + { + // Anything else + shouldProcessNode: function (node) { + return true; + }, + processNode: processNodeDefinitions.processDefaultNode, + }, + ]; + + var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, + processingInstructions); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + assert.equal(reactHtml, htmlExpected); + }); + + it('should return capitalized content for all

elements', function () { + var htmlInput = '

Title

Paragraph

' + + '

Another title

'; + var htmlExpected = '

TITLE

Paragraph

' + + '

ANOTHER TITLE

'; + + var isValidNode = function () { + return true; + }; + + var processingInstructions = [ + { + // Custom

processing + shouldProcessNode: function (node) { + return node.parent && node.parent.name && + node.parent.name === 'h1'; + }, + processNode: function (node, children) { + return node.data.toUpperCase(); + }, + }, { + // Anything else + shouldProcessNode: function (node) { + return true; + }, + processNode: processNodeDefinitions.processDefaultNode, + }, + ]; + var reactComponent = parser.parseWithInstructions(htmlInput, isValidNode, + processingInstructions); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + assert.equal(reactHtml, htmlExpected); + }); + + it('should return false in case of invalid node', function () { + var htmlInput = '

'; + var processingInstructions = [{ + shouldProcessNode: function (node) { return true; }, + processNode: processNodeDefinitions.processDefaultNode, + }, ]; + var reactComponent = parser.parseWithInstructions(htmlInput, + function () { return false; }, processingInstructions); + + assert.equal(reactComponent, false); + }); + + it('should generate only valid children', function () { + var htmlInput = '

'; + + var processingInstructions = [{ + shouldProcessNode: function (node) { return true; }, + processNode: processNodeDefinitions.processDefaultNode, + }, ]; + var reactComponent = parser.parseWithInstructions(htmlInput, function (node) { + // skip whitespace text nodes to clean up children + if (node.type === 'text') { + return node.data.trim() !== ''; + } + return true; + }, processingInstructions); + + assert.equal(reactComponent.props.children.length, 2); + }); + + it('should not affect unhandled whitespace', function () { + var htmlInput = '

'; + + var reactComponent = parser.parse(htmlInput); + + assert.equal(reactComponent.props.children.length, 5); + }); }); + }); }); From 773227616ce48102413d57dcd424330f785f8a0d Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Mon, 17 Oct 2016 19:07:30 +0200 Subject: [PATCH 074/227] Bump version --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58a74e4..7b7ccb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## [v1.2.1](https://github.com/aknuds1/html-to-react/tree/v1.2.1) + +[Full Changelog](https://github.com/aknuds1/html-to-react/compare/v1.2.0...v1.2.1) + ## [v1.2.0](https://github.com/aknuds1/html-to-react/tree/v1.2.0) [Full Changelog](https://github.com/aknuds1/html-to-react/compare/v1.1.2...v1.2.0) diff --git a/package.json b/package.json index 8fa1bae..5311352 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "html-to-react", - "version": "1.2.0", + "version": "1.2.1", "description": "A lightweight library that converts raw HTML to a React DOM structure.", "main": "index.js", "scripts": { From a01d4f9a2bb8b66bdbe8bc1433a36fd7f106e8ee Mon Sep 17 00:00:00 2001 From: "FUJI Goro (gfx)" Date: Fri, 28 Oct 2016 17:00:51 +0900 Subject: [PATCH 075/227] handle xmlns attributes (e.g. xlink:href) --- lib/utils.js | 2 +- test/html-to-react-tests.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index 445ea65..925262d 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -30,7 +30,7 @@ function createElement(node, index, data, children) { elementProps = reduce(function(result, keyAndValue) { var key = keyAndValue[0]; var value = keyAndValue[1]; - key = camelCaseAttrMap[key.replace(/-/, '')] || key; + key = camelCaseAttrMap[key.replace(/[-:]/, '')] || key; if (key === 'style') { value = createStyleJsonFromString(value); } else if (key === 'class') { diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index c0a45bf..75d0d1b 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -390,4 +390,14 @@ describe('Html2React', function () { }); }); }); + + describe('parse SVG', function () { + it('should have correct attributes', function () { + var svgInput = ''; + var reactComponent = parser.parse(svgInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + + assert(/ Date: Fri, 28 Oct 2016 10:35:43 +0200 Subject: [PATCH 076/227] Make line shorter --- test/html-to-react-tests.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index 75d0d1b..283b444 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -397,7 +397,8 @@ describe('Html2React', function () { var reactComponent = parser.parse(svgInput); var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); - assert(/ Date: Tue, 1 Nov 2016 12:44:06 +0000 Subject: [PATCH 077/227] Bump version --- CHANGELOG.md | 10 +++++++++- package.json | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b7ccb5..9b5dd40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log +## [v1.2.2](https://github.com/aknuds1/html-to-react/tree/v1.2.2) + +[Full Changelog](https://github.com/aknuds1/html-to-react/compare/v1.2.1...v1.2.2) + +**Merged pull requests:** + +- handle xmlns attributes (e.g. xlink:href) [\#9](https://github.com/aknuds1/html-to-react/pull/9) ([gfx](https://github.com/gfx)) + ## [v1.2.1](https://github.com/aknuds1/html-to-react/tree/v1.2.1) [Full Changelog](https://github.com/aknuds1/html-to-react/compare/v1.2.0...v1.2.1) @@ -10,7 +18,7 @@ **Merged pull requests:** -- Replace element children [\#2]https://github.com/aknuds1/html-to-react/pull/3) ([hirefrederick](HireFrederick)) +- Replace element children [\#2](https://github.com/aknuds1/html-to-react/pull/3) ([hirefrederick](https://github.com/hirefrederick)) ## [v1.1.2](https://github.com/aknuds1/html-to-react/tree/v1.1.2) diff --git a/package.json b/package.json index 5311352..8069773 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "html-to-react", - "version": "1.2.1", + "version": "1.2.2", "description": "A lightweight library that converts raw HTML to a React DOM structure.", "main": "index.js", "scripts": { From 3bd4301b56e1472354f76ac60cd08a139f2ecd44 Mon Sep 17 00:00:00 2001 From: greenkeeperio-bot Date: Thu, 29 Dec 2016 00:17:44 +0100 Subject: [PATCH 078/227] chore(package): update ramda to version 0.23.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8069773..8e15c40 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "dependencies": { "ent": "^2.2.0", "htmlparser2": "^3.8.3", - "ramda": "^0.22.0", + "ramda": "^0.23.0", "underscore.string.fp": "^1.0.4" }, "peerDependencies": { From 4290073f3ac0e3e9967cca2c55f5ee365cdf8860 Mon Sep 17 00:00:00 2001 From: Gerhard Sletten Date: Tue, 31 Jan 2017 21:32:30 +0100 Subject: [PATCH 079/227] Occurrence of ampersand in attributes are decoded to `&` to avoid another encoding of `&` --- lib/utils.js | 3 +++ test/html-to-react-tests.js | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/lib/utils.js b/lib/utils.js index 925262d..1c29604 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -36,6 +36,9 @@ function createElement(node, index, data, children) { } else if (key === 'class') { key = 'className'; } + if (value && value.indexOf && value.indexOf('&')) { + value = value.replace(/&/g, '&'); + } result[key] = value || key; return result; }, elementProps, toPairs(node.attribs)); diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index 283b444..6eb54b4 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -209,6 +209,15 @@ describe('Html2React', function () { assert.equal(reactHtml, htmlExpected); }); + + it('should decode ampersands in attributes so react do not encode the ampersand', function () { + var htmlInput = '

A link

'; + + var reactComponent = parser.parse(htmlInput); + var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); + + assert.equal(reactHtml, htmlInput); + }); }); describe('parse invalid HTML', function () { From eda76adafec0b2a1ea5c826438d1506d3bf15ac7 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sat, 4 Feb 2017 18:37:00 +0100 Subject: [PATCH 080/227] More robust attribute decoding --- lib/utils.js | 5 +++-- test/html-to-react-tests.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 1c29604..a5b0a41 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -4,6 +4,7 @@ var toPairs = require('ramda/src/toPairs'); var reduce = require('ramda/src/reduce'); var React = require('react'); var camelCaseAttrMap = require('./camel-case-attribute-names'); +var ent = require('ent'); function createStyleJsonFromString(styleString) { if (!styleString) { @@ -36,8 +37,8 @@ function createElement(node, index, data, children) { } else if (key === 'class') { key = 'className'; } - if (value && value.indexOf && value.indexOf('&')) { - value = value.replace(/&/g, '&'); + if (typeof value === 'string') { + value = ent.decode(value); } result[key] = value || key; return result; diff --git a/test/html-to-react-tests.js b/test/html-to-react-tests.js index 6eb54b4..0c276e9 100644 --- a/test/html-to-react-tests.js +++ b/test/html-to-react-tests.js @@ -210,7 +210,7 @@ describe('Html2React', function () { assert.equal(reactHtml, htmlExpected); }); - it('should decode ampersands in attributes so react do not encode the ampersand', function () { + it('should decode attribute values to avoid React re-encoding them', function () { var htmlInput = '

A link

'; var reactComponent = parser.parse(htmlInput); From 37e76e2275e48a6d3259a8ff7dade3dda619ea4a Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sat, 4 Feb 2017 18:37:59 +0100 Subject: [PATCH 081/227] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8e15c40..1dbdf3f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "html-to-react", - "version": "1.2.2", + "version": "1.2.3", "description": "A lightweight library that converts raw HTML to a React DOM structure.", "main": "index.js", "scripts": { From 6eaed3f9212796086b39d75a7f67e9950d2cfa36 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Sat, 4 Feb 2017 18:41:32 +0100 Subject: [PATCH 082/227] Update changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b5dd40..82e0734 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log +## [v1.2.3](https://github.com/aknuds1/html-to-react/tree/v1.2.3) + +[Full Changelog](https://github.com/aknuds1/html-to-react/compare/v1.2.2...v1.2.3) + +**Merged pull requests:** + +- Occurrence of ampersand in attributes are decoded to `&` to avoid ano… [\#15](https://github.com/aknuds1/html-to-react/pull/15) ([gerhardsletten](https://github.com/gerhardsletten)) + ## [v1.2.2](https://github.com/aknuds1/html-to-react/tree/v1.2.2) [Full Changelog](https://github.com/aknuds1/html-to-react/compare/v1.2.1...v1.2.2) From da6f2dde360d1f49e7a09407f0663d0044f2fef8 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Wed, 8 Feb 2017 10:03:47 +0100 Subject: [PATCH 083/227] Add bundled library --- dist/html-to-react.js | 17324 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 17324 insertions(+) create mode 100644 dist/html-to-react.js diff --git a/dist/html-to-react.js b/dist/html-to-react.js new file mode 100644 index 0000000..c1ecae5 --- /dev/null +++ b/dist/html-to-react.js @@ -0,0 +1,17324 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0) { + throw new Error('Invalid string. Length must be a multiple of 4') + } + + // the number of equal signs (place holders) + // if there are two placeholders, than the two characters before it + // represent one byte + // if there is only one, then the three characters before it represent 2 bytes + // this is just a cheap hack to not do indexOf twice + return b64[len - 2] === '=' ? 2 : b64[len - 1] === '=' ? 1 : 0 +} + +function byteLength (b64) { + // base64 is 4/3 + up to two characters of the original data + return b64.length * 3 / 4 - placeHoldersCount(b64) +} + +function toByteArray (b64) { + var i, j, l, tmp, placeHolders, arr + var len = b64.length + placeHolders = placeHoldersCount(b64) + + arr = new Arr(len * 3 / 4 - placeHolders) + + // if there are placeholders, only get up to the last complete 4 chars + l = placeHolders > 0 ? len - 4 : len + + var L = 0 + + for (i = 0, j = 0; i < l; i += 4, j += 3) { + tmp = (revLookup[b64.charCodeAt(i)] << 18) | (revLookup[b64.charCodeAt(i + 1)] << 12) | (revLookup[b64.charCodeAt(i + 2)] << 6) | revLookup[b64.charCodeAt(i + 3)] + arr[L++] = (tmp >> 16) & 0xFF + arr[L++] = (tmp >> 8) & 0xFF + arr[L++] = tmp & 0xFF + } + + if (placeHolders === 2) { + tmp = (revLookup[b64.charCodeAt(i)] << 2) | (revLookup[b64.charCodeAt(i + 1)] >> 4) + arr[L++] = tmp & 0xFF + } else if (placeHolders === 1) { + tmp = (revLookup[b64.charCodeAt(i)] << 10) | (revLookup[b64.charCodeAt(i + 1)] << 4) | (revLookup[b64.charCodeAt(i + 2)] >> 2) + arr[L++] = (tmp >> 8) & 0xFF + arr[L++] = tmp & 0xFF + } + + return arr +} + +function tripletToBase64 (num) { + return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F] +} + +function encodeChunk (uint8, start, end) { + var tmp + var output = [] + for (var i = start; i < end; i += 3) { + tmp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]) + output.push(tripletToBase64(tmp)) + } + return output.join('') +} + +function fromByteArray (uint8) { + var tmp + var len = uint8.length + var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes + var output = '' + var parts = [] + var maxChunkLength = 16383 // must be multiple of 3 + + // go through the array every three bytes, we'll deal with trailing stuff later + for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) { + parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength))) + } + + // pad the end with zeros, but make sure to not forget the extra bytes + if (extraBytes === 1) { + tmp = uint8[len - 1] + output += lookup[tmp >> 2] + output += lookup[(tmp << 4) & 0x3F] + output += '==' + } else if (extraBytes === 2) { + tmp = (uint8[len - 2] << 8) + (uint8[len - 1]) + output += lookup[tmp >> 10] + output += lookup[(tmp >> 4) & 0x3F] + output += lookup[(tmp << 2) & 0x3F] + output += '=' + } + + parts.push(output) + + return parts.join('') +} + +},{}],2:[function(require,module,exports){ + +},{}],3:[function(require,module,exports){ +// shim for using process in browser +var process = module.exports = {}; + +// cached from whatever global is present so that test runners that stub it +// don't break things. But we need to wrap it in a try catch in case it is +// wrapped in strict mode code which doesn't define any globals. It's inside a +// function because try/catches deoptimize in certain engines. + +var cachedSetTimeout; +var cachedClearTimeout; + +function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); +} +function defaultClearTimeout () { + throw new Error('clearTimeout has not been defined'); +} +(function () { + try { + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; + } + } catch (e) { + cachedSetTimeout = defaultSetTimout; + } + try { + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; + } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; + } +} ()) +function runTimeout(fun) { + if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations + return setTimeout(fun, 0); + } + // if setTimeout wasn't available but was latter defined + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch(e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch(e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } + + +} +function runClearTimeout(marker) { + if (cachedClearTimeout === clearTimeout) { + //normal enviroments in sane situations + return clearTimeout(marker); + } + // if clearTimeout wasn't available but was latter defined + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } + + + +} +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; + +function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } +} + +function drainQueue() { + if (draining) { + return; + } + var timeout = runTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + runClearTimeout(timeout); +} + +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + +},{}],4:[function(require,module,exports){ +(function (global){ +'use strict'; + +var buffer = require('buffer'); +var Buffer = buffer.Buffer; +var SlowBuffer = buffer.SlowBuffer; +var MAX_LEN = buffer.kMaxLength || 2147483647; +exports.alloc = function alloc(size, fill, encoding) { + if (typeof Buffer.alloc === 'function') { + return Buffer.alloc(size, fill, encoding); + } + if (typeof encoding === 'number') { + throw new TypeError('encoding must not be number'); + } + if (typeof size !== 'number') { + throw new TypeError('size must be a number'); + } + if (size > MAX_LEN) { + throw new RangeError('size is too large'); + } + var enc = encoding; + var _fill = fill; + if (_fill === undefined) { + enc = undefined; + _fill = 0; + } + var buf = new Buffer(size); + if (typeof _fill === 'string') { + var fillBuf = new Buffer(_fill, enc); + var flen = fillBuf.length; + var i = -1; + while (++i < size) { + buf[i] = fillBuf[i % flen]; + } + } else { + buf.fill(_fill); + } + return buf; +} +exports.allocUnsafe = function allocUnsafe(size) { + if (typeof Buffer.allocUnsafe === 'function') { + return Buffer.allocUnsafe(size); + } + if (typeof size !== 'number') { + throw new TypeError('size must be a number'); + } + if (size > MAX_LEN) { + throw new RangeError('size is too large'); + } + return new Buffer(size); +} +exports.from = function from(value, encodingOrOffset, length) { + if (typeof Buffer.from === 'function' && (!global.Uint8Array || Uint8Array.from !== Buffer.from)) { + return Buffer.from(value, encodingOrOffset, length); + } + if (typeof value === 'number') { + throw new TypeError('"value" argument must not be a number'); + } + if (typeof value === 'string') { + return new Buffer(value, encodingOrOffset); + } + if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) { + var offset = encodingOrOffset; + if (arguments.length === 1) { + return new Buffer(value); + } + if (typeof offset === 'undefined') { + offset = 0; + } + var len = length; + if (typeof len === 'undefined') { + len = value.byteLength - offset; + } + if (offset >= value.byteLength) { + throw new RangeError('\'offset\' is out of bounds'); + } + if (len > value.byteLength - offset) { + throw new RangeError('\'length\' is out of bounds'); + } + return new Buffer(value.slice(offset, offset + len)); + } + if (Buffer.isBuffer(value)) { + var out = new Buffer(value.length); + value.copy(out, 0, 0, value.length); + return out; + } + if (value) { + if (Array.isArray(value) || (typeof ArrayBuffer !== 'undefined' && value.buffer instanceof ArrayBuffer) || 'length' in value) { + return new Buffer(value); + } + if (value.type === 'Buffer' && Array.isArray(value.data)) { + return new Buffer(value.data); + } + } + + throw new TypeError('First argument must be a string, Buffer, ' + 'ArrayBuffer, Array, or array-like object.'); +} +exports.allocUnsafeSlow = function allocUnsafeSlow(size) { + if (typeof Buffer.allocUnsafeSlow === 'function') { + return Buffer.allocUnsafeSlow(size); + } + if (typeof size !== 'number') { + throw new TypeError('size must be a number'); + } + if (size >= MAX_LEN) { + throw new RangeError('size is too large'); + } + return new SlowBuffer(size); +} + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"buffer":5}],5:[function(require,module,exports){ +/*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + */ +/* eslint-disable no-proto */ + +'use strict' + +var base64 = require('base64-js') +var ieee754 = require('ieee754') + +exports.Buffer = Buffer +exports.SlowBuffer = SlowBuffer +exports.INSPECT_MAX_BYTES = 50 + +var K_MAX_LENGTH = 0x7fffffff +exports.kMaxLength = K_MAX_LENGTH + +/** + * If `Buffer.TYPED_ARRAY_SUPPORT`: + * === true Use Uint8Array implementation (fastest) + * === false Print warning and recommend using `buffer` v4.x which has an Object + * implementation (most compatible, even IE6) + * + * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+, + * Opera 11.6+, iOS 4.2+. + * + * We report that the browser does not support typed arrays if the are not subclassable + * using __proto__. Firefox 4-29 lacks support for adding new properties to `Uint8Array` + * (See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438). IE 10 lacks support + * for __proto__ and has a buggy typed array implementation. + */ +Buffer.TYPED_ARRAY_SUPPORT = typedArraySupport() + +if (!Buffer.TYPED_ARRAY_SUPPORT) { + console.error( + 'This browser lacks typed array (Uint8Array) support which is required by ' + + '`buffer` v5.x. Use `buffer` v4.x if you require old browser support.') +} + +function typedArraySupport () { + // Can typed array instances can be augmented? + try { + var arr = new Uint8Array(1) + arr.__proto__ = {__proto__: Uint8Array.prototype, foo: function () { return 42 }} + return arr.foo() === 42 + } catch (e) { + return false + } +} + +function createBuffer (length) { + if (length > K_MAX_LENGTH) { + throw new RangeError('Invalid typed array length') + } + // Return an augmented `Uint8Array` instance + var buf = new Uint8Array(length) + buf.__proto__ = Buffer.prototype + return buf +} + +/** + * The Buffer constructor returns instances of `Uint8Array` that have their + * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of + * `Uint8Array`, so the returned instances will have all the node `Buffer` methods + * and the `Uint8Array` methods. Square bracket notation works as expected -- it + * returns a single octet. + * + * The `Uint8Array` prototype remains unmodified. + */ + +function Buffer (arg, encodingOrOffset, length) { + // Common case. + if (typeof arg === 'number') { + if (typeof encodingOrOffset === 'string') { + throw new Error( + 'If encoding is specified then the first argument must be a string' + ) + } + return allocUnsafe(arg) + } + return from(arg, encodingOrOffset, length) +} + +// Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97 +if (typeof Symbol !== 'undefined' && Symbol.species && + Buffer[Symbol.species] === Buffer) { + Object.defineProperty(Buffer, Symbol.species, { + value: null, + configurable: true, + enumerable: false, + writable: false + }) +} + +Buffer.poolSize = 8192 // not used by this implementation + +function from (value, encodingOrOffset, length) { + if (typeof value === 'number') { + throw new TypeError('"value" argument must not be a number') + } + + if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) { + return fromArrayBuffer(value, encodingOrOffset, length) + } + + if (typeof value === 'string') { + return fromString(value, encodingOrOffset) + } + + return fromObject(value) +} + +/** + * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError + * if value is a number. + * Buffer.from(str[, encoding]) + * Buffer.from(array) + * Buffer.from(buffer) + * Buffer.from(arrayBuffer[, byteOffset[, length]]) + **/ +Buffer.from = function (value, encodingOrOffset, length) { + return from(value, encodingOrOffset, length) +} + +// Note: Change prototype *after* Buffer.from is defined to workaround Chrome bug: +// https://github.com/feross/buffer/pull/148 +Buffer.prototype.__proto__ = Uint8Array.prototype +Buffer.__proto__ = Uint8Array + +function assertSize (size) { + if (typeof size !== 'number') { + throw new TypeError('"size" argument must be a number') + } else if (size < 0) { + throw new RangeError('"size" argument must not be negative') + } +} + +function alloc (size, fill, encoding) { + assertSize(size) + if (size <= 0) { + return createBuffer(size) + } + if (fill !== undefined) { + // Only pay attention to encoding if it's a string. This + // prevents accidentally sending in a number that would + // be interpretted as a start offset. + return typeof encoding === 'string' + ? createBuffer(size).fill(fill, encoding) + : createBuffer(size).fill(fill) + } + return createBuffer(size) +} + +/** + * Creates a new filled Buffer instance. + * alloc(size[, fill[, encoding]]) + **/ +Buffer.alloc = function (size, fill, encoding) { + return alloc(size, fill, encoding) +} + +function allocUnsafe (size) { + assertSize(size) + return createBuffer(size < 0 ? 0 : checked(size) | 0) +} + +/** + * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance. + * */ +Buffer.allocUnsafe = function (size) { + return allocUnsafe(size) +} +/** + * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance. + */ +Buffer.allocUnsafeSlow = function (size) { + return allocUnsafe(size) +} + +function fromString (string, encoding) { + if (typeof encoding !== 'string' || encoding === '') { + encoding = 'utf8' + } + + if (!Buffer.isEncoding(encoding)) { + throw new TypeError('"encoding" must be a valid string encoding') + } + + var length = byteLength(string, encoding) | 0 + var buf = createBuffer(length) + + var actual = buf.write(string, encoding) + + if (actual !== length) { + // Writing a hex string, for example, that contains invalid characters will + // cause everything after the first invalid character to be ignored. (e.g. + // 'abxxcd' will be treated as 'ab') + buf = buf.slice(0, actual) + } + + return buf +} + +function fromArrayLike (array) { + var length = array.length < 0 ? 0 : checked(array.length) | 0 + var buf = createBuffer(length) + for (var i = 0; i < length; i += 1) { + buf[i] = array[i] & 255 + } + return buf +} + +function fromArrayBuffer (array, byteOffset, length) { + array.byteLength // this throws if `array` is not a valid ArrayBuffer + + if (byteOffset < 0 || array.byteLength < byteOffset) { + throw new RangeError('\'offset\' is out of bounds') + } + + if (array.byteLength < byteOffset + (length || 0)) { + throw new RangeError('\'length\' is out of bounds') + } + + var buf + if (byteOffset === undefined && length === undefined) { + buf = new Uint8Array(array) + } else if (length === undefined) { + buf = new Uint8Array(array, byteOffset) + } else { + buf = new Uint8Array(array, byteOffset, length) + } + + // Return an augmented `Uint8Array` instance + buf.__proto__ = Buffer.prototype + return buf +} + +function fromObject (obj) { + if (Buffer.isBuffer(obj)) { + var len = checked(obj.length) | 0 + var buf = createBuffer(len) + + if (buf.length === 0) { + return buf + } + + obj.copy(buf, 0, 0, len) + return buf + } + + if (obj) { + if ((typeof ArrayBuffer !== 'undefined' && + obj.buffer instanceof ArrayBuffer) || 'length' in obj) { + if (typeof obj.length !== 'number' || isnan(obj.length)) { + return createBuffer(0) + } + return fromArrayLike(obj) + } + + if (obj.type === 'Buffer' && Array.isArray(obj.data)) { + return fromArrayLike(obj.data) + } + } + + throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.') +} + +function checked (length) { + // Note: cannot use `length < K_MAX_LENGTH` here because that fails when + // length is NaN (which is otherwise coerced to zero.) + if (length >= K_MAX_LENGTH) { + throw new RangeError('Attempt to allocate Buffer larger than maximum ' + + 'size: 0x' + K_MAX_LENGTH.toString(16) + ' bytes') + } + return length | 0 +} + +function SlowBuffer (length) { + if (+length != length) { // eslint-disable-line eqeqeq + length = 0 + } + return Buffer.alloc(+length) +} + +Buffer.isBuffer = function isBuffer (b) { + return !!(b != null && b._isBuffer) +} + +Buffer.compare = function compare (a, b) { + if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { + throw new TypeError('Arguments must be Buffers') + } + + if (a === b) return 0 + + var x = a.length + var y = b.length + + for (var i = 0, len = Math.min(x, y); i < len; ++i) { + if (a[i] !== b[i]) { + x = a[i] + y = b[i] + break + } + } + + if (x < y) return -1 + if (y < x) return 1 + return 0 +} + +Buffer.isEncoding = function isEncoding (encoding) { + switch (String(encoding).toLowerCase()) { + case 'hex': + case 'utf8': + case 'utf-8': + case 'ascii': + case 'latin1': + case 'binary': + case 'base64': + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return true + default: + return false + } +} + +Buffer.concat = function concat (list, length) { + if (!Array.isArray(list)) { + throw new TypeError('"list" argument must be an Array of Buffers') + } + + if (list.length === 0) { + return Buffer.alloc(0) + } + + var i + if (length === undefined) { + length = 0 + for (i = 0; i < list.length; ++i) { + length += list[i].length + } + } + + var buffer = Buffer.allocUnsafe(length) + var pos = 0 + for (i = 0; i < list.length; ++i) { + var buf = list[i] + if (!Buffer.isBuffer(buf)) { + throw new TypeError('"list" argument must be an Array of Buffers') + } + buf.copy(buffer, pos) + pos += buf.length + } + return buffer +} + +function byteLength (string, encoding) { + if (Buffer.isBuffer(string)) { + return string.length + } + if (typeof ArrayBuffer !== 'undefined' && typeof ArrayBuffer.isView === 'function' && + (ArrayBuffer.isView(string) || string instanceof ArrayBuffer)) { + return string.byteLength + } + if (typeof string !== 'string') { + string = '' + string + } + + var len = string.length + if (len === 0) return 0 + + // Use a for loop to avoid recursion + var loweredCase = false + for (;;) { + switch (encoding) { + case 'ascii': + case 'latin1': + case 'binary': + return len + case 'utf8': + case 'utf-8': + case undefined: + return utf8ToBytes(string).length + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return len * 2 + case 'hex': + return len >>> 1 + case 'base64': + return base64ToBytes(string).length + default: + if (loweredCase) return utf8ToBytes(string).length // assume utf8 + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } +} +Buffer.byteLength = byteLength + +function slowToString (encoding, start, end) { + var loweredCase = false + + // No need to verify that "this.length <= MAX_UINT32" since it's a read-only + // property of a typed array. + + // This behaves neither like String nor Uint8Array in that we set start/end + // to their upper/lower bounds if the value passed is out of range. + // undefined is handled specially as per ECMA-262 6th Edition, + // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization. + if (start === undefined || start < 0) { + start = 0 + } + // Return early if start > this.length. Done here to prevent potential uint32 + // coercion fail below. + if (start > this.length) { + return '' + } + + if (end === undefined || end > this.length) { + end = this.length + } + + if (end <= 0) { + return '' + } + + // Force coersion to uint32. This will also coerce falsey/NaN values to 0. + end >>>= 0 + start >>>= 0 + + if (end <= start) { + return '' + } + + if (!encoding) encoding = 'utf8' + + while (true) { + switch (encoding) { + case 'hex': + return hexSlice(this, start, end) + + case 'utf8': + case 'utf-8': + return utf8Slice(this, start, end) + + case 'ascii': + return asciiSlice(this, start, end) + + case 'latin1': + case 'binary': + return latin1Slice(this, start, end) + + case 'base64': + return base64Slice(this, start, end) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return utf16leSlice(this, start, end) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = (encoding + '').toLowerCase() + loweredCase = true + } + } +} + +// The property is used by `Buffer.isBuffer` and `is-buffer` (in Safari 5-7) to detect +// Buffer instances. +Buffer.prototype._isBuffer = true + +function swap (b, n, m) { + var i = b[n] + b[n] = b[m] + b[m] = i +} + +Buffer.prototype.swap16 = function swap16 () { + var len = this.length + if (len % 2 !== 0) { + throw new RangeError('Buffer size must be a multiple of 16-bits') + } + for (var i = 0; i < len; i += 2) { + swap(this, i, i + 1) + } + return this +} + +Buffer.prototype.swap32 = function swap32 () { + var len = this.length + if (len % 4 !== 0) { + throw new RangeError('Buffer size must be a multiple of 32-bits') + } + for (var i = 0; i < len; i += 4) { + swap(this, i, i + 3) + swap(this, i + 1, i + 2) + } + return this +} + +Buffer.prototype.swap64 = function swap64 () { + var len = this.length + if (len % 8 !== 0) { + throw new RangeError('Buffer size must be a multiple of 64-bits') + } + for (var i = 0; i < len; i += 8) { + swap(this, i, i + 7) + swap(this, i + 1, i + 6) + swap(this, i + 2, i + 5) + swap(this, i + 3, i + 4) + } + return this +} + +Buffer.prototype.toString = function toString () { + var length = this.length + if (length === 0) return '' + if (arguments.length === 0) return utf8Slice(this, 0, length) + return slowToString.apply(this, arguments) +} + +Buffer.prototype.equals = function equals (b) { + if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer') + if (this === b) return true + return Buffer.compare(this, b) === 0 +} + +Buffer.prototype.inspect = function inspect () { + var str = '' + var max = exports.INSPECT_MAX_BYTES + if (this.length > 0) { + str = this.toString('hex', 0, max).match(/.{2}/g).join(' ') + if (this.length > max) str += ' ... ' + } + return '' +} + +Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) { + if (!Buffer.isBuffer(target)) { + throw new TypeError('Argument must be a Buffer') + } + + if (start === undefined) { + start = 0 + } + if (end === undefined) { + end = target ? target.length : 0 + } + if (thisStart === undefined) { + thisStart = 0 + } + if (thisEnd === undefined) { + thisEnd = this.length + } + + if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) { + throw new RangeError('out of range index') + } + + if (thisStart >= thisEnd && start >= end) { + return 0 + } + if (thisStart >= thisEnd) { + return -1 + } + if (start >= end) { + return 1 + } + + start >>>= 0 + end >>>= 0 + thisStart >>>= 0 + thisEnd >>>= 0 + + if (this === target) return 0 + + var x = thisEnd - thisStart + var y = end - start + var len = Math.min(x, y) + + var thisCopy = this.slice(thisStart, thisEnd) + var targetCopy = target.slice(start, end) + + for (var i = 0; i < len; ++i) { + if (thisCopy[i] !== targetCopy[i]) { + x = thisCopy[i] + y = targetCopy[i] + break + } + } + + if (x < y) return -1 + if (y < x) return 1 + return 0 +} + +// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`, +// OR the last index of `val` in `buffer` at offset <= `byteOffset`. +// +// Arguments: +// - buffer - a Buffer to search +// - val - a string, Buffer, or number +// - byteOffset - an index into `buffer`; will be clamped to an int32 +// - encoding - an optional encoding, relevant is val is a string +// - dir - true for indexOf, false for lastIndexOf +function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) { + // Empty buffer means no match + if (buffer.length === 0) return -1 + + // Normalize byteOffset + if (typeof byteOffset === 'string') { + encoding = byteOffset + byteOffset = 0 + } else if (byteOffset > 0x7fffffff) { + byteOffset = 0x7fffffff + } else if (byteOffset < -0x80000000) { + byteOffset = -0x80000000 + } + byteOffset = +byteOffset // Coerce to Number. + if (isNaN(byteOffset)) { + // byteOffset: it it's undefined, null, NaN, "foo", etc, search whole buffer + byteOffset = dir ? 0 : (buffer.length - 1) + } + + // Normalize byteOffset: negative offsets start from the end of the buffer + if (byteOffset < 0) byteOffset = buffer.length + byteOffset + if (byteOffset >= buffer.length) { + if (dir) return -1 + else byteOffset = buffer.length - 1 + } else if (byteOffset < 0) { + if (dir) byteOffset = 0 + else return -1 + } + + // Normalize val + if (typeof val === 'string') { + val = Buffer.from(val, encoding) + } + + // Finally, search either indexOf (if dir is true) or lastIndexOf + if (Buffer.isBuffer(val)) { + // Special case: looking for empty string/buffer always fails + if (val.length === 0) { + return -1 + } + return arrayIndexOf(buffer, val, byteOffset, encoding, dir) + } else if (typeof val === 'number') { + val = val & 0xFF // Search for a byte value [0-255] + if (typeof Uint8Array.prototype.indexOf === 'function') { + if (dir) { + return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset) + } else { + return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset) + } + } + return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir) + } + + throw new TypeError('val must be string, number or Buffer') +} + +function arrayIndexOf (arr, val, byteOffset, encoding, dir) { + var indexSize = 1 + var arrLength = arr.length + var valLength = val.length + + if (encoding !== undefined) { + encoding = String(encoding).toLowerCase() + if (encoding === 'ucs2' || encoding === 'ucs-2' || + encoding === 'utf16le' || encoding === 'utf-16le') { + if (arr.length < 2 || val.length < 2) { + return -1 + } + indexSize = 2 + arrLength /= 2 + valLength /= 2 + byteOffset /= 2 + } + } + + function read (buf, i) { + if (indexSize === 1) { + return buf[i] + } else { + return buf.readUInt16BE(i * indexSize) + } + } + + var i + if (dir) { + var foundIndex = -1 + for (i = byteOffset; i < arrLength; i++) { + if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) { + if (foundIndex === -1) foundIndex = i + if (i - foundIndex + 1 === valLength) return foundIndex * indexSize + } else { + if (foundIndex !== -1) i -= i - foundIndex + foundIndex = -1 + } + } + } else { + if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength + for (i = byteOffset; i >= 0; i--) { + var found = true + for (var j = 0; j < valLength; j++) { + if (read(arr, i + j) !== read(val, j)) { + found = false + break + } + } + if (found) return i + } + } + + return -1 +} + +Buffer.prototype.includes = function includes (val, byteOffset, encoding) { + return this.indexOf(val, byteOffset, encoding) !== -1 +} + +Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, true) +} + +Buffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, false) +} + +function hexWrite (buf, string, offset, length) { + offset = Number(offset) || 0 + var remaining = buf.length - offset + if (!length) { + length = remaining + } else { + length = Number(length) + if (length > remaining) { + length = remaining + } + } + + // must be an even number of digits + var strLen = string.length + if (strLen % 2 !== 0) throw new TypeError('Invalid hex string') + + if (length > strLen / 2) { + length = strLen / 2 + } + for (var i = 0; i < length; ++i) { + var parsed = parseInt(string.substr(i * 2, 2), 16) + if (isNaN(parsed)) return i + buf[offset + i] = parsed + } + return i +} + +function utf8Write (buf, string, offset, length) { + return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length) +} + +function asciiWrite (buf, string, offset, length) { + return blitBuffer(asciiToBytes(string), buf, offset, length) +} + +function latin1Write (buf, string, offset, length) { + return asciiWrite(buf, string, offset, length) +} + +function base64Write (buf, string, offset, length) { + return blitBuffer(base64ToBytes(string), buf, offset, length) +} + +function ucs2Write (buf, string, offset, length) { + return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length) +} + +Buffer.prototype.write = function write (string, offset, length, encoding) { + // Buffer#write(string) + if (offset === undefined) { + encoding = 'utf8' + length = this.length + offset = 0 + // Buffer#write(string, encoding) + } else if (length === undefined && typeof offset === 'string') { + encoding = offset + length = this.length + offset = 0 + // Buffer#write(string, offset[, length][, encoding]) + } else if (isFinite(offset)) { + offset = offset >>> 0 + if (isFinite(length)) { + length = length >>> 0 + if (encoding === undefined) encoding = 'utf8' + } else { + encoding = length + length = undefined + } + // legacy write(string, encoding, offset, length) - remove in v0.13 + } else { + throw new Error( + 'Buffer.write(string, encoding, offset[, length]) is no longer supported' + ) + } + + var remaining = this.length - offset + if (length === undefined || length > remaining) length = remaining + + if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) { + throw new RangeError('Attempt to write outside buffer bounds') + } + + if (!encoding) encoding = 'utf8' + + var loweredCase = false + for (;;) { + switch (encoding) { + case 'hex': + return hexWrite(this, string, offset, length) + + case 'utf8': + case 'utf-8': + return utf8Write(this, string, offset, length) + + case 'ascii': + return asciiWrite(this, string, offset, length) + + case 'latin1': + case 'binary': + return latin1Write(this, string, offset, length) + + case 'base64': + // Warning: maxLength not taken into account in base64Write + return base64Write(this, string, offset, length) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return ucs2Write(this, string, offset, length) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } +} + +Buffer.prototype.toJSON = function toJSON () { + return { + type: 'Buffer', + data: Array.prototype.slice.call(this._arr || this, 0) + } +} + +function base64Slice (buf, start, end) { + if (start === 0 && end === buf.length) { + return base64.fromByteArray(buf) + } else { + return base64.fromByteArray(buf.slice(start, end)) + } +} + +function utf8Slice (buf, start, end) { + end = Math.min(buf.length, end) + var res = [] + + var i = start + while (i < end) { + var firstByte = buf[i] + var codePoint = null + var bytesPerSequence = (firstByte > 0xEF) ? 4 + : (firstByte > 0xDF) ? 3 + : (firstByte > 0xBF) ? 2 + : 1 + + if (i + bytesPerSequence <= end) { + var secondByte, thirdByte, fourthByte, tempCodePoint + + switch (bytesPerSequence) { + case 1: + if (firstByte < 0x80) { + codePoint = firstByte + } + break + case 2: + secondByte = buf[i + 1] + if ((secondByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F) + if (tempCodePoint > 0x7F) { + codePoint = tempCodePoint + } + } + break + case 3: + secondByte = buf[i + 1] + thirdByte = buf[i + 2] + if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F) + if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) { + codePoint = tempCodePoint + } + } + break + case 4: + secondByte = buf[i + 1] + thirdByte = buf[i + 2] + fourthByte = buf[i + 3] + if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F) + if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) { + codePoint = tempCodePoint + } + } + } + } + + if (codePoint === null) { + // we did not generate a valid codePoint so insert a + // replacement char (U+FFFD) and advance only 1 byte + codePoint = 0xFFFD + bytesPerSequence = 1 + } else if (codePoint > 0xFFFF) { + // encode to utf16 (surrogate pair dance) + codePoint -= 0x10000 + res.push(codePoint >>> 10 & 0x3FF | 0xD800) + codePoint = 0xDC00 | codePoint & 0x3FF + } + + res.push(codePoint) + i += bytesPerSequence + } + + return decodeCodePointsArray(res) +} + +// Based on http://stackoverflow.com/a/22747272/680742, the browser with +// the lowest limit is Chrome, with 0x10000 args. +// We go 1 magnitude less, for safety +var MAX_ARGUMENTS_LENGTH = 0x1000 + +function decodeCodePointsArray (codePoints) { + var len = codePoints.length + if (len <= MAX_ARGUMENTS_LENGTH) { + return String.fromCharCode.apply(String, codePoints) // avoid extra slice() + } + + // Decode in chunks to avoid "call stack size exceeded". + var res = '' + var i = 0 + while (i < len) { + res += String.fromCharCode.apply( + String, + codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH) + ) + } + return res +} + +function asciiSlice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i] & 0x7F) + } + return ret +} + +function latin1Slice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i]) + } + return ret +} + +function hexSlice (buf, start, end) { + var len = buf.length + + if (!start || start < 0) start = 0 + if (!end || end < 0 || end > len) end = len + + var out = '' + for (var i = start; i < end; ++i) { + out += toHex(buf[i]) + } + return out +} + +function utf16leSlice (buf, start, end) { + var bytes = buf.slice(start, end) + var res = '' + for (var i = 0; i < bytes.length; i += 2) { + res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256) + } + return res +} + +Buffer.prototype.slice = function slice (start, end) { + var len = this.length + start = ~~start + end = end === undefined ? len : ~~end + + if (start < 0) { + start += len + if (start < 0) start = 0 + } else if (start > len) { + start = len + } + + if (end < 0) { + end += len + if (end < 0) end = 0 + } else if (end > len) { + end = len + } + + if (end < start) end = start + + var newBuf = this.subarray(start, end) + // Return an augmented `Uint8Array` instance + newBuf.__proto__ = Buffer.prototype + return newBuf +} + +/* + * Need to make sure that buffer isn't trying to write out of bounds. + */ +function checkOffset (offset, ext, length) { + if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint') + if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length') +} + +Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + + return val +} + +Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + checkOffset(offset, byteLength, this.length) + } + + var val = this[offset + --byteLength] + var mul = 1 + while (byteLength > 0 && (mul *= 0x100)) { + val += this[offset + --byteLength] * mul + } + + return val +} + +Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 1, this.length) + return this[offset] +} + +Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + return this[offset] | (this[offset + 1] << 8) +} + +Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + return (this[offset] << 8) | this[offset + 1] +} + +Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return ((this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16)) + + (this[offset + 3] * 0x1000000) +} + +Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] * 0x1000000) + + ((this[offset + 1] << 16) | + (this[offset + 2] << 8) | + this[offset + 3]) +} + +Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val +} + +Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) { + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var i = byteLength + var mul = 1 + var val = this[offset + --i] + while (i > 0 && (mul *= 0x100)) { + val += this[offset + --i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val +} + +Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 1, this.length) + if (!(this[offset] & 0x80)) return (this[offset]) + return ((0xff - this[offset] + 1) * -1) +} + +Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset] | (this[offset + 1] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} + +Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset + 1] | (this[offset] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} + +Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16) | + (this[offset + 3] << 24) +} + +Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] << 24) | + (this[offset + 1] << 16) | + (this[offset + 2] << 8) | + (this[offset + 3]) +} + +Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, true, 23, 4) +} + +Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, false, 23, 4) +} + +Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, true, 52, 8) +} + +Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) { + offset = offset >>> 0 + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, false, 52, 8) +} + +function checkInt (buf, value, offset, ext, max, min) { + if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance') + if (value > max || value < min) throw new RangeError('"value" argument is out of bounds') + if (offset + ext > buf.length) throw new RangeError('Index out of range') +} + +Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + var maxBytes = Math.pow(2, 8 * byteLength) - 1 + checkInt(this, value, offset, byteLength, maxBytes, 0) + } + + var mul = 1 + var i = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + byteLength = byteLength >>> 0 + if (!noAssert) { + var maxBytes = Math.pow(2, 8 * byteLength) - 1 + checkInt(this, value, offset, byteLength, maxBytes, 0) + } + + var i = byteLength - 1 + var mul = 1 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0) + this[offset] = (value & 0xff) + return offset + 1 +} + +Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + return offset + 2 +} + +Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + return offset + 2 +} + +Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + this[offset + 3] = (value >>> 24) + this[offset + 2] = (value >>> 16) + this[offset + 1] = (value >>> 8) + this[offset] = (value & 0xff) + return offset + 4 +} + +Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + return offset + 4 +} + +Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + var limit = Math.pow(2, 8 * byteLength - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = 0 + var mul = 1 + var sub = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) { + sub = 1 + } + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + var limit = Math.pow(2, 8 * byteLength - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = byteLength - 1 + var mul = 1 + var sub = 0 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) { + sub = 1 + } + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80) + if (value < 0) value = 0xff + value + 1 + this[offset] = (value & 0xff) + return offset + 1 +} + +Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + return offset + 2 +} + +Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + return offset + 2 +} + +Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + this[offset + 2] = (value >>> 16) + this[offset + 3] = (value >>> 24) + return offset + 4 +} + +Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + if (value < 0) value = 0xffffffff + value + 1 + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + return offset + 4 +} + +function checkIEEE754 (buf, value, offset, ext, max, min) { + if (offset + ext > buf.length) throw new RangeError('Index out of range') + if (offset < 0) throw new RangeError('Index out of range') +} + +function writeFloat (buf, value, offset, littleEndian, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38) + } + ieee754.write(buf, value, offset, littleEndian, 23, 4) + return offset + 4 +} + +Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) { + return writeFloat(this, value, offset, true, noAssert) +} + +Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) { + return writeFloat(this, value, offset, false, noAssert) +} + +function writeDouble (buf, value, offset, littleEndian, noAssert) { + value = +value + offset = offset >>> 0 + if (!noAssert) { + checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308) + } + ieee754.write(buf, value, offset, littleEndian, 52, 8) + return offset + 8 +} + +Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) { + return writeDouble(this, value, offset, true, noAssert) +} + +Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) { + return writeDouble(this, value, offset, false, noAssert) +} + +// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) +Buffer.prototype.copy = function copy (target, targetStart, start, end) { + if (!start) start = 0 + if (!end && end !== 0) end = this.length + if (targetStart >= target.length) targetStart = target.length + if (!targetStart) targetStart = 0 + if (end > 0 && end < start) end = start + + // Copy 0 bytes; we're done + if (end === start) return 0 + if (target.length === 0 || this.length === 0) return 0 + + // Fatal error conditions + if (targetStart < 0) { + throw new RangeError('targetStart out of bounds') + } + if (start < 0 || start >= this.length) throw new RangeError('sourceStart out of bounds') + if (end < 0) throw new RangeError('sourceEnd out of bounds') + + // Are we oob? + if (end > this.length) end = this.length + if (target.length - targetStart < end - start) { + end = target.length - targetStart + start + } + + var len = end - start + var i + + if (this === target && start < targetStart && targetStart < end) { + // descending copy from end + for (i = len - 1; i >= 0; --i) { + target[i + targetStart] = this[i + start] + } + } else if (len < 1000) { + // ascending copy from start + for (i = 0; i < len; ++i) { + target[i + targetStart] = this[i + start] + } + } else { + Uint8Array.prototype.set.call( + target, + this.subarray(start, start + len), + targetStart + ) + } + + return len +} + +// Usage: +// buffer.fill(number[, offset[, end]]) +// buffer.fill(buffer[, offset[, end]]) +// buffer.fill(string[, offset[, end]][, encoding]) +Buffer.prototype.fill = function fill (val, start, end, encoding) { + // Handle string cases: + if (typeof val === 'string') { + if (typeof start === 'string') { + encoding = start + start = 0 + end = this.length + } else if (typeof end === 'string') { + encoding = end + end = this.length + } + if (val.length === 1) { + var code = val.charCodeAt(0) + if (code < 256) { + val = code + } + } + if (encoding !== undefined && typeof encoding !== 'string') { + throw new TypeError('encoding must be a string') + } + if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) { + throw new TypeError('Unknown encoding: ' + encoding) + } + } else if (typeof val === 'number') { + val = val & 255 + } + + // Invalid ranges are not set to a default, so can range check early. + if (start < 0 || this.length < start || this.length < end) { + throw new RangeError('Out of range index') + } + + if (end <= start) { + return this + } + + start = start >>> 0 + end = end === undefined ? this.length : end >>> 0 + + if (!val) val = 0 + + var i + if (typeof val === 'number') { + for (i = start; i < end; ++i) { + this[i] = val + } + } else { + var bytes = Buffer.isBuffer(val) + ? val + : new Buffer(val, encoding) + var len = bytes.length + for (i = 0; i < end - start; ++i) { + this[i + start] = bytes[i % len] + } + } + + return this +} + +// HELPER FUNCTIONS +// ================ + +var INVALID_BASE64_RE = /[^+/0-9A-Za-z-_]/g + +function base64clean (str) { + // Node strips out invalid characters like \n and \t from the string, base64-js does not + str = stringtrim(str).replace(INVALID_BASE64_RE, '') + // Node converts strings with length < 2 to '' + if (str.length < 2) return '' + // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not + while (str.length % 4 !== 0) { + str = str + '=' + } + return str +} + +function stringtrim (str) { + if (str.trim) return str.trim() + return str.replace(/^\s+|\s+$/g, '') +} + +function toHex (n) { + if (n < 16) return '0' + n.toString(16) + return n.toString(16) +} + +function utf8ToBytes (string, units) { + units = units || Infinity + var codePoint + var length = string.length + var leadSurrogate = null + var bytes = [] + + for (var i = 0; i < length; ++i) { + codePoint = string.charCodeAt(i) + + // is surrogate component + if (codePoint > 0xD7FF && codePoint < 0xE000) { + // last char was a lead + if (!leadSurrogate) { + // no lead yet + if (codePoint > 0xDBFF) { + // unexpected trail + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } else if (i + 1 === length) { + // unpaired lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } + + // valid lead + leadSurrogate = codePoint + + continue + } + + // 2 leads in a row + if (codePoint < 0xDC00) { + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + leadSurrogate = codePoint + continue + } + + // valid surrogate pair + codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000 + } else if (leadSurrogate) { + // valid bmp char, but last char was a lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + } + + leadSurrogate = null + + // encode utf8 + if (codePoint < 0x80) { + if ((units -= 1) < 0) break + bytes.push(codePoint) + } else if (codePoint < 0x800) { + if ((units -= 2) < 0) break + bytes.push( + codePoint >> 0x6 | 0xC0, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x10000) { + if ((units -= 3) < 0) break + bytes.push( + codePoint >> 0xC | 0xE0, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x110000) { + if ((units -= 4) < 0) break + bytes.push( + codePoint >> 0x12 | 0xF0, + codePoint >> 0xC & 0x3F | 0x80, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else { + throw new Error('Invalid code point') + } + } + + return bytes +} + +function asciiToBytes (str) { + var byteArray = [] + for (var i = 0; i < str.length; ++i) { + // Node's code seems to be doing this and not & 0x7F.. + byteArray.push(str.charCodeAt(i) & 0xFF) + } + return byteArray +} + +function utf16leToBytes (str, units) { + var c, hi, lo + var byteArray = [] + for (var i = 0; i < str.length; ++i) { + if ((units -= 2) < 0) break + + c = str.charCodeAt(i) + hi = c >> 8 + lo = c % 256 + byteArray.push(lo) + byteArray.push(hi) + } + + return byteArray +} + +function base64ToBytes (str) { + return base64.toByteArray(base64clean(str)) +} + +function blitBuffer (src, dst, offset, length) { + for (var i = 0; i < length; ++i) { + if ((i + offset >= dst.length) || (i >= src.length)) break + dst[i + offset] = src[i] + } + return i +} + +function isnan (val) { + return val !== val // eslint-disable-line no-self-compare +} + +},{"base64-js":1,"ieee754":8}],6:[function(require,module,exports){ +(function (Buffer){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// NOTE: These type checking functions intentionally don't use `instanceof` +// because it is fragile and can be easily faked with `Object.create()`. + +function isArray(arg) { + if (Array.isArray) { + return Array.isArray(arg); + } + return objectToString(arg) === '[object Array]'; +} +exports.isArray = isArray; + +function isBoolean(arg) { + return typeof arg === 'boolean'; +} +exports.isBoolean = isBoolean; + +function isNull(arg) { + return arg === null; +} +exports.isNull = isNull; + +function isNullOrUndefined(arg) { + return arg == null; +} +exports.isNullOrUndefined = isNullOrUndefined; + +function isNumber(arg) { + return typeof arg === 'number'; +} +exports.isNumber = isNumber; + +function isString(arg) { + return typeof arg === 'string'; +} +exports.isString = isString; + +function isSymbol(arg) { + return typeof arg === 'symbol'; +} +exports.isSymbol = isSymbol; + +function isUndefined(arg) { + return arg === void 0; +} +exports.isUndefined = isUndefined; + +function isRegExp(re) { + return objectToString(re) === '[object RegExp]'; +} +exports.isRegExp = isRegExp; + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} +exports.isObject = isObject; + +function isDate(d) { + return objectToString(d) === '[object Date]'; +} +exports.isDate = isDate; + +function isError(e) { + return (objectToString(e) === '[object Error]' || e instanceof Error); +} +exports.isError = isError; + +function isFunction(arg) { + return typeof arg === 'function'; +} +exports.isFunction = isFunction; + +function isPrimitive(arg) { + return arg === null || + typeof arg === 'boolean' || + typeof arg === 'number' || + typeof arg === 'string' || + typeof arg === 'symbol' || // ES6 symbol + typeof arg === 'undefined'; +} +exports.isPrimitive = isPrimitive; + +exports.isBuffer = Buffer.isBuffer; + +function objectToString(o) { + return Object.prototype.toString.call(o); +} + +}).call(this,{"isBuffer":require("../../is-buffer/index.js")}) + +},{"../../is-buffer/index.js":10}],7:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +function EventEmitter() { + this._events = this._events || {}; + this._maxListeners = this._maxListeners || undefined; +} +module.exports = EventEmitter; + +// Backwards-compat with node 0.10.x +EventEmitter.EventEmitter = EventEmitter; + +EventEmitter.prototype._events = undefined; +EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. +EventEmitter.defaultMaxListeners = 10; + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. +EventEmitter.prototype.setMaxListeners = function(n) { + if (!isNumber(n) || n < 0 || isNaN(n)) + throw TypeError('n must be a positive number'); + this._maxListeners = n; + return this; +}; + +EventEmitter.prototype.emit = function(type) { + var er, handler, len, args, i, listeners; + + if (!this._events) + this._events = {}; + + // If there is no 'error' event listener then throw. + if (type === 'error') { + if (!this._events.error || + (isObject(this._events.error) && !this._events.error.length)) { + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event + } else { + // At least give some kind of context to the user + var err = new Error('Uncaught, unspecified "error" event. (' + er + ')'); + err.context = er; + throw err; + } + } + } + + handler = this._events[type]; + + if (isUndefined(handler)) + return false; + + if (isFunction(handler)) { + switch (arguments.length) { + // fast cases + case 1: + handler.call(this); + break; + case 2: + handler.call(this, arguments[1]); + break; + case 3: + handler.call(this, arguments[1], arguments[2]); + break; + // slower + default: + args = Array.prototype.slice.call(arguments, 1); + handler.apply(this, args); + } + } else if (isObject(handler)) { + args = Array.prototype.slice.call(arguments, 1); + listeners = handler.slice(); + len = listeners.length; + for (i = 0; i < len; i++) + listeners[i].apply(this, args); + } + + return true; +}; + +EventEmitter.prototype.addListener = function(type, listener) { + var m; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events) + this._events = {}; + + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (this._events.newListener) + this.emit('newListener', type, + isFunction(listener.listener) ? + listener.listener : listener); + + if (!this._events[type]) + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener; + else if (isObject(this._events[type])) + // If we've already got an array, just append. + this._events[type].push(listener); + else + // Adding the second element, need to change to array. + this._events[type] = [this._events[type], listener]; + + // Check for listener leak + if (isObject(this._events[type]) && !this._events[type].warned) { + if (!isUndefined(this._maxListeners)) { + m = this._maxListeners; + } else { + m = EventEmitter.defaultMaxListeners; + } + + if (m && m > 0 && this._events[type].length > m) { + this._events[type].warned = true; + console.error('(node) warning: possible EventEmitter memory ' + + 'leak detected. %d listeners added. ' + + 'Use emitter.setMaxListeners() to increase limit.', + this._events[type].length); + if (typeof console.trace === 'function') { + // not supported in IE 10 + console.trace(); + } + } + } + + return this; +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.once = function(type, listener) { + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + var fired = false; + + function g() { + this.removeListener(type, g); + + if (!fired) { + fired = true; + listener.apply(this, arguments); + } + } + + g.listener = listener; + this.on(type, g); + + return this; +}; + +// emits a 'removeListener' event iff the listener was removed +EventEmitter.prototype.removeListener = function(type, listener) { + var list, position, length, i; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events || !this._events[type]) + return this; + + list = this._events[type]; + length = list.length; + position = -1; + + if (list === listener || + (isFunction(list.listener) && list.listener === listener)) { + delete this._events[type]; + if (this._events.removeListener) + this.emit('removeListener', type, listener); + + } else if (isObject(list)) { + for (i = length; i-- > 0;) { + if (list[i] === listener || + (list[i].listener && list[i].listener === listener)) { + position = i; + break; + } + } + + if (position < 0) + return this; + + if (list.length === 1) { + list.length = 0; + delete this._events[type]; + } else { + list.splice(position, 1); + } + + if (this._events.removeListener) + this.emit('removeListener', type, listener); + } + + return this; +}; + +EventEmitter.prototype.removeAllListeners = function(type) { + var key, listeners; + + if (!this._events) + return this; + + // not listening for removeListener, no need to emit + if (!this._events.removeListener) { + if (arguments.length === 0) + this._events = {}; + else if (this._events[type]) + delete this._events[type]; + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (key in this._events) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = {}; + return this; + } + + listeners = this._events[type]; + + if (isFunction(listeners)) { + this.removeListener(type, listeners); + } else if (listeners) { + // LIFO order + while (listeners.length) + this.removeListener(type, listeners[listeners.length - 1]); + } + delete this._events[type]; + + return this; +}; + +EventEmitter.prototype.listeners = function(type) { + var ret; + if (!this._events || !this._events[type]) + ret = []; + else if (isFunction(this._events[type])) + ret = [this._events[type]]; + else + ret = this._events[type].slice(); + return ret; +}; + +EventEmitter.prototype.listenerCount = function(type) { + if (this._events) { + var evlistener = this._events[type]; + + if (isFunction(evlistener)) + return 1; + else if (evlistener) + return evlistener.length; + } + return 0; +}; + +EventEmitter.listenerCount = function(emitter, type) { + return emitter.listenerCount(type); +}; + +function isFunction(arg) { + return typeof arg === 'function'; +} + +function isNumber(arg) { + return typeof arg === 'number'; +} + +function isObject(arg) { + return typeof arg === 'object' && arg !== null; +} + +function isUndefined(arg) { + return arg === void 0; +} + +},{}],8:[function(require,module,exports){ +exports.read = function (buffer, offset, isLE, mLen, nBytes) { + var e, m + var eLen = nBytes * 8 - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var nBits = -7 + var i = isLE ? (nBytes - 1) : 0 + var d = isLE ? -1 : 1 + var s = buffer[offset + i] + + i += d + + e = s & ((1 << (-nBits)) - 1) + s >>= (-nBits) + nBits += eLen + for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {} + + m = e & ((1 << (-nBits)) - 1) + e >>= (-nBits) + nBits += mLen + for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {} + + if (e === 0) { + e = 1 - eBias + } else if (e === eMax) { + return m ? NaN : ((s ? -1 : 1) * Infinity) + } else { + m = m + Math.pow(2, mLen) + e = e - eBias + } + return (s ? -1 : 1) * m * Math.pow(2, e - mLen) +} + +exports.write = function (buffer, value, offset, isLE, mLen, nBytes) { + var e, m, c + var eLen = nBytes * 8 - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0) + var i = isLE ? 0 : (nBytes - 1) + var d = isLE ? 1 : -1 + var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0 + + value = Math.abs(value) + + if (isNaN(value) || value === Infinity) { + m = isNaN(value) ? 1 : 0 + e = eMax + } else { + e = Math.floor(Math.log(value) / Math.LN2) + if (value * (c = Math.pow(2, -e)) < 1) { + e-- + c *= 2 + } + if (e + eBias >= 1) { + value += rt / c + } else { + value += rt * Math.pow(2, 1 - eBias) + } + if (value * c >= 2) { + e++ + c /= 2 + } + + if (e + eBias >= eMax) { + m = 0 + e = eMax + } else if (e + eBias >= 1) { + m = (value * c - 1) * Math.pow(2, mLen) + e = e + eBias + } else { + m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen) + e = 0 + } + } + + for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {} + + e = (e << mLen) | m + eLen += mLen + for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {} + + buffer[offset + i - d] |= s * 128 +} + +},{}],9:[function(require,module,exports){ +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } +} + +},{}],10:[function(require,module,exports){ +/*! + * Determine if an object is a Buffer + * + * @author Feross Aboukhadijeh + * @license MIT + */ + +// The _isBuffer check is for Safari 5-7 support, because it's missing +// Object.prototype.constructor. Remove this eventually +module.exports = function (obj) { + return obj != null && (isBuffer(obj) || isSlowBuffer(obj) || !!obj._isBuffer) +} + +function isBuffer (obj) { + return !!obj.constructor && typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj) +} + +// For Node v0.10 support. Remove this eventually. +function isSlowBuffer (obj) { + return typeof obj.readFloatLE === 'function' && typeof obj.slice === 'function' && isBuffer(obj.slice(0, 0)) +} + +},{}],11:[function(require,module,exports){ +var toString = {}.toString; + +module.exports = Array.isArray || function (arr) { + return toString.call(arr) == '[object Array]'; +}; + +},{}],12:[function(require,module,exports){ +(function (process){ +'use strict'; + +if (!process.version || + process.version.indexOf('v0.') === 0 || + process.version.indexOf('v1.') === 0 && process.version.indexOf('v1.8.') !== 0) { + module.exports = nextTick; +} else { + module.exports = process.nextTick; +} + +function nextTick(fn, arg1, arg2, arg3) { + if (typeof fn !== 'function') { + throw new TypeError('"callback" argument must be a function'); + } + var len = arguments.length; + var args, i; + switch (len) { + case 0: + case 1: + return process.nextTick(fn); + case 2: + return process.nextTick(function afterTickOne() { + fn.call(null, arg1); + }); + case 3: + return process.nextTick(function afterTickTwo() { + fn.call(null, arg1, arg2); + }); + case 4: + return process.nextTick(function afterTickThree() { + fn.call(null, arg1, arg2, arg3); + }); + default: + args = new Array(len - 1); + i = 0; + while (i < args.length) { + args[i++] = arguments[i]; + } + return process.nextTick(function afterTick() { + fn.apply(null, args); + }); + } +} + +}).call(this,require('_process')) + +},{"_process":3}],13:[function(require,module,exports){ +(function (global){ +/*! https://mths.be/punycode v1.4.1 by @mathias */ +;(function(root) { + + /** Detect free variables */ + var freeExports = typeof exports == 'object' && exports && + !exports.nodeType && exports; + var freeModule = typeof module == 'object' && module && + !module.nodeType && module; + var freeGlobal = typeof global == 'object' && global; + if ( + freeGlobal.global === freeGlobal || + freeGlobal.window === freeGlobal || + freeGlobal.self === freeGlobal + ) { + root = freeGlobal; + } + + /** + * The `punycode` object. + * @name punycode + * @type Object + */ + var punycode, + + /** Highest positive signed 32-bit float value */ + maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1 + + /** Bootstring parameters */ + base = 36, + tMin = 1, + tMax = 26, + skew = 38, + damp = 700, + initialBias = 72, + initialN = 128, // 0x80 + delimiter = '-', // '\x2D' + + /** Regular expressions */ + regexPunycode = /^xn--/, + regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars + regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators + + /** Error messages */ + errors = { + 'overflow': 'Overflow: input needs wider integers to process', + 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', + 'invalid-input': 'Invalid input' + }, + + /** Convenience shortcuts */ + baseMinusTMin = base - tMin, + floor = Math.floor, + stringFromCharCode = String.fromCharCode, + + /** Temporary variable */ + key; + + /*--------------------------------------------------------------------------*/ + + /** + * A generic error utility function. + * @private + * @param {String} type The error type. + * @returns {Error} Throws a `RangeError` with the applicable error message. + */ + function error(type) { + throw new RangeError(errors[type]); + } + + /** + * A generic `Array#map` utility function. + * @private + * @param {Array} array The array to iterate over. + * @param {Function} callback The function that gets called for every array + * item. + * @returns {Array} A new array of values returned by the callback function. + */ + function map(array, fn) { + var length = array.length; + var result = []; + while (length--) { + result[length] = fn(array[length]); + } + return result; + } + + /** + * A simple `Array#map`-like wrapper to work with domain name strings or email + * addresses. + * @private + * @param {String} domain The domain name or email address. + * @param {Function} callback The function that gets called for every + * character. + * @returns {Array} A new string of characters returned by the callback + * function. + */ + function mapDomain(string, fn) { + var parts = string.split('@'); + var result = ''; + if (parts.length > 1) { + // In email addresses, only the domain name should be punycoded. Leave + // the local part (i.e. everything up to `@`) intact. + result = parts[0] + '@'; + string = parts[1]; + } + // Avoid `split(regex)` for IE8 compatibility. See #17. + string = string.replace(regexSeparators, '\x2E'); + var labels = string.split('.'); + var encoded = map(labels, fn).join('.'); + return result + encoded; + } + + /** + * Creates an array containing the numeric code points of each Unicode + * character in the string. While JavaScript uses UCS-2 internally, + * this function will convert a pair of surrogate halves (each of which + * UCS-2 exposes as separate characters) into a single code point, + * matching UTF-16. + * @see `punycode.ucs2.encode` + * @see + * @memberOf punycode.ucs2 + * @name decode + * @param {String} string The Unicode input string (UCS-2). + * @returns {Array} The new array of code points. + */ + function ucs2decode(string) { + var output = [], + counter = 0, + length = string.length, + value, + extra; + while (counter < length) { + value = string.charCodeAt(counter++); + if (value >= 0xD800 && value <= 0xDBFF && counter < length) { + // high surrogate, and there is a next character + extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { // low surrogate + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); + } else { + // unmatched surrogate; only append this code unit, in case the next + // code unit is the high surrogate of a surrogate pair + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + return output; + } + + /** + * Creates a string based on an array of numeric code points. + * @see `punycode.ucs2.decode` + * @memberOf punycode.ucs2 + * @name encode + * @param {Array} codePoints The array of numeric code points. + * @returns {String} The new Unicode string (UCS-2). + */ + function ucs2encode(array) { + return map(array, function(value) { + var output = ''; + if (value > 0xFFFF) { + value -= 0x10000; + output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); + value = 0xDC00 | value & 0x3FF; + } + output += stringFromCharCode(value); + return output; + }).join(''); + } + + /** + * Converts a basic code point into a digit/integer. + * @see `digitToBasic()` + * @private + * @param {Number} codePoint The basic numeric code point value. + * @returns {Number} The numeric value of a basic code point (for use in + * representing integers) in the range `0` to `base - 1`, or `base` if + * the code point does not represent a value. + */ + function basicToDigit(codePoint) { + if (codePoint - 48 < 10) { + return codePoint - 22; + } + if (codePoint - 65 < 26) { + return codePoint - 65; + } + if (codePoint - 97 < 26) { + return codePoint - 97; + } + return base; + } + + /** + * Converts a digit/integer into a basic code point. + * @see `basicToDigit()` + * @private + * @param {Number} digit The numeric value of a basic code point. + * @returns {Number} The basic code point whose value (when used for + * representing integers) is `digit`, which needs to be in the range + * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is + * used; else, the lowercase form is used. The behavior is undefined + * if `flag` is non-zero and `digit` has no uppercase form. + */ + function digitToBasic(digit, flag) { + // 0..25 map to ASCII a..z or A..Z + // 26..35 map to ASCII 0..9 + return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); + } + + /** + * Bias adaptation function as per section 3.4 of RFC 3492. + * https://tools.ietf.org/html/rfc3492#section-3.4 + * @private + */ + function adapt(delta, numPoints, firstTime) { + var k = 0; + delta = firstTime ? floor(delta / damp) : delta >> 1; + delta += floor(delta / numPoints); + for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { + delta = floor(delta / baseMinusTMin); + } + return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); + } + + /** + * Converts a Punycode string of ASCII-only symbols to a string of Unicode + * symbols. + * @memberOf punycode + * @param {String} input The Punycode string of ASCII-only symbols. + * @returns {String} The resulting string of Unicode symbols. + */ + function decode(input) { + // Don't use UCS-2 + var output = [], + inputLength = input.length, + out, + i = 0, + n = initialN, + bias = initialBias, + basic, + j, + index, + oldi, + w, + k, + digit, + t, + /** Cached calculation results */ + baseMinusT; + + // Handle the basic code points: let `basic` be the number of input code + // points before the last delimiter, or `0` if there is none, then copy + // the first basic code points to the output. + + basic = input.lastIndexOf(delimiter); + if (basic < 0) { + basic = 0; + } + + for (j = 0; j < basic; ++j) { + // if it's not a basic code point + if (input.charCodeAt(j) >= 0x80) { + error('not-basic'); + } + output.push(input.charCodeAt(j)); + } + + // Main decoding loop: start just after the last delimiter if any basic code + // points were copied; start at the beginning otherwise. + + for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { + + // `index` is the index of the next character to be consumed. + // Decode a generalized variable-length integer into `delta`, + // which gets added to `i`. The overflow checking is easier + // if we increase `i` as we go, then subtract off its starting + // value at the end to obtain `delta`. + for (oldi = i, w = 1, k = base; /* no condition */; k += base) { + + if (index >= inputLength) { + error('invalid-input'); + } + + digit = basicToDigit(input.charCodeAt(index++)); + + if (digit >= base || digit > floor((maxInt - i) / w)) { + error('overflow'); + } + + i += digit * w; + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + + if (digit < t) { + break; + } + + baseMinusT = base - t; + if (w > floor(maxInt / baseMinusT)) { + error('overflow'); + } + + w *= baseMinusT; + + } + + out = output.length + 1; + bias = adapt(i - oldi, out, oldi == 0); + + // `i` was supposed to wrap around from `out` to `0`, + // incrementing `n` each time, so we'll fix that now: + if (floor(i / out) > maxInt - n) { + error('overflow'); + } + + n += floor(i / out); + i %= out; + + // Insert `n` at position `i` of the output + output.splice(i++, 0, n); + + } + + return ucs2encode(output); + } + + /** + * Converts a string of Unicode symbols (e.g. a domain name label) to a + * Punycode string of ASCII-only symbols. + * @memberOf punycode + * @param {String} input The string of Unicode symbols. + * @returns {String} The resulting Punycode string of ASCII-only symbols. + */ + function encode(input) { + var n, + delta, + handledCPCount, + basicLength, + bias, + j, + m, + q, + k, + t, + currentValue, + output = [], + /** `inputLength` will hold the number of code points in `input`. */ + inputLength, + /** Cached calculation results */ + handledCPCountPlusOne, + baseMinusT, + qMinusT; + + // Convert the input in UCS-2 to Unicode + input = ucs2decode(input); + + // Cache the length + inputLength = input.length; + + // Initialize the state + n = initialN; + delta = 0; + bias = initialBias; + + // Handle the basic code points + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue < 0x80) { + output.push(stringFromCharCode(currentValue)); + } + } + + handledCPCount = basicLength = output.length; + + // `handledCPCount` is the number of code points that have been handled; + // `basicLength` is the number of basic code points. + + // Finish the basic string - if it is not empty - with a delimiter + if (basicLength) { + output.push(delimiter); + } + + // Main encoding loop: + while (handledCPCount < inputLength) { + + // All non-basic code points < n have been handled already. Find the next + // larger one: + for (m = maxInt, j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue >= n && currentValue < m) { + m = currentValue; + } + } + + // Increase `delta` enough to advance the decoder's state to , + // but guard against overflow + handledCPCountPlusOne = handledCPCount + 1; + if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { + error('overflow'); + } + + delta += (m - n) * handledCPCountPlusOne; + n = m; + + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + + if (currentValue < n && ++delta > maxInt) { + error('overflow'); + } + + if (currentValue == n) { + // Represent delta as a generalized variable-length integer + for (q = delta, k = base; /* no condition */; k += base) { + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + if (q < t) { + break; + } + qMinusT = q - t; + baseMinusT = base - t; + output.push( + stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) + ); + q = floor(qMinusT / baseMinusT); + } + + output.push(stringFromCharCode(digitToBasic(q, 0))); + bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); + delta = 0; + ++handledCPCount; + } + } + + ++delta; + ++n; + + } + return output.join(''); + } + + /** + * Converts a Punycode string representing a domain name or an email address + * to Unicode. Only the Punycoded parts of the input will be converted, i.e. + * it doesn't matter if you call it on a string that has already been + * converted to Unicode. + * @memberOf punycode + * @param {String} input The Punycoded domain name or email address to + * convert to Unicode. + * @returns {String} The Unicode representation of the given Punycode + * string. + */ + function toUnicode(input) { + return mapDomain(input, function(string) { + return regexPunycode.test(string) + ? decode(string.slice(4).toLowerCase()) + : string; + }); + } + + /** + * Converts a Unicode string representing a domain name or an email address to + * Punycode. Only the non-ASCII parts of the domain name will be converted, + * i.e. it doesn't matter if you call it with a domain that's already in + * ASCII. + * @memberOf punycode + * @param {String} input The domain name or email address to convert, as a + * Unicode string. + * @returns {String} The Punycode representation of the given domain name or + * email address. + */ + function toASCII(input) { + return mapDomain(input, function(string) { + return regexNonASCII.test(string) + ? 'xn--' + encode(string) + : string; + }); + } + + /*--------------------------------------------------------------------------*/ + + /** Define the public API */ + punycode = { + /** + * A string representing the current Punycode.js version number. + * @memberOf punycode + * @type String + */ + 'version': '1.4.1', + /** + * An object of methods to convert from JavaScript's internal character + * representation (UCS-2) to Unicode code points, and back. + * @see + * @memberOf punycode + * @type Object + */ + 'ucs2': { + 'decode': ucs2decode, + 'encode': ucs2encode + }, + 'decode': decode, + 'encode': encode, + 'toASCII': toASCII, + 'toUnicode': toUnicode + }; + + /** Expose `punycode` */ + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + typeof define == 'function' && + typeof define.amd == 'object' && + define.amd + ) { + define('punycode', function() { + return punycode; + }); + } else if (freeExports && freeModule) { + if (module.exports == freeExports) { + // in Node.js, io.js, or RingoJS v0.8.0+ + freeModule.exports = punycode; + } else { + // in Narwhal or RingoJS v0.7.0- + for (key in punycode) { + punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]); + } + } + } else { + // in Rhino or a web browser + root.punycode = punycode; + } + +}(this)); + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{}],14:[function(require,module,exports){ +module.exports = require("./lib/_stream_duplex.js") + +},{"./lib/_stream_duplex.js":15}],15:[function(require,module,exports){ +// a duplex stream is just a stream that is both readable and writable. +// Since JS doesn't have multiple prototypal inheritance, this class +// prototypally inherits from Readable, and then parasitically from +// Writable. + +'use strict'; + +/**/ + +var objectKeys = Object.keys || function (obj) { + var keys = []; + for (var key in obj) { + keys.push(key); + }return keys; +}; +/**/ + +module.exports = Duplex; + +/**/ +var processNextTick = require('process-nextick-args'); +/**/ + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +var Readable = require('./_stream_readable'); +var Writable = require('./_stream_writable'); + +util.inherits(Duplex, Readable); + +var keys = objectKeys(Writable.prototype); +for (var v = 0; v < keys.length; v++) { + var method = keys[v]; + if (!Duplex.prototype[method]) Duplex.prototype[method] = Writable.prototype[method]; +} + +function Duplex(options) { + if (!(this instanceof Duplex)) return new Duplex(options); + + Readable.call(this, options); + Writable.call(this, options); + + if (options && options.readable === false) this.readable = false; + + if (options && options.writable === false) this.writable = false; + + this.allowHalfOpen = true; + if (options && options.allowHalfOpen === false) this.allowHalfOpen = false; + + this.once('end', onend); +} + +// the no-half-open enforcer +function onend() { + // if we allow half-open state, or if the writable side ended, + // then we're ok. + if (this.allowHalfOpen || this._writableState.ended) return; + + // no more data can be written. + // But allow more writes to happen in this tick. + processNextTick(onEndNT, this); +} + +function onEndNT(self) { + self.end(); +} + +function forEach(xs, f) { + for (var i = 0, l = xs.length; i < l; i++) { + f(xs[i], i); + } +} +},{"./_stream_readable":17,"./_stream_writable":19,"core-util-is":6,"inherits":9,"process-nextick-args":12}],16:[function(require,module,exports){ +// a passthrough stream. +// basically just the most minimal sort of Transform stream. +// Every written chunk gets output as-is. + +'use strict'; + +module.exports = PassThrough; + +var Transform = require('./_stream_transform'); + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +util.inherits(PassThrough, Transform); + +function PassThrough(options) { + if (!(this instanceof PassThrough)) return new PassThrough(options); + + Transform.call(this, options); +} + +PassThrough.prototype._transform = function (chunk, encoding, cb) { + cb(null, chunk); +}; +},{"./_stream_transform":18,"core-util-is":6,"inherits":9}],17:[function(require,module,exports){ +(function (process){ +'use strict'; + +module.exports = Readable; + +/**/ +var processNextTick = require('process-nextick-args'); +/**/ + +/**/ +var isArray = require('isarray'); +/**/ + +/**/ +var Duplex; +/**/ + +Readable.ReadableState = ReadableState; + +/**/ +var EE = require('events').EventEmitter; + +var EElistenerCount = function (emitter, type) { + return emitter.listeners(type).length; +}; +/**/ + +/**/ +var Stream; +(function () { + try { + Stream = require('st' + 'ream'); + } catch (_) {} finally { + if (!Stream) Stream = require('events').EventEmitter; + } +})(); +/**/ + +var Buffer = require('buffer').Buffer; +/**/ +var bufferShim = require('buffer-shims'); +/**/ + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +/**/ +var debugUtil = require('util'); +var debug = void 0; +if (debugUtil && debugUtil.debuglog) { + debug = debugUtil.debuglog('stream'); +} else { + debug = function () {}; +} +/**/ + +var BufferList = require('./internal/streams/BufferList'); +var StringDecoder; + +util.inherits(Readable, Stream); + +function prependListener(emitter, event, fn) { + // Sadly this is not cacheable as some libraries bundle their own + // event emitter implementation with them. + if (typeof emitter.prependListener === 'function') { + return emitter.prependListener(event, fn); + } else { + // This is a hack to make sure that our error handler is attached before any + // userland ones. NEVER DO THIS. This is here only because this code needs + // to continue to work with older versions of Node.js that do not include + // the prependListener() method. The goal is to eventually remove this hack. + if (!emitter._events || !emitter._events[event]) emitter.on(event, fn);else if (isArray(emitter._events[event])) emitter._events[event].unshift(fn);else emitter._events[event] = [fn, emitter._events[event]]; + } +} + +function ReadableState(options, stream) { + Duplex = Duplex || require('./_stream_duplex'); + + options = options || {}; + + // object stream flag. Used to make read(n) ignore n and to + // make all the buffer merging and length checks go away + this.objectMode = !!options.objectMode; + + if (stream instanceof Duplex) this.objectMode = this.objectMode || !!options.readableObjectMode; + + // the point at which it stops calling _read() to fill the buffer + // Note: 0 is a valid value, means "don't call _read preemptively ever" + var hwm = options.highWaterMark; + var defaultHwm = this.objectMode ? 16 : 16 * 1024; + this.highWaterMark = hwm || hwm === 0 ? hwm : defaultHwm; + + // cast to ints. + this.highWaterMark = ~ ~this.highWaterMark; + + // A linked list is used to store data chunks instead of an array because the + // linked list can remove elements from the beginning faster than + // array.shift() + this.buffer = new BufferList(); + this.length = 0; + this.pipes = null; + this.pipesCount = 0; + this.flowing = null; + this.ended = false; + this.endEmitted = false; + this.reading = false; + + // a flag to be able to tell if the onwrite cb is called immediately, + // or on a later tick. We set this to true at first, because any + // actions that shouldn't happen until "later" should generally also + // not happen before the first write call. + this.sync = true; + + // whenever we return null, then we set a flag to say + // that we're awaiting a 'readable' event emission. + this.needReadable = false; + this.emittedReadable = false; + this.readableListening = false; + this.resumeScheduled = false; + + // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + this.defaultEncoding = options.defaultEncoding || 'utf8'; + + // when piping, we only care about 'readable' events that happen + // after read()ing all the bytes and not getting any pushback. + this.ranOut = false; + + // the number of writers that are awaiting a drain event in .pipe()s + this.awaitDrain = 0; + + // if true, a maybeReadMore has been scheduled + this.readingMore = false; + + this.decoder = null; + this.encoding = null; + if (options.encoding) { + if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; + this.decoder = new StringDecoder(options.encoding); + this.encoding = options.encoding; + } +} + +function Readable(options) { + Duplex = Duplex || require('./_stream_duplex'); + + if (!(this instanceof Readable)) return new Readable(options); + + this._readableState = new ReadableState(options, this); + + // legacy + this.readable = true; + + if (options && typeof options.read === 'function') this._read = options.read; + + Stream.call(this); +} + +// Manually shove something into the read() buffer. +// This returns true if the highWaterMark has not been hit yet, +// similar to how Writable.write() returns true if you should +// write() some more. +Readable.prototype.push = function (chunk, encoding) { + var state = this._readableState; + + if (!state.objectMode && typeof chunk === 'string') { + encoding = encoding || state.defaultEncoding; + if (encoding !== state.encoding) { + chunk = bufferShim.from(chunk, encoding); + encoding = ''; + } + } + + return readableAddChunk(this, state, chunk, encoding, false); +}; + +// Unshift should *always* be something directly out of read() +Readable.prototype.unshift = function (chunk) { + var state = this._readableState; + return readableAddChunk(this, state, chunk, '', true); +}; + +Readable.prototype.isPaused = function () { + return this._readableState.flowing === false; +}; + +function readableAddChunk(stream, state, chunk, encoding, addToFront) { + var er = chunkInvalid(state, chunk); + if (er) { + stream.emit('error', er); + } else if (chunk === null) { + state.reading = false; + onEofChunk(stream, state); + } else if (state.objectMode || chunk && chunk.length > 0) { + if (state.ended && !addToFront) { + var e = new Error('stream.push() after EOF'); + stream.emit('error', e); + } else if (state.endEmitted && addToFront) { + var _e = new Error('stream.unshift() after end event'); + stream.emit('error', _e); + } else { + var skipAdd; + if (state.decoder && !addToFront && !encoding) { + chunk = state.decoder.write(chunk); + skipAdd = !state.objectMode && chunk.length === 0; + } + + if (!addToFront) state.reading = false; + + // Don't add to the buffer if we've decoded to an empty string chunk and + // we're not in object mode + if (!skipAdd) { + // if we want the data now, just emit it. + if (state.flowing && state.length === 0 && !state.sync) { + stream.emit('data', chunk); + stream.read(0); + } else { + // update the buffer info. + state.length += state.objectMode ? 1 : chunk.length; + if (addToFront) state.buffer.unshift(chunk);else state.buffer.push(chunk); + + if (state.needReadable) emitReadable(stream); + } + } + + maybeReadMore(stream, state); + } + } else if (!addToFront) { + state.reading = false; + } + + return needMoreData(state); +} + +// if it's past the high water mark, we can push in some more. +// Also, if we have no data yet, we can stand some +// more bytes. This is to work around cases where hwm=0, +// such as the repl. Also, if the push() triggered a +// readable event, and the user called read(largeNumber) such that +// needReadable was set, then we ought to push more, so that another +// 'readable' event will be triggered. +function needMoreData(state) { + return !state.ended && (state.needReadable || state.length < state.highWaterMark || state.length === 0); +} + +// backwards compatibility. +Readable.prototype.setEncoding = function (enc) { + if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; + this._readableState.decoder = new StringDecoder(enc); + this._readableState.encoding = enc; + return this; +}; + +// Don't raise the hwm > 8MB +var MAX_HWM = 0x800000; +function computeNewHighWaterMark(n) { + if (n >= MAX_HWM) { + n = MAX_HWM; + } else { + // Get the next highest power of 2 to prevent increasing hwm excessively in + // tiny amounts + n--; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + n++; + } + return n; +} + +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function howMuchToRead(n, state) { + if (n <= 0 || state.length === 0 && state.ended) return 0; + if (state.objectMode) return 1; + if (n !== n) { + // Only flow one buffer at a time + if (state.flowing && state.length) return state.buffer.head.data.length;else return state.length; + } + // If we're asking for more than the current hwm, then raise the hwm. + if (n > state.highWaterMark) state.highWaterMark = computeNewHighWaterMark(n); + if (n <= state.length) return n; + // Don't have enough + if (!state.ended) { + state.needReadable = true; + return 0; + } + return state.length; +} + +// you can override either this method, or the async _read(n) below. +Readable.prototype.read = function (n) { + debug('read', n); + n = parseInt(n, 10); + var state = this._readableState; + var nOrig = n; + + if (n !== 0) state.emittedReadable = false; + + // if we're doing read(0) to trigger a readable event, but we + // already have a bunch of data in the buffer, then just trigger + // the 'readable' event and move on. + if (n === 0 && state.needReadable && (state.length >= state.highWaterMark || state.ended)) { + debug('read: emitReadable', state.length, state.ended); + if (state.length === 0 && state.ended) endReadable(this);else emitReadable(this); + return null; + } + + n = howMuchToRead(n, state); + + // if we've ended, and we're now clear, then finish it up. + if (n === 0 && state.ended) { + if (state.length === 0) endReadable(this); + return null; + } + + // All the actual chunk generation logic needs to be + // *below* the call to _read. The reason is that in certain + // synthetic stream cases, such as passthrough streams, _read + // may be a completely synchronous operation which may change + // the state of the read buffer, providing enough data when + // before there was *not* enough. + // + // So, the steps are: + // 1. Figure out what the state of things will be after we do + // a read from the buffer. + // + // 2. If that resulting state will trigger a _read, then call _read. + // Note that this may be asynchronous, or synchronous. Yes, it is + // deeply ugly to write APIs this way, but that still doesn't mean + // that the Readable class should behave improperly, as streams are + // designed to be sync/async agnostic. + // Take note if the _read call is sync or async (ie, if the read call + // has returned yet), so that we know whether or not it's safe to emit + // 'readable' etc. + // + // 3. Actually pull the requested chunks out of the buffer and return. + + // if we need a readable event, then we need to do some reading. + var doRead = state.needReadable; + debug('need readable', doRead); + + // if we currently have less than the highWaterMark, then also read some + if (state.length === 0 || state.length - n < state.highWaterMark) { + doRead = true; + debug('length less than watermark', doRead); + } + + // however, if we've ended, then there's no point, and if we're already + // reading, then it's unnecessary. + if (state.ended || state.reading) { + doRead = false; + debug('reading or ended', doRead); + } else if (doRead) { + debug('do read'); + state.reading = true; + state.sync = true; + // if the length is currently zero, then we *need* a readable event. + if (state.length === 0) state.needReadable = true; + // call internal read method + this._read(state.highWaterMark); + state.sync = false; + // If _read pushed data synchronously, then `reading` will be false, + // and we need to re-evaluate how much data we can return to the user. + if (!state.reading) n = howMuchToRead(nOrig, state); + } + + var ret; + if (n > 0) ret = fromList(n, state);else ret = null; + + if (ret === null) { + state.needReadable = true; + n = 0; + } else { + state.length -= n; + } + + if (state.length === 0) { + // If we have nothing in the buffer, then we want to know + // as soon as we *do* get something into the buffer. + if (!state.ended) state.needReadable = true; + + // If we tried to read() past the EOF, then emit end on the next tick. + if (nOrig !== n && state.ended) endReadable(this); + } + + if (ret !== null) this.emit('data', ret); + + return ret; +}; + +function chunkInvalid(state, chunk) { + var er = null; + if (!Buffer.isBuffer(chunk) && typeof chunk !== 'string' && chunk !== null && chunk !== undefined && !state.objectMode) { + er = new TypeError('Invalid non-string/buffer chunk'); + } + return er; +} + +function onEofChunk(stream, state) { + if (state.ended) return; + if (state.decoder) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) { + state.buffer.push(chunk); + state.length += state.objectMode ? 1 : chunk.length; + } + } + state.ended = true; + + // emit 'readable' now to make sure it gets picked up. + emitReadable(stream); +} + +// Don't emit readable right away in sync mode, because this can trigger +// another read() call => stack overflow. This way, it might trigger +// a nextTick recursion warning, but that's not so bad. +function emitReadable(stream) { + var state = stream._readableState; + state.needReadable = false; + if (!state.emittedReadable) { + debug('emitReadable', state.flowing); + state.emittedReadable = true; + if (state.sync) processNextTick(emitReadable_, stream);else emitReadable_(stream); + } +} + +function emitReadable_(stream) { + debug('emit readable'); + stream.emit('readable'); + flow(stream); +} + +// at this point, the user has presumably seen the 'readable' event, +// and called read() to consume some data. that may have triggered +// in turn another _read(n) call, in which case reading = true if +// it's in progress. +// However, if we're not ended, or reading, and the length < hwm, +// then go ahead and try to read some more preemptively. +function maybeReadMore(stream, state) { + if (!state.readingMore) { + state.readingMore = true; + processNextTick(maybeReadMore_, stream, state); + } +} + +function maybeReadMore_(stream, state) { + var len = state.length; + while (!state.reading && !state.flowing && !state.ended && state.length < state.highWaterMark) { + debug('maybeReadMore read 0'); + stream.read(0); + if (len === state.length) + // didn't get any data, stop spinning. + break;else len = state.length; + } + state.readingMore = false; +} + +// abstract method. to be overridden in specific implementation classes. +// call cb(er, data) where data is <= n in length. +// for virtual (non-string, non-buffer) streams, "length" is somewhat +// arbitrary, and perhaps not very meaningful. +Readable.prototype._read = function (n) { + this.emit('error', new Error('_read() is not implemented')); +}; + +Readable.prototype.pipe = function (dest, pipeOpts) { + var src = this; + var state = this._readableState; + + switch (state.pipesCount) { + case 0: + state.pipes = dest; + break; + case 1: + state.pipes = [state.pipes, dest]; + break; + default: + state.pipes.push(dest); + break; + } + state.pipesCount += 1; + debug('pipe count=%d opts=%j', state.pipesCount, pipeOpts); + + var doEnd = (!pipeOpts || pipeOpts.end !== false) && dest !== process.stdout && dest !== process.stderr; + + var endFn = doEnd ? onend : cleanup; + if (state.endEmitted) processNextTick(endFn);else src.once('end', endFn); + + dest.on('unpipe', onunpipe); + function onunpipe(readable) { + debug('onunpipe'); + if (readable === src) { + cleanup(); + } + } + + function onend() { + debug('onend'); + dest.end(); + } + + // when the dest drains, it reduces the awaitDrain counter + // on the source. This would be more elegant with a .once() + // handler in flow(), but adding and removing repeatedly is + // too slow. + var ondrain = pipeOnDrain(src); + dest.on('drain', ondrain); + + var cleanedUp = false; + function cleanup() { + debug('cleanup'); + // cleanup event handlers once the pipe is broken + dest.removeListener('close', onclose); + dest.removeListener('finish', onfinish); + dest.removeListener('drain', ondrain); + dest.removeListener('error', onerror); + dest.removeListener('unpipe', onunpipe); + src.removeListener('end', onend); + src.removeListener('end', cleanup); + src.removeListener('data', ondata); + + cleanedUp = true; + + // if the reader is waiting for a drain event from this + // specific writer, then it would cause it to never start + // flowing again. + // So, if this is awaiting a drain, then we just call it now. + // If we don't know, then assume that we are waiting for one. + if (state.awaitDrain && (!dest._writableState || dest._writableState.needDrain)) ondrain(); + } + + // If the user pushes more data while we're writing to dest then we'll end up + // in ondata again. However, we only want to increase awaitDrain once because + // dest will only emit one 'drain' event for the multiple writes. + // => Introduce a guard on increasing awaitDrain. + var increasedAwaitDrain = false; + src.on('data', ondata); + function ondata(chunk) { + debug('ondata'); + increasedAwaitDrain = false; + var ret = dest.write(chunk); + if (false === ret && !increasedAwaitDrain) { + // If the user unpiped during `dest.write()`, it is possible + // to get stuck in a permanently paused state if that write + // also returned false. + // => Check whether `dest` is still a piping destination. + if ((state.pipesCount === 1 && state.pipes === dest || state.pipesCount > 1 && indexOf(state.pipes, dest) !== -1) && !cleanedUp) { + debug('false write response, pause', src._readableState.awaitDrain); + src._readableState.awaitDrain++; + increasedAwaitDrain = true; + } + src.pause(); + } + } + + // if the dest has an error, then stop piping into it. + // however, don't suppress the throwing behavior for this. + function onerror(er) { + debug('onerror', er); + unpipe(); + dest.removeListener('error', onerror); + if (EElistenerCount(dest, 'error') === 0) dest.emit('error', er); + } + + // Make sure our error handler is attached before userland ones. + prependListener(dest, 'error', onerror); + + // Both close and finish should trigger unpipe, but only once. + function onclose() { + dest.removeListener('finish', onfinish); + unpipe(); + } + dest.once('close', onclose); + function onfinish() { + debug('onfinish'); + dest.removeListener('close', onclose); + unpipe(); + } + dest.once('finish', onfinish); + + function unpipe() { + debug('unpipe'); + src.unpipe(dest); + } + + // tell the dest that it's being piped to + dest.emit('pipe', src); + + // start the flow if it hasn't been started already. + if (!state.flowing) { + debug('pipe resume'); + src.resume(); + } + + return dest; +}; + +function pipeOnDrain(src) { + return function () { + var state = src._readableState; + debug('pipeOnDrain', state.awaitDrain); + if (state.awaitDrain) state.awaitDrain--; + if (state.awaitDrain === 0 && EElistenerCount(src, 'data')) { + state.flowing = true; + flow(src); + } + }; +} + +Readable.prototype.unpipe = function (dest) { + var state = this._readableState; + + // if we're not piping anywhere, then do nothing. + if (state.pipesCount === 0) return this; + + // just one destination. most common case. + if (state.pipesCount === 1) { + // passed in one, but it's not the right one. + if (dest && dest !== state.pipes) return this; + + if (!dest) dest = state.pipes; + + // got a match. + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; + if (dest) dest.emit('unpipe', this); + return this; + } + + // slow case. multiple pipe destinations. + + if (!dest) { + // remove all. + var dests = state.pipes; + var len = state.pipesCount; + state.pipes = null; + state.pipesCount = 0; + state.flowing = false; + + for (var i = 0; i < len; i++) { + dests[i].emit('unpipe', this); + }return this; + } + + // try to find the right one. + var index = indexOf(state.pipes, dest); + if (index === -1) return this; + + state.pipes.splice(index, 1); + state.pipesCount -= 1; + if (state.pipesCount === 1) state.pipes = state.pipes[0]; + + dest.emit('unpipe', this); + + return this; +}; + +// set up data events if they are asked for +// Ensure readable listeners eventually get something +Readable.prototype.on = function (ev, fn) { + var res = Stream.prototype.on.call(this, ev, fn); + + if (ev === 'data') { + // Start flowing on next tick if stream isn't explicitly paused + if (this._readableState.flowing !== false) this.resume(); + } else if (ev === 'readable') { + var state = this._readableState; + if (!state.endEmitted && !state.readableListening) { + state.readableListening = state.needReadable = true; + state.emittedReadable = false; + if (!state.reading) { + processNextTick(nReadingNextTick, this); + } else if (state.length) { + emitReadable(this, state); + } + } + } + + return res; +}; +Readable.prototype.addListener = Readable.prototype.on; + +function nReadingNextTick(self) { + debug('readable nexttick read 0'); + self.read(0); +} + +// pause() and resume() are remnants of the legacy readable stream API +// If the user uses them, then switch into old mode. +Readable.prototype.resume = function () { + var state = this._readableState; + if (!state.flowing) { + debug('resume'); + state.flowing = true; + resume(this, state); + } + return this; +}; + +function resume(stream, state) { + if (!state.resumeScheduled) { + state.resumeScheduled = true; + processNextTick(resume_, stream, state); + } +} + +function resume_(stream, state) { + if (!state.reading) { + debug('resume read 0'); + stream.read(0); + } + + state.resumeScheduled = false; + state.awaitDrain = 0; + stream.emit('resume'); + flow(stream); + if (state.flowing && !state.reading) stream.read(0); +} + +Readable.prototype.pause = function () { + debug('call pause flowing=%j', this._readableState.flowing); + if (false !== this._readableState.flowing) { + debug('pause'); + this._readableState.flowing = false; + this.emit('pause'); + } + return this; +}; + +function flow(stream) { + var state = stream._readableState; + debug('flow', state.flowing); + while (state.flowing && stream.read() !== null) {} +} + +// wrap an old-style stream as the async data source. +// This is *not* part of the readable stream interface. +// It is an ugly unfortunate mess of history. +Readable.prototype.wrap = function (stream) { + var state = this._readableState; + var paused = false; + + var self = this; + stream.on('end', function () { + debug('wrapped end'); + if (state.decoder && !state.ended) { + var chunk = state.decoder.end(); + if (chunk && chunk.length) self.push(chunk); + } + + self.push(null); + }); + + stream.on('data', function (chunk) { + debug('wrapped data'); + if (state.decoder) chunk = state.decoder.write(chunk); + + // don't skip over falsy values in objectMode + if (state.objectMode && (chunk === null || chunk === undefined)) return;else if (!state.objectMode && (!chunk || !chunk.length)) return; + + var ret = self.push(chunk); + if (!ret) { + paused = true; + stream.pause(); + } + }); + + // proxy all the other methods. + // important when wrapping filters and duplexes. + for (var i in stream) { + if (this[i] === undefined && typeof stream[i] === 'function') { + this[i] = function (method) { + return function () { + return stream[method].apply(stream, arguments); + }; + }(i); + } + } + + // proxy certain important events. + var events = ['error', 'close', 'destroy', 'pause', 'resume']; + forEach(events, function (ev) { + stream.on(ev, self.emit.bind(self, ev)); + }); + + // when we try to consume some more bytes, simply unpause the + // underlying stream. + self._read = function (n) { + debug('wrapped _read', n); + if (paused) { + paused = false; + stream.resume(); + } + }; + + return self; +}; + +// exposed for testing purposes only. +Readable._fromList = fromList; + +// Pluck off n bytes from an array of buffers. +// Length is the combined lengths of all the buffers in the list. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function fromList(n, state) { + // nothing buffered + if (state.length === 0) return null; + + var ret; + if (state.objectMode) ret = state.buffer.shift();else if (!n || n >= state.length) { + // read it all, truncate the list + if (state.decoder) ret = state.buffer.join('');else if (state.buffer.length === 1) ret = state.buffer.head.data;else ret = state.buffer.concat(state.length); + state.buffer.clear(); + } else { + // read part of list + ret = fromListPartial(n, state.buffer, state.decoder); + } + + return ret; +} + +// Extracts only enough buffered data to satisfy the amount requested. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function fromListPartial(n, list, hasStrings) { + var ret; + if (n < list.head.data.length) { + // slice is the same for buffers and strings + ret = list.head.data.slice(0, n); + list.head.data = list.head.data.slice(n); + } else if (n === list.head.data.length) { + // first chunk is a perfect match + ret = list.shift(); + } else { + // result spans more than one buffer + ret = hasStrings ? copyFromBufferString(n, list) : copyFromBuffer(n, list); + } + return ret; +} + +// Copies a specified amount of characters from the list of buffered data +// chunks. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function copyFromBufferString(n, list) { + var p = list.head; + var c = 1; + var ret = p.data; + n -= ret.length; + while (p = p.next) { + var str = p.data; + var nb = n > str.length ? str.length : n; + if (nb === str.length) ret += str;else ret += str.slice(0, n); + n -= nb; + if (n === 0) { + if (nb === str.length) { + ++c; + if (p.next) list.head = p.next;else list.head = list.tail = null; + } else { + list.head = p; + p.data = str.slice(nb); + } + break; + } + ++c; + } + list.length -= c; + return ret; +} + +// Copies a specified amount of bytes from the list of buffered data chunks. +// This function is designed to be inlinable, so please take care when making +// changes to the function body. +function copyFromBuffer(n, list) { + var ret = bufferShim.allocUnsafe(n); + var p = list.head; + var c = 1; + p.data.copy(ret); + n -= p.data.length; + while (p = p.next) { + var buf = p.data; + var nb = n > buf.length ? buf.length : n; + buf.copy(ret, ret.length - n, 0, nb); + n -= nb; + if (n === 0) { + if (nb === buf.length) { + ++c; + if (p.next) list.head = p.next;else list.head = list.tail = null; + } else { + list.head = p; + p.data = buf.slice(nb); + } + break; + } + ++c; + } + list.length -= c; + return ret; +} + +function endReadable(stream) { + var state = stream._readableState; + + // If we get here before consuming all the bytes, then that is a + // bug in node. Should never happen. + if (state.length > 0) throw new Error('"endReadable()" called on non-empty stream'); + + if (!state.endEmitted) { + state.ended = true; + processNextTick(endReadableNT, state, stream); + } +} + +function endReadableNT(state, stream) { + // Check that we didn't get one last unshift. + if (!state.endEmitted && state.length === 0) { + state.endEmitted = true; + stream.readable = false; + stream.emit('end'); + } +} + +function forEach(xs, f) { + for (var i = 0, l = xs.length; i < l; i++) { + f(xs[i], i); + } +} + +function indexOf(xs, x) { + for (var i = 0, l = xs.length; i < l; i++) { + if (xs[i] === x) return i; + } + return -1; +} +}).call(this,require('_process')) + +},{"./_stream_duplex":15,"./internal/streams/BufferList":20,"_process":3,"buffer":5,"buffer-shims":4,"core-util-is":6,"events":7,"inherits":9,"isarray":11,"process-nextick-args":12,"string_decoder/":26,"util":2}],18:[function(require,module,exports){ +// a transform stream is a readable/writable stream where you do +// something with the data. Sometimes it's called a "filter", +// but that's not a great name for it, since that implies a thing where +// some bits pass through, and others are simply ignored. (That would +// be a valid example of a transform, of course.) +// +// While the output is causally related to the input, it's not a +// necessarily symmetric or synchronous transformation. For example, +// a zlib stream might take multiple plain-text writes(), and then +// emit a single compressed chunk some time in the future. +// +// Here's how this works: +// +// The Transform stream has all the aspects of the readable and writable +// stream classes. When you write(chunk), that calls _write(chunk,cb) +// internally, and returns false if there's a lot of pending writes +// buffered up. When you call read(), that calls _read(n) until +// there's enough pending readable data buffered up. +// +// In a transform stream, the written data is placed in a buffer. When +// _read(n) is called, it transforms the queued up data, calling the +// buffered _write cb's as it consumes chunks. If consuming a single +// written chunk would result in multiple output chunks, then the first +// outputted bit calls the readcb, and subsequent chunks just go into +// the read buffer, and will cause it to emit 'readable' if necessary. +// +// This way, back-pressure is actually determined by the reading side, +// since _read has to be called to start processing a new chunk. However, +// a pathological inflate type of transform can cause excessive buffering +// here. For example, imagine a stream where every byte of input is +// interpreted as an integer from 0-255, and then results in that many +// bytes of output. Writing the 4 bytes {ff,ff,ff,ff} would result in +// 1kb of data being output. In this case, you could write a very small +// amount of input, and end up with a very large amount of output. In +// such a pathological inflating mechanism, there'd be no way to tell +// the system to stop doing the transform. A single 4MB write could +// cause the system to run out of memory. +// +// However, even in such a pathological case, only a single written chunk +// would be consumed, and then the rest would wait (un-transformed) until +// the results of the previous transformed chunk were consumed. + +'use strict'; + +module.exports = Transform; + +var Duplex = require('./_stream_duplex'); + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +util.inherits(Transform, Duplex); + +function TransformState(stream) { + this.afterTransform = function (er, data) { + return afterTransform(stream, er, data); + }; + + this.needTransform = false; + this.transforming = false; + this.writecb = null; + this.writechunk = null; + this.writeencoding = null; +} + +function afterTransform(stream, er, data) { + var ts = stream._transformState; + ts.transforming = false; + + var cb = ts.writecb; + + if (!cb) return stream.emit('error', new Error('no writecb in Transform class')); + + ts.writechunk = null; + ts.writecb = null; + + if (data !== null && data !== undefined) stream.push(data); + + cb(er); + + var rs = stream._readableState; + rs.reading = false; + if (rs.needReadable || rs.length < rs.highWaterMark) { + stream._read(rs.highWaterMark); + } +} + +function Transform(options) { + if (!(this instanceof Transform)) return new Transform(options); + + Duplex.call(this, options); + + this._transformState = new TransformState(this); + + var stream = this; + + // start out asking for a readable event once data is transformed. + this._readableState.needReadable = true; + + // we have implemented the _read method, and done the other things + // that Readable wants before the first _read call, so unset the + // sync guard flag. + this._readableState.sync = false; + + if (options) { + if (typeof options.transform === 'function') this._transform = options.transform; + + if (typeof options.flush === 'function') this._flush = options.flush; + } + + // When the writable side finishes, then flush out anything remaining. + this.once('prefinish', function () { + if (typeof this._flush === 'function') this._flush(function (er, data) { + done(stream, er, data); + });else done(stream); + }); +} + +Transform.prototype.push = function (chunk, encoding) { + this._transformState.needTransform = false; + return Duplex.prototype.push.call(this, chunk, encoding); +}; + +// This is the part where you do stuff! +// override this function in implementation classes. +// 'chunk' is an input chunk. +// +// Call `push(newChunk)` to pass along transformed output +// to the readable side. You may call 'push' zero or more times. +// +// Call `cb(err)` when you are done with this chunk. If you pass +// an error, then that'll put the hurt on the whole operation. If you +// never call cb(), then you'll never get another chunk. +Transform.prototype._transform = function (chunk, encoding, cb) { + throw new Error('_transform() is not implemented'); +}; + +Transform.prototype._write = function (chunk, encoding, cb) { + var ts = this._transformState; + ts.writecb = cb; + ts.writechunk = chunk; + ts.writeencoding = encoding; + if (!ts.transforming) { + var rs = this._readableState; + if (ts.needTransform || rs.needReadable || rs.length < rs.highWaterMark) this._read(rs.highWaterMark); + } +}; + +// Doesn't matter what the args are here. +// _transform does all the work. +// That we got here means that the readable side wants more data. +Transform.prototype._read = function (n) { + var ts = this._transformState; + + if (ts.writechunk !== null && ts.writecb && !ts.transforming) { + ts.transforming = true; + this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform); + } else { + // mark that we need a transform, so that any data that comes in + // will get processed, now that we've asked for it. + ts.needTransform = true; + } +}; + +function done(stream, er, data) { + if (er) return stream.emit('error', er); + + if (data !== null && data !== undefined) stream.push(data); + + // if there's nothing in the write buffer, then that means + // that nothing more will ever be provided + var ws = stream._writableState; + var ts = stream._transformState; + + if (ws.length) throw new Error('Calling transform done when ws.length != 0'); + + if (ts.transforming) throw new Error('Calling transform done when still transforming'); + + return stream.push(null); +} +},{"./_stream_duplex":15,"core-util-is":6,"inherits":9}],19:[function(require,module,exports){ +(function (process){ +// A bit simpler than readable streams. +// Implement an async ._write(chunk, encoding, cb), and it'll handle all +// the drain event emission and buffering. + +'use strict'; + +module.exports = Writable; + +/**/ +var processNextTick = require('process-nextick-args'); +/**/ + +/**/ +var asyncWrite = !process.browser && ['v0.10', 'v0.9.'].indexOf(process.version.slice(0, 5)) > -1 ? setImmediate : processNextTick; +/**/ + +/**/ +var Duplex; +/**/ + +Writable.WritableState = WritableState; + +/**/ +var util = require('core-util-is'); +util.inherits = require('inherits'); +/**/ + +/**/ +var internalUtil = { + deprecate: require('util-deprecate') +}; +/**/ + +/**/ +var Stream; +(function () { + try { + Stream = require('st' + 'ream'); + } catch (_) {} finally { + if (!Stream) Stream = require('events').EventEmitter; + } +})(); +/**/ + +var Buffer = require('buffer').Buffer; +/**/ +var bufferShim = require('buffer-shims'); +/**/ + +util.inherits(Writable, Stream); + +function nop() {} + +function WriteReq(chunk, encoding, cb) { + this.chunk = chunk; + this.encoding = encoding; + this.callback = cb; + this.next = null; +} + +function WritableState(options, stream) { + Duplex = Duplex || require('./_stream_duplex'); + + options = options || {}; + + // object stream flag to indicate whether or not this stream + // contains buffers or objects. + this.objectMode = !!options.objectMode; + + if (stream instanceof Duplex) this.objectMode = this.objectMode || !!options.writableObjectMode; + + // the point at which write() starts returning false + // Note: 0 is a valid value, means that we always return false if + // the entire buffer is not flushed immediately on write() + var hwm = options.highWaterMark; + var defaultHwm = this.objectMode ? 16 : 16 * 1024; + this.highWaterMark = hwm || hwm === 0 ? hwm : defaultHwm; + + // cast to ints. + this.highWaterMark = ~ ~this.highWaterMark; + + // drain event flag. + this.needDrain = false; + // at the start of calling end() + this.ending = false; + // when end() has been called, and returned + this.ended = false; + // when 'finish' is emitted + this.finished = false; + + // should we decode strings into buffers before passing to _write? + // this is here so that some node-core streams can optimize string + // handling at a lower level. + var noDecode = options.decodeStrings === false; + this.decodeStrings = !noDecode; + + // Crypto is kind of old and crusty. Historically, its default string + // encoding is 'binary' so we have to make this configurable. + // Everything else in the universe uses 'utf8', though. + this.defaultEncoding = options.defaultEncoding || 'utf8'; + + // not an actual buffer we keep track of, but a measurement + // of how much we're waiting to get pushed to some underlying + // socket or file. + this.length = 0; + + // a flag to see when we're in the middle of a write. + this.writing = false; + + // when true all writes will be buffered until .uncork() call + this.corked = 0; + + // a flag to be able to tell if the onwrite cb is called immediately, + // or on a later tick. We set this to true at first, because any + // actions that shouldn't happen until "later" should generally also + // not happen before the first write call. + this.sync = true; + + // a flag to know if we're processing previously buffered items, which + // may call the _write() callback in the same tick, so that we don't + // end up in an overlapped onwrite situation. + this.bufferProcessing = false; + + // the callback that's passed to _write(chunk,cb) + this.onwrite = function (er) { + onwrite(stream, er); + }; + + // the callback that the user supplies to write(chunk,encoding,cb) + this.writecb = null; + + // the amount that is being written when _write is called. + this.writelen = 0; + + this.bufferedRequest = null; + this.lastBufferedRequest = null; + + // number of pending user-supplied write callbacks + // this must be 0 before 'finish' can be emitted + this.pendingcb = 0; + + // emit prefinish if the only thing we're waiting for is _write cbs + // This is relevant for synchronous Transform streams + this.prefinished = false; + + // True if the error was already emitted and should not be thrown again + this.errorEmitted = false; + + // count buffered requests + this.bufferedRequestCount = 0; + + // allocate the first CorkedRequest, there is always + // one allocated and free to use, and we maintain at most two + this.corkedRequestsFree = new CorkedRequest(this); +} + +WritableState.prototype.getBuffer = function getBuffer() { + var current = this.bufferedRequest; + var out = []; + while (current) { + out.push(current); + current = current.next; + } + return out; +}; + +(function () { + try { + Object.defineProperty(WritableState.prototype, 'buffer', { + get: internalUtil.deprecate(function () { + return this.getBuffer(); + }, '_writableState.buffer is deprecated. Use _writableState.getBuffer ' + 'instead.') + }); + } catch (_) {} +})(); + +// Test _writableState for inheritance to account for Duplex streams, +// whose prototype chain only points to Readable. +var realHasInstance; +if (typeof Symbol === 'function' && Symbol.hasInstance && typeof Function.prototype[Symbol.hasInstance] === 'function') { + realHasInstance = Function.prototype[Symbol.hasInstance]; + Object.defineProperty(Writable, Symbol.hasInstance, { + value: function (object) { + if (realHasInstance.call(this, object)) return true; + + return object && object._writableState instanceof WritableState; + } + }); +} else { + realHasInstance = function (object) { + return object instanceof this; + }; +} + +function Writable(options) { + Duplex = Duplex || require('./_stream_duplex'); + + // Writable ctor is applied to Duplexes, too. + // `realHasInstance` is necessary because using plain `instanceof` + // would return false, as no `_writableState` property is attached. + + // Trying to use the custom `instanceof` for Writable here will also break the + // Node.js LazyTransform implementation, which has a non-trivial getter for + // `_writableState` that would lead to infinite recursion. + if (!realHasInstance.call(Writable, this) && !(this instanceof Duplex)) { + return new Writable(options); + } + + this._writableState = new WritableState(options, this); + + // legacy. + this.writable = true; + + if (options) { + if (typeof options.write === 'function') this._write = options.write; + + if (typeof options.writev === 'function') this._writev = options.writev; + } + + Stream.call(this); +} + +// Otherwise people can pipe Writable streams, which is just wrong. +Writable.prototype.pipe = function () { + this.emit('error', new Error('Cannot pipe, not readable')); +}; + +function writeAfterEnd(stream, cb) { + var er = new Error('write after end'); + // TODO: defer error events consistently everywhere, not just the cb + stream.emit('error', er); + processNextTick(cb, er); +} + +// If we get something that is not a buffer, string, null, or undefined, +// and we're not in objectMode, then that's an error. +// Otherwise stream chunks are all considered to be of length=1, and the +// watermarks determine how many objects to keep in the buffer, rather than +// how many bytes or characters. +function validChunk(stream, state, chunk, cb) { + var valid = true; + var er = false; + // Always throw error if a null is written + // if we are not in object mode then throw + // if it is not a buffer, string, or undefined. + if (chunk === null) { + er = new TypeError('May not write null values to stream'); + } else if (!Buffer.isBuffer(chunk) && typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { + er = new TypeError('Invalid non-string/buffer chunk'); + } + if (er) { + stream.emit('error', er); + processNextTick(cb, er); + valid = false; + } + return valid; +} + +Writable.prototype.write = function (chunk, encoding, cb) { + var state = this._writableState; + var ret = false; + + if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } + + if (Buffer.isBuffer(chunk)) encoding = 'buffer';else if (!encoding) encoding = state.defaultEncoding; + + if (typeof cb !== 'function') cb = nop; + + if (state.ended) writeAfterEnd(this, cb);else if (validChunk(this, state, chunk, cb)) { + state.pendingcb++; + ret = writeOrBuffer(this, state, chunk, encoding, cb); + } + + return ret; +}; + +Writable.prototype.cork = function () { + var state = this._writableState; + + state.corked++; +}; + +Writable.prototype.uncork = function () { + var state = this._writableState; + + if (state.corked) { + state.corked--; + + if (!state.writing && !state.corked && !state.finished && !state.bufferProcessing && state.bufferedRequest) clearBuffer(this, state); + } +}; + +Writable.prototype.setDefaultEncoding = function setDefaultEncoding(encoding) { + // node::ParseEncoding() requires lower case. + if (typeof encoding === 'string') encoding = encoding.toLowerCase(); + if (!(['hex', 'utf8', 'utf-8', 'ascii', 'binary', 'base64', 'ucs2', 'ucs-2', 'utf16le', 'utf-16le', 'raw'].indexOf((encoding + '').toLowerCase()) > -1)) throw new TypeError('Unknown encoding: ' + encoding); + this._writableState.defaultEncoding = encoding; + return this; +}; + +function decodeChunk(state, chunk, encoding) { + if (!state.objectMode && state.decodeStrings !== false && typeof chunk === 'string') { + chunk = bufferShim.from(chunk, encoding); + } + return chunk; +} + +// if we're already writing something, then just put this +// in the queue, and wait our turn. Otherwise, call _write +// If we return false, then we need a drain event, so set that flag. +function writeOrBuffer(stream, state, chunk, encoding, cb) { + chunk = decodeChunk(state, chunk, encoding); + + if (Buffer.isBuffer(chunk)) encoding = 'buffer'; + var len = state.objectMode ? 1 : chunk.length; + + state.length += len; + + var ret = state.length < state.highWaterMark; + // we must ensure that previous needDrain will not be reset to false. + if (!ret) state.needDrain = true; + + if (state.writing || state.corked) { + var last = state.lastBufferedRequest; + state.lastBufferedRequest = new WriteReq(chunk, encoding, cb); + if (last) { + last.next = state.lastBufferedRequest; + } else { + state.bufferedRequest = state.lastBufferedRequest; + } + state.bufferedRequestCount += 1; + } else { + doWrite(stream, state, false, len, chunk, encoding, cb); + } + + return ret; +} + +function doWrite(stream, state, writev, len, chunk, encoding, cb) { + state.writelen = len; + state.writecb = cb; + state.writing = true; + state.sync = true; + if (writev) stream._writev(chunk, state.onwrite);else stream._write(chunk, encoding, state.onwrite); + state.sync = false; +} + +function onwriteError(stream, state, sync, er, cb) { + --state.pendingcb; + if (sync) processNextTick(cb, er);else cb(er); + + stream._writableState.errorEmitted = true; + stream.emit('error', er); +} + +function onwriteStateUpdate(state) { + state.writing = false; + state.writecb = null; + state.length -= state.writelen; + state.writelen = 0; +} + +function onwrite(stream, er) { + var state = stream._writableState; + var sync = state.sync; + var cb = state.writecb; + + onwriteStateUpdate(state); + + if (er) onwriteError(stream, state, sync, er, cb);else { + // Check if we're actually ready to finish, but don't emit yet + var finished = needFinish(state); + + if (!finished && !state.corked && !state.bufferProcessing && state.bufferedRequest) { + clearBuffer(stream, state); + } + + if (sync) { + /**/ + asyncWrite(afterWrite, stream, state, finished, cb); + /**/ + } else { + afterWrite(stream, state, finished, cb); + } + } +} + +function afterWrite(stream, state, finished, cb) { + if (!finished) onwriteDrain(stream, state); + state.pendingcb--; + cb(); + finishMaybe(stream, state); +} + +// Must force callback to be called on nextTick, so that we don't +// emit 'drain' before the write() consumer gets the 'false' return +// value, and has a chance to attach a 'drain' listener. +function onwriteDrain(stream, state) { + if (state.length === 0 && state.needDrain) { + state.needDrain = false; + stream.emit('drain'); + } +} + +// if there's something in the buffer waiting, then process it +function clearBuffer(stream, state) { + state.bufferProcessing = true; + var entry = state.bufferedRequest; + + if (stream._writev && entry && entry.next) { + // Fast case, write everything using _writev() + var l = state.bufferedRequestCount; + var buffer = new Array(l); + var holder = state.corkedRequestsFree; + holder.entry = entry; + + var count = 0; + while (entry) { + buffer[count] = entry; + entry = entry.next; + count += 1; + } + + doWrite(stream, state, true, state.length, buffer, '', holder.finish); + + // doWrite is almost always async, defer these to save a bit of time + // as the hot path ends with doWrite + state.pendingcb++; + state.lastBufferedRequest = null; + if (holder.next) { + state.corkedRequestsFree = holder.next; + holder.next = null; + } else { + state.corkedRequestsFree = new CorkedRequest(state); + } + } else { + // Slow case, write chunks one-by-one + while (entry) { + var chunk = entry.chunk; + var encoding = entry.encoding; + var cb = entry.callback; + var len = state.objectMode ? 1 : chunk.length; + + doWrite(stream, state, false, len, chunk, encoding, cb); + entry = entry.next; + // if we didn't call the onwrite immediately, then + // it means that we need to wait until it does. + // also, that means that the chunk and cb are currently + // being processed, so move the buffer counter past them. + if (state.writing) { + break; + } + } + + if (entry === null) state.lastBufferedRequest = null; + } + + state.bufferedRequestCount = 0; + state.bufferedRequest = entry; + state.bufferProcessing = false; +} + +Writable.prototype._write = function (chunk, encoding, cb) { + cb(new Error('_write() is not implemented')); +}; + +Writable.prototype._writev = null; + +Writable.prototype.end = function (chunk, encoding, cb) { + var state = this._writableState; + + if (typeof chunk === 'function') { + cb = chunk; + chunk = null; + encoding = null; + } else if (typeof encoding === 'function') { + cb = encoding; + encoding = null; + } + + if (chunk !== null && chunk !== undefined) this.write(chunk, encoding); + + // .end() fully uncorks + if (state.corked) { + state.corked = 1; + this.uncork(); + } + + // ignore unnecessary end() calls. + if (!state.ending && !state.finished) endWritable(this, state, cb); +}; + +function needFinish(state) { + return state.ending && state.length === 0 && state.bufferedRequest === null && !state.finished && !state.writing; +} + +function prefinish(stream, state) { + if (!state.prefinished) { + state.prefinished = true; + stream.emit('prefinish'); + } +} + +function finishMaybe(stream, state) { + var need = needFinish(state); + if (need) { + if (state.pendingcb === 0) { + prefinish(stream, state); + state.finished = true; + stream.emit('finish'); + } else { + prefinish(stream, state); + } + } + return need; +} + +function endWritable(stream, state, cb) { + state.ending = true; + finishMaybe(stream, state); + if (cb) { + if (state.finished) processNextTick(cb);else stream.once('finish', cb); + } + state.ended = true; + stream.writable = false; +} + +// It seems a linked list but it is not +// there will be only 2 of these for each stream +function CorkedRequest(state) { + var _this = this; + + this.next = null; + this.entry = null; + + this.finish = function (err) { + var entry = _this.entry; + _this.entry = null; + while (entry) { + var cb = entry.callback; + state.pendingcb--; + cb(err); + entry = entry.next; + } + if (state.corkedRequestsFree) { + state.corkedRequestsFree.next = _this; + } else { + state.corkedRequestsFree = _this; + } + }; +} +}).call(this,require('_process')) + +},{"./_stream_duplex":15,"_process":3,"buffer":5,"buffer-shims":4,"core-util-is":6,"events":7,"inherits":9,"process-nextick-args":12,"util-deprecate":27}],20:[function(require,module,exports){ +'use strict'; + +var Buffer = require('buffer').Buffer; +/**/ +var bufferShim = require('buffer-shims'); +/**/ + +module.exports = BufferList; + +function BufferList() { + this.head = null; + this.tail = null; + this.length = 0; +} + +BufferList.prototype.push = function (v) { + var entry = { data: v, next: null }; + if (this.length > 0) this.tail.next = entry;else this.head = entry; + this.tail = entry; + ++this.length; +}; + +BufferList.prototype.unshift = function (v) { + var entry = { data: v, next: this.head }; + if (this.length === 0) this.tail = entry; + this.head = entry; + ++this.length; +}; + +BufferList.prototype.shift = function () { + if (this.length === 0) return; + var ret = this.head.data; + if (this.length === 1) this.head = this.tail = null;else this.head = this.head.next; + --this.length; + return ret; +}; + +BufferList.prototype.clear = function () { + this.head = this.tail = null; + this.length = 0; +}; + +BufferList.prototype.join = function (s) { + if (this.length === 0) return ''; + var p = this.head; + var ret = '' + p.data; + while (p = p.next) { + ret += s + p.data; + }return ret; +}; + +BufferList.prototype.concat = function (n) { + if (this.length === 0) return bufferShim.alloc(0); + if (this.length === 1) return this.head.data; + var ret = bufferShim.allocUnsafe(n >>> 0); + var p = this.head; + var i = 0; + while (p) { + p.data.copy(ret, i); + i += p.data.length; + p = p.next; + } + return ret; +}; +},{"buffer":5,"buffer-shims":4}],21:[function(require,module,exports){ +module.exports = require("./lib/_stream_passthrough.js") + +},{"./lib/_stream_passthrough.js":16}],22:[function(require,module,exports){ +(function (process){ +var Stream = (function (){ + try { + return require('st' + 'ream'); // hack to fix a circular dependency issue when used with browserify + } catch(_){} +}()); +exports = module.exports = require('./lib/_stream_readable.js'); +exports.Stream = Stream || exports; +exports.Readable = exports; +exports.Writable = require('./lib/_stream_writable.js'); +exports.Duplex = require('./lib/_stream_duplex.js'); +exports.Transform = require('./lib/_stream_transform.js'); +exports.PassThrough = require('./lib/_stream_passthrough.js'); + +if (!process.browser && process.env.READABLE_STREAM === 'disable' && Stream) { + module.exports = Stream; +} + +}).call(this,require('_process')) + +},{"./lib/_stream_duplex.js":15,"./lib/_stream_passthrough.js":16,"./lib/_stream_readable.js":17,"./lib/_stream_transform.js":18,"./lib/_stream_writable.js":19,"_process":3}],23:[function(require,module,exports){ +module.exports = require("./lib/_stream_transform.js") + +},{"./lib/_stream_transform.js":18}],24:[function(require,module,exports){ +module.exports = require("./lib/_stream_writable.js") + +},{"./lib/_stream_writable.js":19}],25:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +module.exports = Stream; + +var EE = require('events').EventEmitter; +var inherits = require('inherits'); + +inherits(Stream, EE); +Stream.Readable = require('readable-stream/readable.js'); +Stream.Writable = require('readable-stream/writable.js'); +Stream.Duplex = require('readable-stream/duplex.js'); +Stream.Transform = require('readable-stream/transform.js'); +Stream.PassThrough = require('readable-stream/passthrough.js'); + +// Backwards-compat with node 0.4.x +Stream.Stream = Stream; + + + +// old-style streams. Note that the pipe method (the only relevant +// part of this class) is overridden in the Readable class. + +function Stream() { + EE.call(this); +} + +Stream.prototype.pipe = function(dest, options) { + var source = this; + + function ondata(chunk) { + if (dest.writable) { + if (false === dest.write(chunk) && source.pause) { + source.pause(); + } + } + } + + source.on('data', ondata); + + function ondrain() { + if (source.readable && source.resume) { + source.resume(); + } + } + + dest.on('drain', ondrain); + + // If the 'end' option is not supplied, dest.end() will be called when + // source gets the 'end' or 'close' events. Only dest.end() once. + if (!dest._isStdio && (!options || options.end !== false)) { + source.on('end', onend); + source.on('close', onclose); + } + + var didOnEnd = false; + function onend() { + if (didOnEnd) return; + didOnEnd = true; + + dest.end(); + } + + + function onclose() { + if (didOnEnd) return; + didOnEnd = true; + + if (typeof dest.destroy === 'function') dest.destroy(); + } + + // don't leave dangling pipes when there are errors. + function onerror(er) { + cleanup(); + if (EE.listenerCount(this, 'error') === 0) { + throw er; // Unhandled stream error in pipe. + } + } + + source.on('error', onerror); + dest.on('error', onerror); + + // remove all the event listeners that were added. + function cleanup() { + source.removeListener('data', ondata); + dest.removeListener('drain', ondrain); + + source.removeListener('end', onend); + source.removeListener('close', onclose); + + source.removeListener('error', onerror); + dest.removeListener('error', onerror); + + source.removeListener('end', cleanup); + source.removeListener('close', cleanup); + + dest.removeListener('close', cleanup); + } + + source.on('end', cleanup); + source.on('close', cleanup); + + dest.on('close', cleanup); + + dest.emit('pipe', source); + + // Allow for unix-like usage: A.pipe(B).pipe(C) + return dest; +}; + +},{"events":7,"inherits":9,"readable-stream/duplex.js":14,"readable-stream/passthrough.js":21,"readable-stream/readable.js":22,"readable-stream/transform.js":23,"readable-stream/writable.js":24}],26:[function(require,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var Buffer = require('buffer').Buffer; + +var isBufferEncoding = Buffer.isEncoding + || function(encoding) { + switch (encoding && encoding.toLowerCase()) { + case 'hex': case 'utf8': case 'utf-8': case 'ascii': case 'binary': case 'base64': case 'ucs2': case 'ucs-2': case 'utf16le': case 'utf-16le': case 'raw': return true; + default: return false; + } + } + + +function assertEncoding(encoding) { + if (encoding && !isBufferEncoding(encoding)) { + throw new Error('Unknown encoding: ' + encoding); + } +} + +// StringDecoder provides an interface for efficiently splitting a series of +// buffers into a series of JS strings without breaking apart multi-byte +// characters. CESU-8 is handled as part of the UTF-8 encoding. +// +// @TODO Handling all encodings inside a single object makes it very difficult +// to reason about this code, so it should be split up in the future. +// @TODO There should be a utf8-strict encoding that rejects invalid UTF-8 code +// points as used by CESU-8. +var StringDecoder = exports.StringDecoder = function(encoding) { + this.encoding = (encoding || 'utf8').toLowerCase().replace(/[-_]/, ''); + assertEncoding(encoding); + switch (this.encoding) { + case 'utf8': + // CESU-8 represents each of Surrogate Pair by 3-bytes + this.surrogateSize = 3; + break; + case 'ucs2': + case 'utf16le': + // UTF-16 represents each of Surrogate Pair by 2-bytes + this.surrogateSize = 2; + this.detectIncompleteChar = utf16DetectIncompleteChar; + break; + case 'base64': + // Base-64 stores 3 bytes in 4 chars, and pads the remainder. + this.surrogateSize = 3; + this.detectIncompleteChar = base64DetectIncompleteChar; + break; + default: + this.write = passThroughWrite; + return; + } + + // Enough space to store all bytes of a single character. UTF-8 needs 4 + // bytes, but CESU-8 may require up to 6 (3 bytes per surrogate). + this.charBuffer = new Buffer(6); + // Number of bytes received for the current incomplete multi-byte character. + this.charReceived = 0; + // Number of bytes expected for the current incomplete multi-byte character. + this.charLength = 0; +}; + + +// write decodes the given buffer and returns it as JS string that is +// guaranteed to not contain any partial multi-byte characters. Any partial +// character found at the end of the buffer is buffered up, and will be +// returned when calling write again with the remaining bytes. +// +// Note: Converting a Buffer containing an orphan surrogate to a String +// currently works, but converting a String to a Buffer (via `new Buffer`, or +// Buffer#write) will replace incomplete surrogates with the unicode +// replacement character. See https://codereview.chromium.org/121173009/ . +StringDecoder.prototype.write = function(buffer) { + var charStr = ''; + // if our last write ended with an incomplete multibyte character + while (this.charLength) { + // determine how many remaining bytes this buffer has to offer for this char + var available = (buffer.length >= this.charLength - this.charReceived) ? + this.charLength - this.charReceived : + buffer.length; + + // add the new bytes to the char buffer + buffer.copy(this.charBuffer, this.charReceived, 0, available); + this.charReceived += available; + + if (this.charReceived < this.charLength) { + // still not enough chars in this buffer? wait for more ... + return ''; + } + + // remove bytes belonging to the current character from the buffer + buffer = buffer.slice(available, buffer.length); + + // get the character that was split + charStr = this.charBuffer.slice(0, this.charLength).toString(this.encoding); + + // CESU-8: lead surrogate (D800-DBFF) is also the incomplete character + var charCode = charStr.charCodeAt(charStr.length - 1); + if (charCode >= 0xD800 && charCode <= 0xDBFF) { + this.charLength += this.surrogateSize; + charStr = ''; + continue; + } + this.charReceived = this.charLength = 0; + + // if there are no more bytes in this buffer, just emit our char + if (buffer.length === 0) { + return charStr; + } + break; + } + + // determine and set charLength / charReceived + this.detectIncompleteChar(buffer); + + var end = buffer.length; + if (this.charLength) { + // buffer the incomplete character bytes we got + buffer.copy(this.charBuffer, 0, buffer.length - this.charReceived, end); + end -= this.charReceived; + } + + charStr += buffer.toString(this.encoding, 0, end); + + var end = charStr.length - 1; + var charCode = charStr.charCodeAt(end); + // CESU-8: lead surrogate (D800-DBFF) is also the incomplete character + if (charCode >= 0xD800 && charCode <= 0xDBFF) { + var size = this.surrogateSize; + this.charLength += size; + this.charReceived += size; + this.charBuffer.copy(this.charBuffer, size, 0, size); + buffer.copy(this.charBuffer, 0, 0, size); + return charStr.substring(0, end); + } + + // or just emit the charStr + return charStr; +}; + +// detectIncompleteChar determines if there is an incomplete UTF-8 character at +// the end of the given buffer. If so, it sets this.charLength to the byte +// length that character, and sets this.charReceived to the number of bytes +// that are available for this character. +StringDecoder.prototype.detectIncompleteChar = function(buffer) { + // determine how many bytes we have to check at the end of this buffer + var i = (buffer.length >= 3) ? 3 : buffer.length; + + // Figure out if one of the last i bytes of our buffer announces an + // incomplete char. + for (; i > 0; i--) { + var c = buffer[buffer.length - i]; + + // See http://en.wikipedia.org/wiki/UTF-8#Description + + // 110XXXXX + if (i == 1 && c >> 5 == 0x06) { + this.charLength = 2; + break; + } + + // 1110XXXX + if (i <= 2 && c >> 4 == 0x0E) { + this.charLength = 3; + break; + } + + // 11110XXX + if (i <= 3 && c >> 3 == 0x1E) { + this.charLength = 4; + break; + } + } + this.charReceived = i; +}; + +StringDecoder.prototype.end = function(buffer) { + var res = ''; + if (buffer && buffer.length) + res = this.write(buffer); + + if (this.charReceived) { + var cr = this.charReceived; + var buf = this.charBuffer; + var enc = this.encoding; + res += buf.slice(0, cr).toString(enc); + } + + return res; +}; + +function passThroughWrite(buffer) { + return buffer.toString(this.encoding); +} + +function utf16DetectIncompleteChar(buffer) { + this.charReceived = buffer.length % 2; + this.charLength = this.charReceived ? 2 : 0; +} + +function base64DetectIncompleteChar(buffer) { + this.charReceived = buffer.length % 3; + this.charLength = this.charReceived ? 3 : 0; +} + +},{"buffer":5}],27:[function(require,module,exports){ +(function (global){ + +/** + * Module exports. + */ + +module.exports = deprecate; + +/** + * Mark that a method should not be used. + * Returns a modified function which warns once by default. + * + * If `localStorage.noDeprecation = true` is set, then it is a no-op. + * + * If `localStorage.throwDeprecation = true` is set, then deprecated functions + * will throw an Error when invoked. + * + * If `localStorage.traceDeprecation = true` is set, then deprecated functions + * will invoke `console.trace()` instead of `console.error()`. + * + * @param {Function} fn - the function to deprecate + * @param {String} msg - the string to print to the console when `fn` is invoked + * @returns {Function} a new "deprecated" version of `fn` + * @api public + */ + +function deprecate (fn, msg) { + if (config('noDeprecation')) { + return fn; + } + + var warned = false; + function deprecated() { + if (!warned) { + if (config('throwDeprecation')) { + throw new Error(msg); + } else if (config('traceDeprecation')) { + console.trace(msg); + } else { + console.warn(msg); + } + warned = true; + } + return fn.apply(this, arguments); + } + + return deprecated; +} + +/** + * Checks `localStorage` for boolean values for the given `name`. + * + * @param {String} name + * @returns {Boolean} + * @api private + */ + +function config (name) { + // accessing global.localStorage can trigger a DOMException in sandboxed iframes + try { + if (!global.localStorage) return false; + } catch (_) { + return false; + } + var val = global.localStorage[name]; + if (null == val) return false; + return String(val).toLowerCase() === 'true'; +} + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{}],28:[function(require,module,exports){ +'use strict'; + +var parser = require('./lib/parser'); +var processingInstructions = require('./lib/processing-instructions'); +var isValidNodeDefinitions = require('./lib/is-valid-node-definitions'); +var processNodeDefinitions = require('./lib/process-node-definitions'); + +module.exports = { + Parser: parser, + ProcessingInstructions: processingInstructions, + IsValidNodeDefinitions: isValidNodeDefinitions, + ProcessNodeDefinitions: processNodeDefinitions, +}; + +},{"./lib/is-valid-node-definitions":30,"./lib/parser":31,"./lib/process-node-definitions":32,"./lib/processing-instructions":33}],29:[function(require,module,exports){ +// These are all sourced from https://facebook.github.io/react/docs/tags-and-attributes.html - +// all attributes regardless of whether they have a different case to their HTML equivalents are +// listed to reduce the chance of human error and make it easier to just copy-paste the new list if +// it changes. +'use strict'; +var HTML_ATTRIBUTES = [ + 'accept', 'acceptCharset', 'accessKey', 'action', 'allowFullScreen', 'allowTransparency', + 'alt', 'async', 'autoComplete', 'autoFocus', 'autoPlay', 'capture', 'cellPadding', + 'cellSpacing', 'challenge', 'charSet', 'checked', 'cite', 'classID', 'className', + 'colSpan', 'cols', 'content', 'contentEditable', 'contextMenu', 'controls', 'coords', + 'crossOrigin', 'data', 'dateTime', 'default', 'defer', 'dir', 'disabled', 'download', + 'draggable', 'encType', 'form', 'formAction', 'formEncType', 'formMethod', 'formNoValidate', + 'formTarget', 'frameBorder', 'headers', 'height', 'hidden', 'high', 'href', 'hrefLang', + 'htmlFor', 'httpEquiv', 'icon', 'id', 'inputMode', 'integrity', 'is', 'keyParams', 'keyType', + 'kind', 'label', 'lang', 'list', 'loop', 'low', 'manifest', 'marginHeight', 'marginWidth', + 'max', 'maxLength', 'media', 'mediaGroup', 'method', 'min', 'minLength', 'multiple', 'muted', + 'name', 'noValidate', 'nonce', 'open', 'optimum', 'pattern', 'placeholder', 'poster', + 'preload', 'profile', 'radioGroup', 'readOnly', 'rel', 'required', 'reversed', 'role', + 'rowSpan', 'rows', 'sandbox', 'scope', 'scoped', 'scrolling', 'seamless', 'selected', + 'shape', 'size', 'sizes', 'span', 'spellCheck', 'src', 'srcDoc', 'srcLang', 'srcSet', 'start', + 'step', 'style', 'summary', 'tabIndex', 'target', 'title', 'type', 'useMap', 'value', 'width', + 'wmode', 'wrap', +]; + +var NON_STANDARD_ATTRIBUTES = [ + 'autoCapitalize', 'autoCorrect', 'color', 'itemProp', 'itemScope', 'itemType', 'itemRef', + 'itemID', 'security', 'unselectable', 'results', 'autoSave', +]; + +var SVG_ATTRIBUTES = [ + 'accentHeight', 'accumulate', 'additive', 'alignmentBaseline', 'allowReorder', 'alphabetic', + 'amplitude', 'arabicForm', 'ascent', 'attributeName', 'attributeType', 'autoReverse', + 'azimuth', 'baseFrequency', 'baseProfile', 'baselineShift', 'bbox', 'begin', 'bias', 'by', + 'calcMode', 'capHeight', 'clip', 'clipPath', 'clipPathUnits', 'clipRule', 'colorInterpolation', + 'colorInterpolationFilters', 'colorProfile', 'colorRendering', 'contentScriptType', + 'contentStyleType', 'cursor', 'cx', 'cy', 'd', 'decelerate', 'descent', 'diffuseConstant', + 'direction', 'display', 'divisor', 'dominantBaseline', 'dur', 'dx', 'dy', 'edgeMode', + 'elevation', 'enableBackground', 'end', 'exponent', 'externalResourcesRequired', 'fill', + 'fillOpacity', 'fillRule', 'filter', 'filterRes', 'filterUnits', 'floodColor', 'floodOpacity', + 'focusable', 'fontFamily', 'fontSize', 'fontSizeAdjust', 'fontStretch', 'fontStyle', + 'fontVariant', 'fontWeight', 'format', 'from', 'fx', 'fy', 'g1', 'g2', 'glyphName', + 'glyphOrientationHorizontal', 'glyphOrientationVertical', 'glyphRef', 'gradientTransform', + 'gradientUnits', 'hanging', 'horizAdvX', 'horizOriginX', 'ideographic', 'imageRendering', + 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kernelMatrix', 'kernelUnitLength', + 'kerning', 'keyPoints', 'keySplines', 'keyTimes', 'lengthAdjust', 'letterSpacing', + 'lightingColor', 'limitingConeAngle', 'local', 'markerEnd', 'markerHeight', 'markerMid', + 'markerStart', 'markerUnits', 'markerWidth', 'mask', 'maskContentUnits', 'maskUnits', + 'mathematical', 'mode', 'numOctaves', 'offset', 'opacity', 'operator', 'order', + 'orient', 'orientation', 'origin', 'overflow', 'overlinePosition', 'overlineThickness', + 'paintOrder', 'panose1', 'pathLength', 'patternContentUnits', 'patternTransform', + 'patternUnits', 'pointerEvents', 'points', 'pointsAtX', 'pointsAtY', 'pointsAtZ', + 'preserveAlpha', 'preserveAspectRatio', 'primitiveUnits', 'r', 'radius', 'refX', 'refY', + 'renderingIntent', 'repeatCount', 'repeatDur', 'requiredExtensions', 'requiredFeatures', + 'restart', 'result', 'rotate', 'rx', 'ry', 'scale', 'seed', 'shapeRendering', 'slope', + 'spacing', 'specularConstant', 'specularExponent', 'speed', 'spreadMethod', 'startOffset', + 'stdDeviation', 'stemh', 'stemv', 'stitchTiles', 'stopColor', 'stopOpacity', + 'strikethroughPosition', 'strikethroughThickness', 'string', 'stroke', 'strokeDasharray', + 'strokeDashoffset', 'strokeLinecap', 'strokeLinejoin', 'strokeMiterlimit', + 'strokeOpacity', 'strokeWidth', 'surfaceScale', 'systemLanguage', 'tableValues', 'targetX', + 'targetY', 'textAnchor', 'textDecoration', 'textLength', 'textRendering', 'to', 'transform', + 'u1', 'u2', 'underlinePosition', 'underlineThickness', 'unicode', 'unicodeBidi', + 'unicodeRange', 'unitsPerEm', 'vAlphabetic', 'vHanging', 'vIdeographic', 'vMathematical', + 'values', 'vectorEffect', 'version', 'vertAdvY', 'vertOriginX', 'vertOriginY', 'viewBox', + 'viewTarget', 'visibility', 'widths', 'wordSpacing', 'writingMode', 'x', 'x1', 'x2', + 'xChannelSelector', 'xHeight', 'xlinkActuate', 'xlinkArcrole', 'xlinkHref', 'xlinkRole', + 'xlinkShow', 'xlinkTitle', 'xlinkType', 'xmlBase', 'xmlLang', 'xmlSpace', 'y', 'y1', 'y2', + 'yChannelSelector', 'z', 'zoomAndPan', +]; + +var camelCaseMap = HTML_ATTRIBUTES + .concat(NON_STANDARD_ATTRIBUTES) + .concat(SVG_ATTRIBUTES) + .reduce(function (soFar, attr) { + var lower = attr.toLowerCase(); + if (lower !== attr) { + soFar[lower] = attr; + } + return soFar; + }, {}); + +module.exports = camelCaseMap; + +},{}],30:[function(require,module,exports){ +'use strict'; +function alwaysValid() { + return true; +} + +module.exports = { + alwaysValid: alwaysValid, +}; + +},{}],31:[function(require,module,exports){ +'use strict'; +var find = require('ramda/src/find'); +var reject = require('ramda/src/reject'); +var addIndex = require('ramda/src/addIndex'); +var map = require('ramda/src/map'); +var htmlParser = require('htmlparser2'); +var ProcessingInstructions = require('./processing-instructions'); +var IsValidNodeDefinitions = require('./is-valid-node-definitions'); +var utils = require('./utils'); + +function Html2ReactParser(options) { + function parseHtmlToTree(html) { + var handler = new htmlParser.DomHandler(); + var parser = new htmlParser.Parser(handler, options); + parser.parseComplete(html); + return handler.dom; + }; + + function traverseDom(node, isValidNode, processingInstructions, index) { + if (isValidNode(node)) { + var processingInstruction = find(function (processingInstruction) { + return processingInstruction.shouldProcessNode(node); + }, processingInstructions); + if (processingInstruction != null) { + var children = reject(function (x) {return x == null || x === false;}, + addIndex(map)(function (child, i) { + return traverseDom(child, isValidNode, processingInstructions, i); + }, node.children || [])); + + if (processingInstruction.replaceChildren) { + return utils.createElement(node, index, node.data, [ + processingInstruction.processNode(node, children, index), + ]); + } + + return processingInstruction.processNode(node, children, index); + } else { + return false; + } + } else { + return false; + } + }; + + function parseWithInstructions(html, isValidNode, processingInstructions) { + var domTree = parseHtmlToTree(html); + // TODO: Deal with HTML that contains more than one root level node + if (domTree && domTree.length !== 1) { + throw new Error( + 'html-to-react currently only supports HTML with one single root element. ' + + 'The HTML provided contains ' + domTree.length + + ' root elements. You can fix that by simply wrapping your HTML ' + + 'in a
element.'); + } + return traverseDom(domTree[0], isValidNode, processingInstructions, 0); + }; + + function parse(html) { + var processingInstructions = new ProcessingInstructions(); + return parseWithInstructions(html, + IsValidNodeDefinitions.alwaysValid, + processingInstructions.defaultProcessingInstructions); + }; + + return { + parse: parse, + parseWithInstructions: parseWithInstructions, + }; +}; + +module.exports = Html2ReactParser; + +},{"./is-valid-node-definitions":30,"./processing-instructions":33,"./utils":35,"htmlparser2":80,"ramda/src/addIndex":83,"ramda/src/find":87,"ramda/src/map":115,"ramda/src/reject":117}],32:[function(require,module,exports){ +'use strict'; +var ent = require('ent'); +var utils = require('./utils'); + +// https://github.com/facebook/react/blob/15.0-stable/src/renderers/dom/shared/ReactDOMComponent.js#L457 +var voidElementTags = [ + 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', + 'source', 'track', 'wbr', 'menuitem', 'textarea', +]; + +function ProcessNodeDefinitions() { + function processDefaultNode(node, children, index) { + if (node.type === 'text') { + return ent.decode(node.data); + } else if (node.type === 'comment') { + // FIXME: The following doesn't work as the generated HTML results in + // "<!-- This is a comment -->" + // return ''; + return false; + } + + if (voidElementTags.indexOf(node.name) > -1) { + return utils.createElement(node, index); + } else { + return utils.createElement(node, index, node.data, children); + } + } + + return { + processDefaultNode: processDefaultNode, + }; +} + +module.exports = ProcessNodeDefinitions; + +},{"./utils":35,"ent":57}],33:[function(require,module,exports){ +'use strict'; +var ShouldProcessNodeDefinitions = require('./should-process-node-definitions'); +var ProcessNodeDefinitions = require('./process-node-definitions'); + +function ProcessingInstructions() { + var processNodeDefinitions = new ProcessNodeDefinitions(); + + return { + defaultProcessingInstructions: [{ + shouldProcessNode: ShouldProcessNodeDefinitions.shouldProcessEveryNode, + processNode: processNodeDefinitions.processDefaultNode, + },], + }; +}; + +module.exports = ProcessingInstructions; + +},{"./process-node-definitions":32,"./should-process-node-definitions":34}],34:[function(require,module,exports){ +'use strict'; +function shouldProcessEveryNode(node) { + return true; +} + +module.exports = { + shouldProcessEveryNode: shouldProcessEveryNode, +}; + +},{}],35:[function(require,module,exports){ +'use strict'; +var camelize = require('underscore.string.fp/camelize'); +var toPairs = require('ramda/src/toPairs'); +var reduce = require('ramda/src/reduce'); +var React = require('react'); +var camelCaseAttrMap = require('./camel-case-attribute-names'); +var ent = require('ent'); + +function createStyleJsonFromString(styleString) { + if (!styleString) { + return {}; + } + var styles = styleString.split(';'); + var singleStyle, key, value, jsonStyles = {}; + for (var i = 0; i < styles.length; i++) { + singleStyle = styles[i].split(':'); + key = camelize(singleStyle[0]); + value = singleStyle[1]; + if (key.length > 0 && value.length > 0) { + jsonStyles[key] = value; + } + } + return jsonStyles; +} + +function createElement(node, index, data, children) { + var elementProps = { + key: index, + }; + if (node.attribs) { + elementProps = reduce(function(result, keyAndValue) { + var key = keyAndValue[0]; + var value = keyAndValue[1]; + key = camelCaseAttrMap[key.replace(/[-:]/, '')] || key; + if (key === 'style') { + value = createStyleJsonFromString(value); + } else if (key === 'class') { + key = 'className'; + } + if (typeof value === 'string') { + value = ent.decode(value); + } + result[key] = value || key; + return result; + }, elementProps, toPairs(node.attribs)); + } + + children = children || []; + var allChildren = data != null ? [data,].concat(children) : children; + return React.createElement.apply( + null, [node.name, elementProps,].concat(allChildren) + ); +} + +module.exports = { + createElement: createElement, +}; + +},{"./camel-case-attribute-names":29,"ent":57,"ramda/src/reduce":116,"ramda/src/toPairs":118,"react":143,"underscore.string.fp/camelize":144}],36:[function(require,module,exports){ +var merge = require('./lib/merge'), + countDefinedItems = require('./lib/countDefinedItems'), + slice = Array.prototype.slice, + __; + + +function curry(fn, length, curryArgs) { + return function() { + var args = slice.call(arguments), + concatArgs = curryArgs.concat(args), + mergedArgs = []; + + if (length <= countDefinedItems(concatArgs)) { + mergedArgs = merge(args, curryArgs); + return fn.apply(null, mergedArgs); + } else { + if (length >= concatArgs.length) { + return curry(fn, length, concatArgs); + } else { + return curry(fn, length, merge(args, curryArgs)); + } + } + }; +} + +module.exports = function(fn) { + var args = slice.call(arguments, 1); + + return curry(fn, fn.length, args); +}; + +module.exports.n = function(fn, length) { + var args = slice.call(arguments, 2); + + return curry(fn, length, args); +}; + +module.exports.__ = __; + +},{"./lib/countDefinedItems":37,"./lib/merge":40}],37:[function(require,module,exports){ +var forEach = require('./forEach'); + +module.exports = function(args) { + var count = 0; + + forEach(args, function(key, item) { + if (typeof item !== 'undefined') { + count = count + 1; + } + }); + + return count; +}; + +},{"./forEach":38}],38:[function(require,module,exports){ +module.exports = function forEach(arr, fn) { + for (var i = 0; i < arr.length; i++) { + fn(i, arr[i]); + } +}; + +},{}],39:[function(require,module,exports){ +var forEach = require('./forEach'); + +module.exports = function map(arr, fn) { + var newArr = []; + + forEach(arr, function(key, item) { + newArr.push(fn(key, item)); + }); + + return newArr; +}; + +},{"./forEach":38}],40:[function(require,module,exports){ +var map = require('./map'); + +module.exports = function merge(args, curryArgs) { + var mergedArgs = []; + + mergedArgs = map(curryArgs, function(key, item) { + if (typeof item === 'undefined') { + return args.shift(); + } else { + return item; + } + }); + + return mergedArgs.concat(args); +}; + +},{"./map":39}],41:[function(require,module,exports){ +/* + Module dependencies +*/ +var ElementType = require('domelementtype'); +var entities = require('entities'); + +/* + Boolean Attributes +*/ +var booleanAttributes = { + __proto__: null, + allowfullscreen: true, + async: true, + autofocus: true, + autoplay: true, + checked: true, + controls: true, + default: true, + defer: true, + disabled: true, + hidden: true, + ismap: true, + loop: true, + multiple: true, + muted: true, + open: true, + readonly: true, + required: true, + reversed: true, + scoped: true, + seamless: true, + selected: true, + typemustmatch: true +}; + +var unencodedElements = { + __proto__: null, + style: true, + script: true, + xmp: true, + iframe: true, + noembed: true, + noframes: true, + plaintext: true, + noscript: true +}; + +/* + Format attributes +*/ +function formatAttrs(attributes, opts) { + if (!attributes) return; + + var output = '', + value; + + // Loop through the attributes + for (var key in attributes) { + value = attributes[key]; + if (output) { + output += ' '; + } + + if (!value && booleanAttributes[key]) { + output += key; + } else { + output += key + '="' + (opts.decodeEntities ? entities.encodeXML(value) : value) + '"'; + } + } + + return output; +} + +/* + Self-enclosing tags (stolen from node-htmlparser) +*/ +var singleTag = { + __proto__: null, + area: true, + base: true, + basefont: true, + br: true, + col: true, + command: true, + embed: true, + frame: true, + hr: true, + img: true, + input: true, + isindex: true, + keygen: true, + link: true, + meta: true, + param: true, + source: true, + track: true, + wbr: true, +}; + + +var render = module.exports = function(dom, opts) { + if (!Array.isArray(dom) && !dom.cheerio) dom = [dom]; + opts = opts || {}; + + var output = ''; + + for(var i = 0; i < dom.length; i++){ + var elem = dom[i]; + + if (elem.type === 'root') + output += render(elem.children, opts); + else if (ElementType.isTag(elem)) + output += renderTag(elem, opts); + else if (elem.type === ElementType.Directive) + output += renderDirective(elem); + else if (elem.type === ElementType.Comment) + output += renderComment(elem); + else if (elem.type === ElementType.CDATA) + output += renderCdata(elem); + else + output += renderText(elem, opts); + } + + return output; +}; + +function renderTag(elem, opts) { + // Handle SVG + if (elem.name === "svg") opts = {decodeEntities: opts.decodeEntities, xmlMode: true}; + + var tag = '<' + elem.name, + attribs = formatAttrs(elem.attribs, opts); + + if (attribs) { + tag += ' ' + attribs; + } + + if ( + opts.xmlMode + && (!elem.children || elem.children.length === 0) + ) { + tag += '/>'; + } else { + tag += '>'; + if (elem.children) { + tag += render(elem.children, opts); + } + + if (!singleTag[elem.name] || opts.xmlMode) { + tag += ''; + } + } + + return tag; +} + +function renderDirective(elem) { + return '<' + elem.data + '>'; +} + +function renderText(elem, opts) { + var data = elem.data || ''; + + // if entities weren't decoded, no need to encode them back + if (opts.decodeEntities && !(elem.parent && elem.parent.name in unencodedElements)) { + data = entities.encodeXML(data); + } + + return data; +} + +function renderCdata(elem) { + return ''; +} + +function renderComment(elem) { + return ''; +} + +},{"domelementtype":42,"entities":59}],42:[function(require,module,exports){ +//Types of elements found in the DOM +module.exports = { + Text: "text", //Text + Directive: "directive", // + Comment: "comment", // + Script: "script", //