You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

447 lines
10 KiB

  1. 'use strict'
  2. let Comment = require('./comment')
  3. let Declaration = require('./declaration')
  4. let Node = require('./node')
  5. let { isClean, my } = require('./symbols')
  6. let AtRule, parse, Root, Rule
  7. function cleanSource(nodes) {
  8. return nodes.map(i => {
  9. if (i.nodes) i.nodes = cleanSource(i.nodes)
  10. delete i.source
  11. return i
  12. })
  13. }
  14. function markTreeDirty(node) {
  15. node[isClean] = false
  16. if (node.proxyOf.nodes) {
  17. for (let i of node.proxyOf.nodes) {
  18. markTreeDirty(i)
  19. }
  20. }
  21. }
  22. class Container extends Node {
  23. get first() {
  24. if (!this.proxyOf.nodes) return undefined
  25. return this.proxyOf.nodes[0]
  26. }
  27. get last() {
  28. if (!this.proxyOf.nodes) return undefined
  29. return this.proxyOf.nodes[this.proxyOf.nodes.length - 1]
  30. }
  31. append(...children) {
  32. for (let child of children) {
  33. let nodes = this.normalize(child, this.last)
  34. for (let node of nodes) this.proxyOf.nodes.push(node)
  35. }
  36. this.markDirty()
  37. return this
  38. }
  39. cleanRaws(keepBetween) {
  40. super.cleanRaws(keepBetween)
  41. if (this.nodes) {
  42. for (let node of this.nodes) node.cleanRaws(keepBetween)
  43. }
  44. }
  45. each(callback) {
  46. if (!this.proxyOf.nodes) return undefined
  47. let iterator = this.getIterator()
  48. let index, result
  49. while (this.indexes[iterator] < this.proxyOf.nodes.length) {
  50. index = this.indexes[iterator]
  51. result = callback(this.proxyOf.nodes[index], index)
  52. if (result === false) break
  53. this.indexes[iterator] += 1
  54. }
  55. delete this.indexes[iterator]
  56. return result
  57. }
  58. every(condition) {
  59. return this.nodes.every(condition)
  60. }
  61. getIterator() {
  62. if (!this.lastEach) this.lastEach = 0
  63. if (!this.indexes) this.indexes = {}
  64. this.lastEach += 1
  65. let iterator = this.lastEach
  66. this.indexes[iterator] = 0
  67. return iterator
  68. }
  69. getProxyProcessor() {
  70. return {
  71. get(node, prop) {
  72. if (prop === 'proxyOf') {
  73. return node
  74. } else if (!node[prop]) {
  75. return node[prop]
  76. } else if (
  77. prop === 'each' ||
  78. (typeof prop === 'string' && prop.startsWith('walk'))
  79. ) {
  80. return (...args) => {
  81. return node[prop](
  82. ...args.map(i => {
  83. if (typeof i === 'function') {
  84. return (child, index) => i(child.toProxy(), index)
  85. } else {
  86. return i
  87. }
  88. })
  89. )
  90. }
  91. } else if (prop === 'every' || prop === 'some') {
  92. return cb => {
  93. return node[prop]((child, ...other) =>
  94. cb(child.toProxy(), ...other)
  95. )
  96. }
  97. } else if (prop === 'root') {
  98. return () => node.root().toProxy()
  99. } else if (prop === 'nodes') {
  100. return node.nodes.map(i => i.toProxy())
  101. } else if (prop === 'first' || prop === 'last') {
  102. return node[prop].toProxy()
  103. } else {
  104. return node[prop]
  105. }
  106. },
  107. set(node, prop, value) {
  108. if (node[prop] === value) return true
  109. node[prop] = value
  110. if (prop === 'name' || prop === 'params' || prop === 'selector') {
  111. node.markDirty()
  112. }
  113. return true
  114. }
  115. }
  116. }
  117. index(child) {
  118. if (typeof child === 'number') return child
  119. if (child.proxyOf) child = child.proxyOf
  120. return this.proxyOf.nodes.indexOf(child)
  121. }
  122. insertAfter(exist, add) {
  123. let existIndex = this.index(exist)
  124. let nodes = this.normalize(add, this.proxyOf.nodes[existIndex]).reverse()
  125. existIndex = this.index(exist)
  126. for (let node of nodes) this.proxyOf.nodes.splice(existIndex + 1, 0, node)
  127. let index
  128. for (let id in this.indexes) {
  129. index = this.indexes[id]
  130. if (existIndex < index) {
  131. this.indexes[id] = index + nodes.length
  132. }
  133. }
  134. this.markDirty()
  135. return this
  136. }
  137. insertBefore(exist, add) {
  138. let existIndex = this.index(exist)
  139. let type = existIndex === 0 ? 'prepend' : false
  140. let nodes = this.normalize(
  141. add,
  142. this.proxyOf.nodes[existIndex],
  143. type
  144. ).reverse()
  145. existIndex = this.index(exist)
  146. for (let node of nodes) this.proxyOf.nodes.splice(existIndex, 0, node)
  147. let index
  148. for (let id in this.indexes) {
  149. index = this.indexes[id]
  150. if (existIndex <= index) {
  151. this.indexes[id] = index + nodes.length
  152. }
  153. }
  154. this.markDirty()
  155. return this
  156. }
  157. normalize(nodes, sample) {
  158. if (typeof nodes === 'string') {
  159. nodes = cleanSource(parse(nodes).nodes)
  160. } else if (typeof nodes === 'undefined') {
  161. nodes = []
  162. } else if (Array.isArray(nodes)) {
  163. nodes = nodes.slice(0)
  164. for (let i of nodes) {
  165. if (i.parent) i.parent.removeChild(i, 'ignore')
  166. }
  167. } else if (nodes.type === 'root' && this.type !== 'document') {
  168. nodes = nodes.nodes.slice(0)
  169. for (let i of nodes) {
  170. if (i.parent) i.parent.removeChild(i, 'ignore')
  171. }
  172. } else if (nodes.type) {
  173. nodes = [nodes]
  174. } else if (nodes.prop) {
  175. if (typeof nodes.value === 'undefined') {
  176. throw new Error('Value field is missed in node creation')
  177. } else if (typeof nodes.value !== 'string') {
  178. nodes.value = String(nodes.value)
  179. }
  180. nodes = [new Declaration(nodes)]
  181. } else if (nodes.selector || nodes.selectors) {
  182. nodes = [new Rule(nodes)]
  183. } else if (nodes.name) {
  184. nodes = [new AtRule(nodes)]
  185. } else if (nodes.text) {
  186. nodes = [new Comment(nodes)]
  187. } else {
  188. throw new Error('Unknown node type in node creation')
  189. }
  190. let processed = nodes.map(i => {
  191. /* c8 ignore next */
  192. if (!i[my]) Container.rebuild(i)
  193. i = i.proxyOf
  194. if (i.parent) i.parent.removeChild(i)
  195. if (i[isClean]) markTreeDirty(i)
  196. if (!i.raws) i.raws = {}
  197. if (typeof i.raws.before === 'undefined') {
  198. if (sample && typeof sample.raws.before !== 'undefined') {
  199. i.raws.before = sample.raws.before.replace(/\S/g, '')
  200. }
  201. }
  202. i.parent = this.proxyOf
  203. return i
  204. })
  205. return processed
  206. }
  207. prepend(...children) {
  208. children = children.reverse()
  209. for (let child of children) {
  210. let nodes = this.normalize(child, this.first, 'prepend').reverse()
  211. for (let node of nodes) this.proxyOf.nodes.unshift(node)
  212. for (let id in this.indexes) {
  213. this.indexes[id] = this.indexes[id] + nodes.length
  214. }
  215. }
  216. this.markDirty()
  217. return this
  218. }
  219. push(child) {
  220. child.parent = this
  221. this.proxyOf.nodes.push(child)
  222. return this
  223. }
  224. removeAll() {
  225. for (let node of this.proxyOf.nodes) node.parent = undefined
  226. this.proxyOf.nodes = []
  227. this.markDirty()
  228. return this
  229. }
  230. removeChild(child) {
  231. child = this.index(child)
  232. this.proxyOf.nodes[child].parent = undefined
  233. this.proxyOf.nodes.splice(child, 1)
  234. let index
  235. for (let id in this.indexes) {
  236. index = this.indexes[id]
  237. if (index >= child) {
  238. this.indexes[id] = index - 1
  239. }
  240. }
  241. this.markDirty()
  242. return this
  243. }
  244. replaceValues(pattern, opts, callback) {
  245. if (!callback) {
  246. callback = opts
  247. opts = {}
  248. }
  249. this.walkDecls(decl => {
  250. if (opts.props && !opts.props.includes(decl.prop)) return
  251. if (opts.fast && !decl.value.includes(opts.fast)) return
  252. decl.value = decl.value.replace(pattern, callback)
  253. })
  254. this.markDirty()
  255. return this
  256. }
  257. some(condition) {
  258. return this.nodes.some(condition)
  259. }
  260. walk(callback) {
  261. return this.each((child, i) => {
  262. let result
  263. try {
  264. result = callback(child, i)
  265. } catch (e) {
  266. throw child.addToError(e)
  267. }
  268. if (result !== false && child.walk) {
  269. result = child.walk(callback)
  270. }
  271. return result
  272. })
  273. }
  274. walkAtRules(name, callback) {
  275. if (!callback) {
  276. callback = name
  277. return this.walk((child, i) => {
  278. if (child.type === 'atrule') {
  279. return callback(child, i)
  280. }
  281. })
  282. }
  283. if (name instanceof RegExp) {
  284. return this.walk((child, i) => {
  285. if (child.type === 'atrule' && name.test(child.name)) {
  286. return callback(child, i)
  287. }
  288. })
  289. }
  290. return this.walk((child, i) => {
  291. if (child.type === 'atrule' && child.name === name) {
  292. return callback(child, i)
  293. }
  294. })
  295. }
  296. walkComments(callback) {
  297. return this.walk((child, i) => {
  298. if (child.type === 'comment') {
  299. return callback(child, i)
  300. }
  301. })
  302. }
  303. walkDecls(prop, callback) {
  304. if (!callback) {
  305. callback = prop
  306. return this.walk((child, i) => {
  307. if (child.type === 'decl') {
  308. return callback(child, i)
  309. }
  310. })
  311. }
  312. if (prop instanceof RegExp) {
  313. return this.walk((child, i) => {
  314. if (child.type === 'decl' && prop.test(child.prop)) {
  315. return callback(child, i)
  316. }
  317. })
  318. }
  319. return this.walk((child, i) => {
  320. if (child.type === 'decl' && child.prop === prop) {
  321. return callback(child, i)
  322. }
  323. })
  324. }
  325. walkRules(selector, callback) {
  326. if (!callback) {
  327. callback = selector
  328. return this.walk((child, i) => {
  329. if (child.type === 'rule') {
  330. return callback(child, i)
  331. }
  332. })
  333. }
  334. if (selector instanceof RegExp) {
  335. return this.walk((child, i) => {
  336. if (child.type === 'rule' && selector.test(child.selector)) {
  337. return callback(child, i)
  338. }
  339. })
  340. }
  341. return this.walk((child, i) => {
  342. if (child.type === 'rule' && child.selector === selector) {
  343. return callback(child, i)
  344. }
  345. })
  346. }
  347. }
  348. Container.registerParse = dependant => {
  349. parse = dependant
  350. }
  351. Container.registerRule = dependant => {
  352. Rule = dependant
  353. }
  354. Container.registerAtRule = dependant => {
  355. AtRule = dependant
  356. }
  357. Container.registerRoot = dependant => {
  358. Root = dependant
  359. }
  360. module.exports = Container
  361. Container.default = Container
  362. /* c8 ignore start */
  363. Container.rebuild = node => {
  364. if (node.type === 'atrule') {
  365. Object.setPrototypeOf(node, AtRule.prototype)
  366. } else if (node.type === 'rule') {
  367. Object.setPrototypeOf(node, Rule.prototype)
  368. } else if (node.type === 'decl') {
  369. Object.setPrototypeOf(node, Declaration.prototype)
  370. } else if (node.type === 'comment') {
  371. Object.setPrototypeOf(node, Comment.prototype)
  372. } else if (node.type === 'root') {
  373. Object.setPrototypeOf(node, Root.prototype)
  374. }
  375. node[my] = true
  376. if (node.nodes) {
  377. node.nodes.forEach(child => {
  378. Container.rebuild(child)
  379. })
  380. }
  381. }
  382. /* c8 ignore stop */