class odf_document(object):
    """Abstraction of the ODF document.
    """
    def __init__(self, container):
        if not isinstance(container, odf_container):
            raise TypeError, "container is not an ODF container"
        self.container = container

        # Cache of XML parts
        self.__xmlparts = {}

        # Cache of the body
        self.__body = None


    #
    # Public API
    #

    def get_xmlpart(self, part_name):
        if part_name not in ODF_PARTS:
            raise ValueError, '"%s" is not an XML part' % part_name
        parts = self.__xmlparts
        part = parts.get(part_name)
        if part is None:
            container = self.container
            if part_name == 'content':
                part = odf_content(part_name, container)
            elif part_name == 'meta':
                part = odf_meta(part_name, container)
            elif part_name == 'styles':
                part = odf_styles(part_name, container)
            else:
                part = odf_xmlpart(part_name, container)
            parts[part_name] = part
        return part


    def get_content(self):
        """Return the content part.

        Return: odf_content
        """
        return self.get_xmlpart('content')


    def get_meta(self):
        """Return the meta part.

        Return: odf_meta
        """
        return self.get_xmlpart('meta')


    def get_styles(self):
        """Return the styles part.

        Return: odf_styles
        """
        return self.get_xmlpart('styles')


    def get_type(self):
        """
        Get the ODF type (also called class) of this document.

        Return: 'chart', 'database', 'formula', 'graphics',
            'graphics-template', 'image', 'presentation',
            'presentation-template', 'spreadsheet', 'spreadsheet-template',
            'text', 'text-master', 'text-template' or 'text-web'
        """

        mimetype = self.container.get_part('mimetype').strip()

        # The mimetype must be with the form:
        # application/vnd.oasis.opendocument.text

        # Isolate and return the last part
        return mimetype.split('.')[-1]


    def get_body(self):
        if self.__body is None:
            content = self.get_content()
            self.__body = content.get_body()
        return self.__body


    def get_files(self):
        return self.container.get_contents()


    def get_file_data(self, filename):
        return self.container.get_part(filename)


    def get_formatted_text(self, rst_mode=False):
        # For the moment, only "type='text'"
        type = self.get_type()
        if type not in ('text', 'text-template', 'presentation',
                'presentation-template'):
            raise NotImplementedError, ('Type of document "%s" not '
                                        'supported yet' % type)
        # Initialize an empty context
        context = {'document': self,
                   'footnotes': [],
                   'endnotes': [],
                   'annotations': [],
                   'rst_mode': rst_mode}
        body = self.get_body()
        # Get the text
        result = []
        for element in body.get_children():
            if element.get_tagname() == 'table:table':
                result.append(element.get_formatted_text(context))
            else:
                result.append(element.get_formatted_text(context))
                # Insert the notes
                footnotes = context['footnotes']
                # Separate text from notes
                if footnotes:
                    if rst_mode:
                        result.append(u'\n')
                    else:
                        result.append(u'----\n')
                    for citation, body in footnotes:
                        if rst_mode:
                            result.append(u'.. [#] %s\n' % body)
                        else:
                            result.append(u'[%s] %s\n' % (citation, body))
                    # Append a \n after the notes
                    result.append(u'\n')
                    # Reset for the next paragraph
                    context['footnotes'] = []
                # Insert the annotations
                annotations = context['annotations']
                # With a separation
                if annotations:
                    if rst_mode:
                        result.append(u'\n')
                    else:
                        result.append(u'----\n')
                    for annotation in annotations:
                        if rst_mode:
                            result.append('.. [#] %s\n' % annotation)
                        else:
                            result.append('[*] %s\n' % annotation)
                    context['annotations'] = []
        # Append the end notes
        endnotes = context['endnotes']
        if endnotes:
            if rst_mode:
                result.append(u'\n\n')
            else:
                result.append(u'\n========\n')
            for citation, body in endnotes:
                if rst_mode:
                    result.append(u'.. [*] %s\n' % body)
                else:
                    result.append(u'(%s) %s\n' % (citation, body))
        return u''.join(result)


    def get_formated_meta(self):
        result = []

        meta = self.get_meta()

        # Simple values
        def print_info(name, value):
            if value:
                result.append("%s: %s" % (name, value))

        print_info("Title", meta.get_title())
        print_info("Subject", meta.get_subject())
        print_info("Language", meta.get_language())
        print_info("Modification date", meta.get_modification_date())
        print_info("Creation date", meta.get_creation_date())
        print_info("Initial creator", meta.get_initial_creator())
        print_info("Keyword", meta.get_keyword())
        print_info("Editing duration", meta.get_editing_duration())
        print_info("Editing cycles", meta.get_editing_cycles())
        print_info("Generator", meta.get_generator())

        # Statistic
        result.append("Statistic:")
        statistic =  meta.get_statistic()
        for name, value in statistic.iteritems():
            result.append("  - %s: %s" % (
                              name[5:].replace('-', ' ').capitalize(),
                              value))

        # User defined metadata
        result.append("User defined metadata:")
        user_metadata = meta.get_user_defined_metadata()
        for name, value in user_metadata.iteritems():
            result.append("  - %s: %s" % (name, value))

        # And the description
        print_info("Description", meta.get_description())

        return u"\n".join(result) + '\n'


    def add_file(self, uri_or_file):
        if type(uri_or_file) is unicode or type(uri_or_file) is str:
            uri_or_file = uri_or_file.encode('utf_8')
            file = vfs.open(uri_or_file)
        else:
            file = uri_or_file
        name = 'Pictures/%s' % uuid4()
        data= file.read()
        self.container.set_part(name, data)
        return name


    def clone(self):
        """Return an exact copy of the document.

        Return: odf_document
        """
        clone = object.__new__(self.__class__)
        for name in self.__dict__:
            if name == 'container':
                setattr(clone, name, self.container.clone())
            elif name == '_odf_document__xmlparts':
                xmlparts = {}
                for key, value in self.__xmlparts.iteritems():
                    xmlparts[key] = value.clone()
                setattr(clone, name, xmlparts)
            else:
                value = getattr(self, name)
                value = deepcopy(value)
                setattr(clone, name, value)
        return clone


    def save(self, target=None, packaging=None, pretty=False):
        """Save the document, at the same place it was opened or at the given
        URI (target). Target can also be a file like object. It can be saved as
        a Zip file or as a plain XML file. The XML can be pretty printed.

        Arguments:

            target -- str -or- file like object

            packaging -- 'zip' or 'flat'

            pretty -- bool
        """
        # Synchronize data with container
        for part_name, part in self.__xmlparts.items():
            if part is not None:
                self.container.set_part(part_name, part.serialize(pretty))

        # Save the container
        self.container.save(target, packaging)


    #
    # Styles over several parts
    #

    def get_style_list(self, family=None, automatic=False):
        content = self.get_content()
        styles = self.get_styles()
        return (content.get_style_list(family=family)
                + styles.get_style_list(family=family, automatic=automatic))


    def get_style(self, family, name_or_element=None, display_name=None):
        """Return the style uniquely identified by the name/family pair. If
        the argument is already a style object, it will return it.

        If the name is None, the default style is fetched.

        If the name is not the internal name but the name you gave in a
        desktop application, use display_name instead.

        Arguments:

            family -- 'paragraph', 'text',  'graphic', 'table', 'list',
                      'number', 'page-layout', 'master-page'

            name -- unicode or odf_element or None

            display_name -- unicode

        Returns: odf_style or None if not found.
        """
        # 1. content.xml
        content = self.get_content()
        element = content.get_style(family, name_or_element=name_or_element,
                display_name=display_name)
        if element is not None:
            return element
        # 2. styles.xml
        styles = self.get_styles()
        return styles.get_style(family, name_or_element=name_or_element,
                display_name=display_name)


    def insert_style(self, style, name=None, automatic=False, default=False):
        """Insert the given style object in the document, as required by the
        style family and type.

        The style is expected to be a common style with a name. In case it
        was created with no name, the given can be set on the fly.

        If automatic is True, the style will be inserted as an automatic
        style.

        If default is True, the style will be inserted as a default style and
        would replace any existing default style of the same family. Any name
        or display name would be ignored.

        Automatic and default arguments are mutually exclusive.

        All styles can’t be used as default styles. Default styles are
        allowed for the following families: paragraph, text, section, table,
        table-column, table-row, table-cell, table-page, chart, drawing-page,
        graphic, presentation, control and ruby.

        Arguments:

            style -- odf_style

            name -- unicode

            automatic -- bool

            default -- bool
        """

        # Get family and name
        family = style.get_style_family()
        if name is None:
            name = style.get_style_name()

        # Master page style
        if isinstance(style, odf_master_page):
            part = self.get_styles()
            container = part.get_element("office:master-styles")
            existing = part.get_style(family, name)
        # Font face declarations
        elif isinstance(style, odf_font_style):
            # XXX If inserted in styles.xml => It doesn't work, it's normal?
            part = self.get_content()
            container = part.get_element("office:font-face-decls")
            existing = part.get_style(family, name)
        # Common style
        elif isinstance(style, odf_style):
            # Common style
            if name and automatic is False and default is False:
                part = self.get_styles()
                container = part.get_element("office:styles")
                existing = part.get_style(family, name)

            # Automatic style
            elif automatic is True and default is False:
                part = self.get_content()
                container = part.get_element("office:automatic-styles")

                # A name ?
                if name is None:
                    # Make a beautiful name

                    # TODO: Use prefixes of Ooo: Mpm1, ...
                    prefix = 'lpod_auto_'

                    styles = self.get_style_list(family=family, automatic=True)
                    names = [ s.get_style_name () for s in styles ]
                    numbers = [ int(name[len(prefix):]) for name in names
                                if name and name.startswith(prefix) ]
                    if numbers:
                        number = max(numbers) + 1
                    else:
                        number = 1
                    name = prefix + str(number)

                    # And set it
                    style.set_style_name(name)
                    existing = None
                else:
                    existing = part.get_style(family, name)

            # Default style
            elif automatic is False and default is True:
                part = self.get_styles()
                container = part.get_element("office:styles")

                # Force default style
                style.set_tagname("style:default-style")
                if name is not None:
                    style.del_attribute("style:name")

                existing = part.get_style(family)

            # Error
            else:
              raise AttributeError, "invalid combination of arguments"
        # Invalid style
        else:
            raise ValueError, "invalid style"

        # Insert it!
        if existing is not None:
            container.delete_element(existing)
        container.append_element(style)


    def get_styled_elements(self, name=True):
        """Brute-force to find paragraphs, tables, etc. using the given style
        name (or all by default).

        Arguments:

            name -- unicode

        Return: list
        """
        content = self.get_content()
        # Header, footer, etc. have styles too
        styles = self.get_styles()
        return (content.get_root().get_styled_elements(name)
                + styles.get_root().get_styled_elements(name))


    def show_styles(self, automatic=True, common=True, properties=False):
        infos = []
        for style in self.get_style_list():
            name = style.get_style_name()
            is_auto = (style.get_parent().get_tagname()
                    == 'office:automatic-styles')
            if (is_auto and automatic is False
                    or not is_auto and common is False):
                continue
            is_used = bool(self.get_styled_elements(name))
            infos.append({'type': u"auto  " if is_auto else u"common",
                          'used': u"y" if is_used else u"n",
                          'family': style.get_style_family(),
                          'parent': style.get_parent_style_name() or u"",
                          'name': name or u"",
                          'display_name': style.get_style_display_name(),
                          'properties': style.get_style_properties() if
                                        properties else None})
        if not infos:
            return u""
        # Sort by family and name
        infos.sort(key=itemgetter('family', 'name'))
        # Show common and used first
        infos.sort(key=itemgetter('type', 'used'), reverse=True)
        max_family = unicode(max([len(x['family']) for x in infos]))
        max_parent = unicode(max([len(x['parent']) for x in infos]))
        format = (u"%(type)s used:%(used)s family:%(family)-0" + max_family
                + u"s parent:%(parent)-0" + max_parent + u"s name:%(name)s")
        output = []
        for info in infos:
            line = format % info
            if info['display_name']:
                line += u' display_name:' + info['display_name']
            output.append(line)
            if info['properties']:
                for name, value in info['properties'].iteritems():
                    output.append("   - %s: %s" % (name, value))
        output.append(u"")
        return u"\n".join(output)


    def delete_styles(self):
        """Remove all style information from content and all styles.

        Return: number of deleted styles
        """
        # First remove references to styles
        for element in self.get_styled_elements():
            for attribute in ('text:style-name', 'draw:style-name',
                    'draw:text-style-name', 'table:style-name',
                    'style:page-layout-name'):
                try:
                    element.del_attribute(attribute)
                except KeyError:
                    continue
        # Then remove supposedly orphaned styles
        i = 0
        for style in self.get_style_list():
            if style.get_style_name() is None:
                # Don't delete default styles
                continue
            elif type(style) is odf_master_page:
                # Don't suppress header and footer, just styling was removed
                continue
            style.get_parent().delete_element(style)
            i += 1
        return i


    def merge_styles_from(self, document):
        """Copy all the styles of a document into ourself.

        Styles with the same type and name will be replaced, so only unique
        styles will be preserved.
        """
        styles = self.get_styles()
        content = self.get_content()
        for style in document.get_style_list():
            tagname = style.get_tagname()
            family = style.get_style_family()
            if family is None:
                family = _get_style_family(tagname)
            stylename = style.get_style_name()
            container = style.get_parent()
            container_name = container.get_tagname()
            partname = container.get_parent().get_tagname()
            # The destination part
            if partname == "office:document-styles":
                part = styles
            elif partname == "office:document-content":
                part = content
            else:
                raise NotImplementedError, partname
            # Implemented containers
            if container_name not in ('office:styles',
                                      'office:automatic-styles',
                                      'office:master-styles',
                                      'office:font-face-decls'):
                raise NotImplementedError, container_name
            dest = part.get_element('//%s' % container_name)
            # Implemented style types
            if tagname not in ('style:default-style', 'style:style',
                               'style:style', 'style:page-layout',
                               'style:master-page', 'style:font-face',
                               'text:list-style', 'number:number-style',
                               'text:outline-style', 'number:date-style'):
                raise NotImplementedError, tagname
            duplicate = part.get_style(family, stylename)
            if duplicate is not None:
                duplicate.get_parent().delete_element(duplicate)
            dest.append_element(style) def odf_get_document(uri):
    """Return an "odf_document" instance of the ODF document stored in the
    given file-like object or at the given URI.

    Example::

        >>> document = odf_get_document(stringio)
        >>> uri = 'uri://of/a/document.odt'
        >>> document = odf_get_document(uri)
    """
    container = odf_get_container(uri)
    return odf_document(container) class odf_container(object):
    """Representation of the ODF file.
    """
    def __init__(self, uri_or_file):
        # Internal state
        self.__zipfile = None
        self.__parts = {}
        # URI
        if type(uri_or_file) is str:
            self.uri = uri_or_file
            if not vfs.exists(uri_or_file):
                raise ValueError, 'URI "%s" is not found' % uri_or_file
            if vfs.is_folder(uri_or_file):
                raise NotImplementedError, ("reading uncompressed ODF "
                                            "is not supported")
            mimetype = vfs.get_mimetype(uri_or_file)
            if not mimetype in ODF_MIMETYPES:
                raise ValueError, 'mimetype "%s" is unknown' % mimetype
            self.mimetype = mimetype
            self.__data = None
        # File-like assumed
        else:
            self.uri = None
            self.__data = uri_or_file.read()
            # FIXME Zip format assumed
            self.mimetype = self.__get_part_zip('mimetype')


    #
    # Private API (internal helpers)
    #

    def __get_data(self):
        """Store bytes of the ODF in memory.
        """
        if self.__data is None:
            file = vfs.open(self.uri)
            self.__data = file.read()
            file.close()
        return self.__data


    def __get_zipfile(self):
        """Open a Zip object on the archive ODF.
        """
        data = self.__get_data()
        # StringIO will not duplicate the string, how big it is
        filelike = StringIO(data)
        if self.__zipfile is None:
            self.__zipfile = ZipFile(filelike)
        return self.__zipfile


    def __get_part_xml(self, part_name):
        """Get bytes of a part from the XML-only ODF.
        """
        if part_name not in ODF_PARTS:
            raise ValueError, ("Third-party parts are not supported "
                               "in an XML-only ODF document")
        if part_name == 'mimetype':
            part = self.mimetype
        else:
            data = self.__get_data()
            start_tag = '<office:document-%s>' % part_name
            start = data.index(start_tag)
            end_tag = '</office:document-%s>' % part_name
            end = data.index(end_tag)
            part = data[start:end + len(end_tag)]
        return part


    def __get_part_zip(self, part_name):
        """Get bytes of a part from the archive ODF.
        """
        zipfile = self.__get_zipfile()
        if part_name in ODF_PARTS and part_name != 'mimetype':
            file = zipfile.open('%s.xml' % part_name)
            part = file.read()
            file.close()
        else:
            file = zipfile.open(part_name)
            part = file.read()
            file.close()
        return part


    def __get_xml_contents(self):
        """Get the list of members in the XML-only ODF.
        """
        raise NotImplementedError


    def __get_zip_contents(self):
        """Get the list of members in the archive ODF.
        """
        zipfile = self.__get_zipfile()
        result = []
        for part in zipfile.infolist():
            filename = part.filename
            if filename.endswith('.xml') and filename[:-4] in ODF_PARTS:
                result.append(filename[:-4])
            else:
                result.append(filename)
        return result


    def __make_xml(self):
        """Make an XML-only ODF from the available parts.
        """
        raise NotImplementedError


    def __make_zip(self):
        """Make an archive ODF from the available parts.
        """
        data = StringIO()
        filezip = ZipFile(data, 'w')

        for name, part_data in self.__parts.iteritems():
            if name in ODF_PARTS and name != 'mimetype':
                name += '.xml'
            filezip.writestr(name, part_data)

        filezip.close()
        return data.getvalue()


    #
    # Public API
    #

    def get_contents(self):
        """Get the list of members.
        """
        if self.mimetype == 'application/xml':
            return self.__get_xml_contents()
        else:
            return self.__get_zip_contents()


    def clone(self):
        """Make a copy of this container with no URI.
        """
        clone = object.__new__(self.__class__)
        # Load state
        self.__get_data()
        for name in self.__dict__:
            # "__zipfile" is not safe to copy
            # but can be recreated from "__data"
            if name in ('uri', '_odf_container__zipfile'):
                setattr(clone, name, None)
            else:
                value = getattr(self, name)
                value = deepcopy(value)
                setattr(clone, name, value)
        return clone


    def get_part(self, part_name):
        """Get the bytes of a part of the ODF.
        """
        loaded_parts = self.__parts
        if part_name in loaded_parts:
            part = loaded_parts[part_name]
            if part is None:
                raise ValueError, "part is deleted"
            return part
        if self.mimetype == 'application/xml':
            part = self.__get_part_xml(part_name)
        else:
            part = self.__get_part_zip(part_name)
        loaded_parts[part_name] = part
        return part


    def set_part(self, part_name, data):
        """Replace or add a new part.
        """
        self.__parts[part_name] = data


    def del_part(self, part_name):
        """Mark a part for deletion.
        """
        self.__parts[part_name] = None


    def save(self, target=None, packaging=None):
        """Save the container to the given URI (target) or into this file like
        object (if supported).
        """
        parts = self.__parts
        # Get all parts
        for part in self.get_contents():
            if part not in parts:
                self.get_part(part)
        # Packaging
        if packaging is None:
            packaging = ('flat' if self.mimetype == 'application/xml' else
                         'zip')
        # Get data
        if packaging == 'flat':
            data = self.__make_xml()
        elif packaging == 'zip':
            data = self.__make_zip()
        else:
            raise ValueError, '"%s" packaging type not supported' % packaging
        # Save it
        if target is None:
            container = vfs.open(self.uri, WRITE)
        elif isinstance(target, str):
            container = vfs.open(target, WRITE)
        else:
            container = target
        container.write(data) def odf_get_container(uri):
    """Return an "odf_container" instance of the ODF document stored at the
    given URI.
    """
    return odf_container(uri) class odf_paragraph(odf_element):
    """Specialised element for paragraphs.
    """
    def get_formatted_text(self, context):
        result = [_get_formatted_text(self, context, with_text=True)]
        result.append(u'\n')
        return u''.join(result)


    def insert_note(self, note_element=None, after=None,
                    note_class='footnote', note_id=None, citation=None,
                    body=None, *args, **kw):
        if note_element is None:
            note_element = odf_create_note(note_class=note_class,
                                           note_id=note_id,
                                           citation=citation, body=body)
        else:
            # XXX clone or modify the argument?
            if note_class:
                note_element.set_note_class(note_class)
            if note_id:
                note_element.set_note_id(note_id, *args, **kw)
            if citation:
                note_element.set_note_citation(citation)
            if body:
                note_element.set_note_body(body)
        note_element.check_validity()
        if type(after) is unicode:
            self._insert(note_element, after=after)
        elif isinstance(after, odf_element):
            after.insert_element(note_element, FIRST_CHILD)
        else:
            self.insert_element(note_element, FIRST_CHILD)


    def insert_annotation(self, annotation_element=None, after=None,
                          body=None, creator=None, date=None):
        if annotation_element is None:
            annotation_element = odf_create_annotation(body,
                                                       creator=creator,
                                                       date=date)
        else:
            # XXX clone or modify the argument?
            if body:
                annotation_element.set_annotation_body(body)
            if creator:
                annotation_element.set_dc_creator(creator)
            if date:
                annotation_element.set_dc_date(date)
        annotation_element.check_validity()
        if type(after) is unicode:
            self._insert(annotation_element, after=after)
        elif isinstance(after, odf_element):
            after.insert_element(annotation_element, FIRST_CHILD)
        else:
            self.insert_element(annotation_element, FIRST_CHILD)


    def insert_variable(self, variable_element,  after):
        self._insert(variable_element, after=after)


    def set_span(self, style, regex=None, offset=None, length=0):
        """Apply the given style to text content matching the regex OR the
        positional arguments offset and length.

        Arguments:

            style -- style element or name

            regex -- unicode regular expression

            offset -- int

            length -- int
        """
        # XXX FIX ME cyclic import
        from span import odf_create_span

        if isinstance(style, odf_style):
            style = style.get_style_name()
        if offset:
            # XXX quickly hacking the offset
            text = self.get_text()
            if length:
                regex = text[offset:offset + length]
            else:
                regex = text[offset:]
            regex = escape(regex)
        if regex:
            pattern = compile(unicode(regex))
            for text in self.xpath('descendant::text()'):
                # Static information about the text node
                container = text.get_parent()
                wrapper = container.get_parent()
                is_text = text.is_text()
                # Group positions are calculated and static, so apply in
                # reverse order to preserve positions
                for group in reversed(list(pattern.finditer(text))):
                    start, end = group.span()
                    # Do not use the text node as it changes at each loop
                    if is_text:
                        text = container.get_text()
                    else:
                        text = container.get_tail()
                    before = text[:start]
                    match = text[start:end]
                    after = text[end:]
                    span = odf_create_span(match, style=style)
                    span.set_tail(after)
                    if is_text:
                        container.set_text(before)
                        # Insert as first child
                        container.insert_element(span, position=0)
                    else:
                        container.set_tail(before)
                        # Insert as next sibling
                        index = wrapper.index(container)
                        wrapper.insert_element(span, position=index + 1)


    def set_link(self, url, regex=None, offset=None, length=0):
        """Make a link from text content matching the regex OR the positional
        arguments.
        """
        raise NotImplementedError


    def set_bookmark(self, name, before=None, after=None, position=0,
                     role=None, content=None, limits=None):
        """Insert a bookmark before or after the characters in the text which
        match the regexp before/after. When the regexp matches more of one part
        of the text, position can be set to choice which part must be used. If
        before and after are None, we use only position that is the number of
        characters. So, by default, this function inserts a bookmark before the
        first character of the content. Role can be None, "start" or "end", we
        insert respectively a position bookmark a bookmark-start or a
        bookmark-end. If content is not None these 2 calls are equivalent::

          paragraph.set_bookmark("bookmark", content="xyz")

        ::

          paragraph.set_bookmark("bookmark", before="xyz", role="start")
          paragraph.set_bookmark("bookmark", after="xyz", role="end")

        If limits is not None these 2 calls are equivalent::

          paragraph.set_bookmark("bookmark", limits=(10, 20))

        ::

          paragraph.set_bookmark("bookmark", position=10, role="start")
          paragraph.set_bookmark("bookmark", position=20, role="end")

        Arguments:

            name -- string

            before -- regexp (unicode)

            after -- regexp (unicode)

            position -- int

            role -- None, "start" or "end"

            content -- regexp (unicode)

            limits -- (int, int)
        """

        # With "content" => automatically insert a "start" and an "end"
        # bookmark
        if (before is None and
            after is None and
            role is None and
            content is not None and
            limits is None):

            # Start
            start = odf_create_bookmark_start(name)
            self._insert(start, before=content, position=position)

            # End
            end = odf_create_bookmark_end(name)
            self._insert(end, after=content, position=position)

            return start, end

        # With "limits" =>  automatically insert a "start" and an "end"
        # bookmark
        if (before is None and
            after is None and
            role is None and
            content is None and
            position == 0 and
            limits is not None):

            # Start
            start = odf_create_bookmark_start(name)
            self._insert(start, position=limits[0])

            # End
            end = odf_create_bookmark_end(name)
            self._insert(end, position=limits[1])

            return start, end

        # Without "content" and "limits"
        if content is not None or limits is not None:
            raise ValueError, "bad arguments"

        # Role
        if role is None:
            bookmark = odf_create_bookmark(name)
        elif role == "start":
            bookmark = odf_create_bookmark_start(name)
        elif role == "end":
            bookmark = odf_create_bookmark_end(name)
        else:
            raise ValueError, "bad arguments"

        # Insert
        self._insert(bookmark, before=before, after=after, position=position)

        return bookmark class odf_span(odf_paragraph):
    pass class odf_frame(odf_element):

    def get_frame_name(self):
        return self.get_attribute('draw:name')


    def set_frame_name(self, name):
        self.set_attribute('draw:name', name)


    def get_frame_size(self):
        """Get the size of the frame.

        Size is a (width, height) tuple with items including the unit,
        e.g. ('10cm', '15cm').


        Return: (str, str)
        """
        get_attr = self.get_attribute
        return get_attr('svg:width'), get_attr('svg:height')


    def set_frame_size(self, size):
        """Set the size of the frame.

        Size is a (width, height) tuple with items including the unit,
        e.g. ('10cm', '15cm'). The dimensions can be None.

        Arguments:

            size -- (str, str)
        """
        self.set_attribute('svg:width', size[0])
        self.set_attribute('svg:height', size[1])


    def get_frame_position(self):
        """Get the position of the frame relative to its anchor
        point.

        Position is a (left, top) tuple with items including the unit,
        e.g. ('10cm', '15cm').

        Return: (str, str)
        """
        get_attr = self.get_attribute
        return get_attr('svg:x'), get_attr('svg:y')


    def set_frame_position(self, position):
        """Set the position of the frame relative to its anchor
        point.

        Position is a (left, top) tuple with items including the unit,
        e.g. ('10cm', '15cm').

        Arguments:

            position -- (str, str)
        """
        self.set_attribute('svg:x', position[0])
        self.set_attribute('svg:y', position[1])


    def get_frame_anchor_type(self):
        """Get how the frame is attached to its environment.

        Return: 'page', 'frame', 'paragraph', 'char' or 'as-char'
        """
        return self.get_attribute('text:anchor-type')


    def set_frame_anchor_type(self, anchor_type, page_number=None):
        """Set how the frame is attached to its environment.

        When the type is 'page', you can give the number of the page where to
        attach.

        Arguments:

            anchor_type -- 'page', 'frame', 'paragraph', 'char' or 'as-char'

            page_number -- int (when anchor_type == 'page')
        """
        self.set_attribute('text:anchor-type', anchor_type)
        if anchor_type == 'page' and page_number:
            self.set_frame_page_number(page_number)


    def get_frame_page_number(self):
        """Get the number of the page where the frame is attached when the
        anchor type is 'page'.

        Return: int
        """
        page_number = self.get_attribute('text:anchor-page-number')
        if page_number is None:
            return None
        return int(page_number)


    def set_frame_page_number(self, page_number):
        """Set the number of the page where the frame is attached when the
        anchor type is 'page', or None to delete it

        Arguments:

            page_number -- int or None
        """
        if page_number is None:
            self.set_attribute('text:anchor-page-number', None)
        self.set_attribute('text:anchor-page-number', str(page_number))


    def get_formatted_text(self, context):
        result = []
        for element in self.get_children():
            tag = element.get_tagname()
            if tag == 'draw:image':
                if context['rst_mode']:
                    result.append(u'\n.. image:: %s\n' %
                                  element.get_attribute('xlink:href'))
                else:
                    result.append(u'[Image %s]\n' %
                                  element.get_attribute('xlink:href'))
            elif tag == 'draw:text-box':
                subresult = [u'  ']
                for element in element.get_children():
                    subresult.append(element.get_formatted_text(context))
                subresult = u''.join(subresult)
                subresult = subresult.replace(u'\n', u'\n  ')
                subresult.rstrip(' ')
                result.append(subresult)
            else:
                result.append(element.get_formatted_text(context))
        result.append(u'\n')
        return u''.join(result) class odf_draw_page(odf_element):
    """Specialised element for pages of presentation and drawing.
    """
    def get_page_name(self):
        return self.get_attribute('draw:name')


    def set_page_name(self, name):
        self.set_attribute('draw:name', name)


    def get_page_id(self):
        return self.get_attribute('draw:id')


    def set_page_id(self, page_id):
        self.set_attribute('draw:id', page_id)


    def set_transition(self, type, subtype=None, dur='2s'):
        # Create the new animation
        anim_page = odf_create_anim_par(presentation_node_type="timing-root")
        my_page_id = self.get_page_id()
        anim_begin = odf_create_anim_par(smil_begin="%s.begin" % my_page_id)
        transition = odf_create_anim_transitionFilter(smil_dur=dur,
                                                      smil_type=type,
                                                      smil_subtype=subtype)
        anim_page.append_element(anim_begin)
        anim_begin.append_element(transition)

        # Replace when already a transition:
        #   anim:seq => After the frame's transition
        #   cf page 349 of OpenDocument-v1.0-os.pdf
        #   Conclusion: We must delete the first child 'anim:par'
        existing = self.get_element('anim:par')
        if existing:
            self.delete_element(existing)
        self.append_element(anim_page)


    def get_formatted_text(self, context):
        result = []
        for element in self.get_children():
            if element.get_tagname() == 'presentation:notes':
                # No need for an advanced odf_notes.get_formatted_text()
                # because the text seems to be only contained in paragraphs
                # and frames, that we already handle
                for child in element.get_children():
                    result.append(child.get_formatted_text(context))
                result.append(u"\n")
            result.append(element.get_formatted_text(context))
        result.append(u"\n")
        return u"".join(result) class odf_list(odf_element):
    """Specialised element for lists.
    """

    def get_item_list(self, regex=None):
        return _get_element_list(self, 'text:list-item', regex=regex)


    def get_item_by_position(self, position):
        return _get_element(self, 'text:list-item', position=position)


    def get_item_by_content(self, regex):
        query = 'descendant::text:list-item/text:p'
        paragraphs = [p for p in self.get_element_list(query)
                      if p.match(regex)]
        if paragraphs:
            return paragraphs[0].get_parent()
        return None


    def set_header(self, text_or_element):
        if not isinstance(text_or_element, (list, tuple)):
            text_or_element = [text_or_element]
        # Remove existing header
        for element in self.get_element_list('text:p'):
            self.delete_element(element)
        for paragraph in reversed(text_or_element):
            if type(paragraph) is unicode:
                paragraph = odf_create_paragraph(paragraph)
            self.insert_element(paragraph, FIRST_CHILD)


    def insert_item(self, item, position=None, before=None, after=None):
        # Check if the item is already a list-item
        tag_name = item.get_tagname() if isinstance(item, odf_element) else None
        if tag_name != 'text:list-item':
            item = odf_create_list_item(item)

        if before is not None:
            before.insert_element(item, xmlposition=PREV_SIBLING)
        elif after is not None:
            after.insert_element(item, xmlposition=NEXT_SIBLING)
        elif position is not None:
            self.insert_element(item, position=position)
        else:
            raise ValueError, "position must be defined"


    def append_item(self, item):
        # Check if the item is already a list-item
        tag_name = item.get_tagname() if isinstance(item, odf_element) else None
        if tag_name != 'text:list-item':
            item = odf_create_list_item(item)
        self.append_element(item)


    def get_formatted_text(self, context):
        rst_mode = context["rst_mode"]

        result = []
        if rst_mode:
            result.append('\n')
        for list_item in self.get_element_list('text:list-item'):
            text = []
            for children in list_item.get_children():
                text.append(children.get_formatted_text(context))
            text = u''.join(text)
            text = text.strip('\n')
            # Indent the text
            text = u'- %s\n' % text.replace(u'\n', u'\n  ')
            result.append(text)
        if rst_mode:
            result.append('\n')
        return u''.join(result) class odf_element(object):
    """Representation of an XML element.  Abstraction of the XML library
    behind.
    """

    def __init__(self, native_element):
        if not isinstance(native_element, _Element):
            raise TypeError, ('"%s" is not an element node' %
                              type(native_element))
        self.__element = native_element


    def __str__(self):
        return '%s "%s"' % (super(odf_element, self).__str__(),
                            self.get_tagname())


    def _insert(self, element, before=None, after=None, position=0):
        """Insert an element before or after the characters in the text which
        match the regexp before/after. When the regexp matches more of one part
        of the text, position can be set to choice which part must be used. If
        before and after are None, we use only position that is the number of
        characters. If position is positive and before=after=None, we insert
        before the position character. But if position=-1, we insert after the
        last character.

        Arguments:

        element -- odf_element

        before -- regexp (unicode)

        after -- regexp (unicode)

        position -- int
        """

        current = self.__element
        element = element.__element

        # 1) before xor after is not None
        if (before is not None) ^ (after is not None):
            regexp = compile(before) if before is not None else compile(after)

            # position = -1
            if position < 0:
                # Found the last text that matches the regexp
                text = None
                for a_text in current.xpath("//text()"):
                    if regexp.search(a_text) is not None:
                        text = a_text
                if text is None:
                    raise ValueError, "text not found"
                sre = list(regexp.finditer(text))[-1]
            # position >= 0
            else:
                count = 0
                for text in current.xpath("//text()"):
                    found_nb = len(regexp.findall(text))
                    if found_nb + count >= position + 1:
                        break
                    count += found_nb
                else:
                    raise ValueError, "text not found"
                sre = list(regexp.finditer(text))[position - count]

            # Compute pos
            pos = sre.start() if before is not None else sre.end()
        # 2) before=after=None => only with position
        elif before is None and after is None:
            # Hack if position is negative => quickly
            if position < 0:
                current.append(element)
                return

            # Found the text
            count = 0
            for text in current.xpath("//text()"):
                found_nb = len(text)
                if found_nb + count >= position:
                    break
                count += found_nb
            else:
                raise ValueError, "text not found"

            # We insert before the character
            pos = position - count
        else:
            raise ValueError, "bad combination of arguments"

        # Compute new texts
        text_before = text[:pos] if text[:pos] else None
        text_after  = text[pos:] if text[pos:] else None

        # Insert!
        parent = text.getparent()
        if text.is_text:
            parent.text = text_before
            element.tail = text_after
            parent.insert(0, element)
        else:
            parent.addnext(element)
            parent.tail = text_before
            element.tail = text_after


    def _insert_between(self, element, from_, to):
        """Insert the given empty element to wrap the text beginning with
        "from_" and ending with "to".

        Example 1: '<p>toto tata titi</p>

        We want to insert a link around "tata".

        Result 1: '<p>toto <a>tata</a> titi</p>

        Example 2: '<p><span>toto</span> tata titi</p>

        We want to insert a link around "tata".

        Result 2: '<p><span>toto</span> <a>tata</a> titi</p>

        Example 3: '<p>toto <span> tata </span> titi</p>'

        We want to insert a link from "tata" to "titi" included.

        Result 3: '<p>toto <span> </span>'
                  '<a><span>tata </span> titi</a></p>'

        Example 4: '<p>toto <span>tata titi</span> tutu</p>'

        We want to insert a link from "titi" to "tutu"

        Result 4: '<p>toto <span>tata </span><a><span>titi</span></a>'
                  '<a> tutu</a></p>'

        Example 5: '<p>toto <span>tata titi</span> '
                   '<span>tutu tyty</span></p>'

        We want to insert a link from "titi" to "tutu"

        Result 5: '<p>toto <span>tata </span><a><span>titi</span><a> '
                  '<a> <span>tutu</span></a><span> tyty</span></p>'
        """
        current = self.__element
        wrapper = element.__element
        end_element = deepcopy(wrapper)
        for text in current.xpath('descendant::text()'):
            if not from_ in text:
                continue
            from_index = text.index(from_)
            text_before = text[:from_index]
            text_after = text[from_index:]
            from_container = text.getparent()
            # Include from_index to match a single word
            to_index = text.find(to, from_index)
            if to_index >= 0:
                # Simple case: "from" and "to" in the same element
                to_end = to_index + len(to)
                if text.is_text:
                    from_container.text = text_before
                    wrapper.text = text[to_index:to_end]
                    wrapper.tail = text[to_end:]
                    from_container.insert(0, wrapper)
                else:
                    from_container.tail = text_before
                    wrapper.text = text[to_index:to_end]
                    wrapper.tail = text[to_end:]
                    parent = from_container.getparent()
                    index = parent.index(from_container)
                    parent.insert(index + 1, wrapper)
                return
            else:
                # Exit to the second part where we search for the end text
                break
        else:
            raise "start text not found"
        # The container is split in two
        container2 = deepcopy(from_container)
        if text.is_text:
            from_container.text = text_before
            from_container.tail = None
            container2.text = text_after
            from_container.tail = None
        else:
            from_container.tail = text_before
            container2.tail = text_after
        # Stack the copy into the surrounding element
        wrapper.append(container2)
        parent = from_container.getparent()
        index = parent.index(from_container)
        parent.insert(index + 1, wrapper)
        for text in wrapper.xpath('descendant::text()'):
            if not to in text:
                continue
            to_end = text.index(to) + len(to)
            text_before = text[:to_end]
            text_after = text[to_end:]
            container_to = text.getparent()
            if text.is_text:
                container_to.text = text_before
                container_to.tail = text_after
            else:
                container_to.tail = text_before
                next = container_to.getnext()
                if next is None:
                    next = container_to.getparent()
                next.tail = text_after
            return
        raise ValueError, "end text not found"


    def get_tagname(self):
        element = self.__element
        return _get_prefixed_name(element.tag)


    def set_tagname(self, qname):
        """XXX side effects?
        and the wrapping class won't change
        """
        element = self.__element
        element.tag = '{%s}%s' % _decode_qname(qname)
        return _make_odf_element(element)


    def get_element_list(self, xpath_query):
        element = self.__element
        result = element.xpath(xpath_query, namespaces=ODF_NAMESPACES)
        return [_make_odf_element(e) for e in result]


    def get_element(self, xpath_query):
        result = self.get_element_list(xpath_query)
        if result:
            return result[0]
        return None


    def get_attributes(self):
        attributes = {}
        element = self.__element
        for key, value in element.attrib.iteritems():
            attributes[_get_prefixed_name(key)] = value
        # FIXME lxml has mixed bytestring and unicode
        return attributes


    def get_attribute(self, name):
        element = self.__element
        uri, name = _decode_qname(name)
        if uri is not None:
            name = '{%s}%s' % (uri, name)
        value = element.get(name)
        if value is None:
            return None
        return unicode(value)


    def set_attribute(self, name, value):
        element = self.__element
        uri, name = _decode_qname(name)
        if uri is not None:
            name = '{%s}%s' % (uri, name)
        if value is None:
            try:
                del element.attrib[name]
            except KeyError:
                pass
            return
        element.set(name, value)


    def del_attribute(self, name):
        element = self.__element
        uri, name = _decode_qname(name)
        if uri is not None:
            name = '{%s}%s' % (uri, name)
        del element.attrib[name]


    def get_text(self, recursive=False):
        """Return the text content of the element.

        If recursive is True, all text contents of the subtree.
        """
        if recursive:
            return u''.join(self.__element.itertext())
        return unicode(self.__element.text)


    def set_text(self, text):
        """Set the text content of the element.
        """
        self.__element.text = text


    def get_tail(self):
        """Return the text immediately following the element.

        Inspired by lxml.
        """
        return unicode(self.__element.tail)


    def set_tail(self, text):
        """Set the text immediately following the element.

        Inspired by lxml.
        """
        self.__element.tail = text


    def search(self, pattern):
        """Return the first position of the pattern in the text content of the
        element, or None if not found.

        Python regular expression syntax applies.

        Arguments:

            pattern -- unicode

        Returns: int or None
        """
        if isinstance(pattern, str):
            # Fail properly if the pattern is an non-ascii bytestring
            pattern = unicode(pattern)
        text = self.get_text(recursive=True)
        match = search(pattern, text)
        if match is None:
            return None
        return match.start()


    def match(self, pattern):
        """return True if the pattern is found one or more times anywhere in
        the text content of the element.

        Python regular expression syntax applies.

        Arguments:

            pattern -- unicode

        Returns: bool
        """
        return self.search(pattern) is not None


    def replace(self, pattern, new=None):
        """Replace the pattern with the given text, or delete if text is an
        empty string, and return the number of replacements. By default, only
        return the number of occurences that would be replaced.

        It cannot replace patterns found across several element, like a word
        split into two consecutive spans.

        Python regular expression syntax applies.

        Arguments:

            pattern -- unicode

            new -- unicode

        Returns: int
        """
        if isinstance(pattern, str):
            # Fail properly if the pattern is an non-ascii bytestring
            pattern = unicode(pattern)
        pattern = compile(pattern)
        count = 0
        for text in self.xpath('descendant::text()'):
            if new is None:
                count += len(pattern.findall(text))
            else:
                new_text, number = pattern.subn(new, text)
                container = text.get_parent()
                if text.is_text():
                    container.set_text(new_text)
                else:
                    container.set_tail(new_text)
                count += number
        return count


    def get_parent(self):
        element = self.__element
        parent = element.getparent()
        if parent is None:
            # Already at root
            return None
        return _make_odf_element(parent)


    def get_next_sibling(self):
        element = self.__element
        next = element.getnext()
        if next is None:
            return None
        return _make_odf_element(next)


    def get_prev_sibling(self):
        element = self.__element
        prev = element.getprevious()
        if prev is None:
            return None
        return _make_odf_element(prev)


    def get_children(self):
        element = self.__element
        return [_make_odf_element(e) for e in element]


    def index(self, child):
        """Return the position of the child in this element.

        Inspired by lxml
        """
        return self.__element.index(child.__element)


    def get_text_content(self):
        """Like "get_text" but return the text of the embedded paragraph:
        annotations, cells...
        """
        element = self.__element
        return element.xpath('string(text:p)', namespaces=ODF_NAMESPACES)


    def set_text_content(self, text):
        """Like "set_text" but set the text of the embedded paragraph:
        annotations, cells...

        Create the paragraph if missing.
        """
        paragraphs = self.get_element_list('text:p')
        if not paragraphs:
            # E.g., text:p in draw:text-box in draw:frame
            paragraphs = self.get_element_list('*/text:p')
        if paragraphs:
            paragraph = paragraphs.pop(0)
            for obsolete in paragraphs:
                obsolete.get_parent().delete_element(obsolete)
        else:
            paragraph = odf_create_element('<text:p/>')
            self.insert_element(paragraph, FIRST_CHILD)
        # As "get_text_content" returned all text nodes, "set_text_content"
        # will overwrite all text nodes and children that may contain them
        element = paragraph.__element
        # Clear but the attributes
        del element[:]
        element.text = text


    def insert_element(self, element, xmlposition=None, position=None):
        """Insert an element relatively to ourself.

        Insert either using DOM vocabulary or by numeric position.

        Position start at 0.

        Arguments:

            element -- odf_element

            xmlposition -- FIRST_CHILD, LAST_CHILD, NEXT_SIBLING
                           or PREV_SIBLING

            position -- int
        """
        current = self.__element
        element = element.__element
        if position is not None:
            current.insert(position, element)
        elif xmlposition is FIRST_CHILD:
            current.insert(0, element)
        elif xmlposition is LAST_CHILD:
            current.append(element)
        elif xmlposition is NEXT_SIBLING:
            parent = current.getparent()
            index = parent.index(current)
            parent.insert(index + 1, element)
        elif xmlposition is PREV_SIBLING:
            parent = current.getparent()
            index = parent.index(current)
            parent.insert(index, element)
        else:
            raise ValueError, "(xml)position must be defined"


    def append_element(self, unicode_or_element):
        """Insert element or text in the last position.
        """
        current = self.__element

        # Unicode ?
        if isinstance(unicode_or_element, unicode):

            # Has children ?
            children = current.getchildren()
            if children:
                # Append to tail of the last child
                last_child = children[-1]

                text = last_child.tail
                text = text if text is not None else u""
                text += unicode_or_element

                last_child.tail = text
            else:
                # Append to text of the element
                text = current.text

                text = text if text is not None else u""
                text += unicode_or_element

                current.text = text
        # Element ?
        else:
            current.append(unicode_or_element.__element)


    def delete_element(self, child):
        self.__element.remove(child.__element)


    def xpath(self, xpath_query):
        """Apply XPath query to the element and its subtree. Return list of
        odf_element or odf_text instances translated from the nodes found.
        """
        element = self.__element
        elements = element.xpath(xpath_query, namespaces=ODF_NAMESPACES)
        result = []
        for obj in elements:
            if (type(obj) is _ElementStringResult or
                    type(obj) is _ElementUnicodeResult):
                result.append(odf_text(obj))
            elif type(obj) is _Element:
                result.append(_make_odf_element(obj))
            else:
                result.append(obj)
        return result


    def clear(self):
        """Remove text, children and attributes from the element.
        """
        self.__element.clear()


    def clone(self):
        clone = deepcopy(self.__element)
        # Now the clone is its own root and lxml lost unused namespace
        # prefixes.
        # Re-attach it to a root with all namespaces
        root = Element('ROOT', nsmap=ODF_NAMESPACES)
        root.append(clone)
        return self.__class__(clone)


    def serialize(self, pretty=False):
        element = deepcopy(self.__element)
        data = tostring(element, with_tail=False, pretty_print=pretty)
        # Hack over lxml: remove namespaces
        return ns_stripper.sub('', data)


    #
    # Element helpers usable from any context
    #

    def get_body(self):
        return self.get_element('//office:body/*[1]')


    def get_formatted_text(self, context):
        """This function must return a beautiful version of the text
        """
        return u''


    def get_styled_elements(self, name=True):
        """Brute-force to find paragraphs, tables, etc. using the given style
        name (or all by default).

        Arguments:

            name -- unicode

        Return: list
        """
        # FIXME incomplete (and possibly inaccurate)
        return (_get_element_list(self, 'descendant::*', text_style=name)
                + _get_element_list(self, 'descendant::*', draw_style=name)
                + _get_element_list(self, 'descendant::*', style_name=name)
                + _get_element_list(self, 'descendant::*', draw_text_style=name)
                + _get_element_list(self, 'descendant::*', table_style=name)
                + _get_element_list(self, 'descendant::*', page_layout=name)
		)

    #
    # Common attributes
    #

    def get_outline_level(self):
        return int(self.get_attribute('text:outline-level'))


    def set_outline_level(self, level):
        self.set_attribute('text:outline-level', unicode(level))


    def get_text_style(self):
        return self.get_attribute('text:style-name')


    def set_text_style(self, style):
        self.set_attribute('text:style-name', style)


    #
    # Dublin core
    #

    def get_dc_creator(self):
        dc_creator = self.get_element('descendant::dc:creator')
        if dc_creator is None:
            return None
        return dc_creator.get_text()


    def set_dc_creator(self, creator):
        dc_creator = self.get_element('descendant::dc:creator')
        if dc_creator is None:
            dc_creator = odf_create_element('<dc:creator/>')
            self.append_element(dc_creator)
        dc_creator.set_text(creator)


    def get_dc_date(self):
        dc_date = self.get_element('descendant::dc:date')
        if dc_date is None:
            return None
        date = dc_date.get_text()
        return DateTime.decode(date)


    def set_dc_date(self, date):
        dc_date = self.get_element('descendant::dc:date')
        if dc_date is None:
            dc_date = odf_create_element('<dc:date/>')
            self.append_element(dc_date)
        dc_date.set_text(DateTime.encode(date))

    #
    # Sections
    #

    def get_section_list(self, style=None, regex=None):
        return _get_element_list(self, 'text:section', text_style=style,
                regex=regex)


    def get_section_by_position(self, position):
        return _get_element(self, 'descendant::text:section',
                            position=position)


    def get_section_by_content(self, regex):
        return _get_element(self, 'descendant::text:section', regex=regex)


    #
    # Paragraphs
    #

    def get_paragraph_list(self, style=None, regex=None):
        return _get_element_list(self, 'descendant::text:p',
                text_style=style, regex=regex)


    def get_paragraph_by_position(self, position):
        return _get_element(self, 'descendant::text:p', position=position)


    def get_paragraph_by_content(self, regex):
        return _get_element(self, 'descendant::text:p', regex=regex)


    #
    # Span
    #

    def get_span_list(self, style=None, regex=None):
        return _get_element_list(self, 'descendant::text:span',
                text_style=style, regex=regex)


    def get_span_by_position(self, position):
        return _get_element(self, 'descendant::text:span', position=position)


    def get_span_by_content(self, regex):
        return _get_element(self, 'descendant::text:span', regex=regex)


    #
    # Headings
    #

    def get_heading_list(self, style=None, level=None, regex=None):
        return _get_element_list(self, 'descendant::text:h',
                text_style=style, outline_level=level, regex=regex)


    def get_heading_by_position(self, position, level=None):
        return _get_element(self, 'descendant::text:h', position=position,
                outline_level=level)


    def get_heading_by_content(self, regex, level=None):
        return _get_element(self, 'descendant::text:h', regex=regex,
                outline_level=level)


    #
    # Lists
    #

    def get_list_list(self, style=None, regex=None):
        return _get_element_list(self, 'descendant::text:list',
                text_style=style, regex=regex)


    def get_list_by_position(self, position):
        return _get_element(self, 'descendant::text:list', position=position)


    def get_list_by_content(self, regex):
        return _get_element(self, 'descendant::text:list', regex=regex)


    #
    # Frames
    #

    def get_frame_list(self, style=None, title=None, description=None,
                       regex=None):
        return _get_element_list(self, 'descendant::draw:frame',
                                 draw_style=style, svg_title=title,
                                 svg_desc=description, regex=regex)


    def get_frame_by_name(self, name):
        return _get_element(self, 'descendant::draw:frame', draw_name=name)


    def get_frame_by_position(self, position):
        return _get_element(self, 'descendant::draw:frame', position=position)


    def get_frame_by_content(self, regex):
        return _get_element(self, 'descendant::draw:frame', regex=regex)


    def get_frame_by_title(self, regex):
        return _get_element(self, 'descendant::draw:frame', svg_title=regex)


    def get_frame_by_description(self, regex):
        return _get_element(self, 'descendant::draw:frame', svg_desc=regex)


    #
    # Images
    #

    def get_image_list(self, style=None, href=None, regex=None):
        """Get all image elements matching the criteria. Style is the style
        name. Set link to False to get only internal images, and True to
        get only external images (not in the container). Href is a regex to
        find all images with their path matching.

        Arguments:

            style -- str

            link -- bool

            href -- unicode regex

        Return: list of odf_element
        """
        return _get_element_list(self, 'descendant::draw:image',
                text_style=style, href=href, regex=regex)


    def get_image_by_name(self, name):
        # The frame is holding the name
        frame = _get_element(self, 'descendant::draw:frame', draw_name=name)
        if frame is None:
            return None
        return frame.get_element('draw:image')


    def get_image_by_position(self, position):
        return _get_element(self, 'descendant::draw:image', position=position)


    def get_image_by_path(self, regex):
        return _get_element(self, 'descendant::draw:image', href=regex)


    def get_image_by_content(self, regex):
        return _get_element(self, 'descendant::draw:image', regex=regex)


    #
    # Tables
    #

    def get_table_list(self, style=None, regex=None):
        return _get_element_list(self, 'descendant::table:table',
                table_style=style, regex=regex)


    def get_table_by_name(self, name):
        return _get_element(self, 'descendant::table:table', table_name=name)


    def get_table_by_position(self, position):
        return _get_element(self, 'descendant::table:table', position=position)


    def get_table_by_content(self, regex):
        return _get_element(self, 'descendant::table:table', regex=regex)


    #
    # Notes
    #

    def get_note_list(self, note_class=None, regex=None):
        """Return the list of all note element, optionally the ones of the
        given class or that match the given regex.

        Arguments:

            note_class -- 'footnote' or 'endnote'

            regex -- unicode

        Return: list of odf_element
        """
        return _get_element_list(self, 'descendant::text:note',
                                 note_class=note_class,
                                 regex=regex)


    def get_note_by_id(self, note_id):
        return _get_element(self, 'descendant::text:note', text_id=note_id)


    def get_note_by_class(self, note_class):
        return _get_element_list(self, 'descendant::text:note',
                                 note_class=note_class)


    def get_note_by_content(self, regex):
        return _get_element(self, 'descendant::text:note', regex=regex)


    #
    # Annotations
    #

    def get_annotation_list(self, creator=None, start_date=None,
                            end_date=None, regex=None):
        """XXX end date is not included (as expected in Python).
        """
        annotations = []
        for annotation in _get_element_list(self,
                'descendant::office:annotation', regex=regex):
            if (creator is not None
                    and creator != annotation.get_dc_creator()):
                continue
            date = annotation.get_dc_date()
            if start_date is not None and date < start_date:
                continue
            if end_date is not None and date >= end_date:
                continue
            annotations.append(annotation)
        return annotations


    def get_annotation(self, creator=None, start_date=None, end_date=None,
                       regex=None):
        annotations = self.get_annotation_list(creator=creator,
                                               start_date=start_date,
                                               end_date=end_date,
                                               regex=regex)
        if annotations:
            return annotations[0]
        return None


    #
    # Variables
    #

    def get_variable_decls(self):
        variable_decls = self.get_element('//text:variable-decls')
        if variable_decls is None:
            from variable import odf_create_variable_decls
            body = self.get_body()
            body.insert_element(odf_create_variable_decls(), FIRST_CHILD)
            variable_decls = body.get_element('//text:variable-decls')

        return variable_decls


    def get_variable_list(self):
        return _get_element_list(self, 'descendant::text:variable-decl')


    def get_variable_decl(self, name):
        return _get_element(self, 'descendant::text:variable-decl',
                            text_name=name)


    def get_variable_sets(self, name):
        return _get_element_list(self, 'descendant::text:variable-set',
                                 text_name=name)


    def get_variable_value(self, name, value_type=None):
        variable_sets = self.get_variable_sets(name)
        # Nothing ?
        if not variable_sets:
            return None
        # Get the last value
        return get_value(variable_sets[-1], value_type)


    #
    # User fields
    #

    def get_user_field_decls(self):
        user_field_decls = self.get_element('//text:user-field-decls')
        if user_field_decls is None:
            from variable import odf_create_user_field_decls
            body = self.get_body()
            body.insert_element(odf_create_user_field_decls(), FIRST_CHILD)
            user_field_decls = body.get_element('//text:user-field-decls')

        return user_field_decls


    def get_user_field_list(self):
        return _get_element_list(self, 'descendant::text:user-field-decl')


    def get_user_field_decl(self, name):
        return _get_element(self, 'descendant::text:user-field-decl',
                            text_name=name)


    def get_user_field_value(self, name, value_type=None):
        user_field_decl = self.get_user_field_decl(name)
        # Nothing ?
        if user_field_decl is None:
            return None
        return get_value(user_field_decl, value_type)


    #
    # Draw Pages
    #
    def get_draw_page_list(self, style=None, regex=None):
        return _get_element_list(self, 'descendant::draw:page',
                                 draw_style=style, regex=regex)


    def get_draw_page_by_name(self, name):
        return _get_element(self, 'descendant::draw:page', draw_name=name)


    def get_draw_page_by_position(self, position):
        return _get_element(self, 'descendant::draw:page', position=position)


    def get_draw_page_by_content(self, regex):
        return _get_element(self, 'descendant::draw:page', regex=regex)


    #
    # Links
    #

    def get_link_list(self, name=None, title=None, href=None, regex=None):
        return _get_element_list(self, 'descendant::text:a', office_name=name,
                                 office_title=title, href=href, regex=regex)


    def get_link_by_name(self, name):
        return _get_element(self, 'descendant::text:a', office_name=name)


    def get_link_by_path(self, regex):
        return _get_element(self, 'descendant::text:a', href=regex)


    def get_link_by_content(self, regex):
        return _get_element(self, 'descendant::text:a', regex=regex)


    #
    # Bookmarks
    #

    def get_bookmark_list(self):
        return _get_element_list(self, 'descendant::text:bookmark')


    def get_bookmark_by_name(self, name):
        return _get_element(self, 'descendant::text:bookmark', text_name=name)


    def get_bookmark_start_list(self):
        return _get_element_list(self, 'descendant::text:bookmark-start')


    def get_bookmark_start_by_name(self, name):
        return _get_element(self, 'descendant::text:bookmark-start',
                            text_name=name)


    def get_bookmark_end_list(self):
        return _get_element_list(self, 'descendant::text:bookmark-end')


    def get_bookmark_end_by_name(self, name):
        return _get_element(self, 'descendant::text:bookmark-end',
                            text_name=name)


    #
    # Reference marks
    #

    def get_reference_mark_list(self):
        return _get_element_list(self, 'descendant::text:reference-mark')


    def get_reference_mark_by_name(self, name):
        return _get_element(self, 'descendant::text:reference-mark',
                            text_name=name)


    def get_reference_mark_start_list(self):
        return _get_element_list(self, 'descendant::text:reference-mark-start')


    def get_reference_mark_start_by_name(self, name):
        return _get_element(self, 'descendant::text:reference-mark-start',
                            text_name=name)


    def get_reference_mark_end_list(self):
        return _get_element_list(self, 'descendant::text:reference-mark-end')


    def get_reference_mark_end_by_name(self, name):
        return _get_element(self, 'descendant::text:reference-mark-end',
                            text_name=name)


    #
    # Shapes elements
    #

    #
    # Lines
    #

    def get_draw_line_list(self, draw_style=None, draw_text_style=None,
                           regex=None):
        return _get_element_list(self, 'descendant::draw:line',
                                 draw_style=draw_style,
                                 draw_text_style=draw_text_style, regex=regex)


    def get_draw_line_by_content(self, regex):
        return _get_element(self, 'descendant::draw:line', regex=regex)


    def get_draw_line_by_id(self, id):
        lines = self.get_draw_line_list()
        lines = [line for line in lines
                      if line.get_attribute('draw:id') == id]
        return lines[0] if lines else None


    #
    # Rectangles
    #

    def get_draw_rectangle_list(self, draw_style=None, draw_text_style=None,
                                regex=None):
        return _get_element_list(self, 'descendant::draw:rect',
                                 draw_style=draw_style,
                                 draw_text_style=draw_text_style, regex=regex)


    def get_draw_rectangle_by_content(self, regex):
        return _get_element(self, 'descendant::draw:rect', regex=regex)


    def get_draw_rectangle_by_id(self, id):
        rectangles = self.get_draw_rectangle_list()
        rectangles = [rectangle for rectangle in rectangles
                                if rectangle.get_attribute('draw:id') == id]
        return rectangles[0] if rectangle else None


    #
    # Ellipse
    #

    def get_draw_ellipse_list(self, draw_style=None, draw_text_style=None,
                              regex=None):
        return _get_element_list(self, 'descendant::draw:ellipse',
                                 draw_style=draw_style,
                                 draw_text_style=draw_text_style, regex=regex)


    def get_draw_ellipse_by_content(self, regex):
        return _get_element(self, 'descendant::draw:ellipse', regex=regex)


    def get_draw_ellipse_by_id(self, id):
        ellipses = self.get_draw_ellipse_list()
        ellipses = [ellipse for ellipse in ellipses
                            if ellipse.get_attribute('draw:id') == id]
        return ellipses[0] if ellipse else None


    #
    # Connectors
    #

    def get_draw_connector_list(self, draw_style=None, draw_text_style=None,
                                regex=None):
        return _get_element_list(self, 'descendant::draw:connector',
                                 draw_style=draw_style,
                                 draw_text_style=draw_text_style, regex=regex)


    def get_draw_connector_by_content(self, regex):
        return _get_element(self, 'descendant::draw:connector', regex=regex)


    def get_draw_connector_by_id(self, id):
        connectors = self.get_draw_connector_list()
        connectors = [connector for connector in connectors
                                if connector.get_attribute('draw:id') == id]
        return connectors[0] if connector else None


    def get_draw_orphans_connectors(self):
        """Return a list of connectors, which havn't any shape connected to
        them.
        """
        connectors = []
        for connector in self.get_draw_connector_list():
            start_shape = connector.get_attribute('draw:start-shape')
            end_shape = connector.get_attribute('draw:end-shape')
            if start_shape is None and end_shape is None:
                connectors.append(connector)
        return connectors


    #
    # Tracked changes
    #

    def get_changes_ids(self):
        """Return a list of ids that refers to a change region in the tracked
        changes list.
        """
        # Insertion changes
        xpath_query = 'descendant::text:change-start/@text:change-id'
        # Deletion changes
        xpath_query += ' | descendant::text:change/@text:change-id'
        return self.xpath(xpath_query)


    #
    # Table Of Content
    #

    def get_toc_list(self):
        return _get_element_list(self, 'text:table-of-content')


    def get_toc(self):
        return _get_element(self, 'text:table-of-content')


    #
    # Styles
    #

    def _get_style_tagname(self, family, is_default=False):
        """Widely match possible tag names given the family (or not).
        """
        if family is None:
            tagname = '(style:default-style|*[@style:name])'
            famattr = None
        elif is_default is True:
            # Default style
            tagname = 'style:default-style'
            famattr = family
        else:
            tagname, famattr = _get_style_tagname(family)
            if famattr:
                # Include family default style
                tagname = '(%s|style:default-style)' % tagname
        return tagname, famattr


    def get_style_list(self, family=None):
        # Both common and default styles
        tagname, famattr = self._get_style_tagname(family)
        return _get_element_list(self, tagname, family=famattr)


    def get_style(self, family, name_or_element=None, display_name=None):
        """Return the style uniquely identified by the name/family pair. If
        the argument is already a style object, it will return it.

        If the name is not the internal name but the name you gave in the
        desktop application, use display_name instead.

        Arguments:

            family -- 'paragraph', 'text', 'graphic', 'table', 'list',
                      'number'

            name_or_element -- unicode or odf_style

            display_name -- unicode

        Return: odf_style or None if not found
        """
        from style import odf_style

        if isinstance(name_or_element, odf_style):
            return name_or_element
        style_name = name_or_element
        is_default = not (style_name or display_name)
        tagname, famattr = self._get_style_tagname(family,
                is_default=is_default)
        # famattr became None if no "style:family" attribute
        return _get_element(self, tagname, style_name=style_name,
                display_name=display_name, family=famattr) def register_element_class(qname, cls):
    # Turn tag name into what lxml is expecting
    tag = '{%s}%s' % _decode_qname(qname)
