# Copyright (c) 2003, 2005 by Intevation GmbH
# Authors:
# Jonathan Coles <jonathan@intevation.de>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with Thuban for details.

"""
Functions to save an XML file
"""

__version__ = "$Revision: 1.3 $"

import os
from types import UnicodeType

from Thuban import unicode_from_internal

#
# one level of indention
#
TAB = "    "

def escape(data):
    """Escape &, \", ', <, and > in a string of data.
    """
    data = data.replace("&", "&amp;")
    data = data.replace("<", "&lt;")
    data = data.replace(">", "&gt;")
    data = data.replace('"', "&quot;")
    data = data.replace("'", "&apos;")
    return data

class XMLWriter:
    """Abstract XMLWriter.

    Should be overridden to provide specific object saving functionality.
    """

    def __init__(self):
        self.filename = None
        pass

    def write(self, file_or_filename):
        """Write the session to a file.

        The argument may be either a file object or a filename. If it's
        a filename, the file will be opened for writing. Files of
        shapefiles will be stored as filenames relative to the directory
        the file is stored in (as given by os.path.dirname(filename)) if
        they have a common parent directory other than the root
        directory.

        If the argument is a file object (which is determined by the
        presence of a write method) all filenames will be absolute
        filenames.
        """

        # keep track of how many levels of indentation to write
        self.indent_level = 0 
        # track whether an element is currently open. see open_element().
        self.element_open = 0

        if hasattr(file_or_filename, "write"):
            # it's a file object
            self.file = file_or_filename
            self.dir = ""
        else:
            self.filename = file_or_filename
            self.dir = os.path.dirname(self.filename)
            self.file = open(self.filename, 'w')

    def close(self):
        assert self.indent_level == 0
        if self.filename is not None:
            self.file.close()

    def write_header(self, doctype, system):
        """Write the XML header"""
        self.file.write('<?xml version="1.0" encoding="UTF-8"?>\n')
        self.file.write('<!DOCTYPE %s SYSTEM "%s">\n' % (doctype, system))

    def open_element(self, element, attrs = {}):

        #
        # we note when an element is opened so that if two open_element()
        # calls are made successively we can end the currently open
        # tag and will later write a proper close tag. otherwise,
        # if a close_element() call is made directly after an open_element()
        # call we will close the tag with a />
        #
        if self.element_open == 1:
            self.file.write(">\n")

        self.element_open = 1

        # Helper function to write an element open tag with attributes
        self.file.write("%s<%s" % (TAB*self.indent_level, element))
        self.__write_attribs(attrs)

        self.indent_level += 1

    def close_element(self, element):
        self.indent_level -= 1
        assert self.indent_level >= 0

        # see open_element() for an explanation
        if self.element_open == 1:
            self.element_open = 0
            self.file.write("/>\n")
        else:
            self.file.write("%s</%s>\n" % (TAB*self.indent_level, element))

    def write_element(self, element, attrs = {}):
        """write an element that won't need a closing tag"""
        self.open_element(element, attrs)
        self.close_element(element)

    def __write_attribs(self, attrs):
        for name, value in attrs.items():
            self.file.write(' %s="%s"' % (self.encode(name), 
                                          self.encode(value)))

    def encode(self, s):
        """Return an XML-escaped and UTF-8 encoded copy of the string s.

        The parameter must be a string in Thuban's internal string
        representation or a unicode object.
        """
        if not isinstance(s, unicode):
            s = unicode_from_internal(s)
        return escape(s).encode("utf8")
