├── demo ├── src │ ├── js │ │ ├── data │ │ │ ├── simple │ │ │ │ ├── options.js │ │ │ │ ├── index.js │ │ │ │ └── html.html │ │ │ ├── entities │ │ │ │ ├── options.js │ │ │ │ ├── index.js │ │ │ │ └── html.html │ │ │ ├── transform │ │ │ │ ├── index.js │ │ │ │ ├── html.html │ │ │ │ ├── display.txt │ │ │ │ └── options.js │ │ │ └── index.js │ │ ├── containers │ │ │ ├── Html.js │ │ │ ├── Content.js │ │ │ └── Editor.js │ │ ├── actions.js │ │ ├── index.js │ │ ├── components │ │ │ ├── App.js │ │ │ ├── Html.js │ │ │ ├── Header.js │ │ │ └── Editor.js │ │ └── reducers.js │ ├── sass │ │ ├── html.scss │ │ ├── header.scss │ │ ├── editor.scss │ │ └── app.scss │ └── index.html ├── .babelrc ├── server.js ├── webpack.config.development.js ├── webpack.config.production.js ├── webpack.config.base.js └── package.json ├── .gitignore ├── .npmignore ├── .babelrc ├── tests.webpack.js ├── src ├── index.js ├── utils │ ├── isValidTagOrAttributeName.js │ ├── isEmptyTextNode.js │ ├── generatePropsFromAttributes.js │ ├── inlineStyleToObject.js │ └── htmlAttributesToReact.js ├── elementTypes │ ├── TextElementType.js │ ├── UnsupportedElementType.js │ ├── StyleElementType.js │ ├── index.js │ └── TagElementType.js ├── dom │ ├── elements │ │ └── VoidElements.js │ └── attributes │ │ ├── BooleanAttributes.js │ │ └── ReactAttributes.js ├── convertNodeToElement.js ├── HtmlParser.js └── processNodes.js ├── webpack.config.development.js ├── test ├── unit │ ├── elementTypes │ │ ├── TextElementType.spec.js │ │ ├── UnsupportedElementType.spec.js │ │ ├── StyleElementType.spec.js │ │ └── TagElementType.spec.js │ ├── utils │ │ ├── isEmptyTextNode.spec.js │ │ ├── isValidTagOrAttributeName.spec.js │ │ ├── inlineStyleToObject.spec.js │ │ ├── generatePropsFromAttributes.spec.js │ │ └── htmlAttributesToReact.spec.js │ ├── HtmlParser.spec.js │ ├── convertNodeToElement.spec.js │ └── processNodes.spec.js └── integration │ └── integration.spec.js ├── .travis.yml ├── webpack.config.production.js ├── webpack.config.test.js ├── .eslintrc ├── webpack.config.base.js ├── scripts └── attributes-diff.js ├── karma.conf.js ├── LICENSE.md ├── CHANGELOG.md ├── package.json └── README.md /demo/src/js/data/simple/options.js: -------------------------------------------------------------------------------- 1 | export default {}; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | .idea 3 | node_modules 4 | dist/ 5 | coverage/ 6 | lib/ -------------------------------------------------------------------------------- /demo/src/js/data/entities/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | decodeEntities: false 3 | }; 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .babelrc 3 | .eslintrc 4 | .travis.yml 5 | karma.conf.js 6 | tests.webpack.js 7 | webpack.config.*.js 8 | coverage/ 9 | test/ -------------------------------------------------------------------------------- /demo/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react", 4 | "es2015", 5 | "stage-2" 6 | ], 7 | "plugins": ["react-hot-loader/babel"] 8 | } -------------------------------------------------------------------------------- /demo/src/js/data/entities/index.js: -------------------------------------------------------------------------------- 1 | import html from './html.html'; 2 | import options from './options'; 3 | 4 | export default { 5 | html, 6 | options 7 | }; 8 | -------------------------------------------------------------------------------- /demo/src/js/data/simple/index.js: -------------------------------------------------------------------------------- 1 | import html from './html.html'; 2 | import options from './options'; 3 | 4 | export default { 5 | html, 6 | options 7 | }; 8 | -------------------------------------------------------------------------------- /demo/src/js/data/entities/html.html: -------------------------------------------------------------------------------- 1 |
This example does not decode HTML entities
2 |HTML entities such as £ » and & will be displayed as written
-------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react", 4 | "es2015" 5 | ], 6 | "plugins": [ 7 | "transform-object-assign", 8 | "transform-object-rest-spread" 9 | ] 10 | } -------------------------------------------------------------------------------- /demo/src/js/data/transform/index.js: -------------------------------------------------------------------------------- 1 | import html from './html.html'; 2 | import options from './options'; 3 | import display from './display.txt'; 4 | 5 | export default { 6 | html, 7 | options, 8 | display 9 | }; 10 | -------------------------------------------------------------------------------- /demo/src/sass/html.scss: -------------------------------------------------------------------------------- 1 | #html { 2 | background: #fff; 3 | border: 1px solid #aaa; 4 | padding: 0.5rem; 5 | font-size: 0.9rem; 6 | overflow-y: scroll; 7 | h1, h2, h3, h4, h5, p, ul { 8 | margin: 0.5rem 0; 9 | } 10 | } -------------------------------------------------------------------------------- /demo/src/js/data/index.js: -------------------------------------------------------------------------------- 1 | import simple from './simple/index'; 2 | import entities from './entities/index'; 3 | import transform from './transform/index'; 4 | 5 | export default { 6 | simple, 7 | entities, 8 | transform 9 | }; 10 | -------------------------------------------------------------------------------- /tests.webpack.js: -------------------------------------------------------------------------------- 1 | // require all test files 2 | const testsContext = require.context('./test', true, /\.spec\.js$/); 3 | testsContext.keys().forEach(testsContext); 4 | 5 | // require all src files 6 | const componentsContext = require.context('./src/', true, /\.js$/); 7 | componentsContext.keys().forEach(componentsContext); 8 | -------------------------------------------------------------------------------- /demo/src/js/containers/Html.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import Content from '../components/Html'; 3 | 4 | const mapStateToProps = state => { 5 | return { 6 | html: state.html, 7 | selectedExample: state.selectedExample 8 | }; 9 | }; 10 | 11 | export default connect( 12 | mapStateToProps 13 | )(Content); 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import HtmlParser from './HtmlParser'; 2 | export default HtmlParser; 3 | 4 | export { default as processNodes } from './processNodes'; 5 | export { default as convertNodeToElement } from './convertNodeToElement'; 6 | 7 | // expose htmlparser2 so it can be used if required 8 | export { default as htmlparser2 } from 'htmlparser2'; 9 | -------------------------------------------------------------------------------- /src/utils/isValidTagOrAttributeName.js: -------------------------------------------------------------------------------- 1 | const VALID_TAG_REGEX = /^[a-zA-Z][a-zA-Z:_\.\-\d]*$/; 2 | 3 | const nameCache = {}; 4 | 5 | export default function isValidTagOrAttributeName(tagName) { 6 | if (!nameCache.hasOwnProperty(tagName)) { 7 | nameCache[tagName] = VALID_TAG_REGEX.test(tagName); 8 | } 9 | return nameCache[tagName]; 10 | } 11 | -------------------------------------------------------------------------------- /src/elementTypes/TextElementType.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts a text node to a React text element 3 | * 4 | * @param {Object} node The text node 5 | * @returns {String} The text 6 | */ 7 | export default function TextElementType(node) { 8 | 9 | // React will accept plain text for rendering so just return the node data 10 | return node.data; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /webpack.config.development.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var config = require('./webpack.config.base'); 3 | 4 | var config = Object.create(config); 5 | 6 | config.plugins = [ 7 | new webpack.optimize.OccurenceOrderPlugin(), 8 | new webpack.DefinePlugin({ 9 | 'process.env.NODE_ENV': JSON.stringify('development') 10 | }) 11 | ]; 12 | 13 | module.exports = config; -------------------------------------------------------------------------------- /test/unit/elementTypes/TextElementType.spec.js: -------------------------------------------------------------------------------- 1 | import TextElementType from 'elementTypes/TextElementType'; 2 | 3 | describe('Testing `elementTypes/TextElementType', () => { 4 | 5 | it('should return the value from the node data property', () => { 6 | const node = { 7 | data: 'test' 8 | }; 9 | expect(TextElementType(node)).toBe('test'); 10 | }); 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /demo/src/js/actions.js: -------------------------------------------------------------------------------- 1 | export function updateHtml(html) { 2 | return { 3 | type: 'UPDATE_HTML', 4 | html 5 | }; 6 | } 7 | 8 | export function updateSelectedExample(example) { 9 | return { 10 | type: 'UPDATE_SELECTED_EXAMPLE', 11 | example 12 | }; 13 | } 14 | 15 | export function setView(view) { 16 | return { 17 | type: 'SET_VIEW', 18 | view 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/elementTypes/UnsupportedElementType.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Handles an unsupported element type by returning null so nothing is rendered 3 | * @returns {null} 4 | */ 5 | export default function UnsupportedElementType() { 6 | 7 | // do nothing because the element type is unsupported 8 | // comment, directive, script, cdata, doctype are all currently unsupported 9 | return null; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 8 5 | 6 | env: 7 | - REACT_VERSION=16 8 | - REACT_VERSION=15 9 | 10 | script: 11 | - npm run lint 12 | - npm test 13 | 14 | before_script: 15 | - npm install react@$REACT_VERSION react-dom@$REACT_VERSION 16 | 17 | after_script: 18 | - "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" # Send coverage data to Coveralls 19 | -------------------------------------------------------------------------------- /src/dom/elements/VoidElements.js: -------------------------------------------------------------------------------- 1 | /** 2 | * List of void elements 3 | * These elements are not allowed to have children 4 | * @type {Array} 5 | */ 6 | export default [ 7 | 'area', 8 | 'base', 9 | 'br', 10 | 'col', 11 | 'command', 12 | 'embed', 13 | 'hr', 14 | 'img', 15 | 'input', 16 | 'keygen', 17 | 'link', 18 | 'meta', 19 | 'param', 20 | 'source', 21 | 'track', 22 | 'wbr' 23 | ]; 24 | -------------------------------------------------------------------------------- /demo/src/js/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { createStore } from 'redux'; 4 | import { Provider } from 'react-redux'; 5 | import App from './components/App'; 6 | import reducer from './reducers'; 7 | 8 | let store = createStore(reducer); 9 | 10 | render( 11 |Update this HTML to see React HTML Parser in action.
3 |Attributes will be converted to React equivalent props
14 |Inline styles can also be used
15 |HTML entities such as £, » and & are decoded by default
-------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "parserOptions": { 4 | "ecmaVersion": 6, 5 | "sourceType": "module", 6 | "ecmaFeatures": { 7 | "jsx": true, 8 | "experimentalObjectRestSpread": true 9 | } 10 | }, 11 | "env": { 12 | "browser": true, 13 | "node": true, 14 | "jasmine": true, 15 | "es6": true 16 | }, 17 | "plugins": [ 18 | "react" 19 | ], 20 | "rules": { 21 | "semi": [2, "always"], 22 | "quotes": [2, "single"], 23 | "indent": [2, 2, {"SwitchCase": 1}], 24 | "eol-last": 2, 25 | "react/jsx-uses-react": 1, 26 | "react/jsx-uses-vars": 1 27 | } 28 | } -------------------------------------------------------------------------------- /src/HtmlParser.js: -------------------------------------------------------------------------------- 1 | import htmlparser2 from 'htmlparser2'; 2 | import processNodes from './processNodes'; 3 | 4 | /** 5 | * Parses a HTML string and returns a list of React components generated from it 6 | * 7 | * @param {String} html The HTML to convert into React component 8 | * @param {Object} options Options to pass 9 | * @returns {Array} List of top level React elements 10 | */ 11 | export default function HtmlParser(html, { 12 | decodeEntities = true, 13 | transform, 14 | preprocessNodes = nodes => nodes 15 | }={}) { 16 | const nodes = preprocessNodes(htmlparser2.parseDOM(html, { decodeEntities })); 17 | return processNodes(nodes, transform); 18 | } 19 | -------------------------------------------------------------------------------- /demo/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |11 | React components can be returned directly. 12 | This bold tag will be replaced directly with manually specified React element 13 |
14 | 15 |
16 | Attributes can also be modified.
17 | All links like this one
18 | and this one
19 | will automatically have the target="_blank" attribute added to them.
20 |