#----------------------------------------------------------------------------
#
#	$Id : Text.pm 2.238 2010-01-27 JMG$
#
#	Created and maintained by Jean-Marie Gouarne
#	Copyright 2009 by Genicorp, S.A. (www.genicorp.com)
#
#-----------------------------------------------------------------------------

package OpenOffice::OODoc::Text;
use	5.008_000;
use	OpenOffice::OODoc::XPath	2.232;
our	@ISA		= qw ( OpenOffice::OODoc::XPath );
our	$VERSION	= 2.238;

#-----------------------------------------------------------------------------
# synonyms

BEGIN	{
	*findElementsByContent		= *selectElementsByContent;
	*replaceAll			= *selectElementsByContent;
	*findTextContent		= *selectTextContent;
	*getHeaderList			= *getHeadingList;
	*getHeaderTextList		= *getHeadingTextList;
	*getBibliographyElements	= *getBibliographyMarks;
	*bibliographyElementContent	= *bibliographyEntryContent;
	*setBibliographyElement		= *setBibliographyMark;
	*bookmarkElement		= *setBookmark;
	*removeBookmark			= *deleteBookmark;
	*getHeader			= *getHeading;
	*getHeaderContent		= *getHeadingContent;
	*getHeaderText			= *getHeadingText;
	*getOutlineLevel		= *getLevel;
	*setOutlineLevel		= *setLevel;
	*getSections			= *getSectionList;
	*getChapter			= *getChapterContent;
	*getParagraphContent		= *getParagraphText;
	*createTextBox			= *createTextBoxElement;
	*getTextBox			= *getTextBoxElement;
	*getTextBoxElements		= *getTextBoxElementList;
	*getList			= *getItemList;
	*getColumn			= *getTableColumn;
	*getRow				= *getTableRow;
	*getHeaderRow			= *getTableHeaderRow;
	*getCell			= *getTableCell;
	*getSheet			= *getTable;
	*selectTableByName		= *getTableByName;
	*getSheetByName			= *getTableByName;
	*getTableContent		= *getTableText;
	*normalizeTable			= *normalizeSheet;
	*normalizeTables		= *normalizeSheets;
	*expandSheet			= *expandTable;
	*insertColumn			= *insertTableColumn;
	*deleteColumn			= *deleteTableColumn;
	*replicateRow			= *replicateTableRow;
	*insertRow			= *insertTableRow;
	*appendRow			= *appendTableRow;
	*deleteRow			= *deleteTableRow;
	*appendHeader			= *appendHeading;
	*insertHeader			= *insertHeading;
	*removeHeader			= *removeHeading;
	*deleteHeading			= *removeHeading;
	*getNote			= *getNoteElement;
	*getNoteList			= *getNoteElementList;
	*getHeadingText			= *getHeadingContent;
	*getUserFieldElement		= *getUserField;
	*cellType			= *fieldType;
	*cellValueAttributeName		= *fieldValueAttributeName;
	*cellCurrency			= *fieldCurrency;
	*getStyle			= *textStyle;
	*setStyle			= *textStyle;
	}

#-----------------------------------------------------------------------------
# default text style attributes

our	%DEFAULT_TEXT_STYLE	=
	(
	references	=>
		{
		'style:name'			=> undef,
		'style:family'			=> 'paragraph',
		'style:parent-style-name'	=> 'Standard',
		'style:next-style-name'		=> 'Standard',
		'style:class'			=> 'text'
		},
	properties	=>
		{
		}
	);

#-----------------------------------------------------------------------------
# default delimiters for flat text export

our	%DEFAULT_DELIMITERS	=
	(
	'text:footnote-citation'	=>
		{
		begin	=>	'[',
		end	=>	']'
		},
	'text:note-citation'		=>
		{
		begin	=>	'[',
		end	=>	']'
		},
	'text:footnote-body'		=>
		{
		begin	=>	'{NOTE: ',
		end	=>	'}'
		},
	'text:note-body'		=>
		{
		begin	=>	'{NOTE: ',
		end	=>	'}'
		},
	'text:span'			=>
		{
		begin	=>	'<<',
		end	=>	'>>'
		},
	'text:list-item'		=>
		{
		begin	=>	'- ',
		end	=>	''
		},
	);

#-----------------------------------------------------------------------------

our $ROW_REPEAT_ATTRIBUTE       = 'table:number-rows-repeated';
our $COL_REPEAT_ATTRIBUTE       = 'table:number-columns-repeated';

#-----------------------------------------------------------------------------

sub	fieldType
	{
	my $self	= shift;
	my $field	= shift		or return undef;
	my $newtype	= shift;
	my $prefix	= 'office';
	unless ($self->{'opendocument'})
		{
		$prefix = $field->isTableCell() ? 'table' : 'text';
		}
	my $attribute	= $prefix . ':value-type';
	my $oldtype	= $field->att($attribute);
	unless (defined $newtype)
		{
		return $oldtype;
		}
	else
		{
		if (($newtype eq 'date') || ($newtype eq 'time'))
			{
			$field->del_att($prefix . ':value');
			}
		else
			{
			$field->del_att($prefix . ':date-value');
			$field->del_att($prefix . ':time-value');
			}
		return $field->set_att($attribute, $newtype);
		}
	}

sub	fieldValueAttributeName
	{
	my $self	= shift;
	my $field	= shift		or return undef;

	my $value_type	= ref $field ?
				$self->fieldType($field)	:
				$field;
	my $attribute	= "";

	my $prefix	= 'office';
	unless ($self->{'opendocument'})
		{
		$prefix = $field->isTableCell() ? 'table' : 'text';
		}

	if	(
			($value_type eq 'string')	||
			($value_type eq 'date')		||
			($value_type eq 'time')
		)
		{
		$attribute = $prefix . ':' . $value_type . '-value';
		}
	else
		{
		$attribute = $prefix . ':value';
		}
	return $attribute;
	}

#-----------------------------------------------------------------------------
# constructor

sub	new
	{
	my $caller	= shift;
	my $class	= ref($caller) || $caller;
	my %options	=
		(
		level_attr	=> 'text:outline-level',
		paragraph_style	=> 'Standard',
		heading_style	=> 'Heading_20_1',
		use_delimiters	=> 'on',
		field_separator	=> ';',
		line_separator	=> "\n",
		max_rows	=> 32,
		max_cols	=> 26,
		delimiters	=>
			{ %OpenOffice::OODoc::Text::DEFAULT_DELIMITERS },
		@_
		);
	$options{heading_style} = $options{header_style}
		if $options{header_style};

	my $object	= $class->SUPER::new(%options);

	if ($object)
		{
		bless $object, $class;
		unless ($object->{'opendocument'})
			{
			$object->{'level_attr'}		= 'text:level';
			$object->{'heading_style'}	= 'Heading 1';
			}
		}
	return $object;
	}

#-----------------------------------------------------------------------------
# getText() method adaptation for complex elements
# and text output "enrichment"
# (overrides getText from OODoc::XPath)

sub	getText
	{
	my $self	= shift;
	my $element	= $self->getElement(@_);
	return undef	unless ($element && $element->isElementNode);

	my $text	= undef;
	my $begin_text	= '';
	my $end_text	= '';

	my $line_break	= $self->{'line_separator'} || '';
	if ($self->{'use_delimiters'} && $self->{'use_delimiters'} eq 'on')
		{
		my $name	= $element->getName;
		$begin_text	=
		    defined $self->{'delimiters'}{$name}{'begin'}	?
		        $self->{'delimiters'}{$name}{'begin'}		:
			($self->{'delimiters'}{'default'}{'begin'} || '');
		$end_text	=
		    defined $self->{'delimiters'}{$name}{'end'}		?
		        $self->{'delimiters'}{$name}{'end'}		:
			($self->{'delimiters'}{'default'}{'end'} || '');
		}

	$text	= $begin_text;

	if	($element->isParagraph)
		{
		my $t = $self->SUPER::getText($element);
		$text .= $t if defined $t;
		}
	elsif	($element->isItemList)
		{
		return $self->getItemListText($element);
		}
	elsif	(
		$element->isListItem		||
		$element->isNoteBody		||
		$element->isTableCell		||
		$element->isSection		||
		$element->isTextBox
		)
		{
		$element = $element->first_child('draw:text-box')
			if ($element->hasTag('draw:frame'));
		my @paragraphs = $element->children(qr '^text:(p|h)$');
		while (@paragraphs)
			{
			my $p = shift @paragraphs;
			my $t = $self->SUPER::getText($p);
			$text .= $t if defined $t;
			$text .= $line_break if @paragraphs;
			}
		}
	elsif	($element->isNote)
		{
		my $b = $element->selectChildElement
				('text:(|foot|end)note-body');
		return $self->getText($b);
		}
	elsif	($element->isTable)
		{
		$text .= $self->getTableContent($element);
		}
	else
		{
		my $t = $self->SUPER::getText($element);
		$text .= $t if defined $t;
		}

	$text	.= $end_text;

	return $text;
	}

#-----------------------------------------------------------------------------
# use or don't use delimiters for flat text output

sub	outputDelimitersOn
	{
	my $self	= shift;
	$self->{'use_delimiters'}	= 'on' ;
	}

sub	outputDelimitersOff
	{
	my $self	= shift;
	$self->{'use_delimiters'}	= 'off';
	}

sub	defaultOutputTerminator
	{
	my $self	= shift;
	my $delimiter	= shift;
	$self->{'delimiters'}{'default'}{'end'} = $delimiter
		if defined $delimiter;
	return $self->{'delimiters'}{'default'}{'end'};
	}

#-----------------------------------------------------------------------------
# setText() method adaptation for complex elements
# overrides setText from OODoc::XPath

sub	setText
	{
	my $self	= shift;
	my $path	= shift;
	my $pos		= (ref $path) ? undef : shift;
	my $element	= $self->getElement($path, $pos);
	return undef	unless $element;

	return $self->SUPER::setText($element, @_) if $element->isParagraph;

	my $line_break	= $self->{'line_separator'} || '';
	if	($element->isItemList)
		{
		my @text	= @_;
		foreach my $line (@text)
			{
			$self->appendItem($element, text => $line);
			}
		return wantarray ? @text : join $line_break, @text;
		}
	elsif	($element->isListItem)
		{
		return $self->setItemText($element, @_);
		}
	elsif	($element->isTableCell)
		{
		return $self->updateCell($element, @_);
		}
	elsif	(
		$element->isNoteBody		||
		$element->isTableCell		||
		$element->isSection
		)
		{
		$element->cut_children;
		return $self->appendParagraph
			(
			attachment	=> $element,
			text		=> shift,
			@_
			);
		}
	elsif	($element->isTextBox)
		{
		return $self->setTextBoxContent($element, shift);
		}
	elsif	($element->isNote)
		{
		my $b = $element->selectChildElement
				('text:(|foot|end)note-body');
		return $self->setText($b, @_);
		}
	else
		{
		return $self->SUPER::setText($element, @_);
		}
	}

#-----------------------------------------------------------------------------
# get the whole text content of the document in a readable (non-XML) form
# result is a list of strings or a single string

sub	getTextContent
	{
	my $self	= shift;
	return $self->selectTextContent('.*', @_);
	}

#-----------------------------------------------------------------------------
# get/set the text:id attribute of a given element

sub	textId
	{
	my $self	= shift;
	my $element	= shift		or return undef;
	return $element->textId(@_);
	}

#-----------------------------------------------------------------------------
# selects headings, paragraph & list item elements matching a given pattern
# returns a list of elements
# if $action is defined, it's treated as a reference to a callback procedure
# to be executed for each node matching the pattern, with the node as arg.

sub	selectElementsByContent
	{
	my $self	= shift;
	my $pattern	= shift;

	my $context = $self->{'context'}->isa('OpenOffice::OODoc::Element') ?
			$self->{'context'} : $self->{'body'};


	my @elements	= ();
	foreach my $element ($context->getChildNodes)
		{
		next if
			(
				(! $element->isElementNode)
				||
				($element->isSequenceDeclarations)
			);
		push @elements, $element
			if (
				(! $pattern)
				||
				($pattern eq '.*')
				||
				(defined $self->_search_content
					($element, $pattern, @_, $element))
			   );
		}

	return @elements;
	}

#-----------------------------------------------------------------------------

sub	selectElementByTextId
	{
	my $self	= shift;
	my $id		= $self->inputTextConversion(shift) or return undef;
	return $self->getNodeByXPath("//*[\@text:id=\"$id\"]");
	}

#-----------------------------------------------------------------------------
# select the 1st element matching a given pattern

sub	selectElementByContent
	{
	my $self	= shift;
	my $pattern	= shift;

	my $context = $self->{'context'}->isa('OpenOffice::OODoc::Element') ?
			$self->{'context'} : $self->{'body'};

	foreach my $element ($context->getChildNodes)
		{
		next if
			(
				(! $element->isElementNode)
				||
				($element->isSequenceDeclarations)
			);
		return $element
			if (
				(! $pattern)
				||
				($pattern eq '.*')
				||
				(defined $self->_search_content
					($element, $pattern, @_, $element))
			   );
		}
	return undef;
	}

#-----------------------------------------------------------------------------
# selects texts matching a given pattern, with optional replacement on the fly
# returns the whole text content
# result is a list of strings or a single string

sub	selectTextContent
	{
	my $self	= shift;
	my $pattern	= shift;

	my $line_break	= $self->{'line_separator'} || '';
	my @lines	= ();

	my $context = $self->{'context'}->isa('OpenOffice::OODoc::Element') ?
			$self->{'context'} : $self->{'body'};

	foreach my $element ($context->getChildNodes)
		{
		next if
			(
				(! $element->isElementNode)
				||
				($element->isSequenceDeclarations)
			);
		push @lines, $self->getText($element)
			    if (
				(! $pattern)
				||
				($pattern eq '.*')
				||
				(defined $self->_search_content
					($element, $pattern, @_, $element))
			       );
		}
	return wantarray ? @lines : join $line_break, @lines;
	}

#-----------------------------------------------------------------------------
# get the list of text elements

sub	getTextElementList
	{
	my $self	= shift;

	my $context = $self->{'context'}->isa('OpenOffice::OODoc::Element') ?
			$self->{'context'} : $self->{'body'};

	return $self->selectChildElementsByName
			(
			$context,
			qr '^t(ext:(h|p|.*list|table.*)|able:.*)$',
			@_
			);
	}

#-----------------------------------------------------------------------------
# get the list of paragraph elements

sub	getParagraphList
	{
	my $self	= shift;
	return $self->getDescendants('text:p', @_)
	}

#-----------------------------------------------------------------------------
# get the paragraphs as a list of strings

sub	getParagraphTextList
	{
	my $self	= shift;

	return $self->getTextList('//text:p', @_);
	}

#-----------------------------------------------------------------------------
# get the list of heading elements

sub	getHeadingList
	{
	my $self	= shift;
	my %opt		= @_;
	my $path	= undef;

	unless ($opt{'level'})
		{
		return $self->getDescendants('text:h', $opt{'context'});
		}
	else
		{
		$path	=	'//text:h[@' . $self->{'level_attr'}	.
				'="' . $opt{'level'} . '"]';
		}
	return $self->getElementList($path, $opt{'context'});
	}

#-----------------------------------------------------------------------------
# get the headings as a list of strings

sub	getHeadingTextList
	{
	my $self	= shift;
	my @nodes	= $self->getHeadingList(@_);
	if (wantarray)
		{
		my @list = ();
		foreach my $node (@nodes)
			{
			push @list, $self->getText($node);
			}
		return @list;
		}
	else
		{
		my $text = "";
		my $separator = $self->{'line_separator'} || '';
		foreach my $node (@nodes)
			{
			$text .= $self->getText($node);
			$text .= $separator;
			}
		return $text;
		}
	}

#-----------------------------------------------------------------------------
# get the list of span elements (i.e. text elements distinguished from their
# containing paragraph by any kind of attribute such as font, color, etc)

sub	getSpanList
	{
	my $self	= shift;
	return $self->getDescendants('text:span', @_);
	}

#-----------------------------------------------------------------------------
# get the span elements as a list of strings

sub	getSpanTextList
	{
	my $self	= shift;

	return $self->getTextList('//text:span', @_);
	}

#-----------------------------------------------------------------------------
# set text spans that are attributed using a particular style

sub	setSpan
	{
	my $self	= shift;
	my $last	= pop;
	my $tag		= 'text:span';
	if ($last =~ /:/)
		{
		$tag = $last;
		}
	else
		{
		push @_, $last;
		}
	my $path	= shift;
	my $pos		= ref $path ? undef : shift;

	my $element	= undef;
	my $span	= undef;
	if (ref $path)
		{
		if ($path->isElementNode)
			{ $element = $path; }
		else
			{ return undef; }
		}
	else
		{
		my $context	= shift;
		unless (ref $context)
			{
			$element = $self->getElement($path, $pos)
					or return undef;
			unshift @_, $context;
			}
		else
			{
			$element = $self->getElement
						($path, $pos, $context)
					or return undef;
			}
		}
	my $expression = shift; return undef unless defined $expression;
	my $value = shift or return undef;
	my %attrs = (@_);
	if ($tag eq 'text:span')
		{
		$attrs{'text:style-name'}	= $value;
		}
	elsif ($tag eq 'text:a')
		{
		$attrs{'xlink:href'}		= $value;
		my $prefix = $attrs{'-prefix'} || 'text';
		delete $attrs{'-prefix'};
		foreach my $k (keys %attrs)
			{
			unless ($k =~ /:/)
				{
				$attrs{"$prefix:$k"} = $attrs{$k};
				delete $attrs{$k};
				}
			}
		}

	return $element->mark("($expression)", $tag, { %attrs });
	}

#-----------------------------------------------------------------------------

sub	textField
	{
	my $self	= shift;
	my $name	= shift;
	my %opt		=
		(
		'-prefix'	=> 'text',
		@_
		);
	return $self->create_field($name, %opt);
	}

#-----------------------------------------------------------------------------

sub	setTextField
	{
	my $self	= shift;
	my $path	= shift;
	my $pos		= ref $path ? undef : shift;

	my $element	= undef;
	my $span	= undef;
	if (ref $path)
		{
		if ($path->isElementNode)
			{ $element = $path; }
		else
			{ return undef; }
		}
	else
		{
		my $context	= shift;
		unless (ref $context)
			{
			$element = $self->getElement($path, $pos)
					or return undef;
			unshift @_, $context;
			}
		else
			{
			$element = $self->getElement
						($path, $pos, $context)
					or return undef;
			}
		}

	my $expression = shift; return undef unless defined $expression;
	my $field = shift or return undef;
	my $tag = $field =~ /:/ ? $field : "text:$field";
	my %attrs = (@_);
	my $prefix = $attrs{'-prefix'} || 'text'; delete $attrs{'-prefix'};
	foreach my $k (keys %attrs)
		{
		unless ($k =~ /:/)
			{
			$attrs{"$prefix:$k"} = $attrs{$k}; delete $attrs{$k};
			}
		}

	return $element->mark("($expression)", $tag, { %attrs });
	}

#-----------------------------------------------------------------------------

sub	extendText
	{
	my $self	= shift;
	my $path	= shift;
	my $pos		= (ref $path) ? undef : shift;
	my $element = $self->getElement($path, $pos) or return undef;
	my $text	= shift;
	return undef	unless defined $text;
	my $style	= shift;

	if (ref $text)
		{
		my $tagname = $text->getName;
		if ($tagname =~ /^text:(p|h)$/)
			{
			$text = $self->getFlatText($text);
			}
		}

	if ($style)
		{
		$text = $self->createElement('text:span', $text)
					unless ref $text;
		$self->textStyle($text, $style);
		}
	return $self->SUPER::extendText($element, $text, @_);
	}

#-----------------------------------------------------------------------------

sub	setHyperlink
	{
	my $self	= shift;
	return $self->setSpan(@_, 'text:a');
	}

#-----------------------------------------------------------------------------

sub	selectHyperlinkElements
	{
	my $self	= shift;
	my $url		= shift;
	return $self->selectElementsByAttribute
		('//text:a', 'xlink:href', $url, @_);
	}

#-----------------------------------------------------------------------------

sub	selectHyperlinkElement
	{
	my $self	= shift;
	my $url		= shift;
	return $self->selectElementByAttribute
		('//text:a', 'xlink:href', $url, @_);
	}

#-----------------------------------------------------------------------------

sub	hyperlinkURL
	{
	my $self	= shift;
	my $hl		= shift	or return undef;
	unless (ref $hl)
		{
		$hl = $self->selectHyperlinkElement($hl);
		return undef unless $hl;
		}
	my $url		= shift;
	if ($url)
		{
		$self->setAttribute($hl, 'xlink:href', $url);
		}
	return $self->getAttribute($hl, 'xlink:href');
	}

#-----------------------------------------------------------------------------

sub	setAnnotation
	{
	my $self	= shift;
	my $path	= shift;
	my $pos		= ref $path ? undef : shift;
	my $element	= $self->getElement($path, $pos);
	my %opt		=
		(
		'offset'	=> 0,
		'text'		=> "",
		'style'		=> 'Standard',
		@_
		);

	my $creator = $opt{'creator'} || $opt{'author'} || $ENV{'USER'};
	my $date = (defined $opt{'date'}) ?
		$opt{'date'}	:
		OpenOffice::OODoc::XPath::odfLocaltime();
	my $annotation	= $element->insertNewNode
		('office:annotation', 'within', $opt{'offset'});
	$self->appendElement
		($annotation, 'dc:creator', text => $creator);
	$self->appendElement
		($annotation, 'dc:date', text => $date);
	$self->appendParagraph
		(
		attachment	=> $annotation,
		text		=> $opt{'text'},
		style		=> $opt{'style'}
		);

	return $annotation;
	}

#-----------------------------------------------------------------------------
# creates and inserts a footnote or endnote

sub     setNote
        {
        my $self	= shift;
	my $path	= shift;
	my $pos		= ref $path ? undef : shift;
	my $element	= $self->getElement($path, $pos)   or return undef;
	my $text        = shift;
	my %opt		=
		(
		'offset'	=> 0,
		'text'		=> "",
		'style'		=> 'Standard',
		'citation'      => undef,
		'id'            => undef,
		'class'         => 'footnote',
		'label'         => undef,
		@_
		);
        my $note = $element->insertNewNode
                ('text:note', 'within', $opt{'offset'});
        $self->setAttributes
                (
                $note,
                'text:id'               => $opt{'id'},
                'text:note-class'       => $opt{'class'}
                );
        my $note_citation       = $note->appendChild('text:note-citation');
        if (defined $opt{'label'})
                {
                $self->setAttribute
                        ($note_citation, 'text:label', $opt{'label'});
                $opt{'citation'} = $opt{'label'}
                        unless defined $opt{'citation'};
                }
        $self->setText($note_citation, $opt{'citation'});
        my $note_body           = $note->appendChild('text:note-body');
        $self->appendParagraph
                (
                attachment      => $note_body,
                text            => $text,
                style           => $opt{'style'}
                );
        return $note;
        }

#-----------------------------------------------------------------------------

sub	removeSpan
	{
	my $self	= shift;
	my $path	= shift;
	my $pos		= ref $path ? undef : shift;
	my $tagname	= shift	|| 'text:span';

	my $element	= ref $path ?
				$path	:
				$self->getElement($path, @_);
	return undef	unless $element;

	my $text	= "";
	my @nodes	= $element->getChildNodes;
	my $n		= undef;
	my $last_text_node = undef;
	foreach $n (@nodes)
		{
		if	($n->isTextNode)
			{
			$last_text_node	= $n;
			}
		elsif	($n->isElementNode && $n->hasTag($tagname))
			{
			my $t = $n->string_value;
			if ($last_text_node)
				{
				$last_text_node->append_pcdata($t);
				}
			else
				{
				$last_text_node =
				    OpenOffice::OODoc::XPath::new_text_node($t);
				$element->insertBefore($last_text_node, $n);
				}
			$n->delete;
			}
		}

	return $element;
	}

#-----------------------------------------------------------------------------

sub	removeHyperlink
	{
	my $self	= shift;
	return $self->removeSpan(@_, 'text:a');
	}

#-----------------------------------------------------------------------------
# get all the bibliographic entries

sub	getBibliographyMarks
	{
	my $self	= shift;
	my $id		= shift;

	unless ($id)
		{
		return $self->getDescendants('text:bibliography-mark');
		}
	else
		{
		return $self->selectElementsByAttribute
			(
			'//text:bibliography-mark', 'text:identifier',
			$id, @_
			);
		}
	}

#-----------------------------------------------------------------------------
# get/set the content of a bibliography entry

sub	bibliographyEntryContent
	{
	my $self	= shift;
	my $id		= shift;
	my $e		= undef;
	my %desc	= @_;
	unless (ref $id)
		{
		my $i = $self->inputTextConversion($id);
		$e = $self->getNodeByXPath
		      (
		      "//text:bibliography-mark[\@text:identifier=\"$i\"]",
		      $desc{'context'}
		      );
		}
	else
		{
		$e = $id;
		}
	return undef unless $e;

	my $k = undef;
	foreach $k (keys %desc)
		{
		next if $k =~ /:/;
		my $v = $desc{$k};
		delete $desc{$k};
		$k = 'text:' . $k;
		$desc{$k} = $v;
		}
	$self->setAttributes($e, %desc);
	%desc = $self->getAttributes($e);
	foreach $k (keys %desc)
		{
		my $new_key = $k;
		$new_key =~ s/^text://;
		my $v = $desc{$k}; delete $desc{$k}; $desc{$new_key} = $v;
		}
	return %desc;
	}

#-----------------------------------------------------------------------------
# inserts a new bibliography entry within a text element

sub	setBibliographyMark
	{
	my $self	= shift;
	my $path	= shift;
	my $pos		= ref $path ? undef : shift;
	my $element	= $self->getElement($path, $pos);
	my $offset	= shift;
	my %opt		= @_;
# 	my $bib	= $self->createElement('text:bibliography-mark');
	my $bib	= OpenOffice::OODoc::Element->new('text:bibliography-mark');
	$bib->paste_within($element, $offset);
	$self->bibliographyEntryContent($bib, @_);
	return $bib;
	}

#-----------------------------------------------------------------------------
# get a bookmark

sub	getBookmark
	{
	my $self	= shift;
	my $name	= $self->inputTextConversion(shift);

	return	(
		$self->getNodeByXPath
			("//text:bookmark[\@text:name=\"$name\"]")
			||
		$self->getNodeByXPath
			("//text:bookmark-start[\@text:name=\"$name\"]")
		);
	}

#-----------------------------------------------------------------------------
# retrieve the element where is a given bookmark

sub	selectElementByBookmark
	{
	my $self	= shift;

	my $bookmark	= $self->getBookmark(@_);
	return $bookmark ? $bookmark->parent : undef;
	}

#-----------------------------------------------------------------------------
# attach a bookmark to a given element

sub	setBookmark
	{
	my $self	= shift;
	my $path	= shift;
	my $element     = ref $path ? $path : $self->getElement($path, shift);
	return undef unless $element;
	my $name	= shift;
	my $offset	= shift || 0;
	unless ($name)
		{
		warn	"[" . __PACKAGE__ . "::setBookmark] "	.
			"Missing bookmark name\n";
		return undef;
		}
	my $bookmark	= OpenOffice::OODoc::XPath::new_element
						('text:bookmark', @_);
	$self->setAttribute($bookmark, 'text:name', $name);
	return $bookmark->paste_within($element, $offset);
	}

#-----------------------------------------------------------------------------
# delete a bookmark

sub	deleteBookmark
	{
	my $self	= shift;

	$self->removeElement($self->getBookmark(@_));
	}

#-----------------------------------------------------------------------------
# get the footnote bodies in the document

sub	getFootnoteList
	{
	my $self	= shift;

	my $xpath = $self->{'opendocument'}	?
	    '//text:note[@text:note-class="footnote"]/text:note-body' :
	    '//text:footnote-body';
	return $self->getElementList($xpath, @_);
	}

#-----------------------------------------------------------------------------
# get the footnote citations in the document

sub	getFootnoteCitationList
	{
	my $self	= shift;

	my $xpath = $self->{'opendocument'}	?
	    '//text:note[@text:note-class="footnote"]/text:note-citation' :
	    '//text:footnote-citation';
	return $self->getElementList($xpath, @_);
	}

#-----------------------------------------------------------------------------
# get the endnote bodies in the document

sub	getEndnoteList
	{
	my $self	= shift;

	my $xpath = $self->{'opendocument'}	?
	    '//text:note[@text:note-class="endnote"]/text:note-body' :
	    '//text:endnote-body';
	return $self->getElementList($xpath, @_);
	}

#-----------------------------------------------------------------------------
# get the endnote citations in the document

sub	getEndnoteCitationList
	{
	my $self	= shift;

	my $xpath = $self->{'opendocument'}	?
	    '//text:note[@text:note-class="endnote"]/text:note-citation' :
	    '//text:endnote-citation';
	return $self->getElementList($xpath, @_);
	}

#-----------------------------------------------------------------------------
# get the note citations in the document (ODF only)

sub	getNoteCitationList
	{
	my $self	= shift;
	return $self->getDescendants('text:note-citation', @_);
	}

#-----------------------------------------------------------------------------

sub	getNoteElementList
	{
	my $self	= shift;
	my $class	= shift;

	unless ($class)
		{
		if ($self->{'opendocument'})
			{
			return $self->getElementList('//text:note');
			}
		else
			{
			return	(
				$self->getElementList('//text:footnote'),
				$self->getElementList('//text:endnote')
				);
			}
		}
	elsif (($class eq 'footnote') or ($class eq 'endnote'))
		{
		if ($self->{'opendocument'})
			{
			return $self->getElementList
			    ("//text:note\[\@text:note-class=\"$class\"\]");
			}
		else
			{
			return $self->getElementList("//text:$class");
			}
		}
	else
		{
		warn	"[" . __PACKAGE__ . "::getNoteElementList] " .
			"Unknown note class $class\n";
		return undef;
		}
	}

#-----------------------------------------------------------------------------
# retrieve a note element using its identifier (ODF only)

sub	getNoteElement
	{
	my $self	= shift;
	my $p1		= shift;
	if (ref $p1)
		{
		return $p1->isNote ? $p1 : undef;
		}
	else
		{
		unshift @_, $p1;
		}
	my %opt		= @_;

	my $xpath	= undef;
	my $id		= $opt{id};
	my $class	= $opt{class};
	my $citation	= $opt{citation};

	if ($id)
		{
		unless ($self->{'opendocument'})
			{
			return	$self->getElement
				    ("//text:$class\[\@text:id=\"$id\"\]")
				    if $class;
			return 	$self->getElement
				    ("//text:footnote\[\@text:id=\"$id\"\]")
					||
				$self->getElement
				    ("//text:endnote\[\@text:id=\"$id\"\]");
			}
		else
			{
			my $xpath = $class ?
			    "//text:note\[\@text:note-class=\"$class\"" .
			    " and \@text:id=\"$id\"\]"			:
			    "//text:note\[\@text:id=\"$id\"\]";
			return $self->getElement($xpath);
			}
		}
	elsif ($class && defined $citation)
		{
		my @list = $self->getNoteElementList($class);
		my $tagname = $self->{'opendocument'} ?
			"text:note-citation" : "text:$class-citation";
		foreach my $elt (@list)
			{
			next unless $elt;
			my $text = $self->getFlatText
					($elt->first_child($tagname));
			return $elt if $text eq $citation;
			}
		return undef;
		}
	else
		{
		warn	"[" . __PACKAGE__ . "::getNoteElement] " .
			"Requires (Id) OR (class AND citation)\n";
		return undef;
		}
	}

#-----------------------------------------------------------------------------

sub	getNoteClass
	{
	my $self	= shift;
	my $element	= shift	or return undef;
	unless (ref $element)
		{
		unshift @_, $element;
		$element = $self->getNoteElement(@_) or return undef;
		}
	return $element->getNoteClass;
	}

#-----------------------------------------------------------------------------
# get the list of tables in the document

sub	getTableList
	{
	my $self	= shift;
	return $self->getElementList('//table:table', @_);
	}

#-----------------------------------------------------------------------------
# get a heading element selected by position number and level

sub	getHeading
	{
	my $self	= shift;
	my $pos		= shift;
	my %opt		= (@_);
	my $heading	= undef;

	if (ref $pos)
		{
		return undef unless $pos->isHeading;
		if ($opt{'level'})
			{
			my $level = $pos->att($self->{'level_attr'});
			return undef unless
				($level && ($level == $opt{'level'}));
			}
		return $pos;
		}

	unless ($opt{'level'})
		{
		$heading = $self->getElement
				('//text:h', $pos, $opt{'context'});
		}
	else
		{
		my $path	=	'//text:h[@'		.
					$self->{'level_attr'}	.
					'="' . $opt{'level'} . '"]';
		$heading = $self->getElement
			($path, $pos, $opt{'context'});
		}
	return undef unless $heading;
	}

#-----------------------------------------------------------------------------
# get the text of a heading element

sub	getHeadingContent
	{
	my $self	= shift;
	return $self->getText('//text:h', @_);
	}

#-----------------------------------------------------------------------------
# get the level attribute (if defined) of an element
# the level must be defined for heading elements

sub	getLevel
	{
	my $self	= shift;
	my $path	= shift;
	my $pos		= (ref $path) ? undef : shift;

	my $element	= $self->getElement($path, $pos, @_);
	return $element->getAttribute($self->{'level_attr'}) || "";
	}

#-----------------------------------------------------------------------------

sub	setLevel
	{
	my $self	= shift;
	my $path	= shift;
	my $pos		= (ref $path) ? undef : shift;
	my $level	= shift;

	my $element	= $self->getElement($path, $pos, @_) or return undef;
	return $element->setAttribute($self->{'level_attr'} => $level);
	}

#-----------------------------------------------------------------------------

sub	makeHeading
	{
	my $self	= shift;
	my %opt		= @_;
	my $element	= $opt{'element'};
	if ($element)
		{
		$element->set_name('text:h');
		}
	else
		{
		$element = $self->createElement('text:h');
		}
	if ($opt{'level'})
		{
		$element->set_att($self->{'level_attr'}, $opt{'level'});
		}
	my $style = $opt{'style'} ? $opt{'style'} : $self->{'heading_style'};
	$self->setAttribute($element, 'text:style-name', $style);
	if (defined $opt{'text'})
		{
		$self->setText($element, $opt{'text'});
		}
	return $element;
	}

#-----------------------------------------------------------------------------

sub	getSection
	{
	my $self	= shift;
	my $name	= shift;
	return undef unless defined $name;

	if (ref $name)
		{
		return ($name->isSection) ? $name : undef;
		}
	my $context	= shift;
	if (($name =~ /^\d*$/) || ($name =~ /^[\d+-]\d+$/))
		{
		return $self->getElement('//text:section', $name, $context);
		}

	my $n = $self->inputTextConversion($name);
	return $self->getNodeByXPath
			(
			"//text:section[\@text:name=\"$n\"]"
			);
	}

#-----------------------------------------------------------------------------

sub	getSectionList
	{
	my $self	= shift;
	return $self->getDescendants('text:section', @_);
	}

#-----------------------------------------------------------------------------

sub	sectionStyle
	{
	my $self	= shift;
	my $section	= $self->getSection(shift) or return undef;
	my $new_style	= shift;
	return $new_style ?
		$self->setAttribute($section, 'text:style-name', $new_style) :
		$self->getAttribute($section, 'text:style-name');
	}

#-----------------------------------------------------------------------------

sub	renameSection
	{
	my $self	= shift;
	my $section	= $self->getSection(shift) or return undef;
	my $newname	= shift or return undef;

	if ($self->getSection($newname))
		{
		warn	"[" . __PACKAGE__ . "::renameSection] " .
			"Section name $newname already in use\n";
		return undef;
		}
	return $self->setAttribute($section, 'text:name' => $newname);
	}

#-----------------------------------------------------------------------------

sub	sectionName
	{
	my $self	= shift;
	my $section	= $self->getSection(shift) or return undef;
	my $newname	= shift;
	return $newname ?
		$self->renameSection($section, $newname)	:
		$self->getAttribute($section, 'text:name');
	}

#-----------------------------------------------------------------------------

sub	appendSection
	{
	my $self	= shift;
	my $name	= shift;
	my %opt		=
			(
			'attachment'	=> $self->{'body'},
			'style'		=> $name,
			'protected'	=> 'false',
			@_
			);

	if ($self->getSection($name, $self->{'xpath'}))
		{
		warn	"[" . __PACKAGE__ . "::appendSection] "	.
			"Section $name exists\n";
		return	undef;
		}

	my $link	= undef;
	if ($opt{"link"})
		{
		$link	= $opt{'link'}; delete $opt{'link'}
		}

	my $section = $self->appendElement
			(
			$opt{'attachment'}, 'text:section',
			attribute =>
			    {
			    'text:name'			=> $name,
			    'text:style-name'		=> $opt{'style'}
			    },
			%opt
			)
			or return undef;

	$self->insertSubdocument
		($section, $link, $opt{'filter'}) if $link;
	$section->set_att('text:protected', $opt{'protected'})
			if $opt{'protected'};
	$section->set_att('text:protection-key', $opt{'key'})
			if $opt{'key'};

	return $section;
	}

#-----------------------------------------------------------------------------

sub	lockSection
	{
	my $self	= shift;
	my $section	= $self->getSection(shift)	or return undef;
	$section->set_att('text:protected', 'true');
	my $key		= shift;
	$section->set_att('text:protection-key', $key) if $key;
	}

sub	unlockSection
	{
	my $self	= shift;
	my $section	= $self->getSection(shift)	or return undef;
	$section->del_att('text:protected');
	my $key		= $section->att('text:protection-key');
	$section->del_att('text:protection-key');
	return $key;
	}

sub	unlockSections
	{
	my $self	= shift;
	foreach my $section ($self->getSectionList(@_))
		{
		$self->unlockSection($section);
		}
	}

sub	sectionProtectionKey
	{
	my $self	= shift;
	my $section	= $self->getSection(shift)	or return undef;
	return $section->att('text:protection-key');
	}

#-----------------------------------------------------------------------------

sub	insertSection
	{
	my $self	= shift;
	my $path	= shift;
	my $pos		= ref $path ? undef : shift;
	my $name	= shift;
	my %opt		=
			(
			'style'		=> $name,
			'protected'	=> 'false',
			@_
			);
	my $posnode	= $self->getElement($path, $pos, $opt{'context'})
				or return undef;

	if ($self->getSection($name, $self->{'xpath'}))
		{
		warn	"[" . __PACKAGE__ . "::insertSection] "	.
			"Section $name exists\n";
		return	undef;
		}

	my $link	= undef;
	if ($opt{"link"})
		{
		$link	= $opt{'link'}; delete $opt{'link'}
		}

	my $section = $self->insertElement
			(
			$posnode, 'text:section',
			attribute =>
			    {
			    'text:name'			=> $name,
			    'text:style-name'		=> $opt{'style'}
			    },
			%opt
			)
			or return undef;

	$self->insertSubdocument
		($section, $link, $opt{'filter'}) if $link;
	$section->set_att('text:protected', $opt{'protected'})
			if $opt{'protected'};
	$section->set_att('text:protection-key', $opt{'key'})
			if $opt{'key'};

	return $section;
	}

#-----------------------------------------------------------------------------
# link a section to a subdocument

our	$section_source_tag	= "text:section-source";

sub	insertSubdocument
	{
	my $self	= shift;
	my $section_id	= shift;
	my $url		= shift;
	my %attr	= ();

	my $section	= $self->getSection($section_id);
	unless ($section)
		{
		warn	"[" . __PACKAGE__ . "::insertSubdocument] "	.
			"Non existing target section\n";
		return undef;
		}
	my $doclink	=
		$section->first_child($section_source_tag)
				||
		$self->appendElement($section, $section_source_tag);

	if ($attr{'filter'})
		{
		$attr{'text:filter-name'} = $attr{'filter'};
		delete $attr{'filter'};
		}
	$self->setAttributes($doclink, "xlink:href" => $url, %attr);

	return $doclink;
	}

#-----------------------------------------------------------------------------
# get the content depending on a given heading element

sub	getChapterContent
	{
	my $self	= shift;
	my $h		= shift || 0;
	my $heading	= ref $h ? $h : $self->getHeading($h, @_);
	return undef unless $heading;
	my @list	= ();
	my $level	= $self->getLevel($heading) or return @list;

	my $next_element	= $heading->next_sibling;
	while ($next_element)
		{
		my $l = $self->getLevel($next_element);
		last if ($l && $l <= $level);
		push @list, $next_element;
		$next_element = $next_element->next_sibling;
		}

	return @list;
	}

#-----------------------------------------------------------------------------

sub	moveElementsToSection
	{
	my $self	= shift;
	my $section	= $self->getSection(shift) or return undef;
	$section->pickUpChildren(@_);
	return $section;
	}

#-----------------------------------------------------------------------------
# get a paragraph element selected by number

sub	getParagraph
	{
	my $self	= shift;
	return $self->getElement('//text:p', @_);
	}

#-----------------------------------------------------------------------------
# same as getParagraph() but only among the 1st level paragraphs
# and only in text documents

sub	getTopParagraph
	{
	my $self	= shift;
	my $path = $self->{'opendocument'} ?
		'//office:body/office:text/text:p'	:
		'//office:body/text:p';
	return $self->getElement($path, @_);
	}

#-----------------------------------------------------------------------------
# select paragraphs by stylename

sub	selectParagraphsByStyle
	{
	my $self	= shift;
	return $self->selectElementsByAttribute
		('//text:p', 'text:style-name', @_);
	}

#-----------------------------------------------------------------------------
# select a single paragraph by stylename

sub	selectParagraphByStyle
	{
	my $self	= shift;
	return $self->selectElementByAttribute
		('//text:p', 'text:style-name', @_);
	}

#-----------------------------------------------------------------------------
# get text content of a paragraph

sub	getParagraphText
	{
	my $self	= shift;
	return $self->getText('//text:p', @_);
	}

#-----------------------------------------------------------------------------
# select a draw page by name

sub	selectDrawPageByName
	{
	my $self	= shift;
	my $text	= $self->inputTextConversion(shift);
	return $self->selectNodeByXPath
			("//draw:page\[\@draw:name=\"$text\"\]", @_);
	}
#-----------------------------------------------------------------------------
# get a draw page by position or name

sub	getDrawPage
	{
	my $self	= shift;
	my $p		= shift;
	return undef unless defined $p;
	if (ref $p)	{ return ($p->isDrawPage) ? $p : undef; }
	if ($p =~ /^[\-0-9]*$/)
		{
		return $self->getElement('//draw:page', $p, @_);
		}
	else
		{
		return $self->selectDrawPageByName($p, @_);
		}
	}

#-----------------------------------------------------------------------------

sub	getDrawPages
	{
	my $self	= shift;
	return $self->getDescendants('draw:page', @_);
	}

#-----------------------------------------------------------------------------
# create a draw page (to be inserted later)

sub	createDrawPage
	{
	my $self        = shift;
	my $class	= $self->contentClass;
	unless ($class eq 'presentation' || $class eq 'drawing')
		{
		warn	"[" . __PACKAGE__ . "::createDrawPage] "	.
			"Unsupported operation for this document\n";
		return undef;
		}
        my %opt         = @_;
        my $body        = $self->getBody;

        my $p = $self->createElement('draw:page');
        $self->setAttribute($p, 'draw:name' => $opt{'name'})
                        if $opt{'name'};
        $self->setAttribute($p, 'draw:id' => $opt{'id'})
                        if $opt{'id'};
        $self->setAttribute($p, 'draw:style-name' => $opt{'style'})
                        if $opt{'style'};
        $self->setAttribute($p, 'draw:master-page-name' => $opt{'master'})
                        if $opt{'master'};
        return $p;
	}

#-----------------------------------------------------------------------------
# append a new draw page to the document

sub	appendDrawPage
	{
	my $self        = shift;
        my $page	= $self->createDrawPage(@_) or return undef;
        my $body        = $self->getBody;
        $self->appendElement($body, $page);
        return $page;
 	}

#-----------------------------------------------------------------------------
# insert a new draw page before or after an existing one

sub	insertDrawPage
	{
	my $self	= shift;
	my $pos		= shift	or return undef;
	my $pos_page	= $self->getDrawPage($pos);
	unless ($pos_page)
		{
		warn	"[" . __PACKAGE__ . "::insertDrawPage] "	.
			"Unknown position\n";
		return undef;
		}
	my %opt = @_;
	my $page = $self->createDrawPage(%opt) or return undef;
	$self->insertElement($pos_page, $page, position => $opt{'position'});

	return $page;
	}

#-----------------------------------------------------------------------------

sub	drawPageAttribute
	{
	my $self	= shift;
	my $att		= shift;
	my $pos		= shift;
	my $page	= $self->getDrawPage($pos)	or return undef;
	my $value	= shift;

	return $value ?
		$self->setAttribute($page, $att, $value)	:
		$self->getAttribute($page, $att);
	}

#-----------------------------------------------------------------------------

sub	drawPageName
	{
	my $self	= shift;
	return $self->drawPageAttribute('draw:name', @_);
	}

#-----------------------------------------------------------------------------

sub	drawPageStyle
	{
	my $self	= shift;
	return $self->drawPageAttribute('draw:style-name', @_);
	}

#-----------------------------------------------------------------------------

sub	drawPageId
	{
	my $self	= shift;
	return $self->drawPageAttribute('draw:id', @_);
	}

#-----------------------------------------------------------------------------

sub	drawMasterPage
	{
	my $self	= shift;
	return $self->drawPageAttribute('draw:master-page-name', @_);
	}

#-----------------------------------------------------------------------------

sub	createTextBoxElement
	{
	my $self	= shift;
	my %opt		= @_;
	my $frame	= undef;
	my $text_box	= undef;
	if ($self->{'opendocument'})
		{
		$frame = $self->createFrame(tag => 'draw:frame', %opt);
		$text_box = $self->appendElement($frame, 'draw:text-box');
		}
	else
		{
		$text_box = $self->createFrame(tag => 'draw:text-box', %opt);
		$frame = $text_box;
		}
	if ($opt{'content'})
		{
		if (ref $opt{'content'})
			{
			$opt{'content'}->paste_last_child($text_box);
			}
		else
			{
			$self->appendParagraph
				(
				attachment	=> $text_box,
				text		=> $opt{'content'}
				);
			}
		}
	return wantarray ? ($frame, $text_box) : $text_box;
	}

#-----------------------------------------------------------------------------

sub	getTextBoxElement
	{
	my $self	= shift;
	my $tb		= shift;
	return undef unless defined $tb;

	if (ref $tb)
		{
		my $name = $tb->getName;
		if ($name eq 'draw:frame')
			{
			return $tb->first_child('draw:text-box') ?
					$tb : undef;
			}
		elsif ($name eq 'draw:text-box')
			{
			return $tb unless $self->{'opendocument'};
			my $frame = $tb->parent;
			return $frame->isFrame ? $frame : undef;
			}
		else
			{
			return undef;
			}
		}
	else
		{
		if ($tb =~ /^[\-0-9]*$/)
			{
			my $e = $self->getElement('//draw:text-box', $tb, @_);
			return $self->{'opendocument'} ?
				$e->parent() : $e;
			}
		else
			{
			return $self->selectTextBoxElementByName($tb, @_);
			}
		}
	}

#-----------------------------------------------------------------------------

sub	setTextBoxContent
	{
	my $self	= shift;
	my $frame = $self->getTextBoxElement(shift) or return undef;

	if ($frame->isFrame)
		{
		$frame = $frame->first_child('draw:text-box')
			or return undef;
		}

	$frame->cut_children;
	my $content	= shift;
	if (ref $content)
		{
		$content->paste_last_child($frame);
		return $content;
		}
	else
		{
		return $self->appendParagraph
			(
			attachment	=> $frame,
			text		=> $content
			);
		}
	}

#-----------------------------------------------------------------------------
# text box attributes accessors

sub	textBoxCoordinates
	{
	my $self	= shift;
	my $tb		= $self->getTextBoxElement(shift) or return undef;
	my $coord	= shift;
	return (defined $coord) ?
		$self->setObjectCoordinates($tb, $coord)	:
		$self->getObjectCoordinates($tb);
	}

sub	textBoxSize
	{
	my $self	= shift;
	my $tb		= $self->getTextBoxElement(shift) or return undef;
	my $size	= shift;
	return (defined $size) ?
		$self->setObjectSize($tb, $size)	:
		$self->getObjectSize($tb);
	}

sub	textBoxDescription
	{
	my $self	= shift;
	my $tb		= $self->getTextBoxElement(shift) or return undef;
	my $description	= shift;
	return (defined $description) ?
		$self->setObjectDescription($tb, $description)	:
		$self->getObjectDescription($tb);
	}

sub	textBoxName
	{
	my $self	= shift;
	my $tb		= $self->getTextBoxElement(shift) or return undef;
	return $self->objectName($tb, shift);
	}

#-----------------------------------------------------------------------------

sub	selectTextBoxElementByName
	{
	my $self	= shift;
	my $tag = $self->{'opendocument'} ? 'draw:frame' : 'draw:text-box';
	my $frame = $self->getFrameElement(shift, $tag);
	if ($self->{'opendocument'})
		{
		return undef unless ($frame->first_child('draw:text-box'));
		}
	return $frame;
	}

#-----------------------------------------------------------------------------

sub	getTextElementist
	{
	my $self	= shift;
	my $context	= shift;
	my @tblist = $self->getDescendants('draw:text-box', $context);
	return @tblist unless $self->{'opendocumpent'};
	my @frlist = ();
	foreach my $tb (@tblist)
		{
		push @frlist, $tb->parent;
		}
	return @frlist;
	}

#-----------------------------------------------------------------------------
# get list element

sub	getItemList
	{
	my $self	= shift;
	my $pos		= shift;
	if (ref $pos)
		{
		return $pos->isItemList ? $pos : undef;
		}
	return $self->getElement('//text:list', $pos, @_);
	}

#-----------------------------------------------------------------------------
# return the text content of an item list (in array or string)

sub	getItemListText
	{
	my $self	= shift;
	my $list	= $self->getItemList(@_) or return undef;
	my @items	= $list->children('text:list-item');
	if (wantarray)
		{
		my @result = ();
		foreach my $item (@items)
			{
			push @result, $self->getItemText($item);
			}
		return @result;
		}
	else
		{
		my $tagname	= $list->getName;
		my $line_break	=
			$self->{'line_separator'} || '';
		my $item_begin	=
			$self->{'delimiters'}{'text:p'}{'begin'} || '';
		my $item_end	=
			$self->{'delimiters'}{'text:p'}{'end'} || '';
		my $result	=
			$self->{'delimiters'}{$tagname}{'begin'} || '';
		my $end_list	=
			$self->{'delimiters'}{$tagname}{'end'} || '';
		my $count = 0;
		foreach my $item (@items)
			{
			$result .= $line_break if $count > 0;
			$result .= $item_begin;
			$result .= ($self->getItemText($item) || "");
			$result .= $item_end;
			$count++;
			}
		$result .= $end_list;
		return $result;
		}
	}

#-----------------------------------------------------------------------------
# get ordered list root element

sub	getOrderedList
	{
	my $self	= shift;
	my $pos		= shift;
	if (ref $pos)
		{
		return $pos->isOrderedList ? $pos : undef;
		}
	return $self->getElement('//text:ordered-list', $pos, @_);
	}

#-----------------------------------------------------------------------------
# get unordered list root element

sub	getUnorderedList
	{
	my $self	= shift;
	my $pos		= shift;
	if (ref $pos)
		{
		return $pos->isUnorderedList ? $pos : undef;
		}
	return $self->getElement('//text:unordered-list', $pos, @_);
	}

#-----------------------------------------------------------------------------
# get item elements list

sub	getItemElementList
	{
	my $self	= shift;
	my $list	= shift;
	return $list->children('text:list-item');
	}

#-----------------------------------------------------------------------------

sub	getListItem
	{
	my $self	= shift;
	my $list	= $self->getItemList(shift) or return undef;
	return $list->child(shift, 'text:list-item');
	}

#-----------------------------------------------------------------------------
# get item element text

sub	getItemText
	{
	my $self	= shift;
	my $item	= shift;

	return	undef	unless $item;
	my $para = $item->selectChildElement('text:(p|h)');
	return $para ? $self->getText($para) : undef;
	}

#-----------------------------------------------------------------------------
# set item element text

sub	setItemText
	{
	my $self	= shift;
	my $item	= shift;
	return	undef	unless $item;
	my $text	= shift;
	return undef unless (defined $text);

	my $para =	$item->selectChildElement('text:(p|h)')
				||
			$self->appendElement($item, 'text:p');
	return	$self->setText($para, $text);
	}

#-----------------------------------------------------------------------------
# get item element style

sub	getItemStyle
	{
	my $self	= shift;
	my $item	= shift;
	return	undef	unless $item;

	my $para	= $item->selectChildElement('text:(p|h)');
	return	$self->textStyle($para);
	}

#-----------------------------------------------------------------------------
# set item element style

sub	setItemStyle
	{
	my $self	= shift;
	my $item	= shift;
	return	undef	unless $item;
	my $style	= shift;

	my $para	= $item->selectChildElement('text:(p|h)');
	return	$self->textStyle($para, $style);
	}

#-----------------------------------------------------------------------------
# append a new item in a list

sub	appendListItem
	{
	my $self	= shift;
	my $list	= shift;
	return	undef	unless $list;
	my %opt		=
			(
			type	=> 'text:p',
			@_
			);

	my $type	= $opt{'type'};

	my $item	= $self->appendElement($list, 'text:list-item');
	return $item unless $type;

	my $text	= $opt{'text'};
	my $style	= $opt{'style'};
	$style	= $opt{'attribute'}{'text:style-name'}	unless $style;

	unless ($style)
		{
		my $first_item	= $list->selectChildElement('text:list-item');
		if ($first_item)
			{
			my $p	= $first_item->selectChildElement
					('text:(p|h)');
			$style	= $self->textStyle($p)	if ($p);
			}
		}

	if	($type eq 'paragraph')	{ $type = 'text:p'; }
	elsif	($type eq 'heading')	{ $type = 'text:h'; }

	my $para	= $self->appendElement
					(
					$item, $type,
					text => $text
					);
	$style	= $self->{'paragraph_style'}	unless $style;
	$opt{'attribute'}{'text:style-name'} = $style;
	$self->setAttributes($para, %{$opt{'attribute'}});

	return $item;
	}

sub	appendItem
	{
	my $self	= shift;
	return $self->appendListItem(@_);
	}

#-----------------------------------------------------------------------------
# append a new item list

sub	appendItemList
	{
	my $self	= shift;
	my %opt		= @_;
	my $name	= 'text:unordered-list';
	$opt{'attribute'}{'text:style-name'} = $opt{'style'} if $opt{'style'};
	$opt{'attribute'}{'text:style-name'} = $self->{'paragraph_style'}
		unless $opt{'attribute'}{'text:style-name'};
	$opt{'attribute'}{'text:continue-numbering'} =
		$opt{'continue-numbering'} if $opt{'continue-numbering'};

	if ($self->{'opendocument'})
		{
		$name	= 'text:list';
		}
	else
		{
		if (defined $opt{'type'} && ($opt{'type'} eq 'ordered'))
			{ $name = 'text:ordered-list' ; }
		}

	my $attachment = $opt{'attachment'} || $self->{'body'};
	return $self->appendElement($attachment, $name, %opt);
	}

#-----------------------------------------------------------------------------
# insert a new item list

sub	insertItemList
	{
	my $self	= shift;
	my $path	= shift;
	my $posnode	= (ref $path)	?
				$path	:
				$self->getElement($path, shift);
	my %opt		= @_;
	my $name	= 'text:unordered-list';
	$opt{'attribute'}{'text:style-name'} = $opt{'style'} if $opt{'style'};
	$opt{'attribute'}{'text:style-name'} = $self->{'paragraph_style'}
		unless $opt{'attribute'}{'text:style-name'};
	$opt{'attribute'}{'text:continue-numbering'} =
		$opt{'continue-numbering'} if $opt{'continue-numbering'};

	if ($self->{'opendocument'})
		{
		$name	= 'text:list';
		}
	else
		{
		if (defined $opt{'type'} && ($opt{'type'} eq 'ordered'))
			{ $name = 'text:ordered-list' ; }
		}

	return $self->insertElement($posnode, $name, %opt);
	}

#-----------------------------------------------------------------------------
# row expansion utility for _expand_table

sub	_expand_row
	{
	my $self	= shift;
	my $row		= shift;
	unless ($row)
		{
		warn	"[" . __PACKAGE__ . "::_expand_row] "	.
			"Unknown table row\n";
		return undef;
		}
	my $width	= shift;

	my @cells	= $row->selectChildElements
					('table:(covered-|)table-cell');

	my $cell	= undef;
	my $last_cell	= undef;
	my $rep		= 0;
	my $cellnum	= 0;
	while (@cells)
		{
		last	if (defined $width and ($cellnum >= $width));
		$cell = shift @cells;
		$last_cell = $cell;
		$rep  =	$cell	?
				$cell->getAttribute($COL_REPEAT_ATTRIBUTE) :
				0;
		if ($rep)
			{
			$cell->removeAttribute($COL_REPEAT_ATTRIBUTE);
			while ($rep > 1)
				{
				last if
				    (defined $width and ($cellnum >= $width));
				$last_cell = $last_cell->replicateNode;
				$rep--; $cellnum++;
				}
			}
		$cellnum++ if $cell;
		}

	$last_cell->setAttribute($COL_REPEAT_ATTRIBUTE, $rep)
			if ($rep && ($rep > 1));

	return $cellnum;
	}

#-----------------------------------------------------------------------------
# column expansion utility for _expand_table

sub	_expand_columns
	{
	my $self	= shift;
	my $table	= shift;
	return undef unless ($table && ref $table);
	my $width	= shift;

	my @cols	= $table->children('table:table-column');

	my $col		= undef;
	my $last_col	= undef;
	my $rep		= 0;
	my $colnum	= 0;
	while (@cols)
		{
		last if (defined $width and ($colnum >= $width));
		$col	= shift @cols; $last_col = $col;
		$rep =	$col	?
				$col->getAttribute($COL_REPEAT_ATTRIBUTE) :
				0;
		if ($rep)
			{
			$col->removeAttribute($COL_REPEAT_ATTRIBUTE);
			while ($rep > 1)
				{
				last if
				    (defined $width and ($colnum >= $width));
				$last_col = $last_col->replicateNode;
				$rep--; $colnum++;
				}
			}
		$colnum++ if $col;
		}

	$last_col->setAttribute($COL_REPEAT_ATTRIBUTE, $rep)
			if ($rep && ($rep > 1));
	return $colnum;
	}

#-----------------------------------------------------------------------------
# expands repeated table elements in order to address them in spreadsheets
# in the same way as in text documents

sub	_expand_table
	{
	my $self	= shift;
	my $table	= shift;
	my $length	= shift	|| $self->{'max_rows'};
	my $width	= shift || $self->{'max_cols'};
	return undef unless ($table && ref $table);
	if ($length && ($length eq 'full'))
		{
		$length = undef; $width = undef;
		}

	my $new_width = $self->_expand_columns($table, $width);

	my @rows	= ();
	my $header = $table->first_child('table:table-header-rows');
	@rows = $header->children('table:table-row') if $header;
	push @rows, $table->children('table:table-row');

	my $row		= undef;
	my $last_row	= undef;
	my $rep		= 0;
	my $rownum	= 0;
	while (@rows)
		{
		last	if (defined $length and ($rownum >= $length));
		$row	= shift @rows; $last_row = $row;
		my $last_width = $self->_expand_row($row, $width);
		$new_width = $last_width if $last_width > $new_width;
		$rep =	$row	?
				$row->getAttribute($ROW_REPEAT_ATTRIBUTE) :
				0;
		if ($rep)
			{
			$row->removeAttribute($ROW_REPEAT_ATTRIBUTE);
			while ($rep > 1)
				{
				last if
				    (defined $length and ($rownum >= $length));
				$last_row = $last_row->replicateNode;
				$rep--; $rownum++;
				}
			}
		$rownum++ if $row;
		}

	$last_row->setAttribute($ROW_REPEAT_ATTRIBUTE, $rep)
			if ($rep && ($rep > 1));

	return wantarray ? ($rownum, $new_width) : $table;
	}

#-----------------------------------------------------------------------------
# get a table size in ($lines, $columns) form

sub	getTableSize
	{
	my $self	= shift;
	my $table	= $self->getTable(@_)	or return undef;
	my $height	= 0;
	my $width	= 0;

	my @rows	= ();
	my $header = $table->first_child('table:table-header-rows');
	@rows = $header->children('table:table-row') if $header;
	push @rows, $table->children('table:table-row');
	foreach my $row (@rows)
		{
		my $rep = $row->getAttribute($ROW_REPEAT_ATTRIBUTE) || 1;
		$height += $rep;
		my @cells = $row->selectChildElements
					('table:(covered-|)table-cell');
		my $row_width = 0;
		foreach my $cell (@cells)
			{
			my $rep = $cell->getAttribute($COL_REPEAT_ATTRIBUTE);
			$row_width += $rep ? $rep : 1;
			}
		$width = $row_width if $row_width > $width;
		}
	return ($height, $width);
	}

#-----------------------------------------------------------------------------
# increases the size of an existing table
# improved by Barry Slaymaker [rt.cpan.org #41975]

sub	expandTable
	{
	my $self	= shift;
	my $table	= shift;
	my $length	= shift || 0;
	my $width	= shift || 0;
	my $context	= shift;

	my ($old_length, $old_width) = $self->getTableSize($table);
	$table = $self->normalizeSheet($table, 'full');
	unless ($table)
		{
		warn	"[" . __PACKAGE__ . "::expandTable] " .
			"Unknown or badly formed table\n";
		return undef;
		}
	my $last_col	= $self->getTableColumn($table, -1);
	my $last_row	= $self->getRow($table, -1);
	my $i		= 0;
	my $j		= 0;

	# expand column declarations
	for ($i = $old_width ; $i < $width ; $i++)
		{
		$last_col = $last_col->replicateNode;
		}

	# expand existing rows
	for ($i = 0 ; $i < $old_length ; $i++)
		{
		my $row		= $self->getTableRow($table,  $i);
		my $last_cell	= $self->getTableCell($row, -1);
		for ($j = $old_width ; $j < $width ; $j++)
			{
			$last_cell = $last_cell->replicateNode;
			}
		}

	# append new rows
	for ($i = $old_length; $i < $length; $i++)
		{
		$last_row = $last_row->replicateNode;
		}
	return wantarray ? $self->getTableSize($table) : $table;
	}

#-----------------------------------------------------------------------------
# get a table column descriptor element

sub	getTableColumn
	{
	my $self	= shift;
	my $p1		= shift;
	return $p1	if (ref $p1 && $p1->isTableColumn);
	my $col		= shift || 0;
	my $table	= $self->getTable($p1, @_)	or return undef;

	return $table->child($col, 'table:table-column');
	}

#-----------------------------------------------------------------------------
# get/set a column style

sub	columnStyle
	{
	my $self	= shift;
	my $p1		= shift;
	my $column	= undef;
	if (ref $p1 && $p1->isTableColumn)
		{
		$column	= $p1;
		}
	else
		{
		$column = $self->getTableColumn($p1, shift) or return undef;
		}
	my $newstyle	= shift;

	return	defined $newstyle ?
		$self->setAttribute($column, 'table:style-name' => $newstyle)
			:
		$self->getAttribute($column, 'table:style-name');
	}

#-----------------------------------------------------------------------------
# get/set a row style

sub	rowStyle
	{
	my $self	= shift;
	my $p1		= shift;
	my $row		= undef;
	if (ref $p1 && $p1->isTableRow)
		{
		$row	= $p1;
		}
	else
		{
		$row = $self->getTableRow($p1, shift) or return undef;
		}
	my $newstyle	= shift;

	return	defined $newstyle ?
		$self->setAttribute($row, 'table:style-name' => $newstyle)
			:
		$self->getAttribute($row, 'table:style-name');
	}

#-----------------------------------------------------------------------------
# get a row element from table id and row num,
# or the row cells if wantarray

sub	getTableRow
	{
	my $self	= shift;
	my $p1		= shift;
	return $p1	if (ref $p1 && $p1->isTableRow);
	my $line	= shift || 0;
	my $table	= $self->getTable($p1, @_)	or return undef;

	return $table->child($line, 'table:table-row');
	}

#-----------------------------------------------------------------------------
# get a table header container

sub	getTableHeader
	{
	my $self	= shift;
	my $table	= $self->getTable(@_) or return undef;
	return $table->first_child('table:table-header-rows');
	}

#-----------------------------------------------------------------------------
# get a header row in a table

sub	getTableHeaderRow
	{
	my $self	= shift;
	my $p1		= shift;
	if (ref $p1)
		{
		if ($p1->isTableRow)
		    {
		    if ($p1->parent->hasTag('table:table-header-rows'))
		    	{ return $p1;	}
		    else
		    	{ return undef;	}
		    }
		}
	my $line	= shift || 0;
	my $table	= $self->getTable($p1, @_)
		or return undef;
	my $header	= $table->first_child('table:table-header-rows')
		or return undef;
	return $header->child($line, 'table:table-row');
	}

#-----------------------------------------------------------------------------
# insert a table header container

sub	copyRowToHeader
	{
	my $self	= shift;
	my $row		= $self->getTableRow(@_) or return undef;
	my $table	= $row->parent;
	my $header =	$table->first_child('table:table-header-rows');
	unless ($header)
		{
		my $first_row = $self->getTableRow($table, 0);
		unless ($first_row)
			{
			warn	"[" . __PACKAGE__ . "::createTableHeader] " .
				"Not allowed with an empty table\n";
			return undef;
			}
		$header = $self->createElement('table:table-header-rows');
		$header->paste_before($first_row);
		}
	my $header_row = $row->copy;
	$header_row->paste_last_child($header);
	return $header_row;
	}

#-----------------------------------------------------------------------------
# get all the rows in a table

sub	getTableRows
	{
	my $self	= shift;
	my $table	= $self->getTable(@_)	or return undef;

	return $table->children('table:table-row');
	}

#-----------------------------------------------------------------------------
# spreadsheet coordinates conversion utility

sub	_coord_conversion
	{
	my $arg	= shift or return ($arg, @_);
	my $coord = uc $arg;
	return ($arg, @_) unless $coord =~ /[A-Z]/;

	$coord	=~ s/\s*//g;
	$coord	=~ /(^[A-Z]*)(\d*)/;
	my $c	= $1;
	my $r	= $2;
	return ($arg, @_) unless ($c && $r);

	my $rownum	= $r - 1;
	my @csplit	= split '', $c;
	my $colnum	= 0;
	foreach my $p (@csplit)
		{
		$colnum *= 26;
		$colnum	+= ((ord($p) - ord('A')) + 1);
		}
	$colnum--;

	return ($rownum, $colnum, @_);
	}

#-----------------------------------------------------------------------------
# get cell element by 3D coordinates ($tablenum, $line, $column)
# or by ($tablename/$tableref, $line, $column)

sub	getTableCell
	{
	my $self		= shift;
	my $p1			= shift;
	return undef	unless defined $p1;
	my $table		= undef;
	my $row			= undef;
	my $cell		= undef;

	if	(! ref $p1 || ($p1->isTable))
		{
		@_ = OpenOffice::OODoc::Text::_coord_conversion(@_);
		my $r	= shift || 0;
		my $c	= shift || 0;
		if (ref $p1)
			{
			$table = $p1;
			}
		else
			{
			my $context = shift;
			unless (ref $context)
				{
				unshift @_, $context; $context = undef;
				}
			$table	= $self->getTable($p1, $context)
				or return undef;
			}
		$row	= $table->child($r, 'table:table-row')
				or return undef;
		$cell = (
			$row->selectChildElements
				('table:(covered-|)table-cell')
			)[$c];
		}
	elsif	($p1->isTableCell)
		{
		$cell	= $p1;
		}
	else	# assume $p1 is a table row
		{
		$cell = $p1->selectChildElement
				(
				'table:(covered-|)table-cell',
				shift
				);
		}

	return undef unless ($cell && ! $cell->isCovered);
	return wantarray ? ($cell, @_) : $cell;
	}

#-----------------------------------------------------------------------------
# adapted from a suggestion by dhoworth

sub getCellPosition
	{
	my $self	= shift;
	my $cell	= $self->getTableCell(@_);
	unless ($cell && $cell->isTableCell)
		{
		warn	"[" . __PACKAGE__ . "::cellPosition] "	.
			"Non-cell argument\n";
		return undef;
		}
	my $cp		= $cell->pos() - 1;
	my $row		= $cell->parent;
	my $rp		= $row->pos('table:table-row') - 1;
	my $table	= $row->parent;
	my $tp		= $table->pos('table:table') - 1;
	return wantarray ? ($tp, $rp, $cp) : $tp;
	}

#-----------------------------------------------------------------------------
# get all the cells in a row

sub	getRowCells
	{
	my $self	= shift;
	my $row		= $self->getTableRow(@_)	or return undef;

	return $row->children('table:table-cell');
	}

#-----------------------------------------------------------------------------

sub	getCellParagraph
	{
	my $self	= shift;
	my $cell	= $self->getTableCell(@_)	or return undef;
	return $cell->first_child('text:p');
	}

#-----------------------------------------------------------------------------

sub	getCellParagraphs
	{
	my $self	= shift;
	my $cell	= $self->getTableCell(@_)	or return undef;
	return $cell->children('text:p');
	}

#-----------------------------------------------------------------------------
# get table cell value

sub	getCellValue
	{
	my $self	= shift;
	my $cell	= $self->getTableCell(@_) or return undef;

	my $prefix = $self->{'opendocument'} ? 'office' : 'table';
	my $cell_type	= $self->cellType($cell);
	if ((! $cell_type) || ($cell_type eq 'string'))		# text value
		{
		return $self->getText($cell);
		}
	else
		{
		my $attribute = $self->cellValueAttributeName($cell);
		return $cell->att($attribute);
		}
	}

#-----------------------------------------------------------------------------
# get/set a cell value type

sub	cellValueType
	{
	my $self	= shift;
	@_ = $self->getTableCell(@_);
	my $cell	= shift		or return undef;

	return $self->cellType($cell, @_);
	}

#-----------------------------------------------------------------------------
# get/set a cell currency

sub	fieldCurrency
	{
	my $self	= shift;
	@_ = $self->getTableCell(@_);
	my $cell	= shift		or return undef;

	my $newcurrency	= shift;
	my $prefix	= $self->{'opendocument'} ? 'office' : 'table';
	unless ($newcurrency)
		{
		return $cell->att($prefix . ':currency');
		}
	else
		{
		$cell->set_att($prefix . ':value-type', 'currency');
		return $cell->set_att($prefix . ':currency', $newcurrency);
		}
	}

#-----------------------------------------------------------------------------
# get/set accessor for the formula of a table cell

sub	cellFormula
	{
	my $self	= shift;
	@_ = $self->getTableCell(@_);
	my $cell	= shift		or return undef;

	my $formula = shift;
	if (defined $formula)
		{
		if ($formula gt ' ')
			{
			$self->setAttribute($cell, 'table:formula', $formula);
			}
		else
			{
			$self->removeAttribute($cell, 'table:formula');
			}
		}
	return $self->getAttribute($cell, 'table:formula');
	}

#-----------------------------------------------------------------------------
# set value of an existing cell

sub	updateCell
	{
	my $self	= shift;
	@_ = $self->getTableCell(@_);
	my $cell	= shift		or return undef;

	my $value	= shift;
	my $text	= shift;

	$text		= $value	unless defined $text;
	my $cell_type	= $self->cellType($cell);
	unless ($cell_type)
		{
		$cell_type	= 'string';
		$self->cellType($cell, $cell_type);
		}

	my $p = $cell->first_child('text:p');
	unless ($p)
		{
		$p = $self->createParagraph($text);
		$p->paste_last_child($cell);
		}
	else
		{
		$self->SUPER::setText($p, $text);
		}

	unless ($cell_type eq 'string')
		{
		my $attribute = $self->cellValueAttributeName($cell);
		$cell->setAttribute($attribute, $value);
		}
	return $cell;
	}

#-----------------------------------------------------------------------------
# get/set a cell value

sub	cellValue
	{
	my $self	= shift;
	@_ = $self->getTableCell(@_);
	my $cell	= shift		or return undef;
	my $newvalue	= shift;
	if (defined $newvalue)
		{
		$self->updateCell($cell, $newvalue, @_);
		}
	return $self->getCellValue($cell);
	}

#-----------------------------------------------------------------------------
# get/set a cell style

sub	cellStyle
	{
	my $self	= shift;
	@_ = $self->getTableCell(@_);
	my $cell	= shift		or return undef;

	my $newstyle	= shift;

	return defined $newstyle ?
		$self->setAttribute($cell, 'table:style-name' => $newstyle) :
		$self->getAttribute($cell, 'table:style-name');
	}

#-----------------------------------------------------------------------------
# get/set cell spanning (from a contribution by Don_Reid[at]Agilent.com)

sub	removeCellSpan
	{
	my $self	= shift;
	my $cell	= $self->getTableCell(@_) or return undef;
	my $hspan = $cell->getAttribute('table:number-columns-spanned') || 1;
	$cell->removeAttribute('table:number-columns-spanned');
	my $vspan = $cell->getAttribute('table:number-rows-spanned') || 1;
	$cell->removeAttribute('table:number-rows-spanned');
	my $row = $cell->parent('table:table-row');
	my $table = $row->parent('table:table');
	my $vpos = $row->getLocalPosition;
	my $hpos = $cell->getLocalPosition(qr'table:(covered-|)table-cell');
	my $vend = $vpos + $vspan - 1;
	my $hend = $hpos + $hspan - 1;
	my $cell_paragraph = $cell->first_child('text:p');
	ROW: for (my $i = $vpos ; $i <= $vend ; $i++)
		{
		my $cr = $self->getRow($table, $i) or last ROW;
		CELL: for (my $j = $hpos ; $j <= $hend ; $j++)
			{
			my $covered = $cr->selectChildElement
				(qr 'table:(covered-|)table-cell', $j)
				or last CELL;
			next CELL if $covered == $cell;
			$covered->set_name('table:table-cell');
			$covered->set_atts($cell->atts);
			$covered->removeAttribute('table:value');
			if ($cell_paragraph)
				{
				my $p = $cell_paragraph->copy;
				$p->set_text("");
				$p->paste_first_child($covered);
				}
			}
		}
	}

sub	cellSpan
	{
	my $self	= shift;
	@_ = $self->getTableCell(@_);
	my $cell	= shift		or return undef;

	my $rnum	= undef;
	my $cnum	= undef;
	my $table	= undef;

	my $old_hspan	= $cell->att('table:number-columns-spanned')	|| 1;
	my $old_vspan	= $cell->att('table:number-rows-spanned')	|| 1;
	my $hspan	= shift;
	my $vspan	= shift;
	unless ($hspan || $vspan)
		{
		return wantarray ? ($old_hspan, $old_vspan) : $old_hspan;
		}
	$hspan	= $old_hspan unless $hspan;
	$vspan	= $old_vspan unless $vspan;

	$self->removeCellSpan($cell);
	my $row = $cell->parent('table:table-row');
	$table = $row->parent('table:table') unless $table;
	my $vpos = $row->getLocalPosition;
	my $hpos = $cell->getLocalPosition(qr'table:(covered-|)table-cell');
	my $hend = $hpos + $hspan - 1;
	my $vend = $vpos + $vspan - 1;
	$cell->setAttribute('table:number-columns-spanned', $hspan);
	$cell->setAttribute('table:number-rows-spanned', $vspan);

	ROW: for (my $i = $vpos ; $i <= $vend ; $i++)
		{
		my $cr = $self->getRow($table, $i) or last ROW;
		CELL: for (my $j = $hpos ; $j <= $hend ; $j++)
			{
			my $covered = $self->getCell($cr, $j)
				or last CELL;
			next CELL if $covered == $cell;

			my @paras = $covered->children('text:p');
			while (@paras)
				{
				my $p = shift @paras;
				$p->paste_last_child($cell) if
					(defined $p->text && $p->text ge ' ');
				}
			$self->removeCellSpan($covered);
			$covered->set_name('table:covered-table-cell');
			}
		}
	return wantarray ? ($hspan, $vspan) : $hspan;
	}

#-----------------------------------------------------------------------------
# get the content of a table element in a 2D array

sub	_get_row_content
	{
	my $self	= shift;
	my $row		= shift;

	my @row_content	= ();
	foreach my $cell ($row->children('table:table-cell'))
		{
		push @row_content, $self->getText($cell);
		}
	return @row_content;
	}

sub	getTableText
	{
	my $self	= shift;
	my $table	= $self->getTable(shift);

	return undef	unless $table;

	my @table_content = ();
	my $headers	= $table->getFirstChild('table:table-header-rows');
	if ($headers)
		{
		push @table_content, [ $self->_get_row_content($_) ]
			for ($headers->children('table:table-row'));
		}
	push @table_content, [ $self->_get_row_content($_) ]
		for ($table->children('table:table-row'));

	if (wantarray)
		{
		return @table_content;
		}
	else
		{
		my $delimiter	= $self->{'field_separator'} || '';
		my $line_break	= $self->{'line_separator'}  || '';
		my @list	= ();
		foreach my $row (@table_content)
			{
			push @list, join($delimiter, @{$row});
			}
		return join $line_break, @list;
		}
	}

#-----------------------------------------------------------------------------
# get table element selected by number

sub	getTable
	{
	my $self	= shift;
	my $table	= shift;
	my $length	= shift;
	my $width	= shift;
	my $context	= shift;

	if (ref $length)
		{
		$context	= $length;
		$length		= undef;
		$width		= undef;
		}
	elsif (ref $width)
		{
		$context	= $width;
		$width		= undef;
		$length		= undef;
		}

	return undef	unless defined $table;

	my $t	= undef;
	if (ref $table)
		{
		if ($table->isTable)
			{
			$t = $table;
			}
		else
			{
			warn	"[" . __PACKAGE__ . "::getTable] "	.
				"Non table object\n";
			return undef;
			}
		}
	else	# retrieve table by number or name
		{
		if (($table =~ /^\d*$/) || ($table =~ /^[\d+-]\d+$/))
			{
			$t = $self->getElement
				('//table:table', $table, $context);
			}
		unless ($t)
			{
			my $n = $self->inputTextConversion($table);
			$t = $self->getNodeByXPath
				(
				"//table:table[\@table:name=\"$n\"]"
				);
			}
		}
	return undef	unless $t;
	if	(
		$length		||
			(
			$self->{'expand_tables'}		&&
			($self->{'expand_tables'} eq 'on')
			)
		)
		{
		$length = 'full' if ($length && ($length eq 'normalize'));
		return $self->_expand_table($t, $length, $width);
		}
	return wantarray ? $self->getTableSize($t) : $t;
	}

#-----------------------------------------------------------------------------

sub	getTableByName
	{
	my $self	= shift;
	my $name 	= $self->inputTextConversion(shift);
	my $table = $self->getNodeByXPath
		("//table:table[\@table:name=\"$name\"]");
	return $self->getTable($table, @_);
	}

#-----------------------------------------------------------------------------
# user-controlled spreadsheet expansion

sub	normalizeSheet
	{
	my $self	= shift;
	my $table	= shift;
	my $length	= shift;
	my $width	= shift;
	my $context	= shift;
	unless (ref $table)
		{
		if ($table =~ /^\d*$/)
			{
			$table = $self->getElement
				('//table:table', $table, $context);
			}
		else
			{
			my $n = $self->inputTextConversion($table);
			$table = $self->getNodeByXPath
				(
				"//table:table[\@table:name=\"$n\"]",
				$context
				);
			}
		}

	unless ((ref $table) && $table->isTable)
		{
		warn	"[" . __PACKAGE__ . "::normalizeSheet] "	.
			"Missing sheet\n";
		return undef;
		}
	return $self->_expand_table($table, $length, $width, @_);
	}

sub	normalizeSheets
	{
	my $self	= shift;
	my $length	= shift;
	my $width	= shift;
	my @sheets	= $self->getTableList;
	my $count	= 0;
	foreach my $sheet (@sheets)
		{
		$self->normalizeSheet($sheet, $length, $width, @_);
		$count++;
		}
	return $count;
	}

#-----------------------------------------------------------------------------
# activate/deactivate and parametrize automatic spreadsheet expansion

sub	autoSheetNormalizationOn
	{
	my $self	= shift;
	my $length	= shift || $self->{'max_rows'};
	my $width	= shift || $self->{'max_cols'};

	$self->{'expand_tables'}	= 'on';
	$self->{'max_rows'}		= $length;
	$self->{'max_cols'}		= $width;

	return 'on';
	}

sub	autoSheetNormalizationOff
	{
	my $self	= shift;
	my $length	= shift || $self->{'max_rows'};
	my $width	= shift || $self->{'max_cols'};

	$self->{'expand_tables'}	= 'no';
	$self->{'max_rows'}		= $length;
	$self->{'max_cols'}		= $width;

	return 'no';
	}

#-----------------------------------------------------------------------------
# common code for insertTable and appendTable

sub	_build_table
	{
	my $self	= shift;
	my $table	= shift;
	my $rows	= shift || $self->{'max_rows'} || 1;
	my $cols	= shift || $self->{'max_cols'} || 1;
	my %opt		=
			(
			'cell-type'	=> 'string',
			'text-style'	=> 'Table Contents',
			@_
			);

	$rows = $self->{'max_rows'} unless $rows;
	$cols = $self->{'max_cols'} unless $cols;

	my $col_proto	= $self->createElement('table:table-column');
	$self->setAttribute
		($col_proto, 'table:style-name', $opt{'column-style'})
			if $opt{'column-style'};
	$col_proto->paste_first_child($table);
	$col_proto->replicateNode($cols - 1, 'after');

	my $row_proto	= $self->createElement('table:table-row');
	my $cell_proto	= $self->createElement('table:table-cell');
	$self->cellValueType($cell_proto, $opt{'cell-type'});
	$self->cellStyle($cell_proto, $opt{'cell-style'});

	if ($opt{'paragraphs'})
		{
		my $para_proto	= $self->createElement('text:p');
		$self->setAttribute
			($para_proto, 'text:style-name', $opt{'text-style'})
				if $opt{'text-style'};
		$para_proto->paste_last_child($cell_proto);
		}

	$cell_proto->paste_first_child($row_proto);
	$cell_proto->replicateNode($cols - 1, 'after');

	$row_proto->paste_last_child($table);
	$row_proto->replicateNode($rows - 1, 'after');

	return $table;
	}

#-----------------------------------------------------------------------------
# create a new table and append it to the end of the document body (default),
# or attach it as a new child of a given element

sub	appendTable
	{
	my $self	= shift;
	my $name	= shift;
	my $rows	= shift || $self->{'max_rows'} || 1;
	my $cols	= shift || $self->{'max_cols'} || 1;
	my %opt		=
			(
			'attachment'	=> $self->{'body'},
			'table-style'	=> $name,
			@_
			);

	if ($self->getTable($name, $self->{'xpath'}))
		{
		warn	"[" . __PACKAGE__ . "::appendTable] "	.
			"Table $name exists\n";
		return	undef;
		}

	my $table = $self->appendElement
				(
				$opt{'attachment'}, 'table:table',
				attribute =>
					{
					'table:name'		=>
						$name,
					'table:style-name'	=>
						$opt{'table-style'}
					}
				)
			or return undef;

	return $self->_build_table($table, $rows, $cols, %opt);
	}

#-----------------------------------------------------------------------------

sub	insertTable
	{
	my $self	= shift;
	my $path	= shift;
	my $pos		= ref $path ? undef : shift;
	my $name	= shift;
	my $rows	= shift || $self->{'max_rows'} || 1;
	my $cols	= shift || $self->{'max_cols'} || 1;
	my %opt		=
			(
			'table-style'	=> $name,
			@_
			);
	my $posnode	= $self->getElement($path, $pos, $opt{'context'})
				or return undef;

	if ($self->getTable($name, $self->{'xpath'}))
		{
		warn	"[" . __PACKAGE__ . "::insertTable] "	.
			"Table $name exists\n";
		return	undef;
		}

	my $table = $self->insertElement
				(
				$posnode, 'table:table',
				attribute =>
					{
					'table:name'		=>
						$name,
					'table:style-name'	=>
						$opt{'table-style'}
					},
				%opt
				)
			or return undef;

	return $self->_build_table($table, $rows, $cols, %opt);
	}

#-----------------------------------------------------------------------------

sub	renameTable
	{
	my $self	= shift;
	my $table	= $self->getTable(shift) or return undef;
	my $newname	= shift;

	if ($self->getTable($newname, $self->{'xpath'}))
		{
		warn	"[" . __PACKAGE__ . "::renameTable] " .
			"Table name $newname already in use\n";
		return undef;
		}
	return $self->setAttribute($table, 'table:name' => $newname);
	}

#-----------------------------------------------------------------------------

sub	tableName
	{
	my $self	= shift;
	my $table	= $self->getTable(shift) or return undef;
	my $newname	= shift;
	if (ref $newname)
		{
		unshift @_, $newname; $newname = undef;
		}
	$self->renameTable($table, $newname, @_) if $newname;
	return $self->getAttribute($table, 'table:name', @_);
	}

#-----------------------------------------------------------------------------

sub	tableStyle
	{
	my $self	= shift;
	my $table	= $self->getTable(shift) or return undef;
	my $newstyle	= shift;
	if (ref $newstyle)
		{
		unshift @_, $newstyle; $newstyle = undef;
		}

	return defined $newstyle ?
		$self->setAttribute
			($table, 'table:style-name' => $newstyle, @_) :
		$self->getAttribute
			($table, 'table:style-name', @_);
	}

#-----------------------------------------------------------------------------
# replicates a column in a normalized table

sub	insertTableColumn
	{
	my $self	= shift;
	my $table	= shift;
	my $col_num	= shift;
	my %options	=
		(
		position	=> 'before',
		@_
		);
	$table	= $self->getTable($table, $options{'context'})
				or return undef;
	my ($height, $width) = $self->getTableSize($table);
	unless ($col_num < $width)
		{
		warn	"[" . __PACKAGE__ . "::replicateTableColumn] "	.
			"Column number out of range\n";
		return undef;
		}
	$self->_expand_columns($table, $width);
	my $column	= $table->child($col_num, 'table:table-column');
	my $new_cell	= undef;
	if ($column)
		{
		my $new_column = $column->copy;
		$new_column->paste($options{position}, $column);
		}
	my @rows = ();
	my $header = $table->first_child('table:table-header-rows');
	@rows = $header->children('table:table-row') if $header;
	push @rows, $self->getTableRows($table);
	foreach my $row (@rows)
		{
		my $cell = $row->selectChildElement
		  		('table:(covered-|)table-cell', $col_num)
		  	or next;
		$new_cell = $cell->copy;
		$new_cell->paste($options{'position'}, $cell);
		}
	return $column || $new_cell;
	}

#-----------------------------------------------------------------------------
# delete a column in a table

sub	deleteTableColumn
	{
	my $self	= shift;
	my $p1		= shift;
	my $col_num	= shift;
	my $table	= undef;
	if (ref $p1 && $p1->isTableColumn)
		{
		$table = $p1->parent;
		$col_num = $p1->getLocalPosition;
		}
	else
		{
		$table = $p1;
		}
	$table = $self->getTable($table);
	unless ($table)
		{
		warn	"[" . __PACKAGE__ . "::deleteTableColumn] " .
			"Unknown table\n";
		return undef;
		}
	my ($height, $width) = $self->getTableSize($table);
	unless (defined $col_num)
		{
		warn	"[" . __PACKAGE__ . "::deleteTableColumn] "	.
			"Missing column position\n";
		return undef;
		}
	$self->_expand_columns($table, $width);
	my $column = $table->child($col_num, 'table:table-column');
	$column->delete if $column;
	my @rows = ();
	my $header = $table->first_child('table:table-header-rows');
	@rows = $header->children('table:table-row') if $header;
	push @rows, $self->getTableRows($table);
	foreach my $row (@rows)
		{
		my $cell = $row->selectChildElement
		  		('table:(covered-|)table-cell', $col_num)
		 	or next;
		$cell->delete;
		}
	return 1;
	}

#-----------------------------------------------------------------------------
# replicates a row in a table

sub	replicateTableRow
	{
	my $self	= shift;
	my $p1		= shift;
	my $table	= undef;
	my $row		= undef;
	my $line	= undef;

	if (ref $p1 && $p1->isTableRow)
		{
		$row	= $p1;
		}
	else
		{
		$line	= shift;
		}
	my %options	=
		(
		position	=> 'after',
		@_
		);
	if (defined $line)
		{
		$row	= $self->getTableRow($p1, $line, $options{'context'})
			or return undef;
		}

	return $self->replicateElement($row, $row, %options);
	}

#-----------------------------------------------------------------------------
# replicate a row and insert the clone before (default) or after the prototype

sub	insertTableRow
	{
	my $self	= shift;
	my $p1		= shift;
	my $row		= undef;
	my $line	= undef;
	if (ref $p1)
		{
		if  	($p1->isTableRow)
			{ $row = $p1; }
		else
			{
			$line = shift;
			$row = $self->getTableRow($p1, $line);
			}
		}
	else
		{
		$row = $self->getTableRow($p1, shift);
		}
	return undef	unless $row;

	my %options	=
			(
			position	=> 'before',
			@_
			);
	return $self->replicateTableRow($row, %options);
	}

#-----------------------------------------------------------------------------
# append a new row (replicating the last existing one) to a table

sub	appendTableRow
	{
	my $self	= shift;
	my $table	= shift;
	return $self->replicateTableRow($table, -1, position => 'after', @_);
	}

#-----------------------------------------------------------------------------
# delete a given table row

sub	deleteTableRow
	{
	my $self	= shift;
	my $row		= $self->getTableRow(@_) or return undef;
	return $self->removeElement($row);
	}

#-----------------------------------------------------------------------------
# update the user field references according to the internal value

sub	updateUserFieldReferences
	{
	my $self	= shift;
	my $fd		= shift or return undef;
	my $field_decl	= undef;
	my $name	= undef;
	if (ref $fd)
		{
		$name = $self->getAttribute($fd, 'text:name');
		$field_decl = $fd;
		}
	else
		{
		$field_decl= $self->getUserFieldElement($fd);
		$name = $fd;
		}
	unless ($field_decl && $name)
		{
		warn	"[" . __PACKAGE__ . "::updateUserFieldReferences] " .
			"Unknown or bad user field\n";
		return undef;
		}
	my @fields = $self->selectNodesByXPath
		("//text:user-field-get[\@text:name=\"$name\"]");
	my $content = $self->userFieldValue($field_decl) || "";
	my $count = 0;
	foreach my $field (@fields)
		{
		$self->setText($field, $content);
		$count++;
		}
	return $count;
	}

#-----------------------------------------------------------------------------
# get user field element

sub	getUserField
	{
	my $self	= shift;
	my $name	= shift;
	unless ($name)
		{
		warn "[" . __PACKAGE__ . "::getUserField] Missing name\n";
		return undef;
		}
	if (ref $name)
		{
		my $n = $name->getName;
		return ($n eq 'text:user-field-decl') ? $name : undef;
		}
	$name = $self->inputTextConversion($name);
	return $self->getNodeByXPath
			("//text:user-field-decl[\@text:name=\"$name\"]");
	}

#-----------------------------------------------------------------------------
# get/set user field value

sub	userFieldValue
	{
	my $self	= shift;
	my $field	= $self->getUserFieldElement(shift)
				or return undef;
	my $value	= shift;

	my $value_att	= $self->fieldValueAttributeName($field);

	if (defined $value)
		{
		if ($value)
			{
			$self->setAttribute($field, $value_att, $value);
			}
		else
			{
			$field->set_att($value_att => $value);
			}
		}
	return $self->getAttribute($field, $value_att);
	}

#-----------------------------------------------------------------------------
# get a variable element (contributed by Andrew Layton)

sub	getVariableElement
	{
	my $self	= shift;
	my $name	= shift;

	unless ($name) {
		warn	"[" . __PACKAGE__ . "::getVariableElement] " .
			"Missing name\n";
		return undef;
		}

	if (ref $name) {
		my $n = $name->getName;
		return ($n eq 'text:variable-set') ? $name : undef;
	}

	return
	$name = $self->inputTextConversion($name);
	$self->getNodeByXPath("//text:variable-set[\@text:name=\"$name\"]");
	}

#-----------------------------------------------------------------------------
# get/set the content of a variable element (contributed by Andrew Layton)

sub	variableValue
	{
	my $self	= shift;
	my $variable	= $self->getVariableElement(shift) or return undef;
	my $value	= shift;

	my $value_att	= $self->fieldValueAttributeName($variable);

	if (defined $value)
		{
		$self->setAttribute($variable, $value_att, $value);
		$self->setText($variable, $value);
		}

	$value = $self->getAttribute($variable, $value_att);
	return defined $value ? $value : $self->getText($variable);
	}

#-----------------------------------------------------------------------------
# append an element to the document body

sub	appendBodyElement
	{
	my $self	= shift;

	return $self->appendElement($self->{'body'}, @_);
	}

#-----------------------------------------------------------------------------
# create a new paragraph

sub	createParagraph
	{
	my $self	= shift;
	my $text	= shift;
	my $style	= shift || "Standard";

	my $p = OpenOffice::OODoc::XPath::new_element('text:p');
	if (defined $text)
		{
		$self->SUPER::setText($p, $text);
		}
	$self->setAttribute($p, 'text:style-name' => $style);
	return $p;
	}

#-----------------------------------------------------------------------------
# inserts a flat text string within a given text element

sub	insertString
	{
	my $self	= shift;
	my $path	= shift;
	my $pos		= ref $path ? undef : shift;
	my $element	= $self->getElement($path, $pos) or return undef;
	my $text	= shift;
	my $offset	= shift;
	return $element->insertTextChild($text, $offset);
	}

#-----------------------------------------------------------------------------
# add a new or existing text at the end of the document

sub	appendText
	{
	my $self	= shift;
	my $name	= shift;
	my %opt		= @_;

	my $attachment	= $opt{'attachment'} || $self->{'body'};
	$opt{'attribute'}{'text:style-name'} = $opt{'style'}
			if $opt{'style'};
	unless ((ref $name) || $opt{'attribute'}{'text:style-name'})
		{
		$opt{'attribute'}{'text:style-name'} =
					$self->{'paragraph_style'};
		}

	delete $opt{'attachment'};
	delete $opt{'style'};
	return $self->appendElement($attachment, $name, %opt);
	}

#-----------------------------------------------------------------------------
# insert a new or existing text element before or after an given element

sub	insertText
	{
	my $self	= shift;
	my $path	= shift;
	my $pos		= (ref $path) ? undef : shift;
	my $name	= shift;
	my %opt		= @_ ;

	$opt{'attribute'}{'text:style-name'} = $opt{'style'} if $opt{'style'};

	return (ref $path)	?
		$self->insertElement($path, $name, %opt)		:
		$self->insertElement($path, $pos, $name, %opt);
	}

#-----------------------------------------------------------------------------
# create and add a new paragraph at the end of the document

sub	appendParagraph
	{
	my $self	= shift;
	my %opt		=
			(
			style		=> $self->{'paragraph_style'},
			@_
			);

	my $paragraph = $self->createParagraph($opt{'text'}, $opt{'style'});

	my $attachment	= $opt{'attachment'} || $self->{'body'};
	$paragraph->paste_last_child($attachment);

	return $paragraph;
	}

#-----------------------------------------------------------------------------
# add a new heading at the end of the document

sub	appendHeading
	{
	my $self	= shift;
	my %opt		=
			(
			style	=> $self->{'heading_style'},
			level	=> '1',
			@_
			);

	$opt{'attribute'}{$self->{'level_attr'}}	= $opt{'level'};

	return $self->appendText('text:h',%opt);
	}

#-----------------------------------------------------------------------------
# insert a new paragraph at a given position

sub	insertParagraph
	{
	my $self	= shift;
	my $path	= shift;
	my $pos		= (ref $path) ? undef : shift;
	my %opt		=
			(
			style	=> $self->{'paragraph_style'},
			@_
			);

	return (ref $path)	?
		$self->insertText($path, 'text:p', %opt)		:
		$self->insertText($path, $pos, 'text:p', %opt);
	}

#-----------------------------------------------------------------------------
# insert a new heading at a given position

sub	insertHeading
	{
	my $self	= shift;
	my $path	= shift;
	my $pos		= (ref $path) ? undef : shift;
	my %opt		=
			(
			style	=> $self->{'heading_style'},
			level	=> '1',
			@_
			);

	$opt{'attribute'}{$self->{'level_attr'}}	= $opt{'level'};

	return (ref $path) ?
		$self->insertText($path, 'text:h', %opt)		:
		$self->insertText($path, $pos, 'text:h', %opt);
	}

#-----------------------------------------------------------------------------
# remove the paragraph element at a given position

sub	removeParagraph
	{
	my $self	= shift;
	my $pos		= shift;
	return $self->removeElement($pos)	if (ref $pos);
	return $self->removeElement('//text:p', $pos);
	}

#-----------------------------------------------------------------------------
# remove the heading element at a given position

sub	removeHeading
	{
	my $self	= shift;
	my $element = $self->getHeading(@_);
	return $self->removeElement($element);
	}

#-----------------------------------------------------------------------------

sub	textStyle
	{
	my $self	= shift;
	my $path	= shift;
	my $pos		= (ref $path) ? undef : shift;
	my $element	= $self->getElement($path, $pos) or return undef;
	my $newstyle	= shift;

	if (ref $newstyle)
		{
		$newstyle = $self->getAttribute($newstyle, 'style:name');
		unless ($newstyle)
			{
			warn	"[" . __PACKAGE__ . "::textStyle] "	.
				"Bad text style\n";
			return undef;
			}
		}

	if ($element->isListItem)
		{
		return defined $newstyle ?
			$self->setItemStyle($element)	:
			$self->getItemStyle($element);
		}
	else
		{
		return defined $newstyle ?
			$self->setAttribute
				($element, 'text:style-name' => $newstyle) :
			$self->getAttribute($element, 'text:style-name');
		}
	}

#-----------------------------------------------------------------------------
package	OpenOffice::OODoc::Element;
#-----------------------------------------------------------------------------
# text element type detection (add-in for OpenOffice::OODoc::Element)

sub	isOrderedList
	{
	my $element	= shift;
	return $element->hasTag('text:ordered-list');
	}

sub	isUnorderedList
	{
	my $element	= shift;
	return $element->hasTag('text:unordered-list');
	}

sub	isItemList
	{
	my $element	= shift;
	my $name	= $element->getName;
	return ($name =~ /^text:.*list$/) ? 1 : undef;
	}

sub	isListItem
	{
	my $element	= shift;
	return $element->hasTag('text:list-item');
	}

sub	isParagraph
	{
	my $element	= shift;
	return $element->hasTag('text:p');
	}

sub	isHeader	# DEPRECATED
	{
	my $element	= shift;
	return $element->hasTag('text:h');
	}

sub	isHeading
	{
	my $element	= shift;
	return $element->hasTag('text:h');
	}

sub	headerLevel	# DEPRECATED
	{
	my $element	= shift;
	return $element->getAttribute($self->{'level_attr'});
	}

sub	headingLevel
	{
	my $element	= shift;
	return $element->getAttribute($self->{'level_attr'});
	}

sub	isTable
	{
	my $element	= shift;
	return $element->hasTag('table:table');
	}

sub	isTableRow
	{
	my $element	= shift;
	return $element->hasTag('table:table-row');
	}

sub	isTableColumn
	{
	my $element	= shift;
	return $element->hasTag('table:table-column');
	}

sub	isTableCell
	{
	my $element	= shift;
	return $element->hasTag('table:table-cell');
	}

sub	isCovered
	{
	my $element	= shift;
	my $name	= $element->getName;
	return ($name && ($name =~ /covered/)) ? 1 : undef;
	}

sub	isSpan
	{
	my $element	= shift;
	return $element->hasTag('text:span');
	}

sub	isHyperlink
	{
	my $element	= shift;
	return $element->hasTag('text:a');
	}

sub	checkNoteClass
	{
	my ($element, $class)	= @_;
	my $name	= $element->getName;
	return 1 if $name eq "text:$class";
	return undef unless $name eq 'text:note';
	my $elt_class = $element->att('text:note-class');
	return ($elt_class && ($elt_class eq $class));
	}

sub	getNoteClass
	{
	my $element	= shift;
	return undef unless $element->isNote;
	my $class = $element->att('text:note-class');
	return $class if $class;
	my $tagname = $element->getName;
	$tagname =~ /^text:(endnote|footnote)$/;
	return $1;
	}

sub	isEndnote
	{
	my $element	= shift;
	return $element->checkNoteClass('endnote');
	}

sub	isFootnote
	{
	my $element	= shift;
	return $element->checkNoteClass('footnote');
	}

sub	checkNoteBodyClass
	{
	my ($element, $class) = @_;
	my $name	= $element->getName;
	return	($name eq "text:$class-body")	?
		1 : $element->parent->checkNoteClass($class);
	}

sub	checkNoteCitationClass
	{
	my ($element, $class) = @_;
	my $name = $element->getName;
	return	($name eq "text:$class-citation")	?
		1 : $element->parent->checkNoteClass($class);
	}

sub	isFootnoteCitation
	{
	my $element	= shift;
	return $element->checkNoteCitationClass('footnote');
	}

sub	isEndnoteCitation
	{
	my $element	= shift;
	return $element->checkNoteCitationClass('endnote');
	}

sub	isEndnoteBody
	{
	my $element	= shift;
	return $element->checkNoteBodyClass('endnote');
	}

sub	isFootnoteBody
	{
	my $element	= shift;
	return $element->checkNoteBodyClass('footnote');
	}

sub	isNoteBody
	{
	my $element	= shift;
	my $tag		= $element->name;
	return $tag =~ /^text:(|foot|end)note-body$/;
	}

sub	isNoteCitation
	{
	my $element	= shift;
	my $tag		= $element->name;
	return $tag =~ /^text:(|foot|end)note-citation$/;
	}

sub	isNote
	{
	my $element	= shift;
	my $tag		= $element->name;
	return $tag =~ /^text:(|foot|end)note$/;
	}

sub	isSequenceDeclarations
	{
	my $element	= shift;
	return $element->hasTag('text:sequence-decls');
	}

sub	isBibliographyMark
	{
	my $element	= shift;
	return $element->hasTag('text:bibliography-mark');
	}

sub	isDrawPage
	{
	my $element	= shift;
	return $element->hasTag('draw:page');
	}

sub	isSection
	{
	my $element	= shift;
	return $element->hasTag('text:section');
	}

sub	isTextBox
	{
	my $element	= shift;
	my $name	= $element->getName	or return undef;
	if ($name eq 'draw:frame')
		{
		my $child = $element->first_child('draw:text-box');
		return $child ? 1 : undef;
		}
	else
		{
		return ($name eq 'draw:text-box') ? 1 : undef;
		}
	}

sub	textId
	{
	my $element	= shift;
	my $id		= shift;
	my $id_attr	= 'text:id';
	if (defined $id)
		{
		$element->set_att($id_attr => $id);
		}
	return $element->att($id_attr);
	}

#-----------------------------------------------------------------------------
1;
