448 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			448 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
'use strict'
 | 
						|
 | 
						|
let Comment = require('./comment')
 | 
						|
let Declaration = require('./declaration')
 | 
						|
let Node = require('./node')
 | 
						|
let { isClean, my } = require('./symbols')
 | 
						|
 | 
						|
let AtRule, parse, Root, Rule
 | 
						|
 | 
						|
function cleanSource(nodes) {
 | 
						|
  return nodes.map(i => {
 | 
						|
    if (i.nodes) i.nodes = cleanSource(i.nodes)
 | 
						|
    delete i.source
 | 
						|
    return i
 | 
						|
  })
 | 
						|
}
 | 
						|
 | 
						|
function markTreeDirty(node) {
 | 
						|
  node[isClean] = false
 | 
						|
  if (node.proxyOf.nodes) {
 | 
						|
    for (let i of node.proxyOf.nodes) {
 | 
						|
      markTreeDirty(i)
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class Container extends Node {
 | 
						|
  get first() {
 | 
						|
    if (!this.proxyOf.nodes) return undefined
 | 
						|
    return this.proxyOf.nodes[0]
 | 
						|
  }
 | 
						|
 | 
						|
  get last() {
 | 
						|
    if (!this.proxyOf.nodes) return undefined
 | 
						|
    return this.proxyOf.nodes[this.proxyOf.nodes.length - 1]
 | 
						|
  }
 | 
						|
 | 
						|
  append(...children) {
 | 
						|
    for (let child of children) {
 | 
						|
      let nodes = this.normalize(child, this.last)
 | 
						|
      for (let node of nodes) this.proxyOf.nodes.push(node)
 | 
						|
    }
 | 
						|
 | 
						|
    this.markDirty()
 | 
						|
 | 
						|
    return this
 | 
						|
  }
 | 
						|
 | 
						|
  cleanRaws(keepBetween) {
 | 
						|
    super.cleanRaws(keepBetween)
 | 
						|
    if (this.nodes) {
 | 
						|
      for (let node of this.nodes) node.cleanRaws(keepBetween)
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  each(callback) {
 | 
						|
    if (!this.proxyOf.nodes) return undefined
 | 
						|
    let iterator = this.getIterator()
 | 
						|
 | 
						|
    let index, result
 | 
						|
    while (this.indexes[iterator] < this.proxyOf.nodes.length) {
 | 
						|
      index = this.indexes[iterator]
 | 
						|
      result = callback(this.proxyOf.nodes[index], index)
 | 
						|
      if (result === false) break
 | 
						|
 | 
						|
      this.indexes[iterator] += 1
 | 
						|
    }
 | 
						|
 | 
						|
    delete this.indexes[iterator]
 | 
						|
    return result
 | 
						|
  }
 | 
						|
 | 
						|
  every(condition) {
 | 
						|
    return this.nodes.every(condition)
 | 
						|
  }
 | 
						|
 | 
						|
  getIterator() {
 | 
						|
    if (!this.lastEach) this.lastEach = 0
 | 
						|
    if (!this.indexes) this.indexes = {}
 | 
						|
 | 
						|
    this.lastEach += 1
 | 
						|
    let iterator = this.lastEach
 | 
						|
    this.indexes[iterator] = 0
 | 
						|
 | 
						|
    return iterator
 | 
						|
  }
 | 
						|
 | 
						|
  getProxyProcessor() {
 | 
						|
    return {
 | 
						|
      get(node, prop) {
 | 
						|
        if (prop === 'proxyOf') {
 | 
						|
          return node
 | 
						|
        } else if (!node[prop]) {
 | 
						|
          return node[prop]
 | 
						|
        } else if (
 | 
						|
          prop === 'each' ||
 | 
						|
          (typeof prop === 'string' && prop.startsWith('walk'))
 | 
						|
        ) {
 | 
						|
          return (...args) => {
 | 
						|
            return node[prop](
 | 
						|
              ...args.map(i => {
 | 
						|
                if (typeof i === 'function') {
 | 
						|
                  return (child, index) => i(child.toProxy(), index)
 | 
						|
                } else {
 | 
						|
                  return i
 | 
						|
                }
 | 
						|
              })
 | 
						|
            )
 | 
						|
          }
 | 
						|
        } else if (prop === 'every' || prop === 'some') {
 | 
						|
          return cb => {
 | 
						|
            return node[prop]((child, ...other) =>
 | 
						|
              cb(child.toProxy(), ...other)
 | 
						|
            )
 | 
						|
          }
 | 
						|
        } else if (prop === 'root') {
 | 
						|
          return () => node.root().toProxy()
 | 
						|
        } else if (prop === 'nodes') {
 | 
						|
          return node.nodes.map(i => i.toProxy())
 | 
						|
        } else if (prop === 'first' || prop === 'last') {
 | 
						|
          return node[prop].toProxy()
 | 
						|
        } else {
 | 
						|
          return node[prop]
 | 
						|
        }
 | 
						|
      },
 | 
						|
 | 
						|
      set(node, prop, value) {
 | 
						|
        if (node[prop] === value) return true
 | 
						|
        node[prop] = value
 | 
						|
        if (prop === 'name' || prop === 'params' || prop === 'selector') {
 | 
						|
          node.markDirty()
 | 
						|
        }
 | 
						|
        return true
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  index(child) {
 | 
						|
    if (typeof child === 'number') return child
 | 
						|
    if (child.proxyOf) child = child.proxyOf
 | 
						|
    return this.proxyOf.nodes.indexOf(child)
 | 
						|
  }
 | 
						|
 | 
						|
  insertAfter(exist, add) {
 | 
						|
    let existIndex = this.index(exist)
 | 
						|
    let nodes = this.normalize(add, this.proxyOf.nodes[existIndex]).reverse()
 | 
						|
    existIndex = this.index(exist)
 | 
						|
    for (let node of nodes) this.proxyOf.nodes.splice(existIndex + 1, 0, node)
 | 
						|
 | 
						|
    let index
 | 
						|
    for (let id in this.indexes) {
 | 
						|
      index = this.indexes[id]
 | 
						|
      if (existIndex < index) {
 | 
						|
        this.indexes[id] = index + nodes.length
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    this.markDirty()
 | 
						|
 | 
						|
    return this
 | 
						|
  }
 | 
						|
 | 
						|
  insertBefore(exist, add) {
 | 
						|
    let existIndex = this.index(exist)
 | 
						|
    let type = existIndex === 0 ? 'prepend' : false
 | 
						|
    let nodes = this.normalize(
 | 
						|
      add,
 | 
						|
      this.proxyOf.nodes[existIndex],
 | 
						|
      type
 | 
						|
    ).reverse()
 | 
						|
    existIndex = this.index(exist)
 | 
						|
    for (let node of nodes) this.proxyOf.nodes.splice(existIndex, 0, node)
 | 
						|
 | 
						|
    let index
 | 
						|
    for (let id in this.indexes) {
 | 
						|
      index = this.indexes[id]
 | 
						|
      if (existIndex <= index) {
 | 
						|
        this.indexes[id] = index + nodes.length
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    this.markDirty()
 | 
						|
 | 
						|
    return this
 | 
						|
  }
 | 
						|
 | 
						|
  normalize(nodes, sample) {
 | 
						|
    if (typeof nodes === 'string') {
 | 
						|
      nodes = cleanSource(parse(nodes).nodes)
 | 
						|
    } else if (typeof nodes === 'undefined') {
 | 
						|
      nodes = []
 | 
						|
    } else if (Array.isArray(nodes)) {
 | 
						|
      nodes = nodes.slice(0)
 | 
						|
      for (let i of nodes) {
 | 
						|
        if (i.parent) i.parent.removeChild(i, 'ignore')
 | 
						|
      }
 | 
						|
    } else if (nodes.type === 'root' && this.type !== 'document') {
 | 
						|
      nodes = nodes.nodes.slice(0)
 | 
						|
      for (let i of nodes) {
 | 
						|
        if (i.parent) i.parent.removeChild(i, 'ignore')
 | 
						|
      }
 | 
						|
    } else if (nodes.type) {
 | 
						|
      nodes = [nodes]
 | 
						|
    } else if (nodes.prop) {
 | 
						|
      if (typeof nodes.value === 'undefined') {
 | 
						|
        throw new Error('Value field is missed in node creation')
 | 
						|
      } else if (typeof nodes.value !== 'string') {
 | 
						|
        nodes.value = String(nodes.value)
 | 
						|
      }
 | 
						|
      nodes = [new Declaration(nodes)]
 | 
						|
    } else if (nodes.selector || nodes.selectors) {
 | 
						|
      nodes = [new Rule(nodes)]
 | 
						|
    } else if (nodes.name) {
 | 
						|
      nodes = [new AtRule(nodes)]
 | 
						|
    } else if (nodes.text) {
 | 
						|
      nodes = [new Comment(nodes)]
 | 
						|
    } else {
 | 
						|
      throw new Error('Unknown node type in node creation')
 | 
						|
    }
 | 
						|
 | 
						|
    let processed = nodes.map(i => {
 | 
						|
      /* c8 ignore next */
 | 
						|
      if (!i[my]) Container.rebuild(i)
 | 
						|
      i = i.proxyOf
 | 
						|
      if (i.parent) i.parent.removeChild(i)
 | 
						|
      if (i[isClean]) markTreeDirty(i)
 | 
						|
 | 
						|
      if (!i.raws) i.raws = {}
 | 
						|
      if (typeof i.raws.before === 'undefined') {
 | 
						|
        if (sample && typeof sample.raws.before !== 'undefined') {
 | 
						|
          i.raws.before = sample.raws.before.replace(/\S/g, '')
 | 
						|
        }
 | 
						|
      }
 | 
						|
      i.parent = this.proxyOf
 | 
						|
      return i
 | 
						|
    })
 | 
						|
 | 
						|
    return processed
 | 
						|
  }
 | 
						|
 | 
						|
  prepend(...children) {
 | 
						|
    children = children.reverse()
 | 
						|
    for (let child of children) {
 | 
						|
      let nodes = this.normalize(child, this.first, 'prepend').reverse()
 | 
						|
      for (let node of nodes) this.proxyOf.nodes.unshift(node)
 | 
						|
      for (let id in this.indexes) {
 | 
						|
        this.indexes[id] = this.indexes[id] + nodes.length
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    this.markDirty()
 | 
						|
 | 
						|
    return this
 | 
						|
  }
 | 
						|
 | 
						|
  push(child) {
 | 
						|
    child.parent = this
 | 
						|
    this.proxyOf.nodes.push(child)
 | 
						|
    return this
 | 
						|
  }
 | 
						|
 | 
						|
  removeAll() {
 | 
						|
    for (let node of this.proxyOf.nodes) node.parent = undefined
 | 
						|
    this.proxyOf.nodes = []
 | 
						|
 | 
						|
    this.markDirty()
 | 
						|
 | 
						|
    return this
 | 
						|
  }
 | 
						|
 | 
						|
  removeChild(child) {
 | 
						|
    child = this.index(child)
 | 
						|
    this.proxyOf.nodes[child].parent = undefined
 | 
						|
    this.proxyOf.nodes.splice(child, 1)
 | 
						|
 | 
						|
    let index
 | 
						|
    for (let id in this.indexes) {
 | 
						|
      index = this.indexes[id]
 | 
						|
      if (index >= child) {
 | 
						|
        this.indexes[id] = index - 1
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    this.markDirty()
 | 
						|
 | 
						|
    return this
 | 
						|
  }
 | 
						|
 | 
						|
  replaceValues(pattern, opts, callback) {
 | 
						|
    if (!callback) {
 | 
						|
      callback = opts
 | 
						|
      opts = {}
 | 
						|
    }
 | 
						|
 | 
						|
    this.walkDecls(decl => {
 | 
						|
      if (opts.props && !opts.props.includes(decl.prop)) return
 | 
						|
      if (opts.fast && !decl.value.includes(opts.fast)) return
 | 
						|
 | 
						|
      decl.value = decl.value.replace(pattern, callback)
 | 
						|
    })
 | 
						|
 | 
						|
    this.markDirty()
 | 
						|
 | 
						|
    return this
 | 
						|
  }
 | 
						|
 | 
						|
  some(condition) {
 | 
						|
    return this.nodes.some(condition)
 | 
						|
  }
 | 
						|
 | 
						|
  walk(callback) {
 | 
						|
    return this.each((child, i) => {
 | 
						|
      let result
 | 
						|
      try {
 | 
						|
        result = callback(child, i)
 | 
						|
      } catch (e) {
 | 
						|
        throw child.addToError(e)
 | 
						|
      }
 | 
						|
      if (result !== false && child.walk) {
 | 
						|
        result = child.walk(callback)
 | 
						|
      }
 | 
						|
 | 
						|
      return result
 | 
						|
    })
 | 
						|
  }
 | 
						|
 | 
						|
  walkAtRules(name, callback) {
 | 
						|
    if (!callback) {
 | 
						|
      callback = name
 | 
						|
      return this.walk((child, i) => {
 | 
						|
        if (child.type === 'atrule') {
 | 
						|
          return callback(child, i)
 | 
						|
        }
 | 
						|
      })
 | 
						|
    }
 | 
						|
    if (name instanceof RegExp) {
 | 
						|
      return this.walk((child, i) => {
 | 
						|
        if (child.type === 'atrule' && name.test(child.name)) {
 | 
						|
          return callback(child, i)
 | 
						|
        }
 | 
						|
      })
 | 
						|
    }
 | 
						|
    return this.walk((child, i) => {
 | 
						|
      if (child.type === 'atrule' && child.name === name) {
 | 
						|
        return callback(child, i)
 | 
						|
      }
 | 
						|
    })
 | 
						|
  }
 | 
						|
 | 
						|
  walkComments(callback) {
 | 
						|
    return this.walk((child, i) => {
 | 
						|
      if (child.type === 'comment') {
 | 
						|
        return callback(child, i)
 | 
						|
      }
 | 
						|
    })
 | 
						|
  }
 | 
						|
 | 
						|
  walkDecls(prop, callback) {
 | 
						|
    if (!callback) {
 | 
						|
      callback = prop
 | 
						|
      return this.walk((child, i) => {
 | 
						|
        if (child.type === 'decl') {
 | 
						|
          return callback(child, i)
 | 
						|
        }
 | 
						|
      })
 | 
						|
    }
 | 
						|
    if (prop instanceof RegExp) {
 | 
						|
      return this.walk((child, i) => {
 | 
						|
        if (child.type === 'decl' && prop.test(child.prop)) {
 | 
						|
          return callback(child, i)
 | 
						|
        }
 | 
						|
      })
 | 
						|
    }
 | 
						|
    return this.walk((child, i) => {
 | 
						|
      if (child.type === 'decl' && child.prop === prop) {
 | 
						|
        return callback(child, i)
 | 
						|
      }
 | 
						|
    })
 | 
						|
  }
 | 
						|
 | 
						|
  walkRules(selector, callback) {
 | 
						|
    if (!callback) {
 | 
						|
      callback = selector
 | 
						|
 | 
						|
      return this.walk((child, i) => {
 | 
						|
        if (child.type === 'rule') {
 | 
						|
          return callback(child, i)
 | 
						|
        }
 | 
						|
      })
 | 
						|
    }
 | 
						|
    if (selector instanceof RegExp) {
 | 
						|
      return this.walk((child, i) => {
 | 
						|
        if (child.type === 'rule' && selector.test(child.selector)) {
 | 
						|
          return callback(child, i)
 | 
						|
        }
 | 
						|
      })
 | 
						|
    }
 | 
						|
    return this.walk((child, i) => {
 | 
						|
      if (child.type === 'rule' && child.selector === selector) {
 | 
						|
        return callback(child, i)
 | 
						|
      }
 | 
						|
    })
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
Container.registerParse = dependant => {
 | 
						|
  parse = dependant
 | 
						|
}
 | 
						|
 | 
						|
Container.registerRule = dependant => {
 | 
						|
  Rule = dependant
 | 
						|
}
 | 
						|
 | 
						|
Container.registerAtRule = dependant => {
 | 
						|
  AtRule = dependant
 | 
						|
}
 | 
						|
 | 
						|
Container.registerRoot = dependant => {
 | 
						|
  Root = dependant
 | 
						|
}
 | 
						|
 | 
						|
module.exports = Container
 | 
						|
Container.default = Container
 | 
						|
 | 
						|
/* c8 ignore start */
 | 
						|
Container.rebuild = node => {
 | 
						|
  if (node.type === 'atrule') {
 | 
						|
    Object.setPrototypeOf(node, AtRule.prototype)
 | 
						|
  } else if (node.type === 'rule') {
 | 
						|
    Object.setPrototypeOf(node, Rule.prototype)
 | 
						|
  } else if (node.type === 'decl') {
 | 
						|
    Object.setPrototypeOf(node, Declaration.prototype)
 | 
						|
  } else if (node.type === 'comment') {
 | 
						|
    Object.setPrototypeOf(node, Comment.prototype)
 | 
						|
  } else if (node.type === 'root') {
 | 
						|
    Object.setPrototypeOf(node, Root.prototype)
 | 
						|
  }
 | 
						|
 | 
						|
  node[my] = true
 | 
						|
 | 
						|
  if (node.nodes) {
 | 
						|
    node.nodes.forEach(child => {
 | 
						|
      Container.rebuild(child)
 | 
						|
    })
 | 
						|
  }
 | 
						|
}
 | 
						|
/* c8 ignore stop */
 |