#!/usr/bin/env python

"""

Native XML database, entirely in Python.

It is *not* intended for production use, just to illustrate the
concept of XML databases. It has no indexation at all.

Stephane Bortzmeyer <bortzmeyer@nic.fr>

"""

import sys
import os
import re
from cStringIO import StringIO
from xml.dom.ext.reader import Sax2
from xml import xpath
import xml.dom.ext

def error(msg):
    sys.stderr.write(str(msg) + "\n")
    
def usage():
    error("""Usage: %s command database [arg ...]
  "command" is init, add, list, delete or query.
  "arg" is filename for "add", absent for "list", ID for "delete" and Xpath expression for "query".""" % sys.argv[0])
    sys.exit(1)

def getID(database):
    file = open("%s/last-ID" % database, "r")
    lastID = int(file.read())
    file.close()
    return lastID

def nextID(database):
    file = open("%s/last-ID" % database, "r")
    lastID = int(file.read())
    file.close()
    file = open("%s/last-ID" % database, "w")
    file.write ("%i\n" % (lastID + 1))
    file.close()
    return lastID

def file2DOM(filename):
    reader = Sax2.Reader()
    doc = reader.fromStream(filename)
    return doc

def init(database):
    os.mkdir(database)
    file = open("%s/last-ID" % database, "w")
    file.write("1\n")
    file.close()

def add(database, filename):
    doc = file2DOM(filename)
    id = nextID(database)
    outfile = open("%s/%i.xml" % (database, id), "w")
    xml.dom.ext.Print(doc, outfile)
    outfile.close()
    return id

def list(database):
    for file in os.listdir(database):
        datafile = re.search("^([0-9]+)\.xml$", file)
        if not datafile:
            continue
        id = datafile.group(1)
        root = file2DOM("%s/%s" % (database, file)).documentElement
        print "%s: <%s>...</%s>" % (id, root.nodeName, root.nodeName)
        
def delete(database, id):
    os.remove("%s/%i.xml" % (database, int(id)))

def query(database, expr):
    nodes = []
    for file in os.listdir(database):
        datafile = re.search("^([0-9]+)\.xml$", file)
        if not datafile:
            continue
        id = datafile.group(1)
        doc = file2DOM("%s/%s" % (database, file))
        matches = xpath.Evaluate(expr, doc.documentElement)
        if matches:
            nodes.append((id, matches))
    return nodes

if len(sys.argv) < 3:
    usage()

command = sys.argv[1]
database = sys.argv[2]
objects = sys.argv[3:]

if command == "init":
    init(database)
elif command == "add":
    if len(objects) == 0:
        error("At least one file required")
        usage()
    for file in objects:
        id = add(database, file)
    print "Added %s as ID %s" % (file, id)
elif command == "list":
    if len(objects) != 0:
        error("No argument required")
        usage()
    list(database)
elif command == "delete":
    if len(objects) == 0:
        error("At least one ID required")
        usage()
    for id in objects:
        delete(database, id)
elif command == "query":
    if len(objects) != 1:
        error("Only one argument expected (a Xpath expression)")
        usage()
    query_expr = objects[0]
    results = query(database, query_expr)
    for (id, nodes) in results:
        for node in nodes:
            node_text = StringIO()
            xml.dom.ext.Print(node, node_text)
            print "%s: %s" % (id, node_text.getvalue())

else:
    error("Unknown command %s" % command)
    usage()
    
## Local Variables: ##
## mode:python ##
## End: ##

