# # xpathtree.rb # # Copyright (C) Ueno Katsuhiro 2000 # # $Id: xpathtree.rb,v 1.6 2000/12/28 03:43:46 katsu Exp $ # require 'xmlscan' require 'xpath' module XPath # obediently implementation of XPath data model module DataModel class Node < NullNodeAdapter def initialize @parent = nil # @index = 0 end attr_accessor :parent protected :parent= # attr_accessor :index # protected :index= def append_child raise "can't take any children" end def node self end def root (p = @parent) and p.root end def lang (p = @parent) and p.lang end def each_following_siblings if @parent then nodes = @parent.children n = nodes.index(self) raise "BUG" unless n (n + 1).upto(nodes.size - 1) { |i| yield nodes[i] } end end def each_preceding_siblings if @parent then nodes = @parent.children n = nodes.index(self) raise "BUG" unless n (n - 1).downto(0) { |i| yield nodes[i] } end end def index unless @parent then 0 else n = @parent.children.index(self) raise "BUG" unless n n end end def namespace_decls nil end def traverse(level = 0, &block) block.call level, self children.each { |node| node.traverse(level + 1, &block) } end def abs_index indices = [] node = self while node indices.push node.index node = node.parent end indices.reverse! indices.join(':') end def inspect uri = namespace_uri qname = qualified_name strval = string_value dst = "<#{node_type.id2name.capitalize} #{abs_index}" dst << " qname=#{qname.dump}" if qname dst << " ns=#{uri.dump}" if uri dst << " str=#{strval.inspect}" if strval dst << ">" end end class ParentNode < Node def initialize @children = [] super end attr_reader :children def append_child(node) # node.index = @children.size @children.push node node.parent = self end def string_value @children.collect{ |i| i.string_value }.join end end class RootNode < ParentNode private :parent= def root self end def node_type :root end end module NamedNode def initialize(namespace_uri, prefix, localpart) @namespace_uri = namespace_uri @name_prefix, @name_localpart = prefix, localpart super() end attr_reader :namespace_uri, :name_localpart def qualified_name if @name_prefix then @name_prefix + ':' + @name_localpart else @name_localpart end end end class ElementNode < ParentNode include NamedNode def initialize(namespace_uri, prefix, localpart, attrs, namespaces) @namespaces = namespaces @attrnodes = make_attr_nodes(attrs) @namespacenodes = make_namespace_nodes(namespaces) super namespace_uri, prefix, localpart end private def make_namespace_nodes(namespaces) dst = [] namespaces.each { |k,v| node = NamespaceNode.new(k, v) node.parent = self dst.push node } dst end def make_attr_nodes(attrs) attrs.collect { |uri,prefix,name,val| node = AttributeNode.new(uri, prefix, name, val) node.parent = self node } end public def node_type :element end def lang lang = nil @attrnodes.each { |node| if node.name_localpart == 'lang' and node.namespace_uri == 'http://www.w3.org/XML/1998/namespace' then lang = node.string_value break end } lang or super end def attributes @attrnodes end def namespaces @namespacenodes end def namespace_decls @namespaces end def traverse(level = 0, &block) block.call level, self namespaces.each { |node| node.traverse(level + 1, &block) } attributes.each { |node| node.traverse(level + 1, &block) } children.each { |node| node.traverse(level + 1, &block) } end end class NamespaceNode < Node def initialize(prefix, uri) @name_localpart, @string_value = prefix, uri super() end attr_reader :name_localpart, :string_value def node_type :namespace end def index a = @parent.namespaces a.index(self) - a.size - @parent.attributes.size end end class AttributeNode < Node include NamedNode def initialize(namespace_uri, prefix, localpart, value) @string_value = value super namespace_uri, prefix, localpart end attr_reader :string_value def node_type :attribute end def index a = @parent.attributes a.index(self) - a.size end def namespace_decls @parent.namespace_decls end end class TextNode < Node def initialize(text) @string_value = text super() end attr_reader :string_value def node_type :text end end class CommentNode < TextNode def node_type :comment end end class PINode < TextNode def initialize(target, pi) @name_localpart = target super pi end attr_reader :name_localpart def node_type :processing_instruction end end class Builder < XMLScannerWithNamespace private def on_text(str) node = TextNode.new(str) @nodestack[-1].append_child node end def on_comment(strs) node = CommentNode.new(strs.join) @nodestack[-1].append_child node end def on_pi(target, pi) super node = PINode.new(target, pi) @nodestack[-1].append_child node end def expand_attr_namespace(attr) dst = [] attr.each { |key,val| namespace, prefix, name = expand_qualified_name(key) dst.push [ namespace, prefix, name, val ] } dst end def on_start_element(name, attr) namespace, prefix, name = name namespaces = @elemstack.namespace.dup namespaces.delete :default node = ElementNode.new(namespace, prefix, name, attr, namespaces) @nodestack[-1].append_child node @nodestack.push node end def on_end_element(name) @nodestack.pop end public undef step def parse(*args) @root = RootNode.new @nodestack = [ @root ] super @root end end end end if $0 == __FILE__ then require 'readline' STDOUT.sync = STDERR.sync = true module XPath @compiler = Compiler.new(true) if $DEBUG end raise "requires 1 argument" unless ARGV[0] STDERR.print "parsing #{ARGV[0]} ... " root = XPath::DataModel::Builder.new.parse(File.open(ARGV[0])) STDERR.print "done.\n" context = XPath::Context.new(root) if $DEBUG then root.traverse { |level,node| print ' ' * level, node.inspect, "\n" } end while src = Readline.readline("xpath:#{context.node.abs_index}> ", true) cd = (/\Acd\s+/ =~ src) src = $' if cd begin t1 = Time.times.utime proc = XPath.compile(src) t2 = Time.times.utime result = proc.call(context) t3 = Time.times.utime if result.is_a? Array and not result.empty? then result.each { |i| print i.inspect, "\n" } if cd then node = result[0] context = XPath::Context.new(node, node.namespace_decls) end else p result end printf "compile: %.2f sec eval: %.2f sec\n", t2 - t1, t3 - t2 rescue XPath::Error at = $@ printf "%s: %s (%s)\n", at.shift, $!, $!.type at.each { |i| print "\tfrom ", i, "\n" } end end end