| 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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229 |
1×
1×
1×
1×
11112×
62462×
62461×
11112×
1×
5535×
38973×
38972×
5535×
1×
2×
2×
135×
134×
2×
1×
2×
1×
111×
111×
111×
111×
261×
20239×
20239×
724×
724×
261×
111×
1×
66×
66×
66×
66×
18×
18×
48×
38×
38×
38×
38×
10×
1×
784×
784×
784×
681×
1×
18248×
18248×
18248×
17987×
261×
291×
22×
269×
261×
1×
17950×
1×
348×
348×
348×
4872×
4872×
4872×
17488×
17488×
17488×
348×
1×
| /**
* @file util methods
* @author nighca<nighca@live.cn>
*/
var emitter = require('events').EventEmitter.prototype;
var ElementType = require('domelementtype');
var hasOwnProperty = Object.prototype.hasOwnProperty;
/**
* Copy properties from src to target.
*
* @param {Object} target - target object
* @param {?Object} src - src object
* @return {Object} target object
*/
var extend = function (target, src) {
for (var key in src) {
if (hasOwnProperty.call(src, key)) {
target[key] = src[key];
}
}
return target;
};
/**
* Define attributes for target object.
*
* @param {Object} target - target object
* @param {Object} attributes - attributes
* @return {Object} target object
*/
var extendAttribute = function (target, attributes) {
for (var name in attributes) {
if (attributes.hasOwnProperty(name)) {
Object.defineProperty(target, name, attributes[name]);
}
}
return target;
};
/**
* Make given method result-cachable.
*
* @param {Function} getter - given method
* @return {Function} result method which caches result automatically
* @property {Function} clear - clear cache
*/
var cachable = function (getter) {
var storage = {};
var get = function (key, refresh) {
storage[key] = (refresh || !(storage.hasOwnProperty(key))) ? getter(key, refresh) : storage[key];
return storage[key];
};
var clear = function () {
storage = {};
};
return extend(get, {clear: clear});
};
/**
* The position info.
*
* @typedef {Object} Position
* @property {number} line - line number
* @property {number} column - column number
*/
/**
* Get position (line & column) in or a position method for given content.
*
* @param {string} content - given content
* @param {number=} index - target index
* @return {Position|Function} position method / position info
*/
var getPosition = function (content, index) {
var start = 0;
var line = 0;
var column = 0;
// the position method (index -> line & column)
// indexes should be passed with pos-low-to-high
var position = function (index) {
for (; start < index; start++) {
column++;
if (content[start] === '\n') {
column = 0;
line++;
}
}
return {
line: line + 1,
column: column + 1
};
};
return arguments.length > 1 ? position(index) : position;
};
/**
* Extract config info from comment content.
*
* @param {string} comment - comment content
* @return {?Object} config info
*/
var extractCommentInfo = function (comment) {
// htmlcs-disable img-alt, attr-value-double-quotes
var ablePattern = /^[\s\n]*htmlcs-(\w+)(?:\s+([\w\-]+(?:,\s*[\w\-]+)*)?[\s\n]*)?$/;
// htmlcs "img-alt": false
var configPattern = /^[\s\n]*htmlcs\s([\s\S]*)$/;
var result;
if (ablePattern.test(comment)) {
result = ablePattern.exec(comment);
return {
operation: result[1],
content: result[2] ? result[2].split(/\s*,\s*/g) : null
};
}
if (configPattern.test(comment)) {
result = configPattern.exec(comment)[1]
.replace(/([a-zA-Z0-9\-\/]+):/g, '"$1":')
.replace(/(\]|[0-9])\s+(?=")/, '$1,');
try {
result = JSON.parse('{' + result + '}');
return {
operation: 'config',
content: result
};
}
catch (e) {}
}
return null;
};
/**
* Walk a tree (pre-order).
*
* @param {Object} root - the root node of the tree
* @param {Function} handler - the handler
* @param {string} childrenKey - key of children property
*/
var walk = function (root, handler, childrenKey) {
handler(root);
var list = root[childrenKey || 'children'];
list && list.forEach(function (child) {
walk(child, handler, childrenKey);
});
};
var getInlineCfgByIndex = function (rule, index, inlineCfg, initial) {
var result = initial;
var cfgs = inlineCfg[rule];
if (!cfgs) {
return result;
}
for (var i = 0, l = cfgs.length; i < l; i++) {
if (index >= 0 && cfgs[i].index > index) {
break;
}
result = cfgs[i].content;
}
return result;
};
var getCfgByIndex = function (rule, index, inlineCfg, cfg) {
return getInlineCfgByIndex(rule, index, inlineCfg, cfg[rule]);
};
/**
* Make target emittable &
* emit event "xxx" on target while target._cbs.onxxx called.
*
* @param {Object} target - the target
* @param {Array} events - events need to emit
* @return {Object} target itself
*/
var emittable = function (target, events) {
var cbs = target._cbs;
// make target emittable
extend(target, emitter);
// wrap methods based on given event list
events.forEach(function (event) {
// emit "xxx" on target while method "onxxx" in cbs called
var methodName = 'on' + event;
var method = cbs[methodName];
cbs[methodName] = function () {
var args = Array.prototype.slice.call(arguments);
target.emit.apply(target, [event].concat(args));
return method ? method.apply(this, args) : null;
};
});
return target;
};
module.exports = {
nodeType: ElementType,
isElement: ElementType.isTag,
extend: extend,
extendAttribute: extendAttribute,
cachable: cachable,
getPosition: getPosition,
extractCommentInfo: extractCommentInfo,
walk: walk,
getInlineCfgByIndex: getInlineCfgByIndex,
getCfgByIndex: getCfgByIndex,
emittable: emittable
};
|