# encoding: utf-8
module Watir
module HTML
class Visitor < WebIDL::RubySexpVisitor
def initialize
super
# When an interface has multiple IDL definitions in the spec, the inheritance is sometimes
# not repeated. So we'll keep track ourselves.
@inheritance_map = {}
@already_defined = []
end
#
# WebIDL visitor interface
#
def visit_interface(interface)
name = interface.name
parent = interface.inherits.first
$stderr.puts name
return unless name =~ /^HTML/ && name !~ /(Collection|Document)$/
if name == "HTMLElement"
parent = 'Element'
elsif parent
@inheritance_map[name] ||= parent.name
parent = parent.name
else
parent = @inheritance_map[name] || return
end
[ :scope,
[:block,
element_class(interface.name, interface.members.select { |e| e.kind_of?(WebIDL::Ast::Attribute) }, parent),
collection_class(interface.name)
]
]
end
def visit_module(mod)
# ignored
end
def visit_implements_statement(stmt)
# ignored
end
# TODO: do everything in the visitor somehow?
# problem is we lack the tag name info while walking the interface AST
# #
# # Watir generator visitor interface
# #
#
# def visit_tag(tag_name, interface_name)
# tag_string = tag.inspect
# singular = Util.paramify(tag)
# plural = singular.pluralize
# element_class = Util.classify(interfaces.first.name)
# collection_class = "#{element_class}Collection"
#
# [:defn,
# :a,
# [:args, :"*args"],
# [:scope,
# [:block,
# [:call,
# [:const, :Anchor],
# :new,
# [:arglist,
# [:self],
# [:call,
# [:call, nil, :extract_selector, [:arglist, [:lvar, :args]]],
# :merge,
# [:arglist, [:hash, [:lit, :tag_name], [:str, "a"]]]]]]]]]
# end
private
def element_class(name, attributes, parent)
[:class, Util.classify(name), [:const, Util.classify(parent)],
[:scope,
[:block, attributes_call(attributes)]
]
]
end
def collection_class(name)
return if @already_defined.include?(name)
@already_defined << name
name = Util.classify(name)
[:class, "#{name}Collection", [:const, :ElementCollection],
[:scope,
[:defn, :element_class,
[:args],
[:scope,
[:block, [:const, name]]
]
]
]
]
end
def attributes_call(attributes)
return if attributes.empty?
attrs = Hash.new { |hash, key| hash[key] = [] }
attributes.sort_by { |a| a.name }.each do |a|
type = ruby_type_for(a.type)
attrs[type] << ruby_attribute_for(type, a.name)
end
call :attributes, [literal_hash(attrs)]
end
def ruby_attribute_for(type, str)
str = str.snake_case
if str =~ /^is_(.+)/ && type == :bool
str = $1
end
str.to_sym
end
def literal_hash(hash)
[:hash] + hash.map { |k, v| [[:lit, k.to_sym], [:lit, v]] }.flatten(1)
end
def literal_array(arr)
[:array] + arr.map { |e| [:lit, e.to_sym] }
end
def call(name, args)
[:call, nil, name.to_sym, [:arglist] + args]
end
def ruby_type_for(type)
case type.name.to_s
when 'DOMString', 'any'
:string
when 'UnsignedLong', 'Long', 'Integer', 'Short', 'UnsignedShort'
:int
when 'Float', /.*Double$/
:float
when 'Function', /.*EventHandler$/
:function
when 'Boolean'
:bool
when 'Document'
:document
when 'DOMTokenList', 'DOMSettableTokenList'
:token_list
when 'DOMStringMap'
:string_map
when 'HTMLPropertiesCollection'
:properties_collection
when /HTML(.*)Element/
:html_element
when /HTML(.*)Collection/
:html_collection
when 'CSSStyleDeclaration'
:style
when /.+List$/
:list
when 'Date'
:date
when 'Element'
:element
when 'WindowProxy', 'ValidityState', 'MediaError', 'TimeRanges', 'Location',
'Any', 'TimedTrackArray', 'TimedTrack', 'TextTrackArray', 'TextTrack', 'MediaController'
# probably completely wrong.
:string
else
raise "unknown type: #{type.name}"
end
end
end # Visitor
end # Support
end # Watir