module Pakyow
module Presenter
class StringDoc
attr_reader :structure
TITLE_REGEX = /
(.*?)<\/title>/m
def initialize(html)
@structure = StringDocParser.new(html).structure
end
def self.from_structure(structure, node: nil)
instance = allocate
instance.instance_variable_set(:@structure, structure)
instance.instance_variable_set(:@node, node)
return instance
end
def self.ensure(object)
return object if object.is_a?(StringDoc)
StringDoc.new(object)
end
def initialize_copy(original_doc)
super
if original_structure = original_doc.instance_variable_get(:@structure)
@structure = Utils::Dup.deep(original_structure)
end
if original_doc.node?
@node = @structure[original_doc.node_index]
end
end
# Creates a StringDoc instance with the same structure, but a duped node.
#
def soft_copy
StringDoc.from_structure(@structure, node: @node ? Utils::Dup.deep(@node) : nil)
end
def title
title_search do |n, match|
return match[1]
end
end
def title=(title)
title_search do |n, match|
n.gsub!(TITLE_REGEX, "#{title}")
end
end
def attribute?(name)
attributes.key?(name.to_sym)
end
def set_attribute(name, value)
return if attributes.nil?
attributes[name.to_sym] = value
end
alias :update_attribute :set_attribute
def get_attribute(name)
attributes[name.to_sym]
end
def remove_attribute(name)
attributes.delete(name.to_sym)
end
def has_attribute?(name)
attributes.key?(name)
end
def remove
@structure.delete_if { |n| n.equal?(node) }
if @node.nil?
@node = ['', {}, [['', {}, []]]]
else
@node[0] = ''
@node[1] = {}
@node[2][0][0] = ''
@node[2][0][1] = {}
@node[2][0][2] = []
@node[2].slice!(1..-1)
end
@removed = true
end
def clear
children.clear
end
def text
html.gsub(/<[^>]*>/, '')
end
def text=(text)
clear
children << [text, {}, []]
end
def html
StringDocRenderer.render(children)
end
def html=(html)
clear
children << [html, {}, []]
end
def append(doc)
doc = StringDoc.ensure(doc)
if doc.node?
children.push(doc.node)
else
children.concat(doc.structure)
end
end
def prepend(doc)
doc = StringDoc.ensure(doc)
if doc.node?
children.unshift(doc.node)
else
children.unshift(*doc.structure)
end
end
def after(doc)
doc = StringDoc.ensure(doc)
if doc.node?
@structure.insert(node_index + 1, doc.node)
else
@structure.concat(doc.structure)
end
end
def before(doc)
doc = StringDoc.ensure(doc)
if doc.node?
@structure.unshift(doc.node)
else
@structure.unshift(*doc.structure)
end
end
def replace(doc)
doc = StringDoc.ensure(doc)
index = node_index || 0
if doc.node?
@structure.insert(index + 1, node)
else
@structure.insert(index + 1, *doc.structure)
end
@structure.delete_at(index)
end
def scope(scope_name)
scopes.select { |b| b[:scope] == scope_name }
end
def prop(scope_name, prop_name)
return [] unless scope = scopes.select { |s| s[:scope] == scope_name }[0]
scope[:props].select { |p| p[:prop] == prop_name }
end
def container(name)
containers.fetch(name, {})[:doc]
end
def component(name)
components.select { |c| c[:component] == name }
end
def channel(name)
find_channel(scopes, name)
end
def containers
find_containers(@node ? [@node] : @structure)
end
def partials
find_partials(@node ? [@node] : @structure)
end
def scopes
find_scopes(@node ? [@node] : @structure)
end
def components
find_components(@node ? [@node] : @structure)
end
def to_html
StringDocRenderer.render((@node && !@removed) ? [@node] : @structure)
StringDocRenderer.render(@node ? [@node] : @structure)
end
alias :to_s :to_html
def ==(o)
#TODO do this without rendering?
# (at least in the case of comparing StringDoc to StringDoc)
to_s == o.to_s
end
def node
return @structure if @structure.empty? && !@removed
return @node || @structure[0]
end
def node_index
return nil unless node?
@structure.index { |n| n.equal?(@node) }
end
def node?
return false if @node.nil?
return false if @removed
return true
end
def tagname
node[0].gsub(/[^a-zA-Z]/, '')
end
def option(value: nil)
StringDoc.from_structure(node[2][0][2].select { |option|
option[1][:value] == value.to_s
})
end
def exists?
@structure.include?(node)
end
private
def title_search
@structure.flatten.each do |n|
next unless n.is_a?(String)
if match = n.match(TITLE_REGEX)
yield n, match
end
end
end
# Returns the structure representing the attributes for the node
#
def attributes
node[1]
end
def children
if @structure.empty? && !@removed
@structure
else
node[2][0][2]
end
end
def find_containers(structure, primary_structure = @structure, containers = {})
return {} if structure.empty?
structure.inject(containers) { |s, e|
if e[1].has_key?(:container)
s[e[1][:container]] = { doc: StringDoc.from_structure(primary_structure, node: e) }
end
find_containers(e[2], e[2], s)
s
} || {}
end
def find_partials(structure, primary_structure = @structure, partials = {})
structure.inject(partials) { |s, e|
if e[1].has_key?(:partial)
(s[e[1][:partial]] ||= []) << StringDoc.from_structure(primary_structure, node: e)
end
find_partials(e[2], e[2], s)
s
} || {}
end
def find_scopes(structure, primary_structure = @structure, scopes = [])
ret_scopes = structure.inject(scopes) { |s, e|
if e[1].has_key?(:'data-scope')
scope = {
doc: StringDoc.from_structure(primary_structure, node: e),
scope: e[1][:'data-scope'].to_sym,
props: find_node_props(e).concat(find_props(e[2])),
nested: find_scopes(e[2]),
}
if version = e[1][:'data-version']
scope[:version] = version.to_sym
end
s << scope
end
# only find scopes if `e` is the root node or we're not decending into a nested scope
find_scopes(e[2], e[2], s) if e == node || !e[1].has_key?(:'data-scope')
s
} || []
ret_scopes
end
def find_props(structure, primary_structure = @structure, props = [])
structure.each do |e|
find_node_props(e, primary_structure, props)
end
props || []
end
def find_node_props(node, primary_structure = @structure, props = [])
if node[1].has_key?(:'data-prop')
prop = {
doc: StringDoc.from_structure(primary_structure, node: node),
prop: node[1][:'data-prop'].to_sym,
parts: {},
}
if node[1].has_key?(:'data-parts')
prop[:parts][:include] = node[1][:'data-parts'].split(/\s+/).map(&:to_sym)
end
if node[1].has_key?(:'data-parts-exclude')
prop[:parts][:exclude] = node[1][:'data-parts-exclude'].split(/\s+/).map(&:to_sym)
end
props << prop
end
unless node[1].has_key?(:'data-scope')
find_props(node[2], node[2], props)
end
props
end
def find_channel(scopes, name)
scopes.each do |scope|
if scope[:doc].get_attribute(:'data-channel') == name
return scope[:doc]
end
if doc = find_channel(scope[:nested], name)
return doc
end
end
nil
end
def find_components(structure, primary_structure = @structure, components = [])
ret_components = structure.inject(components) { |s, e|
if e[1].has_key?(:'data-ui')
s << {
doc: StringDoc.from_structure(primary_structure, node: e),
component: e[1][:'data-ui'].to_sym,
}
end
find_components(e[2], e[2], s)
s
} || []
ret_components
end
end
end
end