From 82ad7fc57a28bc7bc911098eca7b4f4f8b0cec71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20F=C3=A3o=20Valvassori?= Date: Wed, 15 Feb 2017 14:33:28 -0200 Subject: [PATCH 1/4] Some refactoring * Refactored methods to ESLint specification * Added encapsulate property * Added propTypes --- index.js | 237 +++++++++++++++++++++---------------------------------- 1 file changed, 89 insertions(+), 148 deletions(-) diff --git a/index.js b/index.js index 90697ea..5e7a5f9 100644 --- a/index.js +++ b/index.js @@ -1,75 +1,24 @@ -import parse5 from 'react-native-parse-html' -import React, {Component} from "react" +import parse5 from 'react-native-parse-html'; +import React, { Component, PropTypes } from 'react'; import { StyleSheet, Text, View, -} from 'react-native' - -var BLOCK_ELEMENTS = [ - "address", - "blockquote", - "center", - "dir", - "div", - "dl", - "fieldset", - "form", - "h1", - "h2", - "h3", - "h4", - "h5", - "h6", - "hr", - "isindex", - "menu", - "noframes", - "noscript", - "ol", - "p", - "pre", - "table", - "ul", - "li" -] - -var INLINE_ELEMENTS = [ - "a", - "abbr", - "acronym", - "b", - "basefont", - "bdo", - "big", - "br", - "cite", - "code", - "dfn", - "em", - "font", - "i", - "img", - "input", - "kbd", - "label", - "q", - "s", - "samp", - "select", - "small", - "span", - "strike", - "strong", - "sub", - "sup", - "textarea", - "tt", - "u", - "var" -] - -var DEFAULT_STYLES = { +} from 'react-native'; + +const BLOCK_ELEMENTS = [ + 'address', 'blockquote', 'center', 'dir', 'div', 'dl', 'fieldset', 'form', + 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'isindex', 'menu', 'noframes', + 'noscript', 'ol', 'p', 'pre', 'table', 'ul', 'li' +]; + +const INLINE_ELEMENTS = [ + 'a', 'abbr', 'acronym', 'b', 'basefont', 'bdo', 'big', 'br', 'cite', 'code', + 'dfn', 'em', 'font', 'i', 'img', 'input', 'kbd', 'label', 'q', 's', 'samp', + 'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'textarea', 'tt', 'u', 'const' +]; + +const DEFAULT_STYLES = StyleSheet.create({ a: { }, @@ -86,7 +35,7 @@ var DEFAULT_STYLES = { }, code: { - fontFamily: "Courier" + fontFamily: 'Courier' }, div: { @@ -95,22 +44,22 @@ var DEFAULT_STYLES = { fontStyle: 'italic' }, h1: { - fontWeight: 'bold', + fontSize: 24, }, h2: { - fontWeight: 'bold', + fontSize: 20, }, h3: { - fontWeight: 'bold', + fontSize: 18, }, h4: { - fontWeight: 'bold', + fontSize: 17, }, h5: { - fontWeight: 'bold', + fontSize: 16, }, h6: { - fontWeight: 'bold', + fontSize: 15, }, i: { fontStyle: 'italic' @@ -136,117 +85,107 @@ var DEFAULT_STYLES = { sup: { }, - ol:{ + ol: { marginLeft: 24, }, ul: { marginLeft: 24, }, u: { - textDecorationLine: "underline" + textDecorationLine: 'underline' }, default: { marginBottom: 12 } -} +}); + +const propTypes = { + encapsulate: PropTypes.bool, + html: PropTypes.string, + tagsStyle: PropTypes.instanceOf(StyleSheet) +}; class HtmlParser extends Component { - parse = (html) => { - var fragment = parse5.parseFragment(html) - return fragment - } - isText = (tagName) => { - return tagName === "#text" - } + parse = (html) => parse5.parseFragment(html); + isText = (tagName) => tagName === '#text'; + isBlockElement = tagName => BLOCK_ELEMENTS.indexOf(tagName) !== -1; + isInlineElement = tagName => INLINE_ELEMENTS.indexOf(tagName) !== -1 + isEmpty = node => node.value.trim() === '' - isBlockElement = (tagName) => { - return BLOCK_ELEMENTS.indexOf(tagName) != -1 - } + styleForTag(tagName) { + const { tagsStyle } = this.props; + const styles = []; - isInlineElement = (tagName) => { - return INLINE_ELEMENTS.indexOf(tagName) != -1 - } + if (DEFAULT_STYLES[tagName]) { + styles.push(DEFAULT_STYLES[tagName]); + } else { + styles.push(DEFAULT_STYLES.default); + } - isEmpty = (node) => { - return node.value.trim() == "" - } + if (tagsStyle && tagsStyle.general) { + styles.push(tagsStyle.general); + } - styleForTag = (tagName) => { - const {tagsStyle} = this.props - - var style = tagsStyle - ? tagsStyle[tagName] - ? tagsStyle[tagName] - : DEFAULT_STYLES[tagName] - ? DEFAULT_STYLES[tagName] - : DEFAULT_STYLES["default"] - : DEFAULT_STYLES[tagName] - ? DEFAULT_STYLES[tagName] - : DEFAULT_STYLES["default"] - - return tagsStyle - ? tagsStyle["general"] - ? [tagsStyle["general"], style] - : style - : style + if (tagsStyle && tagsStyle[tagName]) { + styles.push(tagsStyle[tagName]); + } + + return styles; } - processNode = (node, parentKey) => { - var nodeName = node.nodeName - if (this.isText(nodeName)) { - if (this.isEmpty(node)) { - return null - } + processNode(node, parentKey) { + const nodeName = node.nodeName; - var key = `${parentKey}_text` - return ({node.value}) + if (this.isText(nodeName)) { + const key = `${parentKey}_text`; + return ({node.value}); } if (this.isInlineElement(nodeName)) { - var key = `${parentKey}_${nodeName}` - var children = [] + const key = `${parentKey}_${nodeName}`; + const children = []; node.childNodes.forEach((childNode, index) => { if (this.isInlineElement(childNode.nodeName) || this.isText(childNode.nodeName)) { - children.push(this.processNode(childNode, `${key}_${index}`)) + children.push(this.processNode(childNode, `${key}_${index}`)); } else { - console.error(`Inline element ${nodeName} can only have inline children, ${child} is invalid!`) + console.error(`Inline element ${nodeName} can only have inline children, ${child} is invalid!`); } - }) - return ({children}) + }); + + return ({children}); } if (this.isBlockElement(nodeName)) { - var key = `${parentKey}_${nodeName}` - var children = [] - var lastInlineNodes = [] + const key = `${parentKey}_${nodeName}`; + const children = []; + let lastInlineNodes = []; node.childNodes.forEach((childNode, index) => { - var child = this.processNode(childNode, `${key}_${index}`) + const child = this.processNode(childNode, `${key}_${index}`); if (this.isInlineElement(childNode.nodeName) || this.isText(childNode.nodeName)) { - lastInlineNodes.push(child) - + lastInlineNodes.push(child); } else if (this.isBlockElement(childNode.nodeName)) { if (lastInlineNodes.length > 0) { - children.push({lastInlineNodes}) - lastInlineNodes = [] + children.push({lastInlineNodes}) + lastInlineNodes = []; } - children.push(child) + children.push(child); } - }) + }); if (lastInlineNodes.length > 0) { - children.push(({lastInlineNodes})) + children.push(({lastInlineNodes})) } return ( - {nodeName === "li" - ? {children}{"\n"} + {nodeName === 'li' + ? {children}{'\n'} : children } - ) + ); } console.warn(`unsupported node: ${nodeName}`) @@ -254,21 +193,23 @@ class HtmlParser extends Component { } render() { - var html = this.props.html - var fragment = this.parse(html) - var rootKey = "ht_" + const html = this.props.html; + const fragment = this.parse(this.props.encapsulate ? `

${html}

` : html); + const rootKey = 'ht_'; - var children = [] + const children = []; fragment.childNodes.forEach((node, index) => { - children.push(this.processNode(node, `${rootKey}_${index}`)) - }) + children.push(this.processNode(node, `${rootKey}_${index}`)); + }); return ( {children} - ) + ); } } -export default HtmlParser \ No newline at end of file +HtmlParser.propTypes = propTypes; + +export default HtmlParser; From 57392be4ba9d92e49dcfc221d6fb2c34503c91cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20F=C3=A3o=20Valvassori?= Date: Wed, 15 Feb 2017 14:34:35 -0200 Subject: [PATCH 2/4] removed logs. --- index.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 5e7a5f9..d508cc0 100644 --- a/index.js +++ b/index.js @@ -148,8 +148,6 @@ class HtmlParser extends Component { node.childNodes.forEach((childNode, index) => { if (this.isInlineElement(childNode.nodeName) || this.isText(childNode.nodeName)) { children.push(this.processNode(childNode, `${key}_${index}`)); - } else { - console.error(`Inline element ${nodeName} can only have inline children, ${child} is invalid!`); } }); @@ -167,7 +165,7 @@ class HtmlParser extends Component { lastInlineNodes.push(child); } else if (this.isBlockElement(childNode.nodeName)) { if (lastInlineNodes.length > 0) { - children.push({lastInlineNodes}) + children.push({lastInlineNodes}); lastInlineNodes = []; } children.push(child); @@ -175,7 +173,7 @@ class HtmlParser extends Component { }); if (lastInlineNodes.length > 0) { - children.push(({lastInlineNodes})) + children.push(({lastInlineNodes})); } return ( @@ -187,8 +185,6 @@ class HtmlParser extends Component { ); } - - console.warn(`unsupported node: ${nodeName}`) return null; } From c595148968f0a36475fc370450ff9b624d0f7205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20F=C3=A3o=20Valvassori?= Date: Wed, 15 Feb 2017 15:55:46 -0200 Subject: [PATCH 3/4] Styles from html tag --- index.js | 482 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 292 insertions(+), 190 deletions(-) diff --git a/index.js b/index.js index d508cc0..5fd38b5 100644 --- a/index.js +++ b/index.js @@ -1,211 +1,313 @@ import parse5 from 'react-native-parse-html'; import React, { Component, PropTypes } from 'react'; -import { - StyleSheet, - Text, - View, -} from 'react-native'; +import { StyleSheet, Text, View, Platform } from 'react-native'; const BLOCK_ELEMENTS = [ - 'address', 'blockquote', 'center', 'dir', 'div', 'dl', 'fieldset', 'form', - 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'isindex', 'menu', 'noframes', - 'noscript', 'ol', 'p', 'pre', 'table', 'ul', 'li' + 'address', 'blockquote', 'center', 'dir', 'div', 'dl', 'fieldset', 'form', + 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'isindex', 'menu', 'noframes', + 'noscript', 'ol', 'p', 'pre', 'table', 'ul', 'li' ]; const INLINE_ELEMENTS = [ - 'a', 'abbr', 'acronym', 'b', 'basefont', 'bdo', 'big', 'br', 'cite', 'code', - 'dfn', 'em', 'font', 'i', 'img', 'input', 'kbd', 'label', 'q', 's', 'samp', - 'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'textarea', 'tt', 'u', 'const' + 'a', 'abbr', 'acronym', 'b', 'basefont', 'bdo', 'big', 'br', 'cite', 'code', + 'dfn', 'em', 'font', 'i', 'img', 'input', 'kbd', 'label', 'q', 's', 'samp', + 'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'textarea', 'tt', 'u', 'const' ]; +const STYLE_EQUIVALENCE = { + color: { + key: 'color', + }, + 'font-family': { + key: 'fontFamily' + }, + 'font-size': { + key: 'fontSize', + transformValue: parseInt, + }, + 'font-style': { + key: 'fontStyle', + }, + 'font-weight': { + key: 'fontWeight', + }, + 'text-align': { + key: 'textAlign' + }, + 'text-decoration': { + key: 'textDecorationLine', + acceptableValues: ['none', 'underline', 'line-through', 'underline line-through'] + }, + 'line-height': { + key: 'lineHeight', + transformValue: parseInt, + }, + 'letter-spacing': { + key: 'letterSpacing', + transformValue: parseInt, + } +}; + const DEFAULT_STYLES = StyleSheet.create({ - a: { - - }, - b: { - fontWeight: 'bold' - }, - blockquote: { - paddingLeft: 12, - borderLeftWidth: 4, - borderLeftColor: '#cccccc', - marginBottom: 12 - }, - br: { - - }, - code: { - fontFamily: 'Courier' - }, - div: { - - }, - em: { - fontStyle: 'italic' - }, - h1: { - fontSize: 24, - }, - h2: { - fontSize: 20, - }, - h3: { - fontSize: 18, - }, - h4: { - fontSize: 17, - }, - h5: { - fontSize: 16, - }, - h6: { - fontSize: 15, - }, - i: { - fontStyle: 'italic' - }, - p: { - marginBottom: 12, - }, - pre: { - - }, - strong: { - fontWeight: 'bold' - }, - q: { - - }, - span: { - - }, - sub: { - - }, - sup: { - - }, - ol: { - marginLeft: 24, - }, - ul: { - marginLeft: 24, - }, - u: { - textDecorationLine: 'underline' - }, - default: { - marginBottom: 12 - } + a: { + color: '#4169e1', + ...Platform.select({ + android: { + textDecorationLine: 'underline', + }, + ios: { + fontWeight: 'bold', + } + }), + }, + b: { + fontWeight: 'bold' + }, + blockquote: { + paddingLeft: 12, + borderLeftWidth: 4, + borderLeftColor: '#cccccc', + marginBottom: 12 + }, + br: { + + }, + code: { + fontFamily: 'Courier' + }, + div: { + + }, + em: { + fontStyle: 'italic' + }, + h1: { + fontSize: 24, + }, + h2: { + fontSize: 20, + }, + h3: { + fontSize: 18, + }, + h4: { + fontSize: 17, + }, + h5: { + fontSize: 16, + }, + h6: { + fontSize: 15, + }, + i: { + fontStyle: 'italic' + }, + p: { + marginBottom: 12, + }, + pre: { + + }, + strong: { + fontWeight: 'bold' + }, + q: { + + }, + span: { + + }, + sub: { + + }, + sup: { + + }, + ol: { + marginLeft: 24, + }, + ul: { + marginLeft: 24, + }, + u: { + textDecorationLine: 'underline' + }, + default: { + marginBottom: 12 + } }); const propTypes = { - encapsulate: PropTypes.bool, - html: PropTypes.string, - tagsStyle: PropTypes.instanceOf(StyleSheet) + encapsulate: PropTypes.bool, + html: PropTypes.string, + tagsStyle: PropTypes.instanceOf(StyleSheet) }; class HtmlParser extends Component { - parse = (html) => parse5.parseFragment(html); - isText = (tagName) => tagName === '#text'; - isBlockElement = tagName => BLOCK_ELEMENTS.indexOf(tagName) !== -1; - isInlineElement = tagName => INLINE_ELEMENTS.indexOf(tagName) !== -1 - isEmpty = node => node.value.trim() === '' - - styleForTag(tagName) { - const { tagsStyle } = this.props; - const styles = []; - - if (DEFAULT_STYLES[tagName]) { - styles.push(DEFAULT_STYLES[tagName]); - } else { - styles.push(DEFAULT_STYLES.default); - } - - if (tagsStyle && tagsStyle.general) { - styles.push(tagsStyle.general); - } - - if (tagsStyle && tagsStyle[tagName]) { - styles.push(tagsStyle[tagName]); - } - - return styles; - } - - processNode(node, parentKey) { - const nodeName = node.nodeName; - - if (this.isText(nodeName)) { - const key = `${parentKey}_text`; - return ({node.value}); - } - - if (this.isInlineElement(nodeName)) { - const key = `${parentKey}_${nodeName}`; - const children = []; - node.childNodes.forEach((childNode, index) => { - if (this.isInlineElement(childNode.nodeName) || this.isText(childNode.nodeName)) { - children.push(this.processNode(childNode, `${key}_${index}`)); - } - }); - - return ({children}); - } - - if (this.isBlockElement(nodeName)) { - const key = `${parentKey}_${nodeName}`; - const children = []; - let lastInlineNodes = []; - - node.childNodes.forEach((childNode, index) => { - const child = this.processNode(childNode, `${key}_${index}`); - if (this.isInlineElement(childNode.nodeName) || this.isText(childNode.nodeName)) { - lastInlineNodes.push(child); - } else if (this.isBlockElement(childNode.nodeName)) { - if (lastInlineNodes.length > 0) { - children.push({lastInlineNodes}); - lastInlineNodes = []; - } - children.push(child); - } - }); - - if (lastInlineNodes.length > 0) { - children.push(({lastInlineNodes})); - } - - return ( - - {nodeName === 'li' - ? {children}{'\n'} - : children - } - - ); - } - return null; - } - - render() { - const html = this.props.html; - const fragment = this.parse(this.props.encapsulate ? `

${html}

` : html); - const rootKey = 'ht_'; - - const children = []; - fragment.childNodes.forEach((node, index) => { - children.push(this.processNode(node, `${rootKey}_${index}`)); - }); - - return ( - - {children} - - ); - } + parse = (html) => parse5.parseFragment(html); + isText = (tagName) => tagName === '#text'; + isBlockElement = tagName => BLOCK_ELEMENTS.indexOf(tagName) !== -1; + isInlineElement = tagName => INLINE_ELEMENTS.indexOf(tagName) !== -1 + isEmpty = node => node.value.trim() === '' + + decodeStyle(customStyle) { + const elements = {}; + + const splitedStyles = customStyle.split(','); + + for (let i = 0; i < splitedStyles.length; i++) { + const split = splitedStyles[i].split(':'); + + const identifier = STYLE_EQUIVALENCE[split[0].trim()]; + const value = split[1].trim().replace(/'/g, ''); + + if (identifier) { + const finalValue = identifier.transformValue + ? identifier.transformValue(value) + : value; + + + console.log(`${identifier.key}: ${finalValue}`); + + if ( + !identifier.acceptableValues || + ( + identifier.acceptableValues && + ( + identifier.acceptableValues.length === 0 || + identifier.acceptableValues.indexOf(finalValue) > -1 + ) + ) + ) { + elements[identifier.key] = finalValue; + } + } else { + console.log(`${split[0]}: ${value}`); + console.log('No attribute for this'); + } + } + + console.log(elements); + + return elements; + } + + styleForTag(opts) { + const { tagName, extraStyle } = opts; + + const { tagsStyle } = this.props; + const styles = []; + + if (DEFAULT_STYLES[tagName]) { + styles.push(DEFAULT_STYLES[tagName]); + } else { + styles.push(DEFAULT_STYLES.default); + } + + if (tagsStyle && tagsStyle.general) { + styles.push(tagsStyle.general); + } + + if (tagsStyle && tagsStyle[tagName]) { + styles.push(tagsStyle[tagName]); + } + + if (extraStyle) { + styles.push(extraStyle); + } + + return styles; + } + + processNode(node, parentKey) { + const tagName = node.nodeName; + + if (this.isText(tagName)) { + const key = `${parentKey}_text`; + return ({node.value}); + } + + let extraStyle = null; + if (node.attrs.length > 0) { + node.attrs.forEach((element) => { + if (element.name === 'style') { + extraStyle = this.decodeStyle(element.value); + } + }); + } + + if (this.isInlineElement(tagName)) { + const key = `${parentKey}_${tagName}`; + const children = []; + + node.childNodes.forEach((childNode, index) => { + if (this.isInlineElement(childNode.nodeName) || this.isText(childNode.nodeName)) { + children.push(this.processNode(childNode, `${key}_${index}`)); + } + }); + + return ( + + {children} + + ); + } + + if (this.isBlockElement(tagName)) { + const key = `${parentKey}_${tagName}`; + const children = []; + let lastInlineNodes = []; + + node.childNodes.forEach((childNode, index) => { + const child = this.processNode(childNode, `${key}_${index}`); + if (this.isInlineElement(childNode.nodeName) || this.isText(childNode.nodeName)) { + lastInlineNodes.push(child); + } else if (this.isBlockElement(childNode.nodeName)) { + if (lastInlineNodes.length > 0) { + children.push({lastInlineNodes}); + lastInlineNodes = []; + } + children.push(child); + } + }); + + if (lastInlineNodes.length > 0) { + children.push(({lastInlineNodes})); + } + + return ( + + {tagName === 'li' + ? {children}{'\n'} + : children + } + + ); + } + return null; + } + + render() { + const html = this.props.html; + const fragment = this.parse(this.props.encapsulate ? `

${html}

` : html); + const rootKey = 'ht_'; + + const children = []; + fragment.childNodes.forEach((node, index) => { + children.push(this.processNode(node, `${rootKey}_${index}`)); + }); + + return ( + + {children} + + ); + } } HtmlParser.propTypes = propTypes; - export default HtmlParser; From 5b60b9e3a148535313c2ed05437f72fe7b7daca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20F=C3=A3o=20Valvassori?= Date: Wed, 15 Feb 2017 16:10:44 -0200 Subject: [PATCH 4/4] some refactor --- index.js | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/index.js b/index.js index 5fd38b5..f2348bc 100644 --- a/index.js +++ b/index.js @@ -167,9 +167,6 @@ class HtmlParser extends Component { ? identifier.transformValue(value) : value; - - console.log(`${identifier.key}: ${finalValue}`); - if ( !identifier.acceptableValues || ( @@ -182,14 +179,9 @@ class HtmlParser extends Component { ) { elements[identifier.key] = finalValue; } - } else { - console.log(`${split[0]}: ${value}`); - console.log('No attribute for this'); } } - - console.log(elements); - + return elements; } @@ -229,16 +221,29 @@ class HtmlParser extends Component { } let extraStyle = null; + let elementId = null; + if (node.attrs.length > 0) { - node.attrs.forEach((element) => { - if (element.name === 'style') { - extraStyle = this.decodeStyle(element.value); + for (let i = 0; i < node.attrs.length; i++) { + const element = node.attrs[i]; + switch (element.name) { + case 'style': + extraStyle = this.decodeStyle(element.value); + break; + + case 'id': + elementId = element.value; + break; + + default: + break; } - }); + } } + const tagStyles = this.styleForTag({ tagName, extraStyle }); if (this.isInlineElement(tagName)) { - const key = `${parentKey}_${tagName}`; + const key = elementId || `${parentKey}_${tagName}`; const children = []; node.childNodes.forEach((childNode, index) => { @@ -250,7 +255,7 @@ class HtmlParser extends Component { return ( {children} @@ -280,7 +285,7 @@ class HtmlParser extends Component { } return ( - + {tagName === 'li' ? {children}{'\n'} : children @@ -288,6 +293,7 @@ class HtmlParser extends Component { ); } + return null; }