#    if tag in __class_registry:
#        raise ValueError,  'element "%s" already registered' % qname
    __class_registry[tag] = cls class odf_text(unicode):
    """Representation of an XML text node. Created to hide the specifics of
    lxml in searching text nodes using XPath.

    Constructed like any unicode object but only accepts lxml text objects.
    """
    # There's some black magic in inheriting from unicode
    def __init__(self, text_result):
        self.__parent = text_result.getparent()
        self.__is_text = text_result.is_text
        self.__is_tail = text_result.is_tail


    def get_parent(self):
        parent = self.__parent
        # XXX happens just because of the unit test
        if parent is None:
            return None
        return _make_odf_element(parent)


    def is_text(self):
        return self.__is_text


    def is_tail(self):
        return self.__is_tail class odf_style(odf_element):
    """Specialised element for styles, yet generic to all style types.
    """
    def get_style_name(self):
        return self.get_attribute('style:name')


    def set_style_name(self, name):
        self.set_attribute('style:name', name)


    def get_style_display_name(self):
        return self.get_attribute('style:display-name')


    def get_style_family(self):
        return self.get_attribute('style:family')


    def set_style_family(self, family):
        self.set_attribute('style:family', family)


    def get_parent_style_name(self):
        """Will only return a name, not an object, because we don't have
        access to the XML part from here.

        See odf_styles.get_parent_style
        """
        return self.get_attribute('style:parent-style-name')


    def set_parent_style_name(self, name):
        self.set_attribute('style:parent-style-name', name)


    def get_style_properties(self, area=None):
        """Get the mapping of all properties of this style. By default the
        properties of the same family, e.g. a paragraph style and its
        paragraph properties. Specify the area to get the text properties of
        a paragraph style for example.

        Arguments:

            area -- str

        Return: dict
        """
        if area is None:
            area = self.get_style_family()
        element = self.get_element('style:%s-properties' % area)
        if element is None:
            return None
        properties = element.get_attributes()
        # Nested properties are nested dictionaries
        for child in element.get_children():
            properties[child.get_tagname()] = child.get_attributes()
        return properties


    def set_style_properties(self, properties={}, style=None, area=None,
            **kw):
        """Set the properties of the "area" type of this style. Properties
        are given either as a dict or as named arguments (or both). The area
        is identical to the style family by default. If the properties
        element is missing, it is created.

        Instead of properties, you can pass a style with properties of the
        same area. These will be copied.

        Arguments:

            properties -- dict

            style -- odf_style

            area -- 'paragraph', 'text'...
        """
        if area is None:
            area = self.get_style_family()
        element = self.get_element('style:%s-properties' % area)
        if element is None:
            element = odf_create_element('<style:%s-properties/>' % area)
            self.append_element(element)
        if properties or kw:
            properties = _expand_properties(_merge_dicts(properties, kw))
        elif style is not None:
            properties = style.get_style_properties(area=area)
            if properties is None:
                return
        for key, value in properties.iteritems():
            if value is None:
                element.del_attribute(key)
            else:
                element.set_attribute(key, value)


    def del_style_properties(self, properties=[], area=None, *args):
        """Delete the given properties, either by list argument or
        positional argument (or both). Remove only from the given area,
        identical to the style family by default.

        Arguments:

            properties -- list

            area -- str
        """
        if area is None:
            area = self.get_style_family()
        element = self.get_element('style:%s-properties' % area)
        if element is None:
            raise ValueError, "properties element is inexistent"
        for key in _expand_properties(properties):
            element.del_attribute(key)


    def set_background(self, color=None, uri=None, position='center',
                       repeat=None, opacity=None, filter=None):
        """Set the background color of a text style, or the background color
        or image of a paragraph style or page layout.

        With no argument, remove any existing background.

        The position is one or two of 'center', 'left', 'right', 'top' or
        'bottom'.

        The repeat is 'no-repeat', 'repeat' or 'stretch'.

        The opacity is a percentage integer (not a string with the '%s' sign)

        The filter is an application-specific filter name defined elsewhere.

        Though this method is defined on the base style class, it will raise
        an error if the style type is not compatible.

        Arguments:

            color -- '#rrggbb'

            uri -- str

            position -- str

            repeat -- str

            opacity -- int

            filter -- str
        """
        family = self.get_style_family()
        if family not in ('text', 'paragraph', 'page-layout', 'section',
                          'table', 'table-row', 'table-cell', 'graphic'):
            raise TypeError, 'no background support for this family'
        if uri is not None and family == 'text':
            raise TypeError, 'no background image for text styles'
        properties = self.get_element('style:%s-properties' % family)
        if properties is None:
            bg_image = None
        else:
            bg_image = properties.get_element('style:background-image')
        # Erasing
        if color is None and uri is None:
            if properties is None:
                return
            properties.del_attribute('fo:background-color')
            if bg_image is not None:
                properties.delete_element(bg_image)
            return
        # Add the properties if necessary
        if properties is None:
            properties = odf_create_element('<style:%s-properties/>'
                                            % family)
            self.append_element(properties)
        # Add the color...
        if color:
            properties.set_attribute('fo:background-color', color)
            if bg_image is not None:
                properties.delete_element(bg_image)
        # ... or the background
        elif uri:
            properties.set_attribute('fo:background-color', 'transparent')
            if bg_image is None:
                bg_image = odf_create_element('<style:background-image/>')
                properties.append_element(bg_image)
            bg_image.set_attribute('xlink:href',  uri)
            if position:
                bg_image.set_attribute('style:position', position)
            if repeat:
                bg_image.set_attribute('style:repeat', repeat)
            if opacity:
                bg_image.set_attribute('draw:opacity', str(opacity))
            if filter:
                bg_image.set_attribute('style:filter-name', filter) def _get_element_list(context, element_name, regex=None, href=None,
        svg_title=None, svg_desc=None, **kw):
    query = _make_xpath_query(element_name, **kw)
    elements = context.get_element_list(query)
    # Filter the elements with the regex
    if regex is not None:
        elements = [element for element in elements
                            if element.match(regex)]
    if href is not None:
        filtered = []
        for element in elements:
            href_attr = element.get_attribute('xlink:href')
            if search(href, href_attr) is not None:
                filtered.append(element)
        elements = filtered
    if svg_title is not None or svg_desc is not None:
        for variable, childname in [(svg_title, 'svg:title'),
                                    (svg_desc, 'svg:desc')]:
            if not variable:
                continue
            filtered = []
            for element in elements:
                child = element.get_element(childname)
                if child and child.match(variable):
                    filtered.append(element)
            elements = filtered
    return elements def _get_element(context, element_name, **kw):
    result = _get_element_list(context, element_name, **kw)
    if result:
        return result[0]
    return None
