1 | 'use strict';
|
---|
2 |
|
---|
3 | var repeat = require('repeat-string');
|
---|
4 |
|
---|
5 | var splitOnTags = function splitOnTags(str) {
|
---|
6 | return str.split(/(<\/?[^>]+>)/g).filter(function (line) {
|
---|
7 | return line.trim() !== '';
|
---|
8 | });
|
---|
9 | };
|
---|
10 | var isTag = function isTag(str) {
|
---|
11 | return (/<[^>!]+>/.test(str)
|
---|
12 | );
|
---|
13 | };
|
---|
14 | var isClosingTag = function isClosingTag(str) {
|
---|
15 | return (/<\/+[^>]+>/.test(str)
|
---|
16 | );
|
---|
17 | };
|
---|
18 | var isSelfClosingTag = function isSelfClosingTag(str) {
|
---|
19 | return (/<[^>]+\/>/.test(str)
|
---|
20 | );
|
---|
21 | };
|
---|
22 | var isOpeningTag = function isOpeningTag(str) {
|
---|
23 | return isTag(str) && !isClosingTag(str) && !isSelfClosingTag(str);
|
---|
24 | };
|
---|
25 |
|
---|
26 | module.exports = function (xml) {
|
---|
27 | var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
---|
28 | var indentor = config.indentor,
|
---|
29 | textNodesOnSameLine = config.textNodesOnSameLine;
|
---|
30 |
|
---|
31 | var depth = 0;
|
---|
32 | var indicesToRemove = [];
|
---|
33 | indentor = indentor || ' ';
|
---|
34 |
|
---|
35 | var rawResult = lexer(xml).map(function (element, i, arr) {
|
---|
36 | var value = element.value,
|
---|
37 | type = element.type;
|
---|
38 |
|
---|
39 | if (type === 'ClosingTag') {
|
---|
40 | depth--;
|
---|
41 | }
|
---|
42 |
|
---|
43 | var indentation = repeat(indentor, depth);
|
---|
44 | var line = indentation + value;
|
---|
45 |
|
---|
46 | if (type === 'OpeningTag') {
|
---|
47 | depth++;
|
---|
48 | }
|
---|
49 |
|
---|
50 | if (textNodesOnSameLine) {
|
---|
51 | // Lookbehind for [OpeningTag][Text][ClosingTag]
|
---|
52 | var oneBefore = arr[i - 1];
|
---|
53 | var twoBefore = arr[i - 2];
|
---|
54 |
|
---|
55 | if (type === "ClosingTag" && oneBefore.type === "Text" && twoBefore.type === "OpeningTag") {
|
---|
56 | // collapse into a single line
|
---|
57 | line = '' + indentation + twoBefore.value + oneBefore.value + value;
|
---|
58 | indicesToRemove.push(i - 2, i - 1);
|
---|
59 | }
|
---|
60 | }
|
---|
61 |
|
---|
62 | return line;
|
---|
63 | });
|
---|
64 |
|
---|
65 | indicesToRemove.forEach(function (idx) {
|
---|
66 | return rawResult[idx] = null;
|
---|
67 | });
|
---|
68 |
|
---|
69 | return rawResult.filter(function (val) {
|
---|
70 | return !!val;
|
---|
71 | }).join('\n');
|
---|
72 | };
|
---|
73 |
|
---|
74 | function lexer(xmlStr) {
|
---|
75 | var values = splitOnTags(xmlStr);
|
---|
76 | return values.map(function (value) {
|
---|
77 | return {
|
---|
78 | value: value,
|
---|
79 | type: getType(value)
|
---|
80 | };
|
---|
81 | });
|
---|
82 | }
|
---|
83 |
|
---|
84 | // Helpers
|
---|
85 |
|
---|
86 | function getType(str) {
|
---|
87 | if (isClosingTag(str)) {
|
---|
88 | return 'ClosingTag';
|
---|
89 | }
|
---|
90 |
|
---|
91 | if (isOpeningTag(str)) {
|
---|
92 | return 'OpeningTag';
|
---|
93 | }
|
---|
94 |
|
---|
95 | if (isSelfClosingTag(str)) {
|
---|
96 | return 'SelfClosingTag';
|
---|
97 | }
|
---|
98 |
|
---|
99 | return 'Text';
|
---|
100 | } |
---|