-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
114 lines (99 loc) · 3.71 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#!/usr/bin/env node
const fs = require('fs');
const { JSDOM } = require('jsdom');
const minimist = require('minimist');
// args
const args = minimist(process.argv.slice(2), {
string: ['htmlinput', 'htmloutput', 'cssoutput'],
boolean: ['it'],
alias: {
'htmlinput': 'i',
'htmloutput': 'ho',
'cssoutput': 'co',
},
default: {
'htmlinput': '',
'htmloutput': 'o.html',
'cssoutput': 'o.css',
'it': false
}
});
const inputHtmlFileName = args.htmlinput;
const outputHtmlFileName = args.htmloutput;
const outputCssFileName = args.cssoutput;
const interactiveMode = false; // args.it - perhaps down the road we iterate over found style groups, and let user name them on the spot
// arg validations
if (!inputHtmlFileName) {
console.error('Please provide the input HTML file as an argument.');
process.exit(1);
}
// load input file
const inputHtml = fs.readFileSync(inputHtmlFileName, 'utf-8');
// parse input file into jquery
const { window } = new JSDOM(inputHtml);
const $ = require('jquery')(window);
// for every element with a style attribute
const styleAttributeGroups = {};
let classCounter = 1;
$('[style]').each(function () {
// normalize style attribute (order rules alphabetically etc.)
// so that style="color:red;background-color:blue;" is seen as same as style="background-color:blue;color:red;"
const styleAttributeString = $(this).attr('style');
if (!styleAttributeString) {
// if element has a falsey style attr - remove styl attr immediately, we don't want to generate classes for it
$(this).removeAttr('style');
}
else {
const normalizedStyleAttributeString = normalizeStyle(styleAttributeString);
// put the element into a group of elements that have an equivalent style
if (normalizedStyleAttributeString in styleAttributeGroups) {
styleAttributeGroups[normalizedStyleAttributeString].push(this);
} else {
styleAttributeGroups[normalizedStyleAttributeString] = [this];
}
}
});
const styleAttrbiuteStringToCssClass = {};
// for each style attr grouping
Object.keys(styleAttributeGroups).forEach(styleAttributeString => {
// generate a class name for the style group
const className = `inlineAwayClass-${classCounter}`;
classCounter++;
// process the elements
styleAttributeGroups[styleAttributeString].forEach(element => {
// give them a class name for the style group
// remove their style attribute
$(element).addClass(className).removeAttr('style');
});
// save the class name for the style block - we'll output that later
styleAttrbiuteStringToCssClass[styleAttributeString] = className;
});
// generate output
let outputHtml = "";
let outputCss = "";
const node = window.document.doctype;
const docTypeHtml = "<!DOCTYPE "
+ node.name
+ (node.publicId ? ' PUBLIC "' + node.publicId + '"' : '')
+ (!node.publicId && node.systemId ? ' SYSTEM' : '')
+ (node.systemId ? ' "' + node.systemId + '"' : '')
+ '>';
outputHtml += docTypeHtml + "\n";
outputHtml += $("html").prop('outerHTML') + "\n";
Object.keys(styleAttrbiuteStringToCssClass).forEach(styleAttributeString => {
outputCss += `.${styleAttrbiuteStringToCssClass[styleAttributeString]} { ${styleAttributeString} }\n`;
});
// write output
fs.writeFileSync(outputHtmlFileName, outputHtml);
fs.writeFileSync(outputCssFileName, outputCss);
// done
console.log(outputHtmlFileName);
console.log(outputCssFileName);
// helper functions
function normalizeStyle(style) {
return style.split(';')
.map(prop => prop.trim())
.filter(prop => prop) // Remove empty strings after trimming
.sort()
.join('; ') + ';';
}