import sys
import os , os.path
import re
import math
import tempfile
import StringIO
	import pango
	import cairo
	import pangocairo
	import pickle
	import codecs
from optparse import OptionParser
from sys import exit, stdout
from pprint import pprint, pformat
from tools.lpod.document import odf_document, odf_get_document
from tools.lpod.container import odf_container , odf_get_container
from tools.lpod.paragraph import odf_paragraph
from tools.lpod.span import odf_span
from tools.lpod.frame import odf_frame
from tools.lpod.draw_page import odf_draw_page
from tools.lpod.list import odf_list
from tools.lpod.element import odf_element, register_element_class, odf_text
from tools.lpod.style import odf_style
from tools.lpod.utils import _get_element_list , _get_element
			iter = pango_layout.get_iter()
			lines_remains = True
			tmp_ypos = 0
			while lines_remains:
				ink_t , logical_t = iter.get_line_extents()
				ink = map(lambda x:x/pango.SCALE, ink_t)
				logical = map(lambda x:x/pango.SCALE, logical_t)
				line = iter.get_line()
				baseline = iter.get_baseline()

#				cairo_context.move_to( xstart + ink[0] - (ink[0] - logical[0])/2.0 , y_pos + ink[1])
#				_draw_rectangle(ink,(0,1,0),cairo_context)
#				cairo_context.move_to( xstart + logical[0] , y_pos + logical[1])
#				_draw_rectangle(logical,(1,0,0),cairo_context)
				text_xpos = xstart + logical[0] + (logical[0] - ink[0])/2.0
				text_ypos = y_pos + baseline/pango.SCALE
				if bullet is not None and first_line:
					if text_align == 'start':
						bullet_ink,bullet_logic = bullet_layout.get_pixel_extents()
						bullet_xpos = bullet['_space-before']
						bullet_ypos = y_pos + (logical[3] - bullet_logic[3])/2.0

					elif text_align == 'center':
						bullet_ink,bullet_logic = bullet_layout.get_pixel_extents()
						bullet_xpos = text_xpos - bullet['_label-width']
						bullet_ypos = y_pos + (logical[3] - bullet_logic[3])/2.0
						
					elif text_align == 'end':
						bullet_ink,bullet_logic = bullet_layout.get_pixel_extents()
						bullet_xpos = text_xpos - bullet['_label-width']
						bullet_ypos = y_pos + (logical[3] - bullet_logic[3])/2.0

					cairo_context.move_to( bullet_xpos, bullet_ypos )
					cairo_context.set_source_rgb(0.0,0.0,0.0)
					pangocairo_context.show_layout(bullet_layout)

				cairo_context.move_to( text_xpos, text_ypos )
				if font_outline :
					pangocairo_context.layout_line_path(line)
					cairo_context.set_line_width(2.0)
					cairo_context.set_source_rgb(0.0,0.0,0.0)
					cairo_context.stroke_preserve()
					cairo_context.set_source_rgba(1.0,1.0,1.0,1.0)
					cairo_context.fill()
				else :
					pangocairo_context.layout_line_path(line)
					cairo_context.set_source_rgb(0.0,0.0,0.0)
					cairo_context.fill()
					
				first_line = False
				lines_remains = iter.next_line()
				tmp_ypos += logical[3]

			y_pos += tmp_ypos + margin['bottom']
		image_buffer = StringIO.StringIO() 
#		image_surface.write_to_png("./tests/test_cairo/TEST_%s.png" % ("%04d" % text_i))
		image_surface.write_to_png(image_buffer)
		image_buffer.seek(0)
		result = {'name' : "FRAME_%04d" % text_i , 'buffer' : image_buffer} 
		text_i += 1
		return result

def _get_paragraph_style(element,context) :
	#FIXME
	pass

def _check_outline_style(style,context):
	result = None
	if re.match(r'^(.*-outline)[0-9]+$', style) is not None and context.has_key('list-level'):
		result = re.sub(r'^(.*-outline)[0-9]+$',r'\1',style)+'%d'%context['list-level']
#		print "Outline style : %s -> %s" % (style,result)
	return result

def _get_current_styles(element, context):
	doc_styles = context['styles']
	parent = element
	style_names = []

	#Get self and ancestors text style names
	while parent is not None:
		style_name = parent.get_text_style()
		if style_name is not None and style_name not in style_names:
			outline_checked = False
			outline = _check_outline_style(style_name,context)
			if outline is not None:
				style_name = outline
				outline_checked = True
			style = doc_styles[style_name]
			if (style.get_parent_style_name() is not None):
				new_list = []
				new_list.append(style_name)
				parent_style = style.get_parent_style_name()
				while parent_style is not None:
					if outline_checked is False:
						outline = _check_outline_style(parent_style,context)
						if outline is not None:
							parent_style = outline
							outline_checked = True
					new_list.append(parent_style)
					style = doc_styles[parent_style]
					parent_style = style.get_parent_style_name()
				
				style_names.extend(new_list)
			else:
				style_names.append(style_name)
		parent = parent.get_parent()

	style_names.append("standard")
	style_names.reverse()

	return style_names

def _get_list_style(style_names, context , level):
	any_style = ('(text:list-level-style-number'
                 '|text:list-level-style-bullet'
                 '|text:list-level-style-image)')
	doc_styles = context['styles']
	list_prop = None
	for s in style_names:
		style = doc_styles[s]

		level_style = _get_element(style,any_style,level=level)