Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@
"test/**",
"tmp/**",
"lib/**",
"*.{html,jpg}"
"*.{html,jpg}",
".idea/**"
],
"rules": {
"pkg-main": [
Expand Down
14 changes: 13 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,25 @@ Default:
Default: '\n'
Description: *As value is a string symbol which is added to the end of the row*

- **useExistingLineBreaks**
Type: `Boolean`
Default: false
Description: *Preserve existing line breaks. Don't add new line breaks (exception: adds missing line break at the start/end of a tag if it already has line break at the end/start). `blankLines` and `eol` are ignored if this option set to `true`*

- **lowerAttributeName**
Type: `Boolean`
Default: true
Description: *Control case of attribute names*

- **eof** (*end of file*)
Type: `String|Boolean`
Default: '\n'
Description: *As value is a string symbol which is added to the end of the file and will not adds if you specify a boolean value of `false`*

### `mini`
Type: `Object`
Type: `Object|Boolean(only false)`
Description: *Describe options for tidying up html output. Can be turned off with `false` value*

Default:

- **removeAttribute**
Expand Down
187 changes: 142 additions & 45 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,91 @@ const optionsDefault = {
}
};

const clean = tree => parser(render(tree))
.filter(node => {
return typeof node === 'object' || (typeof node === 'string' && (node.trim().length !== 0 || /doctype/gi.test(node)));
})
.map(node => {
if (Object.prototype.hasOwnProperty.call(node, 'content')) {
node.content = clean(node.content);
const horizontalWhitespace = /[\t ]+/g;
const verticalWhitespace = /[\r\n\v\f]+/g;

const getEdgeWhitespace = (node) => {
let leftWhitespace;
let rightWhitespace;
let leftLinebreaks;
let rightLinebreaks;

if (typeof node === 'string') {
const nodeTrimmed = node.trim();

if (nodeTrimmed.length === 0) {
leftWhitespace = node.replace(verticalWhitespace, '');
rightWhitespace = node.replace(verticalWhitespace, '');
leftLinebreaks = node.replace(horizontalWhitespace, '');
rightLinebreaks = node.replace(horizontalWhitespace, '');
} else {
leftWhitespace = node.replace(/\s+$/,'').replace(nodeTrimmed, '');
rightWhitespace = node.replace(/^\s+/,'').replace(nodeTrimmed, '');
leftLinebreaks = leftWhitespace.replace(horizontalWhitespace, '');
rightLinebreaks = rightWhitespace.replace(horizontalWhitespace, '');
}

return typeof node === 'string' ? node.trim() : node;
});
return {leftWhitespace, rightWhitespace, leftLinebreaks, rightLinebreaks};
} else {
return false;
}

};

const clean = (tree, options) => {
let previousNodeRightLinebreaks = '';

return parser(render(tree))
.filter(node => {
return options.rules.useExistingLineBreaks
? node
: typeof node === 'object' || (typeof node === 'string' && (node.trim().length !== 0 || /doctype/gi.test(node)));
})
.map(node => {
if (Object.prototype.hasOwnProperty.call(node, 'content')) {
node.content = clean(node.content, options);
}

if (typeof node === 'string') {
if (!options.rules.useExistingLineBreaks) {
return node.trim();
}

const nodeTrimmed = node.trim();
const {leftWhitespace, rightWhitespace, leftLinebreaks, rightLinebreaks} = getEdgeWhitespace(node);
let nodeCleaned;

const parseConditional = tree => {
if (nodeTrimmed.length === 0) {
nodeCleaned = node.replace(horizontalWhitespace, '') || ' ';
} else {
nodeCleaned =
`${leftLinebreaks || (previousNodeRightLinebreaks ? '' : leftWhitespace.replace(horizontalWhitespace, ' '))}` +
`${nodeTrimmed.replace(horizontalWhitespace, ' ').replace(/([\r\n\v\f]+)[\t ]/g, '$1')}` +
`${rightLinebreaks || rightWhitespace.replace(horizontalWhitespace, ' ')}`;

previousNodeRightLinebreaks = rightLinebreaks;
}

return nodeCleaned;
} else {
previousNodeRightLinebreaks = '';

return node;
}
});
};

const parseConditional = (tree, options) => {
return tree.map(node => {
if (typeof node === 'object' && Object.prototype.hasOwnProperty.call(node, 'content')) {
node.content = parseConditional(node.content);
node.content = parseConditional(node.content, options);
}

if (typeof node === 'string' && /<!(?:--)?\[[\s\S]*?]>/.test(node)) {
const conditional = /^((?:<[^>]+>)?<!(?:--)?\[[\s\S]*?]>(?:<!)?(?:-->)?)([\s\S]*?)(<!(?:--<!)?\[[\s\S]*?](?:--)?>)$/
.exec(node)
.slice(1)
.map((node, index) => index === 1 ? {tag: 'conditional-content', content: clean(parser(node))} : node);
.map((node, index) => index === 1 ? {tag: 'conditional-content', content: clean(parser(node), options)} : node);

return {
tag: 'conditional',
Expand Down Expand Up @@ -68,54 +130,88 @@ const renderConditional = tree => {
}, []);
};

const indent = (tree, {rules: {indent, eol, blankLines}}) => {
const indent = (tree, {rules: {indent, eol, blankLines, useExistingLineBreaks}}) => {
const indentString = typeof indent === 'number' ? ' '.repeat(indent) : '\t';

const getIndent = level => `${eol}${indentString.repeat(level)}`;
const getIndent = level => `${useExistingLineBreaks ? '' : eol}${indentString.repeat(level)}`;

const setIndent = (tree, level = 0) => tree.reduce((previousValue, node, index) => {
if (typeof node === 'object' && Object.prototype.hasOwnProperty.call(node, 'content')) {
node.content = setIndent(node.content, ++level);
--level;
}

if (tree.length === 1 && typeof tree[index] === 'string') {
if (useExistingLineBreaks) {
if (typeof node === 'string') {
node = node.replace(/([\r\n\v\f]+)/g, function (match, p1, offset, string) {
if ((index === tree.length - 1) && (offset + match.length === string.length)) {
--level;
}

return `${p1}${getIndent(Math.max(level, 0))}`
});
}

if (
level > 0
&& (index === tree.length - 1)
&& typeof tree[0] === 'string'
&& getEdgeWhitespace(tree[0]).leftLinebreaks
&& ((typeof node === 'string' && node.trim() && !getEdgeWhitespace(node).rightLinebreaks) || typeof node === 'object')
) {
return [ ...previousValue, node, `\n${getIndent(--level)}` ];
}

if (
level > 0
&& (index === 0)
&& typeof tree[tree.length - 1] === 'string'
&& getEdgeWhitespace(tree[tree.length - 1]).rightLinebreaks
&& ((typeof node === 'string' && node.trim() && !getEdgeWhitespace(node).leftLinebreaks) || typeof node === 'object')
) {
return [ ...previousValue, `\n${getIndent(typeof node === 'string' ? ++level : level)}`, node];
}

return [...previousValue, node];
}
} else {
if (tree.length === 1 && typeof tree[index] === 'string') {
return [...previousValue, node];
}

if (level === 0 && (tree.length - 1) === index && tree.length > 1) {
return [...previousValue, getIndent(level), node];
}
if (level === 0 && (tree.length - 1) === index && tree.length > 1) {
return [...previousValue, getIndent(level), node];
}

if (level === 0 && (tree.length - 1) === index && tree.length === 1) {
return [...previousValue, node];
}
if (level === 0 && (tree.length - 1) === index && tree.length === 1) {
return [...previousValue, node];
}

if (level === 0 && index === 0) {
return [...previousValue, node, blankLines];
}
if (level === 0 && index === 0) {
return [...previousValue, node, blankLines];
}

if (level === 0) {
return [...previousValue, getIndent(level), node, blankLines];
}
if (level === 0) {
return [...previousValue, getIndent(level), node, blankLines];
}

if ((tree.length - 1) === index) {
return [...previousValue, getIndent(level), node, getIndent(--level)];
}
if ((tree.length - 1) === index) {
return [...previousValue, getIndent(level), node, getIndent(--level)];
}

if (typeof node === 'string' && /<!(?:--)?\[endif]*?]>/.test(node)) {
return [...previousValue, getIndent(level), node, blankLines];
}
if (typeof node === 'string' && /<!(?:--)?\[endif]*?]>/.test(node)) {
return [...previousValue, getIndent(level), node, blankLines];
}

if (typeof node === 'string' && /<!(?:--)?\[[\s\S]*?]>/.test(node)) {
return [...previousValue, getIndent(level), node];
}
if (typeof node === 'string' && /<!(?:--)?\[[\s\S]*?]>/.test(node)) {
return [...previousValue, getIndent(level), node];
}

if (node.tag === false) {
return [...previousValue, ...node.content.slice(0, -1)];
}
if (node.tag === false) {
return [...previousValue, ...node.content.slice(0, -1)];
}

return [...previousValue, getIndent(level), node, blankLines];
return [...previousValue, getIndent(level), node, blankLines];
}
}, []);

return setIndent(tree);
Expand All @@ -141,7 +237,7 @@ const attrsBoolean = (tree, {attrs: {boolean}}) => {
return node;
});

return removeAttrValue(tree);
return boolean ? removeAttrValue(tree) : tree;
};

const lowerElementName = (tree, {tags}) => {
Expand All @@ -155,6 +251,7 @@ const lowerElementName = (tree, {tags}) => {
if (
typeof node === 'object' &&
Object.prototype.hasOwnProperty.call(node, 'tag') &&
typeof node.tag === 'string' &&
tags.includes(node.tag.toLowerCase())
) {
node.tag = node.tag.toLowerCase();
Expand All @@ -166,7 +263,7 @@ const lowerElementName = (tree, {tags}) => {
return bypass(tree);
};

const lowerAttributeName = tree => {
const lowerAttributeName = (tree, {rules: {lowerAttributeName}}) => {
const bypass = tree => tree.map(node => {
if (typeof node === 'object' && Object.prototype.hasOwnProperty.call(node, 'content')) {
node.content = bypass(node.content);
Expand All @@ -182,7 +279,7 @@ const lowerAttributeName = tree => {
return node;
});

return bypass(tree);
return lowerAttributeName ? bypass(tree) : tree;
};

const eof = (tree, {rules: {eof}}) => eof ? [...tree, eof] : tree;
Expand Down Expand Up @@ -213,7 +310,7 @@ const mini = (tree, {mini}) => {
return node;
});

return bypass(tree);
return mini ? bypass(tree) : tree;
};

const beautify = (tree, options) => [
Expand Down
4 changes: 3 additions & 1 deletion src/rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ export default {
indent: 2,
blankLines: '\n',
eof: '\n',
eol: '\n'
eol: '\n',
useExistingLineBreaks: false,
lowerAttributeName: true
};
47 changes: 47 additions & 0 deletions test/expected/output-with-line-breaks.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html>
<head>
<link href="main.css" rel="stylesheet" type="text/css">
<!--[if IE 7]><link href="ie7.css" rel="stylesheet" type="text/css"><![endif]-->
<!--[if IE 6]><link href="ie6.css" rel="stylesheet" type="text/css"><![endif]-->
<!--[if IE 5]><link href="ie5.css" rel="stylesheet" type="text/css"><![endif]-->
</head>
<body>
<table>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
<tr>
<td>A</td>
<td>
Description of A
</td>
</tr>
<tr>
<td>B</td>
<td>Description of B</td>
</tr>
</table>

<div>
<p>
<span>Some</span> inline text
</p>

<div class="divs">
A
</div><div class="with">
B
</div><div class="collapsed">
C
</div><div class="whitespace">
D
</div>

<div class="not"></div> <div class="wrapped"></div>

<img src="img.png" alt=""><input type="text" required> <label>to the left</label>
</div>
</body>
</html>
28 changes: 28 additions & 0 deletions test/expected/output-with-unbalanced-line-breaks.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<link href="main.css" rel="stylesheet" type="text/css">
<!--[if IE 7]>
<link href="ie7.css" rel="stylesheet" type="text/css">
<![endif]-->
<!--[if IE 6]><link href="ie6.css" rel="stylesheet" type="text/css"><![endif]-->
</head>
<body>
<ul>
<li>
Need balancing
</li>
<li>
<span>Need balancing</span>
</li>
<li>
Need balancing
</li>
<li>
<span>Need balancing</span>
</li>
<li>Don't need balancing</li>
<li><span>Don't need balancing</span></li>
</ul>
</body>
</html>
Loading