Skip to content

Instantly share code, notes, and snippets.

@vmi
Created January 6, 2012 10:08
Show Gist options
  • Save vmi/1569958 to your computer and use it in GitHub Desktop.
Save vmi/1569958 to your computer and use it in GitHub Desktop.
xpash - Interactive XPath Shell
#!/usr/bin/ruby
# -*- coding: utf-8 -*-
#
# xpash - Interactive XPath Shell
require 'readline'
require 'rexml/document'
include REXML
prompt = '> '
def help
print <<-EOF
Usage: xpash [XMLFILE]
Commands:
- load FILE
load XML file.
- parse STRING
parse STRING as XML.
- QUERY, $VAR/QUERY
query xpath and show result.
- VAR = QUERY
and assign to VAR.
- ?, h, help
show help.
- ^D, q, quit, exit
quit shell.
EOF
end
def dump_text(e)
str = ''
e.each_child do |c|
case c.node_type
when :text
str += c.value
when :element, :document
str += dump_text(c)
end
end
str
end
class XPashRunner
def initialize(file = nil)
@doc = nil
@vars = {}
load(file) if file
end
def load(file)
$stderr.print "Loading [#{file}]..."
begin
@doc = Document.new(File.new file)
$stderr.puts "done."
rescue => e
$stderr.puts e, "Failed."
end
end
def parse(str)
begin
@doc = Document.new(str)
$stderr.puts "Parsed: #{@doc}"
rescue => e
$stderr.puts e, "Failed."
end
end
def run(line)
if /\A([A-Za-z_]\w*)\s*=\s*(.*)\z/ =~ line
var = $1
expr = $2
else
var = nil
expr = line
end
case expr
when 'q', 'quit', 'exit'
puts "Quit."
exit
when '?', 'h', 'help'
help
when /load\s+(.+)/
load($1)
when /parse\s+(.+)/
parse($1)
else
if @doc.nil?
$stderr.puts "Error: Any XML files are not loaded."
break
end
result = query(expr)
if var
@vars[var] = result
$stderr.puts "Assigned to '#{var}'"
end
end
end
def query(expr)
if /\A\$([A-Za-z_]\w*)(?:\[(\d+)\])?(?:\/(.*))?\z/ =~ expr
var = $1
index = $2
expr = $3
if [email protected]_key?(var)
$stderr.puts "Error: Missing variable '#{var}'"
return
end
d = @vars[var]
if index
if d.instance_of? Array
d = d[index.to_i - 1]
else
$stderr.puts "Error: '#{var}' is not array."
return
end
$stderr.puts "Element from '#{var}[#{index}]'"
else
if !d.respond_to?(:elements)
$stderr.puts "Error: '#{var}' is not element. (#{d.class})"
return
end
$stderr.puts "Element from '#{var}'"
end
else
d = @doc
end
text = false
attr = nil
case expr
when /\A(?:(.*)\/)?text\(\)\z/
expr = $1 || '.'
text = true
$stderr.puts "Query: #{expr}/text()"
when /\A(?:(.*)\/)?@(\w+)\z/
expr = $1 || '.'
attr = $2
$stderr.puts "Query: #{expr}/@#{attr}"
else
expr ||= '*'
$stderr.puts "Query: #{expr}"
end
i = 0
result = []
begin
d.elements.each(expr) do |e|
case
when text
next unless e.has_text?
value = dump_text(e)
when attr
next unless e.attributes.has_key?(attr)
value = e.attributes[attr]
else
value = e
end
i += 1
$stderr.print "#{i}: "
puts value
result.push(value)
end
rescue => e
$stderr.puts "Error: #{e}"
return nil
end
case result.length
when 0
$stderr.puts "Empty."
nil
when 1
result[0]
else
result
end
end
end
$stdout.sync = true
runner = XPashRunner.new(*ARGV)
while line = Readline.readline(prompt, true)
line.gsub!(/\A\s+|\s+\z/, '') # trim blank
runner.run(line) unless line.empty?
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment