1# -*- coding: utf-8 -*-
2
3'''
4This file is part of the LibreOffice project.
5
6This Source Code Form is subject to the terms of the Mozilla Public
7License, v. 2.0. If a copy of the MPL was not distributed with this
8file, You can obtain one at http://mozilla.org/MPL/2.0/.
9
10This file incorporates work covered by the following license notice:
11
12   Licensed to the Apache Software Foundation (ASF) under one or more
13   contributor license agreements. See the NOTICE file distributed
14   with this work for additional information regarding copyright
15   ownership. The ASF licenses this file to you under the Apache
16   License, Version 2.0 (the "License"); you may not use this file
17   except in compliance with the License. You may obtain a copy of
18   the License at http://www.apache.org/licenses/LICENSE-2.0 .
19'''
20import uno
21import unittest
22import os.path
23from org.libreoffice.unotest import UnoInProcess, mkPropertyValue
24from tempfile import TemporaryDirectory
25from com.sun.star.uno import RuntimeException
26from com.sun.star.lang import IllegalArgumentException, NoSupportException
27from com.sun.star.beans import PropertyValue, StringPair
28from com.sun.star.rdf.URIs import ODF_PREFIX, ODF_SUFFIX
29from com.sun.star.i18n.NumberFormatIndex import NUMBER_INT
30from com.sun.star.text.ControlCharacter import PARAGRAPH_BREAK, HARD_HYPHEN
31from com.sun.star.text.TextContentAnchorType import (
32                AT_CHARACTER, AS_CHARACTER, AT_PARAGRAPH, AT_PAGE, AT_FRAME)
33
34
35class TreeNode():
36    '''base class for tree nodes. only instance: root of tree.'''
37
38    def __init__(self, content=None):
39        self.content = content
40        self._children = []
41        self.nodetype = "__ROOT__"
42        self.isnesting = False
43
44    def __str__(self):
45        return "<{}>".format(self.nodetype)
46
47    def __eq__(self, other):
48        return type(self) == type(other)
49
50    def __ne__(self, other):
51        return not self == other
52
53    def _dup(self, nodetype, *args):
54        try:
55            return nodetype(*args)
56        except Exception as e:
57            raise RuntimeError("TreeNode.dup") from e
58
59    def createenumeration(self):
60        return iter(self._children)
61
62    def appendchild(self, child):
63        self._children.append(child)
64        return self
65
66
67class ContentNode(TreeNode):
68
69    def __init__(self, content):
70        super().__init__(content)
71
72    def __str__(self):
73        return "{}\tcontent: {}".format(super().__str__(), self.content)
74
75    def __eq__(self, other):
76        try:
77            return other.content == self.content and super().__eq__(other)
78        except AttributeError:
79            return False
80
81    def appendchild(self, child):
82        try:
83            self._children.append(child)
84            return self
85        except Exception as e:
86            raise RuntimeError("ContentNode.appendchild") from e
87
88
89class TextNode(ContentNode):
90
91    def __init__(self, content):
92        super().__init__(content)
93        self.nodetype = "Text"
94
95    def dup(self):
96        return self._dup(TextNode, self.content)
97
98
99class TextFieldNode(ContentNode):
100
101    def __init__(self, content):
102        super().__init__(content)
103        self.nodetype = "TextField"
104
105    def dup(self):
106        return self._dup(TextFieldNode, self.content)
107
108
109class ControlCharacterNode(TreeNode):
110    def __init__(self, char):
111        super().__init__()
112        self.char = char
113        self.nodetype = "ControlCharacter"
114
115    def __str__(self):
116        return "{}\tcontent: {}".format(super().__str__(), self.char)
117
118    def __eq__(self, other):
119        try:
120            return other.char == self.char and super().__eq__(other)
121        except AttributeError:
122            return False
123
124    def dup(self):
125        return self._dup(ControlCharacterNode, self.char)
126
127
128class FootnoteNode(TreeNode):
129
130    def __init__(self, label):
131        super().__init__()
132        self.label = label
133        self.nodetype = "Footnote"
134
135    def __str__(self):
136        return "{}\tlabel: {}".format(super().__str__(), self.label)
137
138    def __eq__(self, other):
139        try:
140            return other.label == self.label and super().__eq__(other)
141        except AttributeError:
142            return False
143
144    def dup(self):
145        return self._dup(FootnoteNode, self.label)
146
147
148class FrameNode(TreeNode):
149    def __init__(self, name, anchor):
150        super().__init__()
151        self.name = name
152        self.anchor = anchor
153        self.nodetype = "Frame"
154
155    def __str__(self):
156        return "{}\tname: {}\tanchor: {}".format(
157                super().__str__(),self.name, self.str_anchor(self.anchor))
158
159    def __eq__(self, other):
160        try:
161            return (other.name == self.name and
162                    other.anchor == self.anchor and
163                    super().__eq__(other))
164        except AttributeError:
165            return False
166
167    def dup(self):
168        return self._dup(FrameNode, self.name, self.anchor)
169
170    def str_anchor(self, anchor):
171        anchors = {str(AS_CHARACTER): "AS_CHARACTER",
172                   str(AT_CHARACTER): "AT_CHARACTER",
173                   str(AT_PARAGRAPH): "AT_PARAGRAPH",
174                   str(AT_PAGE): "AT_PAGE",
175                   str(AT_FRAME): "AT_FRAME"}
176        try:
177            return anchors[str(anchor)]
178        except KeyError:
179            raise RuntimeError("unknown anchor")
180
181
182class MetaNode(TreeNode):
183    def __init__(self, xmlid):
184        super().__init__()
185        self.xmlid = xmlid
186        self.nodetype = "InContentMetadata"
187        self.isnesting = True
188
189    def __str__(self):
190        return "{}\txmlid: {}#{}".format(
191            super().__str__(), self.xmlid.First, self.xmlid.Second)
192
193    def __eq__(self, other):
194        try:
195            return (type(other) == type(self) and
196                    MetaNode.eq(other.xmlid, self.xmlid))
197        except AttributeError:
198            return False
199
200    @classmethod
201    def eq(cls, left, right):
202        return left.First == right.First and left.Second == right.Second
203
204    def dup(self):
205        return self._dup(MetaNode, self.xmlid)
206
207
208class MarkNode(TreeNode):
209    def __init__(self, name, ispoint=True):
210        super().__init__()
211        self.name = name
212        self.ispoint = ispoint
213        self.isstart = False
214
215    def __str__(self):
216        return "{}\tisPoint: {}\tisStart: {}".format(
217                super().__str__(), self.ispoint, self.isstart)
218
219    def __eq__(self, other):
220        try:
221            return (other.name == self.name and
222                    other.ispoint == self.ispoint and
223                    other.isstart == self.isstart)
224        except AttributeError:
225            return False
226
227
228class BookmarkNode(MarkNode):
229    def __init__(self, name, xmlid=StringPair()):
230        super().__init__(name)
231        self.xmlid = xmlid
232        self.nodetype = "Bookmark"
233
234    def __str__(self):
235        return "{}\txmlid: {}#{}".format(
236            super().__str__(), self.xmlid.First, self.xmlid.Second)
237
238    def __eq__(self, other):
239        try:
240            return (type(other) == type(self) and
241                    super().__eq__(other) and
242                    MetaNode.eq(other.xmlid, self.xmlid))
243        except AttributeError:
244            return False
245
246    def dup(self):
247        return self._dup(BookmarkNode, self.name, self.xmlid)
248
249
250class BookmarkStartNode(BookmarkNode):
251
252    def __init__(self, name, xmlid=StringPair()):
253        super().__init__(name, xmlid)
254        self.ispoint = False
255        self.isstart = True
256
257    def dup(self):
258        return self._dup(BookmarkStartNode, self.name)
259
260
261class BookmarkEndNode(BookmarkNode):
262
263    def __init__(self, name, xmlid=StringPair()):
264        super().__init__(name, xmlid)
265        self.ispoint = False
266        self.isstart = False
267
268    def dup(self):
269        return self._dup(BookmarkEndNode, self.name)
270
271
272class ReferenceMarkNode(MarkNode):
273    def __init__(self, name):
274        super().__init__(name)
275        self.nodetype = "ReferenceMark"
276
277    def __eq__(self, other):
278        return (type(other) == type(self) and super().__eq__(other))
279
280    def dup(self):
281        return self._dup(ReferenceMarkNode, self.name)
282
283
284class ReferenceMarkStartNode(ReferenceMarkNode):
285    def __init__(self, name):
286        super().__init__(name)
287        self.ispoint = False
288        self.isstart = True
289
290    def dup(self):
291        return self._dup(ReferenceMarkStartNode, self.name)
292
293
294class ReferenceMarkEndNode(ReferenceMarkNode):
295    def __init__(self, name):
296        super().__init__(name)
297        self.ispoint = False
298        self.isstart = False
299
300    def dup(self):
301        return self._dup(ReferenceMarkEndNode, self.name)
302
303
304class DocumentIndexMarkNode(MarkNode):
305    def __init__(self, name):
306        super().__init__(name)
307        self.nodetype = "DocumentIndexMark"
308
309    def __eq__(self, other):
310        return (type(other) == type(self) and super().__eq__(other))
311
312    def dup(self):
313        return self._dup(DocumentIndexMarkNode, self.name)
314
315
316class DocumentIndexMarkStartNode(DocumentIndexMarkNode):
317    def __init__(self, name):
318        super().__init__(name)
319        self.ispoint = False
320        self.isstart = True
321
322    def dup(self):
323        return self._dup(DocumentIndexMarkStartNode, self.name)
324
325
326class DocumentIndexMarkEndNode(DocumentIndexMarkNode):
327    def __init__(self, name):
328        super().__init__(name)
329        self.ispoint = False
330        self.isstart = False
331
332    def dup(self):
333        return self._dup(DocumentIndexMarkEndNode, self.name)
334
335
336class HyperlinkNode(TreeNode):
337    def __init__(self, url):
338        super().__init__()
339        self.nodetype = "Hyperlink"
340        self.isnesting = True
341        if url:
342            self.url = url
343        else:
344            raise RuntimeError("HyperlinkNode")
345
346    def __str__(self):
347        return "{}\turl: {}".format(super().__str__(), self.url)
348
349    def __eq__(self, other):
350        try:
351            return other.url == self.url and super().__eq__(other)
352        except AttributeError:
353            return False
354
355    def dup(self):
356        return self._dup(HyperlinkNode, self.url)
357
358
359class RubyNode(TreeNode):
360    def __init__(self, ruby):
361        super().__init__()
362        self.nodetype = "Ruby"
363        self.isnesting = True
364        if ruby:
365            self.ruby = ruby
366        else:
367            raise RuntimeError("RubyNode")
368
369    def __str__(self):
370        return "{}\trubytext: {}".format(super().__str__(), self.ruby)
371
372    def __eq__(self, other):
373        try:
374            return other.ruby == self.ruby and super().__eq__(other)
375        except AttributeError:
376            return False
377
378    def dup(self):
379        return self._dup(RubyNode, self.ruby)
380
381
382class MetaFieldNode(MetaNode):
383    def __init__(self, xmlid):
384        super().__init__(xmlid)
385        self.nodetype = "MetadataField"
386
387    def dup(self):
388        return self._dup(MetaFieldNode, self.xmlid)
389
390
391class Range():
392    def __init__(self, start, end, node):
393        self.start = start
394        self.end = end
395        self.node = node
396        self.extent = end - start
397
398
399class Inserter():
400
401    def __init__(self, xDoc):
402        self.xDoc = xDoc
403        self.xText = xDoc.getText()
404        self.xCursor = self.xText.createTextCursor()
405
406    def initparagraph(self):
407        ## we split the first (empty) paragraph, and then insert into the
408        ## second (empty) paragraph; this ensures first is always empty!
409        self.xCursor.gotoStartOfParagraph(False)
410        self.xText.insertControlCharacter(self.xCursor, PARAGRAPH_BREAK, False)
411
412    def inserttext(self, xCursor, text):
413        xCursor.setString(text)
414
415    def inserttextfield(self, xCursor, content):
416        xContent = self.maketextfield(content)
417        xContent.attach(xCursor)
418
419    def maketextfield(self, content):
420        xField = self.xDoc.createInstance("com.sun.star.text.textfield.Author")
421        xField.IsFixed = True
422        xField.FullName = False
423        xField.Content = content
424        return xField
425
426    def insertcontrolcharacter(self, xCursor, cchar):
427        self.xText.insertControlCharacter(xCursor, cchar, False)
428
429    def insertframe(self, xCursor, name, anchor):
430        xContent = self.makeframe(name, anchor)
431        xContent.attach(xCursor)
432
433    def makeframe(self, name, anchor):
434        xFrame = self.xDoc.createInstance("com.sun.star.text.TextFrame")
435        xFrame.AnchorType = anchor
436        xFrame.setName(name)
437        return xFrame
438
439    def insertfootnote(self, xCursor, label):
440        xContent = self.makefootnote(label)
441        xContent.attach(xCursor)
442
443    def makefootnote(self, label):
444        xFootNote = self.xDoc.createInstance("com.sun.star.text.Footnote")
445        xFootNote.setLabel(label)
446        return xFootNote
447
448    def insertbookmark(self, xCursor, name, xmlid):
449        xContent = self.makebookmark(name)
450        xContent.attach(xCursor)
451        if xmlid.First != "":
452            xContent.MetadataReference = xmlid
453
454    def makebookmark(self, name):
455        xBookmark = self.xDoc.createInstance("com.sun.star.text.Bookmark")
456        xBookmark.setName(name)
457        return xBookmark
458
459    def insertreferencemark(self, xCursor, name):
460        xContent = self.makereferencemark(name)
461        xContent.attach(xCursor)
462
463    def makereferencemark(self, name):
464        xMark = self.xDoc.createInstance("com.sun.star.text.ReferenceMark")
465        xMark.setName(name)
466        return xMark
467
468    def insertdocumentindexmark(self, xCursor, key):
469        xContent = self.makedocumentindexmark(key)
470        xContent.attach(xCursor)
471
472    def makedocumentindexmark(self, key):
473        xMark = self.xDoc.createInstance("com.sun.star.text.DocumentIndexMark")
474        xMark.PrimaryKey = key
475        return xMark
476
477    def inserthyperlink(self, xCursor, url):
478        xCursor.HyperLinkURL = url
479
480    def insertruby(self, xCursor, rubytext):
481        xCursor.RubyText = rubytext
482
483    def insertmeta(self, xCursor, xmlid):
484        xContent = self.makemeta()
485        xContent.attach(xCursor)
486        xContent.MetadataReference = xmlid
487        return xContent
488
489    def makemeta(self):
490        xMeta = self.xDoc.createInstance("com.sun.star.text.InContentMetadata")
491        return xMeta
492
493    def insertmetafield(self, xCursor, xmlid):
494        xContent = self.makemetafield()
495        xContent.attach(xCursor)
496        xContent.MetadataReference = xmlid
497        return xContent
498
499    def makemetafield(self):
500        xMeta = self.xDoc.createInstance(
501                                "com.sun.star.text.textfield.MetadataField")
502        return xMeta
503
504
505class TreeInserter(Inserter):
506
507    def __init__(self, xDoc):
508        super().__init__(xDoc)
509        self._bookmarkstarts = {}
510        self._referencemarkstarts = {}
511        self._documentindexmarkstarts = {}
512        self._framehints = []
513
514    def inserttree(self, tree):
515        if tree.nodetype != "__ROOT__":
516            raise RuntimeError("insertTree: test error: no root")
517        self.initparagraph()
518        self.insertchildren(tree.createenumeration())
519        for p in self._framehints:
520            self.insertframe(p[0], p[1].name, p[1].anchor)
521
522    def insertchildren(self, children):
523        xCursor = self.xCursor
524        for node in children:
525            xCursor.gotoEndOfParagraph(False)
526            type_ = node.nodetype
527            if type_ == "Text":
528                self.inserttext(xCursor, node.content)
529            elif type_ == "TextField":
530                self.inserttextfield(xCursor, node.content)
531            elif type_ == "ControlCharacter":
532                self.insertcontrolcharacter(xCursor, node.char)
533            elif type_ == "Footnote":
534                self.insertfootnote(xCursor, node.label)
535            elif type_ == "Frame":
536                if node.anchor == AT_CHARACTER:
537                    self._framehints.append((xCursor.getStart(), node))
538                else:
539                    self.insertframe(xCursor, node.name, node.anchor)
540            elif type_ == "Bookmark":
541                name = node.name
542                id_ = node.xmlid
543                if node.ispoint:
544                    self.insertbookmark(xCursor, name, id_)
545                elif node.isstart:
546                    self._bookmarkstarts[name] = xCursor.getStart()
547                else:
548                    xRange = self._bookmarkstarts[name]
549                    xParaCursor = self.mkcursor(xRange)
550                    self.insertbookmark(xParaCursor, name, id_)
551            elif type_ == "ReferenceMark":
552                name = node.name
553                if node.ispoint:
554                    self.insertreferencemark(xCursor, name)
555                elif node.isstart:
556                    self._referencemarkstarts[name] = xCursor.getStart()
557                else:
558                    xRange = self._referencemarkstarts[name]
559                    xParaCursor = self.mkcursor(xRange)
560                    self.insertreferencemark(xParaCursor, name)
561            elif type_ == "DocumentIndexMark":
562                name = node.name
563                if node.ispoint:
564                    self.insertdocumentindexmark(xCursor, name)
565                elif node.isstart:
566                    self._documentindexmarkstarts[name] = xCursor.getStart()
567                else:
568                    xRange = self._documentindexmarkstarts[name]
569                    xParaCursor = self.mkcursor(xRange)
570                    self.insertdocumentindexmark(xParaCursor, name)
571            elif type_ == "Hyperlink":
572                xRange = xCursor.getStart()
573                self.insertchildren(node.createenumeration())
574                xParaCursor = self.mkcursor(xRange)
575                self.inserthyperlink(xParaCursor, node.url)
576            elif type_ == "Ruby":
577                xRange = xCursor.getStart()
578                self.insertchildren(node.createenumeration())
579                xParaCursor = self.mkcursor(xRange)
580                self.insertruby(xParaCursor, node.ruby)
581            elif type_ == "InContentMetadata":
582                xRange = xCursor.getStart()
583                self.insertchildren(node.createenumeration())
584                xParaCursor = self.mkcursor(xRange)
585                self.insertmeta(xParaCursor, node.xmlid)
586            elif type_ == "MetadataField":
587                xRange = xCursor.getStart()
588                self.insertchildren(node.createenumeration())
589                xParaCursor = self.mkcursor(xRange)
590                self.insertmetafield(xParaCursor, node.xmlid)
591            elif type_ == "SoftPageBreak":
592                raise RuntimeError("sorry, cannot test SoftPageBreak")
593            else:
594                raise RuntimeError("unexpected type: {}".format(type_))
595
596    def mkcursor(self, xRange):
597        xCursor = self.xText.createTextCursorByRange(xRange)
598        xCursor.gotoEndOfParagraph(True)
599        return xCursor
600
601
602# FIXME: this does not account for inserted dummy characters!
603class RangeInserter(Inserter):
604    def __init__(self, xDoc):
605        super().__init__(xDoc)
606        self.initparagraph()
607
608    # def inserttext(self, pos, text):
609        # self.xCursor.gotoStartOfParagraph(False)
610        # self.xCursor.goRight(pos, False)
611        # self.inserttext(self.xCursor, text)
612
613    def insertrange(self, range):
614        self.xCursor.gotoStartOfParagraph(False)
615        self.xCursor.goRight(range.start, False)
616        self.xCursor.goRight(range.extent, True)
617        return self.insertnode(self.xCursor, range.node)
618
619    def insertnode(self, xParaCursor, node):
620        nodetype = node.nodetype
621        if nodetype == "Text":
622            text = node
623            self.inserttext(xParaCursor, text.content)
624        elif nodetype == "Hyperlink":
625            href = node
626            self.inserthyperlink(xParaCursor, href.url)
627        elif nodetype == "Ruby":
628            ruby = node
629            self.insertruby(xParaCursor, ruby.ruby)
630        elif nodetype == "InContentMetadata":
631            meta = node
632            return self.insertmeta(xParaCursor, meta.xmlid)
633        elif nodetype == "MetadataField":
634            meta = node
635            return self.insertmetafield(xParaCursor, meta.xmlid)
636        elif nodetype == "Bookmark":
637            bkmk = node
638            if bkmk.ispoint:
639                raise RuntimeError("range only")
640            self.insertbookmark(xParaCursor, bkmk.name, bkmk.xmlid)
641        elif nodetype == "ReferenceMark":
642            mark = node
643            if mark.ispoint:
644                raise RuntimeError("range only")
645            self.insertreferencemark(xParaCursor, mark.name)
646        elif nodetype == "DocumentIndexMark":
647            mark = node
648            if mark.ispoint:
649                raise RuntimeError("range only")
650            self.insertdocumentindexmark(xParaCursor, mark.name)
651        elif nodetype == "TextField":
652            field = node
653            self.inserttextfield(self.xCursor, field.content)
654        elif nodetype == "Footnote":
655            note = node
656            self.insertfootnote(self.xCursor, note.label)
657        elif nodetype == "Frame":
658            frame = node
659            self.insertframe(xParaCursor, frame.name, frame.anchor)
660        elif nodetype == "ControlCharacter":
661            cchar = node
662            self.insertcontrolcharacter(self.xCursor, cchar.char)
663        elif nodetype == "SoftPageBreak":
664            raise RuntimeError("sorry, cannot test SoftPageBreak")
665        else:
666            raise RuntimeError("unexpected nodetype: {}".format(nodetype))
667        return None
668
669
670class EnumConverter():
671
672    def __init__(self):
673        self._stack = []
674
675    def convert(self, xEnum):
676        root = TreeNode()
677        self._stack.append(root)
678        ret = self.convertchildren(xEnum)
679        assert (len(self._stack)==0), "EnumConverter.convert: stack is not empty"
680        return ret
681
682    def convertchildren(self, xEnum):
683        for xPortion in xEnum:
684            type_ = xPortion.TextPortionType
685            if type_ == "Text":
686                text = xPortion.getString()
687                node = TextNode(text)
688                url = xPortion.HyperLinkURL
689                if len(url) > 0:
690                    temp = node
691                    node = HyperlinkNode(url)
692                    node.appendchild(temp)
693            elif type_ == "TextField":
694                xField = xPortion.TextField
695                if xField.supportsService("com.sun.star.text.textfield.MetadataField"):
696                    xmlid = xField.MetadataReference
697                    node = MetaFieldNode(xmlid)
698                    self._stack.append(node)
699                    xEnumChildren = xField.createEnumeration()
700                    node2 = self.convertchildren(xEnumChildren)
701                    print(node)
702                    print(node2)
703                    assert (node2 is node), "stack error: meta-field"
704                else:
705                    content = xField.Content
706                    isFixed = xField.IsFixed
707                    assert isFixed, "field not fixed?"
708                    node = TextFieldNode(content)
709            elif type_ == "ControlCharacter":
710                c = xPortion.ControlCharacter
711                node = ControlCharacterNode(c)
712            elif type_ == "Footnote":
713                xFootnote = xPortion.Footnote
714                label = xFootnote.getLabel()
715                node = FootnoteNode(label)
716            elif type_ == "Frame":
717                xCEA = xPortion.createContentEnumeration('')
718                while xCEA.hasMoreElements():
719                    xFrame = xCEA.nextElement()
720                    anchor = xFrame.AnchorType
721                    name = xFrame.getName()
722                    node = FrameNode(name, anchor)
723                    self._stack[-1].appendchild(node)
724                continue
725            elif type_ == "Bookmark":
726                xMark = xPortion.Bookmark
727                name = xMark.getName()
728                xmlid = xMark.MetadataReference
729                isCollapsed = xPortion.IsCollapsed
730                if isCollapsed:
731                    node = BookmarkNode(name, xmlid)
732                else:
733                    isStart = xPortion.IsStart
734                    if isStart:
735                        node = BookmarkStartNode(name, xmlid)
736                    else:
737                        node = BookmarkEndNode(name, xmlid)
738            elif type_ == "ReferenceMark":
739                xMark = xPortion.ReferenceMark
740                name = xMark.getName()
741                isCollapsed = xPortion.IsCollapsed
742                if isCollapsed:
743                    node = ReferenceMarkNode(name)
744                else:
745                    isStart = xPortion.IsStart
746                    if isStart:
747                        node = ReferenceMarkStartNode(name)
748                    else:
749                        node = ReferenceMarkEndNode(name)
750            elif type_ == "DocumentIndexMark":
751                xMark = xPortion.DocumentIndexMark
752                name = xMark.PrimaryKey
753                isCollapsed = xPortion.IsCollapsed
754                if isCollapsed:
755                    node = DocumentIndexMarkNode(name)
756                else:
757                    isStart = xPortion.IsStart
758                    if isStart:
759                        node = DocumentIndexMarkStartNode(name)
760                    else:
761                        node = DocumentIndexMarkEndNode(name)
762            elif type_ == "Ruby":
763                isStart = xPortion.IsStart
764                if isStart:
765                    # ARRGH!!! stupid api...
766                    # the text is ONLY at the start!
767                    ruby = xPortion.RubyText
768                    node = RubyNode(ruby)
769                    self._stack.append(node)
770                    continue
771                else:
772                    node = self._stack.pop()
773                    assert (isinstance(node, RubyNode)), "stack error: Ruby expected; is: {}".format(str(node))
774            elif type_ == "InContentMetadata":
775                xMeta = xPortion.InContentMetadata
776                xmlid = xMeta.MetadataReference
777                node = MetaNode(xmlid)
778                self._stack.append(node)
779                xEnumChildren = xMeta.createEnumeration()
780                node2 = self.convertchildren(xEnumChildren)
781                assert (node2 is node), "stack error: meta"
782            elif type_ == "SoftPageBreak":
783                node = SoftPageBreakNode()
784            else:
785                raise RuntimeError("unexpected type: {}".format(type_))
786            self._stack[-1].appendchild(node)
787        ret = self._stack.pop()
788        return ret
789
790
791class FuzzyTester():
792    '''this is where we nail the pudding to the wall'''
793    def __init__(self):
794        self.diffcontent = 0
795        self.diffmissing = 0
796        self.diffnesting = 0
797        self.diffspuriousemptytext = 0
798        self.diffsequence = 0 # ignored?
799        self.stackexpected = []
800        self.stackactual = []
801        self.bufferexpected = []
802        self.bufferactual = []
803
804    def dotest(self, expected, actual):
805        '''idea: traverse both trees, enumerate nodes, stopping at content nodes.
806        then compare buffers.'''
807        assert "__ROOT__" == expected.nodetype
808        assert "__ROOT__" == actual.nodetype
809        self.stackexpected.append((expected, expected.createenumeration()))
810        self.stackactual.append((actual, actual.createenumeration()))
811        while self.stackexpected or self.stackactual:
812            self.traverse(self.stackexpected, self.bufferexpected)
813            self.traverse(self.stackactual, self.bufferactual)
814            self.testbuffer()
815        if self.diffsequence:
816            print("warning: {} differences in sequence".format(
817                                                    self.diffsequence))
818        if self.diffspuriousemptytext:
819            print("warning: {} spurious empty text nodes".format(
820                                                    self.diffspuriousemptytext))
821        if self.diffnesting:
822            print("WARNING: {} differences in nesting".format(
823                                                    self.diffnesting))
824        assert self.diffcontent == 0
825        assert self.diffmissing == 0
826
827    def traverse(self, stack, buffer):
828        while stack:
829            topenum = stack[-1][1]
830            try:
831                node = next(topenum)
832                buffer.append(node)
833                if node._children:
834                    node_enum = node.createenumeration()
835                    stack.append((node, node_enum))
836                if node.content:
837                    if not (isinstance(node, TextNode) and # spurious empty text?
838                            len(node.content) == 0):
839                        return # break here
840            except StopIteration:
841                buffer.append(stack[-1][0])
842                stack.pop()
843
844    def testterminatingnode(self):
845        lenexpected = len(self.bufferexpected)
846        lenactual = len(self.bufferactual)
847        if lenexpected == 0 or lenactual == 0:
848            return
849        expected = self.bufferexpected[-1]
850        actual = self.bufferactual[-1]
851        eroot = expected.nodetype == "__ROOT__"
852        aroot = actual.nodetype == "__ROOT__"
853        if eroot or aroot:
854            if not (eroot and aroot):
855                if aroot:
856                    self.printmissing(expected)
857                else:
858                    self.printunexpected(actual)
859                self.diffmissing += 1
860            return
861        self.testcontentnode(expected, actual)
862        self.bufferexpected[-1] = None
863        self.bufferactual[-1] = None
864
865    def testcontentnode(self, expected, actual):
866        contentexpected = expected.content
867        contentactual = actual.content
868        if expected != actual:
869            self.printdiff("text content differs", contentexpected, contentactual)
870            self.diffcontent += 1
871
872    def testbuffer(self):
873        lenactual = len(self.bufferactual)
874        tmp_bufferactual = self.bufferactual[:]
875        for i, node in enumerate(self.bufferexpected):
876            try:
877                j = tmp_bufferactual.index(node)
878                if j != i:
879                    # FIXME how bad is this?
880                    self.printdiff("position differs", i, j)
881                    # a hacky hack
882                    min_ = min(i,j)
883                    max_ = max(min(lenactual-1, i),j)
884                    for k in range(min_, max_):
885                        tmp = tmp_bufferactual[k]
886                        if tmp and tmp.isnesting:
887                            self.printnesting(node, tmp)
888                            self.diffnesting += 1
889                    self.diffsequence += 1
890                tmp_bufferactual[j] = None
891            except ValueError:
892                print('perdrix')
893                self.printmissing(node)
894                self.diffmissing += 1
895        for j, node in enumerate(tmp_bufferactual):
896            if node:
897                self.printunexpected(node)
898                if isinstance(node, TextNode) and len(node.content) == 0:
899                    self.diffspuriousemptytext += 1
900                else:
901                    print('renard')
902                    self.diffmissing += 1
903        self.testterminatingnode()
904        self.bufferexpected[:] = []
905        self.bufferactual[:] = []
906
907    def printdiff(self, prefix, expected, actual):
908        print("{}:\texpected: {}\tactual: {}".format(prefix, expected, actual))
909
910    def printnesting(self, node, nesting):
911        print("node: {} possibly moved across nesting {}".format(
912                                                    str(node), str(nesting)))
913
914    def printmissing(self, node):
915        print("   missing node: {}".format(str(node)))
916
917    def printunexpected(self, node):
918        print("unexpected node: {}".format(str(node)))
919
920
921class TextPortionEnumerationTest(unittest.TestCase):
922
923    xMSF = None
924    xContext = None
925    tempdir = None
926
927    @classmethod
928    def setUpClass(cls):
929        cls._uno = UnoInProcess()
930        cls._uno.setUp()
931        cls.xDoc = cls._uno.openEmptyWriterDoc()
932        cls.count = 0
933
934    @classmethod
935    def tearDownClass(cls):
936        cls.xDoc.close(True)
937        cls._uno.tearDown()
938        # HACK in case cls.xDoc holds a UNO proxy to an SwXTextDocument (whose dtor calls
939        # Application::GetSolarMutex via sw::UnoImplPtrDeleter), which would potentially only be
940        # garbage-collected after VCL has already been deinitialized:
941        cls.xDoc = None
942
943    def test_text(self):
944        root = TreeNode()
945        text = TextNode("abc")
946        root.appendchild(text)
947        self.dotest(root)
948
949    def test_text_field(self):
950        self.mkname("ruby")
951        root = TreeNode()
952        txtf = TextFieldNode("abc")
953        root.appendchild(txtf)
954        self.dotest(root)
955
956    @unittest.skip("FIXME this is converted to a text portion: ControlCharacter is obsolete")
957    def test_control_char(self):
958        root = TreeNode()
959        cchr = ControlCharacterNode(HARD_HYPHEN)
960        root.appendchild(cchr)
961        self.dotest(root)
962
963    @unittest.skip("FIXME: insert a soft page break: not done")
964    def test_soft_page_break(self):
965        root = TreeNode()
966        spbk =SoftPageBreakNode()
967        text = TextNode("abc")
968        root.appendchild(spbk)
969        root.appendchild(text)
970        self.dotest(root)
971
972    def test_footnote(self):
973        name = self.mkname("ftn")
974        root = TreeNode()
975        ftnd = FootnoteNode(name)
976        root.appendchild(ftnd)
977        self.dotest(root)
978
979    def test_frame_as(self):
980        name = self.mkname("frame")
981        root = TreeNode()
982        fram = FrameNode(name, AS_CHARACTER)
983        root.appendchild(fram)
984        self.dotest(root)
985
986    def test_frame_at(self):
987        name = self.mkname("frame")
988        root = TreeNode()
989        fram = FrameNode(name, AT_CHARACTER)
990        root.appendchild(fram)
991        self.dotest(root)
992
993    def test_bookmark_point(self):
994        name = self.mkname("mark")
995        root = TreeNode()
996        bkmk = BookmarkNode(name)
997        text = TextNode("abc")
998        root.appendchild(bkmk)
999        root.appendchild(text)
1000        self.dotest(root)
1001
1002    def test_bookmark(self):
1003        name = self.mkname("mark")
1004        root = TreeNode()
1005        bkm1 = BookmarkStartNode(name)
1006        text = TextNode("abc")
1007        bkm2 = BookmarkEndNode(name)
1008        root.appendchild(bkm1)
1009        root.appendchild(text)
1010        root.appendchild(bkm2)
1011        self.dotest(root)
1012
1013    def test_bookmark_point_xmlid(self):
1014        name = self.mkname("mark")
1015        id = self.mkid("id")
1016        root = TreeNode()
1017        bkmk = BookmarkNode(name, id)
1018        text = TextNode("abc")
1019        root.appendchild(bkmk)
1020        root.appendchild(text)
1021        self.dotest(root)
1022
1023    def test_bookmark_xmlid(self):
1024        name = self.mkname("mark")
1025        id = self.mkid("id")
1026        root = TreeNode()
1027        bkm1 = BookmarkStartNode(name, id)
1028        text = TextNode("abc")
1029        bkm2 = BookmarkEndNode(name, id)
1030        root.appendchild(bkm1)
1031        root.appendchild(text)
1032        root.appendchild(bkm2)
1033        self.dotest(root)
1034
1035    def test_refmark_point(self):
1036        name = self.mkname("refmark")
1037        root = TreeNode()
1038        rfmk = ReferenceMarkNode(name)
1039        text = TextNode("abc")
1040        root.appendchild(rfmk)
1041        root.appendchild(text)
1042        self.dotest(root)
1043
1044    def test_refmark(self):
1045        name = self.mkname("refmark")
1046        root = TreeNode()
1047        rfm1 = ReferenceMarkStartNode(name)
1048        text = TextNode("abc")
1049        rfm2 = ReferenceMarkEndNode(name)
1050        root.appendchild(rfm1)
1051        root.appendchild(text)
1052        root.appendchild(rfm2)
1053        self.dotest(root)
1054
1055    def test_toxmark_point(self):
1056        name = self.mkname("toxmark")
1057        root = TreeNode()
1058        txmk = DocumentIndexMarkNode(name)
1059        text = TextNode("abc")
1060        root.appendchild(txmk)
1061        root.appendchild(text)
1062        self.dotest(root)
1063
1064    def test_toxmark(self):
1065        name = self.mkname("toxmark")
1066        root = TreeNode()
1067        txm1 = DocumentIndexMarkStartNode(name)
1068        text = TextNode("abc")
1069        txm2 = DocumentIndexMarkEndNode(name)
1070        root.appendchild(txm1)
1071        root.appendchild(text)
1072        root.appendchild(txm2)
1073        self.dotest(root)
1074
1075    def test_hyperlink(self):
1076        name = self.mkname("url")
1077        root = TreeNode()
1078        href = HyperlinkNode(name)
1079        text = TextNode("abc")
1080        href.appendchild(text)
1081        root.appendchild(href)
1082        self.dotest(root)
1083
1084    def test_hyperlink_empty(self):
1085        name = self.mkname("url")
1086        root = TreeNode()
1087        href = HyperlinkNode(name)
1088        text = TextNode("")
1089        href.appendchild(text)
1090        root.appendchild(href)
1091        self.dotest(root)
1092
1093    def test_ruby(self):
1094        name = self.mkname("ruby")
1095        root = TreeNode()
1096        ruby = RubyNode(name)
1097        text = TextNode("abc")
1098        ruby.appendchild(text)
1099        root.appendchild(ruby)
1100        self.dotest(root)
1101
1102    def test_ruby_empty(self):
1103        # BUG: #i91534#
1104        name = self.mkname("ruby")
1105        root = TreeNode()
1106        ruby = RubyNode(name)
1107        root.appendchild(ruby)
1108        self.dotest(root)
1109
1110    def test_meta(self):
1111        id = StringPair("content.xml", self.mkname("id"))
1112        root = TreeNode()
1113        meta = MetaNode(id)
1114        text = TextNode("abc")
1115        root.appendchild(TextNode("123"))
1116        meta.appendchild(text)
1117        root.appendchild(meta)
1118        self.dotest(root)
1119
1120    def test_meta_empty(self):
1121        id = StringPair("content.xml", self.mkname("id"))
1122        root = TreeNode()
1123        meta = MetaNode(id)
1124        root.appendchild(meta)
1125        self.dotest(root)
1126
1127    def test_meta_field(self):
1128        id = StringPair("content.xml", self.mkname("id"))
1129        root = TreeNode()
1130        meta = MetaFieldNode(id)
1131        text = TextNode("abc")
1132        root.appendchild(TextNode("123"))
1133        meta.appendchild(text)
1134        root.appendchild(meta)
1135        self.dotest(root)
1136
1137    def test_meta_field_empty(self):
1138        id = StringPair("content.xml", self.mkname("id"))
1139        root = TreeNode()
1140        meta = MetaFieldNode(id)
1141        root.appendchild(meta)
1142        self.dotest(root)
1143
1144    def test_bookmark1(self):
1145        name1 = self.mkname("mark")
1146        name2 = self.mkname("mark")
1147        name3 = self.mkname("mark")
1148        root = TreeNode()
1149        root.appendchild(BookmarkStartNode(name1))
1150        root.appendchild(BookmarkNode(name2))
1151        root.appendchild(BookmarkStartNode(name3))
1152        root.appendchild(TextNode("abc"))
1153        root.appendchild(BookmarkEndNode(name1))
1154        root.appendchild(TextNode("de"))
1155        root.appendchild(BookmarkEndNode(name3))
1156        self.dotest(root)
1157
1158    def test_bookmark2(self):
1159        name1 = self.mkname("mark")
1160        name2 = self.mkname("mark")
1161        name3 = self.mkname("mark")
1162        root = TreeNode()
1163        root.appendchild(BookmarkStartNode(name1))
1164        root.appendchild(TextNode("abc"))
1165        root.appendchild(BookmarkNode(name2))
1166        root.appendchild(BookmarkStartNode(name3))
1167        root.appendchild(BookmarkEndNode(name1))
1168        root.appendchild(TextNode("de"))
1169        root.appendchild(BookmarkEndNode(name3))
1170        self.dotest(root)
1171
1172    def test_refmark2(self):
1173        name1 = self.mkname("refmark")
1174        root = TreeNode()
1175        root.appendchild(ReferenceMarkStartNode(name1))
1176        root.appendchild(TextNode("abc"))
1177        # BUG: #i102541# (this is actually not unoportenum's fault)
1178        root.appendchild(ReferenceMarkEndNode(name1))
1179        root.appendchild(TextNode("de"))
1180        self.dotest(root)
1181
1182    def test_refmark3(self):
1183        # BUG: #i107672# (non-deterministic; depends on pointer ordering)
1184        name1 = self.mkname("refmark")
1185        name2 = self.mkname("refmark")
1186        name3 = self.mkname("refmark")
1187        name4 = self.mkname("refmark")
1188        name5 = self.mkname("refmark")
1189        name6 = self.mkname("refmark")
1190        name7 = self.mkname("refmark")
1191        root = TreeNode()
1192        root.appendchild(ReferenceMarkStartNode(name1))
1193        root.appendchild(ReferenceMarkStartNode(name2))
1194        root.appendchild(ReferenceMarkStartNode(name3))
1195        root.appendchild(ReferenceMarkStartNode(name4))
1196        root.appendchild(ReferenceMarkStartNode(name5))
1197        root.appendchild(ReferenceMarkStartNode(name6))
1198        root.appendchild(ReferenceMarkStartNode(name7))
1199        root.appendchild(TextNode("abc"))
1200        root.appendchild(ReferenceMarkEndNode(name7))
1201        root.appendchild(ReferenceMarkEndNode(name6))
1202        root.appendchild(ReferenceMarkEndNode(name5))
1203        root.appendchild(ReferenceMarkEndNode(name4))
1204        root.appendchild(ReferenceMarkEndNode(name3))
1205        root.appendchild(ReferenceMarkEndNode(name2))
1206        root.appendchild(ReferenceMarkEndNode(name1))
1207        root.appendchild(TextNode("de"))
1208        self.dotest(root)
1209
1210    def test_toxmark2(self):
1211        name1 = self.mkname("toxmark")
1212        root = TreeNode()
1213        root.appendchild(DocumentIndexMarkStartNode(name1))
1214        root.appendchild(TextNode("abc"))
1215        root.appendchild(DocumentIndexMarkEndNode(name1))
1216        root.appendchild(TextNode("de"))
1217        self.dotest(root)
1218
1219    def test_toxmark3(self):
1220        # BUG: #i107672# (non-deterministic; depends on pointer ordering)
1221        name1 = self.mkname("toxmark")
1222        name2 = self.mkname("toxmark")
1223        name3 = self.mkname("toxmark")
1224        name4 = self.mkname("toxmark")
1225        name5 = self.mkname("toxmark")
1226        name6 = self.mkname("toxmark")
1227        name7 = self.mkname("toxmark")
1228        root = TreeNode()
1229        root.appendchild(DocumentIndexMarkStartNode(name1))
1230        root.appendchild(DocumentIndexMarkStartNode(name2))
1231        root.appendchild(DocumentIndexMarkStartNode(name3))
1232        root.appendchild(DocumentIndexMarkStartNode(name4))
1233        root.appendchild(DocumentIndexMarkStartNode(name5))
1234        root.appendchild(DocumentIndexMarkStartNode(name6))
1235        root.appendchild(DocumentIndexMarkStartNode(name7))
1236        root.appendchild(TextNode("abc"))
1237        root.appendchild(DocumentIndexMarkEndNode(name7))
1238        root.appendchild(DocumentIndexMarkEndNode(name6))
1239        root.appendchild(DocumentIndexMarkEndNode(name5))
1240        root.appendchild(DocumentIndexMarkEndNode(name4))
1241        root.appendchild(DocumentIndexMarkEndNode(name3))
1242        root.appendchild(DocumentIndexMarkEndNode(name2))
1243        root.appendchild(DocumentIndexMarkEndNode(name1))
1244        root.appendchild(TextNode("de"))
1245        self.dotest(root)
1246
1247    def test_marks1(self):
1248        name1 = self.mkname("bookmark")
1249        name2 = self.mkname("toxmark")
1250        name3 = self.mkname("refmark")
1251        name4 = self.mkname("toxmark")
1252        root = TreeNode()
1253        root.appendchild(BookmarkStartNode(name1))
1254        root.appendchild(DocumentIndexMarkNode(name2))
1255        root.appendchild(ReferenceMarkStartNode(name3))
1256        root.appendchild(TextNode("abc"))
1257        root.appendchild(BookmarkEndNode(name1))
1258        root.appendchild(DocumentIndexMarkStartNode(name4))
1259        root.appendchild(TextNode("de"))
1260        root.appendchild(DocumentIndexMarkEndNode(name4))
1261        root.appendchild(ReferenceMarkEndNode(name3))
1262        self.dotest(root)
1263
1264    def test_marks2(self):
1265        name1 = self.mkname("bookmark")
1266        name2 = self.mkname("refmark")
1267        name3 = self.mkname("refmark")
1268        name4 = self.mkname("toxmark")
1269        name5 = self.mkname("refmark")
1270        root = TreeNode()
1271        root.appendchild(BookmarkStartNode(name1))
1272        root.appendchild(ReferenceMarkNode(name2))
1273        root.appendchild(ReferenceMarkStartNode(name3))
1274        root.appendchild(TextNode("abc"))
1275        root.appendchild(DocumentIndexMarkStartNode(name4))
1276        root.appendchild(ReferenceMarkStartNode(name5))
1277        # BUG: #i102541# (this is actually not unoportenum's fault)
1278        root.appendchild(ReferenceMarkEndNode(name3))
1279        root.appendchild(TextNode("de"))
1280        root.appendchild(DocumentIndexMarkEndNode(name4))
1281        root.appendchild(BookmarkEndNode(name1))
1282        root.appendchild(ReferenceMarkEndNode(name5))
1283        self.dotest(root)
1284
1285    def test_marks3(self):
1286        name1 = self.mkname("bookmark")
1287        name2 = self.mkname("refmark")
1288        name3 = self.mkname("refmark")
1289        name4 = self.mkname("toxmark")
1290        name5 = self.mkname("refmark")
1291        root = TreeNode()
1292        root.appendchild(BookmarkStartNode(name1))
1293        root.appendchild(DocumentIndexMarkNode(name2))
1294        root.appendchild(DocumentIndexMarkStartNode(name3))
1295        root.appendchild(TextNode("abc"))
1296        root.appendchild(ReferenceMarkStartNode(name4))
1297        root.appendchild(DocumentIndexMarkStartNode(name5))
1298        root.appendchild(DocumentIndexMarkEndNode(name3))
1299        root.appendchild(TextNode("de"))
1300        root.appendchild(ReferenceMarkEndNode(name4))
1301        root.appendchild(BookmarkEndNode(name1))
1302        root.appendchild(DocumentIndexMarkEndNode(name5))
1303        self.dotest(root)
1304
1305    def test_frame_mark1(self):
1306        name1 = self.mkname("bookmark")
1307        name2 = self.mkname("frame")
1308        root = TreeNode()
1309        root.appendchild(TextNode("abc"))
1310        root.appendchild(BookmarkNode(name1))
1311        root.appendchild(TextNode("de"))
1312        root.appendchild(FrameNode(name2, AS_CHARACTER))
1313        self.dotest(root)
1314
1315    def test_frame_mark2(self):
1316        # BUG: #i98530#
1317        name1 = self.mkname("bookmark")
1318        name2 = self.mkname("frame")
1319        root = TreeNode()
1320        root.appendchild(TextNode("abc"))
1321        root.appendchild(BookmarkNode(name1))
1322        root.appendchild(TextNode("de"))
1323        root.appendchild(FrameNode(name2, AT_CHARACTER))
1324        self.dotest(root)
1325
1326    def test_frame_mark3(self):
1327        name1 = self.mkname("frame")
1328        name2 = self.mkname("bookmark")
1329        root = TreeNode()
1330        root.appendchild(TextNode("abc"))
1331        root.appendchild(FrameNode(name1, AS_CHARACTER))
1332        root.appendchild(TextNode("de"))
1333        root.appendchild(BookmarkNode(name2))
1334        self.dotest(root)
1335
1336    def test_frame_mark4(self):
1337        name1 = self.mkname("frame")
1338        name2 = self.mkname("bookmark")
1339        root = TreeNode()
1340        root.appendchild(TextNode("abc"))
1341        root.appendchild(FrameNode(name1, AT_CHARACTER))
1342        root.appendchild(TextNode("de"))
1343        root.appendchild(BookmarkNode(name2))
1344        self.dotest(root)
1345
1346    def test_frames1(self):
1347        name1 = self.mkname("frame")
1348        name2 = self.mkname("frame")
1349        name3 = self.mkname("frame")
1350        root = TreeNode()
1351        root.appendchild(FrameNode(name1, AT_CHARACTER))
1352        root.appendchild(FrameNode(name2, AT_CHARACTER))
1353        root.appendchild(FrameNode(name3, AT_CHARACTER))
1354        self.dotest(root)
1355
1356    def test_frames2(self):
1357        name1 = self.mkname("frame")
1358        name2 = self.mkname("frame")
1359        name3 = self.mkname("frame")
1360        root = TreeNode()
1361        root.appendchild(FrameNode(name1, AS_CHARACTER))
1362        root.appendchild(FrameNode(name2, AS_CHARACTER))
1363        root.appendchild(FrameNode(name3, AS_CHARACTER))
1364        self.dotest(root)
1365
1366    def test_frames3(self):
1367        name1 = self.mkname("frame")
1368        name2 = self.mkname("frame")
1369        name3 = self.mkname("frame")
1370        root = TreeNode()
1371        root.appendchild(FrameNode(name1, AT_CHARACTER))
1372        root.appendchild(FrameNode(name2, AS_CHARACTER))
1373        root.appendchild(FrameNode(name3, AT_CHARACTER))
1374        self.dotest(root)
1375
1376    def test_frames4(self):
1377        name1 = self.mkname("frame")
1378        name2 = self.mkname("frame")
1379        name3 = self.mkname("frame")
1380        root = TreeNode()
1381        root.appendchild(FrameNode(name1, AT_CHARACTER))
1382        root.appendchild(FrameNode(name2, AT_CHARACTER))
1383        root.appendchild(FrameNode(name3, AS_CHARACTER))
1384        self.dotest(root)
1385
1386    def test_frames5(self):
1387        name1 = self.mkname("frame")
1388        name2 = self.mkname("frame")
1389        name3 = self.mkname("frame")
1390        root = TreeNode()
1391        root.appendchild(FrameNode(name1, AS_CHARACTER))
1392        root.appendchild(FrameNode(name2, AT_CHARACTER))
1393        root.appendchild(FrameNode(name3, AT_CHARACTER))
1394        self.dotest(root)
1395
1396    def test_ruby_hyperlink1(self):
1397        name1 = self.mkname("ruby")
1398        name2 = self.mkname("url")
1399        root = TreeNode()
1400        ruby = RubyNode(name1)
1401        href = HyperlinkNode(name2)
1402        href.appendchild(TextNode("abc"))
1403        ruby.appendchild(href)
1404        root.appendchild(ruby)
1405        self.dotest(root)
1406
1407    def test_ruby_hyperlink2(self):
1408        name1 = self.mkname("url")
1409        name2 = self.mkname("ruby")
1410        root = TreeNode()
1411        href = HyperlinkNode(name1)
1412        ruby = RubyNode(name2)
1413        ruby.appendchild(TextNode("abc"))
1414        href.appendchild(ruby)
1415        root.appendchild(href)
1416        self.dotest(root)
1417
1418    def test_end1(self):
1419        name1 = self.mkname("bookmark")
1420        name2 = self.mkname("toxmark")
1421        name3 = self.mkname("refmark")
1422        root = TreeNode()
1423        root.appendchild(TextNode("abc"))
1424        root.appendchild(BookmarkNode(name1))
1425        root.appendchild(DocumentIndexMarkNode(name2))
1426        root.appendchild(ReferenceMarkNode(name3))
1427        self.dotest(root)
1428
1429    def test_end2(self):
1430        name1 = self.mkname("bookmark")
1431        name2 = self.mkname("frame")
1432        name3 = self.mkname("refmark")
1433        name4 = self.mkname("frame")
1434        name5 = self.mkname("frame")
1435        root = TreeNode()
1436        root.appendchild(TextNode("abc"))
1437        root.appendchild(BookmarkStartNode(name1))
1438        root.appendchild(FrameNode(name2, AT_CHARACTER))
1439        root.appendchild(BookmarkEndNode(name1))
1440        root.appendchild(ReferenceMarkNode(name3))
1441        root.appendchild(FrameNode(name4, AT_CHARACTER))
1442        root.appendchild(FrameNode(name5, AT_CHARACTER))
1443        self.dotest(root)
1444
1445    def test_end3(self):
1446        name1 = self.mkname("ftn")
1447        name2 = self.mkname("toxmark")
1448        root = TreeNode()
1449        root.appendchild(TextNode("abc"))
1450        root.appendchild(FootnoteNode(name1))
1451        root.appendchild(DocumentIndexMarkNode(name2))
1452        self.dotest(root)
1453
1454    def test_end4(self):
1455        name1 = self.mkname("bookmark")
1456        name2 = self.mkname("frame")
1457        root = TreeNode()
1458        root.appendchild(BookmarkStartNode(name1))
1459        root.appendchild(TextNode("abc"))
1460        root.appendchild(FrameNode(name2, AS_CHARACTER))
1461        root.appendchild(BookmarkEndNode(name1))
1462        self.dotest(root)
1463
1464    def test_end5(self):
1465        name1 = self.mkname("refmark")
1466        name2 = self.mkname("ruby")
1467        root = TreeNode()
1468        root.appendchild(ReferenceMarkStartNode(name1))
1469        root.appendchild(TextNode("abc"))
1470        ruby = RubyNode(name2)
1471        ruby.appendchild(TextFieldNode("de"))
1472        root.appendchild(ruby)
1473        root.appendchild(ReferenceMarkEndNode(name1))
1474        self.dotest(root)
1475
1476    def test_empty1(self):
1477        name1 = self.mkname("refmark")
1478        name2 = self.mkname("toxmark")
1479        name3 = self.mkname("bookmark")
1480        name4 = self.mkname("frame")
1481        name7 = self.mkname("refmark")
1482        name8 = self.mkname("toxmark")
1483        name9 = self.mkname("bookmark")
1484        nameA = self.mkname("frame")
1485        root = TreeNode()
1486        root.appendchild(ReferenceMarkNode(name1))
1487        root.appendchild(DocumentIndexMarkNode(name2))
1488        root.appendchild(BookmarkStartNode(name3))
1489        root.appendchild(FrameNode(name4, AT_CHARACTER))
1490        root.appendchild(BookmarkEndNode(name3))
1491        root.appendchild(ReferenceMarkNode(name7))
1492        root.appendchild(DocumentIndexMarkNode(name8))
1493        root.appendchild(BookmarkStartNode(name9))
1494        root.appendchild(FrameNode(nameA, AT_CHARACTER))
1495        root.appendchild(BookmarkEndNode(name9))
1496        self.dotest(root)
1497
1498    def test_empty2(self):
1499        name3 = self.mkname("bookmark")
1500        name4 = self.mkname("frame")
1501        name9 = self.mkname("bookmark")
1502        nameA = self.mkname("frame")
1503        root = TreeNode()
1504        root.appendchild(BookmarkStartNode(name3))
1505        root.appendchild(FrameNode(name4, AT_CHARACTER))
1506        root.appendchild(BookmarkEndNode(name3))
1507        root.appendchild(BookmarkStartNode(name9))
1508        root.appendchild(FrameNode(nameA, AT_CHARACTER))
1509        root.appendchild(BookmarkEndNode(name9))
1510        self.dotest(root)
1511
1512    def test_empty3(self):
1513        name1 = self.mkname("refmark")
1514        name2 = self.mkname("toxmark")
1515        name3 = self.mkname("bookmark")
1516        name4 = self.mkname("frame")
1517        name5 = self.mkname("url")
1518        name6 = self.mkname("ruby")
1519        name7 = self.mkname("refmark")
1520        name8 = self.mkname("toxmark")
1521        name9 = self.mkname("bookmark")
1522        nameA = self.mkname("frame")
1523        root = TreeNode()
1524        root.appendchild(ReferenceMarkNode(name1))
1525        root.appendchild(DocumentIndexMarkNode(name2))
1526        root.appendchild(BookmarkStartNode(name3))
1527        root.appendchild(FrameNode(name4, AT_CHARACTER))
1528        root.appendchild(BookmarkEndNode(name3))
1529        ## currently empty hyperlinks may get eaten...
1530        # href = HyperlinkNode(name5)
1531        # href.appendchild(TextNode(""))
1532        # root.appendchild(href)
1533        ruby = RubyNode(name6)
1534        root.appendchild(ruby)
1535        root.appendchild(ReferenceMarkNode(name7))
1536        root.appendchild(DocumentIndexMarkNode(name8))
1537        root.appendchild(BookmarkStartNode(name9))
1538        root.appendchild(FrameNode(nameA, AT_CHARACTER))
1539        root.appendchild(BookmarkEndNode(name9))
1540        self.dotest(root)
1541
1542    def test1(self):
1543        name1 = self.mkname("frame")
1544        name2 = self.mkname("bookmark")
1545        name3 = self.mkname("ruby")
1546        name4 = self.mkname("ftn")
1547        name5 = self.mkname("frame")
1548        root = TreeNode()
1549        root.appendchild(FrameNode(name1, AT_CHARACTER))
1550        root.appendchild(BookmarkStartNode(name2))
1551        root.appendchild(TextNode("abc"))
1552        ruby = RubyNode(name3)
1553        ruby.appendchild(TextNode("de"))
1554        ruby.appendchild(FootnoteNode(name4))
1555        ruby.appendchild(BookmarkEndNode(name2))
1556        root.appendchild(ruby)
1557        root.appendchild(TextNode("fg"))
1558        root.appendchild(FrameNode(name5, AT_CHARACTER))
1559        root.appendchild(TextFieldNode("h"))
1560        self.dotest(root)
1561
1562    # some range tests for the insertion: these are for the current
1563    # API which treats hyperlinks and rubys not as entities, but as formatting
1564    # attributes; if these ever become entities, they should not be split!'''
1565
1566    def test_range1(self):
1567        name1 = self.mkname("url")
1568        inserter = RangeInserter(self.__class__.xDoc)
1569        text = TextNode("12345")
1570        inserter.insertrange(Range(0, 0, text))
1571        url1 = HyperlinkNode(name1)
1572        range1 = Range(0, 5, url1)
1573        inserter.insertrange(range1)
1574        root = TreeNode()
1575        root.appendchild(url1)
1576        url1.appendchild(text)
1577        self.dotest(root, False)
1578
1579    def test_range_hyperlink_hyperlink(self):
1580        inserter = RangeInserter(self.__class__.xDoc)
1581        text = TextNode("123456789")
1582        inserter.insertrange(Range(0, 0, text))
1583        url1 = HyperlinkNode(self.mkname("url"))
1584        inserter.insertrange(Range(1, 4, url1))
1585        ## overlap left
1586        url2 = HyperlinkNode(self.mkname("url"))
1587        inserter.insertrange(Range(0, 2, url2))
1588        root = TreeNode()
1589        root.appendchild(url2.dup().appendchild(TextNode("12")))
1590        root.appendchild(url1.dup().appendchild(TextNode("34")))
1591        root.appendchild(TextNode("56789"))
1592        self.dotest(root, False)
1593        ## overlap right
1594        url3 = HyperlinkNode(self.mkname("url"))
1595        inserter.insertrange(Range(3, 7, url3))
1596        root = TreeNode()
1597        root.appendchild(url2.dup().appendchild(TextNode("12")))
1598        root.appendchild(url1.dup().appendchild(TextNode("3")))
1599        root.appendchild(url3.dup().appendchild(TextNode("4567")))
1600        root.appendchild(TextNode("89"))
1601        self.dotest(root, False)
1602        ## around
1603        url4 = HyperlinkNode(self.mkname("url"))
1604        inserter.insertrange(Range(3, 7, url4))
1605        root = TreeNode()
1606        root.appendchild(url2.dup().appendchild(TextNode("12")))
1607        root.appendchild(url1.dup().appendchild(TextNode("3")))
1608        root.appendchild(url4.dup().appendchild(TextNode("4567")))
1609        root.appendchild(TextNode("89"))
1610        self.dotest(root, False)
1611        ## inside
1612        url5 = HyperlinkNode(self.mkname("url"))
1613        inserter.insertrange(Range(4, 6, url5))
1614        root = TreeNode()
1615        root.appendchild(url2.dup().appendchild(TextNode("12")))
1616        root.appendchild(url1.dup().appendchild(TextNode("3")))
1617        root.appendchild(url4.dup().appendchild(TextNode("4")))
1618        root.appendchild(url5.dup().appendchild(TextNode("56")))
1619        root.appendchild(url4.dup().appendchild(TextNode("7")))
1620        root.appendchild(TextNode("89"))
1621        self.dotest(root, False)
1622        ## empty
1623        url6 = HyperlinkNode(self.mkname("url"))
1624        inserter.insertrange(Range(7, 7, url6))
1625        root = TreeNode()
1626        root.appendchild(url2.dup().appendchild(TextNode("12")))
1627        root.appendchild(url1.dup().appendchild(TextNode("3")))
1628        root.appendchild(url4.dup().appendchild(TextNode("4")))
1629        root.appendchild(url5.dup().appendchild(TextNode("56")))
1630        root.appendchild(url4.dup().appendchild(TextNode("7")))
1631        ##  this one gets eaten, but we still need to test inserting it (#i106930#)
1632        # root.appendchild(url6.dup().appendchild(TextNode("")))
1633        root.appendchild(TextNode("89"))
1634        ## inside (left-edge)
1635        url7 = HyperlinkNode(self.mkname("url"))
1636        inserter.insertrange(Range(0, 1, url7))
1637        root = TreeNode()
1638        root.appendchild(url7.dup().appendchild(TextNode("1")))
1639        root.appendchild(url2.dup().appendchild(TextNode("2")))
1640        root.appendchild(url1.dup().appendchild(TextNode("3")))
1641        root.appendchild(url4.dup().appendchild(TextNode("4")))
1642        root.appendchild(url5.dup().appendchild(TextNode("56")))
1643        root.appendchild(url4.dup().appendchild(TextNode("7")))
1644        root.appendchild(TextNode("89"))
1645        ## inside (right-edge)
1646        url8 = HyperlinkNode(self.mkname("url"))
1647        inserter.insertrange(Range(5, 6, url8))
1648        root = TreeNode()
1649        root.appendchild(url7.dup().appendchild(TextNode("1")))
1650        root.appendchild(url2.dup().appendchild(TextNode("2")))
1651        root.appendchild(url1.dup().appendchild(TextNode("3")))
1652        root.appendchild(url4.dup().appendchild(TextNode("4")))
1653        root.appendchild(url5.dup().appendchild(TextNode("5")))
1654        root.appendchild(url8.dup().appendchild(TextNode("6")))
1655        root.appendchild(url4.dup().appendchild(TextNode("7")))
1656        root.appendchild(TextNode("89"))
1657        self.dotest(root, False)
1658
1659    def test_range_hyperlink_ruby(self):
1660        inserter = RangeInserter(self.__class__.xDoc)
1661        text = TextNode("123456789")
1662        inserter.insertrange(Range(0, 0, text))
1663        url1 = HyperlinkNode(self.mkname("url"))
1664        inserter.insertrange(Range(1, 4, url1))
1665        # overlap left
1666        rby2 = RubyNode(self.mkname("ruby"))
1667        inserter.insertrange(Range(0, 2, rby2))
1668        root = TreeNode()
1669        root.appendchild(rby2.dup()
1670            .appendchild(TextNode("1"))
1671            .appendchild(url1.dup().appendchild(TextNode("2"))))
1672        root.appendchild(url1.dup().appendchild(TextNode("34")))
1673        root.appendchild(TextNode("56789"))
1674        self.dotest(root, False)
1675        # overlap right
1676        rby3 = RubyNode(self.mkname("ruby"))
1677        inserter.insertrange(Range(3, 5, rby3))
1678        root = TreeNode()
1679        root.appendchild(rby2.dup()
1680                .appendchild(TextNode("1"))
1681                .appendchild(url1.dup().appendchild(TextNode("2"))))
1682        root.appendchild(url1.dup().appendchild(TextNode("3")))
1683        root.appendchild(rby3.dup()
1684                .appendchild(url1.dup().appendchild(TextNode("4")))
1685                .appendchild(TextNode("5")))
1686        root.appendchild(TextNode("6789"))
1687        self.dotest(root, False)
1688        # around
1689        rby4 = RubyNode(self.mkname("ruby"))
1690        inserter.insertrange(Range(2, 3, rby4))
1691        root = TreeNode()
1692        root.appendchild(rby2.dup()
1693                .appendchild(TextNode("1"))
1694                .appendchild(url1.dup().appendchild(TextNode("2"))))
1695        root.appendchild(rby4.dup()
1696                .appendchild(url1.dup().appendchild(TextNode("3"))))
1697        root.appendchild(rby3.dup()
1698                .appendchild(url1.dup().appendchild(TextNode("4")))
1699                .appendchild(TextNode("5")))
1700        root.appendchild(TextNode("6789"))
1701        self.dotest(root, False)
1702        # inside
1703        url5 = HyperlinkNode(self.mkname("url"))
1704        inserter.insertrange(Range(6, 9, url5))
1705        rby6 = RubyNode(self.mkname("ruby"))
1706        inserter.insertrange(Range(7, 8, rby6))
1707        root = TreeNode()
1708        root.appendchild(rby2.dup()
1709                .appendchild(TextNode("1"))
1710                .appendchild(url1.dup().appendchild(TextNode("2"))))
1711        root.appendchild(rby4.dup()
1712                .appendchild(url1.dup().appendchild(TextNode("3"))))
1713        root.appendchild(rby3.dup()
1714                .appendchild(url1.dup().appendchild(TextNode("4")))
1715                .appendchild(TextNode("5")))
1716        root.appendchild(TextNode("6"))
1717        root.appendchild(url5.dup().appendchild(TextNode("7")))
1718        root.appendchild(rby6.dup()
1719                .appendchild(url5.dup().appendchild(TextNode("8"))))
1720        root.appendchild(url5.dup().appendchild(TextNode("9")))
1721        self.dotest(root, False)
1722
1723    def test_range_ruby_hyperlink(self):
1724        inserter = RangeInserter(self.__class__.xDoc)
1725        text = TextNode("123456789")
1726        inserter.insertrange(Range(0, 0, text))
1727        rby1 = RubyNode(self.mkname("ruby"))
1728        inserter.insertrange(Range(1, 6, rby1))
1729        ## overlap left
1730        url2 = HyperlinkNode(self.mkname("url"))
1731        inserter.insertrange(Range(0, 3, url2))
1732        root = TreeNode()
1733        root.appendchild(url2.dup().appendchild(TextNode("1")))
1734        root.appendchild(rby1.dup()
1735                .appendchild(url2.dup().appendchild(TextNode("23")))
1736                .appendchild(TextNode("456")))
1737        root.appendchild(TextNode("789"))
1738        self.dotest(root, False)
1739        ## overlap right
1740        url3 = HyperlinkNode(self.mkname("url"))
1741        inserter.insertrange(Range(5, 7, url3))
1742        root = TreeNode()
1743        root.appendchild(url2.dup().appendchild(TextNode("1")))
1744        root.appendchild(rby1.dup()
1745                .appendchild(url2.dup().appendchild(TextNode("23")))
1746                .appendchild(TextNode("45"))
1747                .appendchild(url3.dup().appendchild(TextNode("6"))))
1748        root.appendchild(url3.dup().appendchild(TextNode("7")))
1749        root.appendchild(TextNode("89"))
1750        self.dotest(root, False)
1751        ## around (not quite, due to API)
1752        url4 = HyperlinkNode(self.mkname("url"))
1753        inserter.insertrange(Range(1, 8, url4))
1754        root = TreeNode()
1755        root.appendchild(url2.dup().appendchild(TextNode("1")))
1756        root.appendchild(rby1.dup()
1757                .appendchild(url4.dup()
1758                    .appendchild(TextNode("23456"))))
1759        root.appendchild(url4.dup().appendchild(TextNode("78")))
1760        root.appendchild(TextNode("9"))
1761        self.dotest(root, False)
1762        ## inside
1763        url5 = HyperlinkNode(self.mkname("url"))
1764        inserter.insertrange(Range(3, 5, url5))
1765        root = TreeNode()
1766        root.appendchild(url2.dup().appendchild(TextNode("1")))
1767        root.appendchild(rby1.dup()
1768                .appendchild(url4.dup()
1769                    .appendchild(TextNode("23")))
1770                .appendchild(url5.dup()
1771                    .appendchild(TextNode("45")))
1772                .appendchild(url4.dup()
1773                    .appendchild(TextNode("6"))))
1774        root.appendchild(url4.dup().appendchild(TextNode("78")))
1775        root.appendchild(TextNode("9"))
1776        self.dotest(root, False)
1777
1778    def test_range_ruby_ruby(self):
1779        inserter = RangeInserter(self.__class__.xDoc)
1780        text = TextNode("123456789")
1781        inserter.insertrange(Range(0, 0, text))
1782        rby1 = RubyNode(self.mkname("ruby"))
1783        inserter.insertrange(Range(1, 4, rby1))
1784        ## overlap left
1785        rby2 = RubyNode(self.mkname("ruby"))
1786        inserter.insertrange(Range(0, 2, rby2))
1787        root = TreeNode()
1788        root.appendchild(rby2.dup().appendchild(TextNode("12")))
1789        root.appendchild(rby1.dup().appendchild(TextNode("34")))
1790        root.appendchild(TextNode("56789"))
1791        self.dotest(root, False)
1792        ## overlap right
1793        rby3 = RubyNode(self.mkname("ruby"))
1794        inserter.insertrange(Range(3, 7, rby3))
1795        root = TreeNode()
1796        root.appendchild(rby2.dup().appendchild(TextNode("12")))
1797        root.appendchild(rby1.dup().appendchild(TextNode("3")))
1798        root.appendchild(rby3.dup().appendchild(TextNode("4567")))
1799        root.appendchild(TextNode("89"))
1800        self.dotest(root, False)
1801        ## around
1802        rby4 = RubyNode(self.mkname("ruby"))
1803        inserter.insertrange(Range(3, 7, rby4))
1804        root = TreeNode()
1805        root.appendchild(rby2.dup().appendchild(TextNode("12")))
1806        root.appendchild(rby1.dup().appendchild(TextNode("3")))
1807        root.appendchild(rby4.dup().appendchild(TextNode("4567")))
1808        root.appendchild(TextNode("89"))
1809        self.dotest(root, False)
1810        ## inside
1811        rby5 = RubyNode(self.mkname("ruby"))
1812        inserter.insertrange(Range(4, 6, rby5))
1813        root = TreeNode()
1814        root.appendchild(rby2.dup().appendchild(TextNode("12")))
1815        root.appendchild(rby1.dup().appendchild(TextNode("3")))
1816        root.appendchild(rby4.dup().appendchild(TextNode("4")))
1817        root.appendchild(rby5.dup().appendchild(TextNode("56")))
1818        root.appendchild(rby4.dup().appendchild(TextNode("7")))
1819        root.appendchild(TextNode("89"))
1820        self.dotest(root, False)
1821
1822    def test_range_hyperlink_meta(self):
1823        inserter = RangeInserter(self.__class__.xDoc)
1824        text = TextNode("123456789")
1825        inserter.insertrange(Range(0, 0, text))
1826        url1 = HyperlinkNode(self.mkname("url"))
1827        inserter.insertrange(Range(1, 4, url1))
1828        ## overlap left
1829        met2 = MetaNode(self.mkid("id"))
1830        inserter.insertrange(Range(0, 2, met2))
1831        root = TreeNode()
1832        root.appendchild(met2.dup()
1833                .appendchild(TextNode("1"))
1834                .appendchild(url1.dup().appendchild(TextNode("2"))))
1835        root.appendchild(url1.dup().appendchild(TextNode("34")))
1836        root.appendchild(TextNode("56789"))
1837        self.dotest(root, False)
1838        ## overlap right
1839        met3 = MetaNode(self.mkid("id"))
1840        # inserter.insertrange(Range(4-1, 6-1, met3))
1841        inserter.insertrange(Range(4, 6, met3))
1842        root = TreeNode()
1843        root.appendchild(met2.dup()
1844                .appendchild(TextNode("1"))
1845                .appendchild(url1.dup().appendchild(TextNode("2"))))
1846        root.appendchild(url1.dup().appendchild(TextNode("3")))
1847        root.appendchild(met3.dup()
1848                .appendchild(url1.dup().appendchild(TextNode("4")))
1849                .appendchild(TextNode("5")))
1850        root.appendchild(TextNode("6789"))
1851        self.dotest(root, False)
1852        ## around
1853        met4 = MetaNode(self.mkid("id"))
1854        # inserter.insertrange(Range(3-1, 4-1, met4))
1855        inserter.insertrange(Range(3, 4, met4))
1856        root = TreeNode()
1857        root.appendchild(met2.dup()
1858                .appendchild(TextNode("1"))
1859                .appendchild(url1.dup().appendchild(TextNode("2"))))
1860        root.appendchild(met4.dup()
1861                .appendchild(url1.dup().appendchild(TextNode("3"))))
1862        root.appendchild(met3.dup()
1863                .appendchild(url1.dup().appendchild(TextNode("4")))
1864                .appendchild(TextNode("5")))
1865        root.appendchild(TextNode("6789"))
1866        self.dotest(root, False)
1867        ## inside
1868        url5 = HyperlinkNode(self.mkname("url"))
1869        # inserter.insertrange(Range(9-3, 12-3, url5))
1870        inserter.insertrange(Range(9, 12, url5))
1871        met6 = MetaNode(self.mkid("id"))
1872        # inserter.insertrange(Range(10-3, 11-3, met6))
1873        inserter.insertrange(Range(10, 11, met6))
1874        root = TreeNode()
1875        root.appendchild(met2.dup()
1876                .appendchild(TextNode("1"))
1877                .appendchild(url1.dup().appendchild(TextNode("2"))))
1878        root.appendchild(met4.dup()
1879                .appendchild(url1.dup().appendchild(TextNode("3"))))
1880        root.appendchild(met3.dup()
1881                .appendchild(url1.dup().appendchild(TextNode("4")))
1882                .appendchild(TextNode("5")))
1883        root.appendchild(TextNode("6"))
1884        root.appendchild(url5.dup().appendchild(TextNode("7")))
1885        root.appendchild(met6.dup()
1886                .appendchild(url5.dup().appendchild(TextNode("8"))))
1887        root.appendchild(url5.dup().appendchild(TextNode("9")))
1888        self.dotest(root, False)
1889
1890    def test_range_ruby_meta(self):
1891        inserter = RangeInserter(self.__class__.xDoc)
1892        text = TextNode("123456789")
1893        inserter.insertrange(Range(0, 0, text))
1894        rby1 = RubyNode(self.mkname("ruby"))
1895        inserter.insertrange(Range(1, 4, rby1))
1896        ## overlap left
1897        met2 = MetaNode(self.mkid("id"))
1898        inserter.insertrange(Range(0, 2, met2))
1899        root = TreeNode()
1900        root.appendchild(met2.dup()
1901                .appendchild(TextNode("1"))
1902                .appendchild(rby1.dup().appendchild(TextNode("2"))))
1903        root.appendchild(rby1.dup().appendchild(TextNode("34")))
1904        root.appendchild(TextNode("56789"))
1905        self.dotest(root, False)
1906        ## overlap right
1907        met3 = MetaNode(self.mkid("id"))
1908        # inserter.insertrange(Range(4-1, 6-1, met3))
1909        inserter.insertrange(Range(4, 6, met3))
1910        root = TreeNode()
1911        root.appendchild(met2.dup()
1912                .appendchild(TextNode("1"))
1913                .appendchild(rby1.dup().appendchild(TextNode("2"))))
1914        root.appendchild(rby1.dup().appendchild(TextNode("3")))
1915        root.appendchild(met3.dup()
1916                .appendchild(rby1.dup().appendchild(TextNode("4")))
1917                .appendchild(TextNode("5")))
1918        root.appendchild(TextNode("6789"))
1919        self.dotest(root, False)
1920        ## around
1921        met4 = MetaNode(self.mkid("id"))
1922        # inserter.insertrange(Range(3-1, 4-1, met4))
1923        inserter.insertrange(Range(3, 4, met4))
1924        root = TreeNode()
1925        root.appendchild(met2.dup()
1926                .appendchild(TextNode("1"))
1927                .appendchild(rby1.dup().appendchild(TextNode("2"))))
1928        root.appendchild(met4.dup()
1929                .appendchild(rby1.dup().appendchild(TextNode("3"))))
1930        root.appendchild(met3.dup()
1931                .appendchild(rby1.dup().appendchild(TextNode("4")))
1932                .appendchild(TextNode("5")))
1933        root.appendchild(TextNode("6789"))
1934        self.dotest(root, False)
1935        ## inside
1936        rby5 = RubyNode(self.mkname("ruby"))
1937        # inserter.insertrange(Range(9-3, 12-3, rby5))
1938        inserter.insertrange(Range(9, 12, rby5))
1939        met6 = MetaNode(self.mkid("id"))
1940        # inserter.insertrange(Range(10-3, 11-3, met6))
1941        inserter.insertrange(Range(10, 11, met6))
1942        root = TreeNode()
1943        root.appendchild(met2.dup()
1944                .appendchild(TextNode("1"))
1945                .appendchild(rby1.dup().appendchild(TextNode("2"))))
1946        root.appendchild(met4.dup()
1947                .appendchild(rby1.dup().appendchild(TextNode("3"))))
1948        root.appendchild(met3.dup()
1949                .appendchild(rby1.dup().appendchild(TextNode("4")))
1950                .appendchild(TextNode("5")))
1951        root.appendchild(TextNode("6"))
1952        root.appendchild(rby5.dup()
1953                .appendchild(TextNode("7"))
1954                .appendchild(met6.dup()
1955                    .appendchild(TextNode("8")))
1956                .appendchild(TextNode("9")))
1957        self.dotest(root, False)
1958
1959    def test_range_meta_hyperlink(self):
1960        inserter = RangeInserter(self.__class__.xDoc)
1961        text = TextNode("123456789")
1962        inserter.insertrange(Range(0, 0, text))
1963        met1 = MetaNode(self.mkid("id"))
1964        inserter.insertrange(Range(1, 6, met1))
1965        ## overlap left
1966        url2 = HyperlinkNode(self.mkname("url"))
1967        # inserter.insertrange(Range(0, 4-1, url2))
1968        inserter.insertrange(Range(0, 4, url2))
1969        root = TreeNode()
1970        root.appendchild(url2.dup().appendchild(TextNode("1")))
1971        root.appendchild(met1.dup()
1972                .appendchild(url2.dup().appendchild(TextNode("23")))
1973                .appendchild(TextNode("456")))
1974        root.appendchild(TextNode("789"))
1975        self.dotest(root, False)
1976        ## overlap right
1977        url3 = HyperlinkNode(self.mkname("url"))
1978        # inserter.insertrange(Range(6-1, 8-1, url3))
1979        inserter.insertrange(Range(6, 8, url3))
1980        root = TreeNode()
1981        root.appendchild(url2.dup().appendchild(TextNode("1")))
1982        root.appendchild(met1.dup()
1983                .appendchild(url2.dup().appendchild(TextNode("23")))
1984                .appendchild(TextNode("45"))
1985                .appendchild(url3.dup().appendchild(TextNode("6"))))
1986        root.appendchild(url3.dup().appendchild(TextNode("7")))
1987        root.appendchild(TextNode("89"))
1988        self.dotest(root, False)
1989        ## around (not quite, due to API)
1990        url4 = HyperlinkNode(self.mkname("url"))
1991        # inserter.insertrange(Range(1, 9-1, url4))
1992        inserter.insertrange(Range(1, 9, url4))
1993        root = TreeNode()
1994        root.appendchild(url2.dup().appendchild(TextNode("1")))
1995        root.appendchild(met1.dup()
1996                .appendchild(url4.dup()
1997                    .appendchild(TextNode("23456"))))
1998        root.appendchild(url4.dup().appendchild(TextNode("78")))
1999        root.appendchild(TextNode("9"))
2000        self.dotest(root, False)
2001        ## inside
2002        url5 = HyperlinkNode(self.mkname("url"))
2003        # inserter.insertrange(Range(4-1, 6-1, url5))
2004        inserter.insertrange(Range(4, 6, url5))
2005        root = TreeNode()
2006        root.appendchild(url2.dup().appendchild(TextNode("1")))
2007        root.appendchild(met1.dup()
2008                .appendchild(url4.dup()
2009                    .appendchild(TextNode("23")))
2010                .appendchild(url5.dup()
2011                    .appendchild(TextNode("45")))
2012                .appendchild(url4.dup()
2013                    .appendchild(TextNode("6"))))
2014        root.appendchild(url4.dup().appendchild(TextNode("78")))
2015        root.appendchild(TextNode("9"))
2016        self.dotest(root, False)
2017
2018    def test_range_meta_ruby(self):
2019        inserter = RangeInserter(self.__class__.xDoc)
2020        text = TextNode("123456789")
2021        inserter.insertrange(Range(0, 0, text))
2022        met1 = MetaNode(self.mkid("id"))
2023        inserter.insertrange(Range(1, 5, met1))
2024        ## overlap left
2025        rby2 = RubyNode(self.mkname("ruby"))
2026        # inserter.insertrange(Range(0, 3-1, rby2))
2027        inserter.insertrange(Range(0, 3, rby2))
2028        root = TreeNode()
2029        root.appendchild(rby2.dup().appendchild(TextNode("1")))
2030        root.appendchild(met1.dup()
2031                .appendchild(rby2.dup().appendchild(TextNode("2")))
2032                .appendchild(TextNode("345")))
2033        root.appendchild(TextNode("6789"))
2034        self.dotest(root, False)
2035        ## overlap right
2036        rby3 = RubyNode(self.mkname("ruby"))
2037        # inserter.insertrange(Range(5-1, 7-1, rby3))
2038        inserter.insertrange(Range(5, 7, rby3))
2039        root = TreeNode()
2040        root.appendchild(rby2.dup().appendchild(TextNode("1")))
2041        root.appendchild(met1.dup()
2042                .appendchild(rby2.dup().appendchild(TextNode("2")))
2043                .appendchild(TextNode("34"))
2044                .appendchild(rby3.dup().appendchild(TextNode("5"))))
2045        root.appendchild(rby3.dup().appendchild(TextNode("6")))
2046        root.appendchild(TextNode("789"))
2047        self.dotest(root, False)
2048        ## // around
2049        rby4 = RubyNode(self.mkname("ruby"))
2050        # inserter.insertrange(Range(1, 7-1, rby4))
2051        inserter.insertrange(Range(1, 7, rby4))
2052        root = TreeNode()
2053        root.appendchild(rby2.dup().appendchild(TextNode("1")))
2054        root.appendchild(rby4.dup()
2055                .appendchild(met1.dup()
2056                    .appendchild(TextNode("2345")))
2057                .appendchild(TextNode("6")))
2058        root.appendchild(TextNode("789"))
2059        self.dotest(root, False)
2060        ## inside
2061        met5 = MetaNode(self.mkid("id"))
2062        # inserter.insertrange(Range(7-1, 9-1, met5))
2063        inserter.insertrange(Range(7, 9, met5))
2064        rby6 = RubyNode(self.mkname("ruby"))
2065        # inserter.insertrange(Range(9-2, 10/-2, rby6))
2066        inserter.insertrange(Range(9, 10, rby6))
2067        root = TreeNode()
2068        root.appendchild(rby2.dup().appendchild(TextNode("1")))
2069        root.appendchild(rby4.dup()
2070                .appendchild(met1.dup()
2071                    .appendchild(TextNode("2345")))
2072                .appendchild(TextNode("6")))
2073        root.appendchild(met5.dup()
2074                .appendchild(TextNode("7"))
2075                .appendchild(rby6.dup()
2076                    .appendchild(TextNode("8"))))
2077        root.appendchild(TextNode("9"))
2078        self.dotest(root, False)
2079        ## inside, with invalid range that includes the dummy char
2080        rby7 = RubyNode(self.mkname("ruby"))
2081        # inserter.insertrange(Range(7-1, 9-2, rby7))
2082        inserter.insertrange(Range(7, 9, rby7))
2083        root = TreeNode()
2084        root.appendchild(rby2.dup().appendchild(TextNode("1")))
2085        root.appendchild(rby4.dup()
2086                .appendchild(met1.dup()
2087                    .appendchild(TextNode("2345")))
2088                .appendchild(TextNode("6")))
2089        root.appendchild(met5.dup()
2090                .appendchild(rby7.dup()
2091                    .appendchild(TextNode("7")))
2092                .appendchild(rby6.dup()
2093                    .appendchild(TextNode("8"))))
2094        root.appendchild(TextNode("9"))
2095        self.dotest(root, False)
2096        ## around, at same position as meta
2097        rby8 = RubyNode(self.mkname("ruby"))
2098        # inserter.insertrange(Range(7-1, 10-2, rby8))
2099        inserter.insertrange(Range(7, 10, rby8))
2100        root = TreeNode()
2101        root.appendchild(rby2.dup().appendchild(TextNode("1")))
2102        root.appendchild(rby4.dup()
2103                .appendchild(met1.dup()
2104                    .appendchild(TextNode("2345")))
2105                .appendchild(TextNode("6")))
2106        root.appendchild(rby8.dup()
2107                .appendchild(met5.dup()
2108                    .appendchild(TextNode("78"))))
2109        root.appendchild(TextNode("9"))
2110        self.dotest(root, False)
2111
2112    def test_range_meta_meta(self):
2113        inserter = RangeInserter(self.__class__.xDoc)
2114        text = TextNode("123456789")
2115        inserter.insertrange(Range(0, 0, text))
2116        met1 = MetaNode(self.mkid("id"))
2117        inserter.insertrange(Range(3, 6, met1))
2118        ## overlap left
2119        met2 = MetaNode(self.mkid("id"))
2120        try:
2121            inserter.insertrange(Range(0, 4, met2))
2122            fail("testRangeMetaMeta: overlap left allowed")
2123        except IllegalArgumentException:
2124            pass
2125        root = TreeNode()
2126        root.appendchild(TextNode("123"))
2127        root.appendchild(met1.dup().appendchild(TextNode("456")))
2128        root.appendchild(TextNode("789"))
2129        self.dotest(root, False)
2130        ## overlap right
2131        met3 = MetaNode(self.mkid("id"))
2132
2133        try:
2134            # inserter.insertrange(Range(5-1, 8-1, met3))
2135            inserter.insertrange(Range(5, 8, met3))
2136            self.fail("testRangeMetaMeta: overlap right allowed")
2137        except IllegalArgumentException:
2138            pass
2139
2140        root = TreeNode()
2141        root.appendchild(TextNode("123"))
2142        root.appendchild(met1.dup().appendchild(TextNode("456")))
2143        root.appendchild(TextNode("789"))
2144        self.dotest(root, False)
2145        ## around
2146        met4 = MetaNode(self.mkid("id"))
2147        # inserter.insertrange(Range(3, 7-1, met4))
2148        inserter.insertrange(Range(3, 7, met4))
2149        root = TreeNode()
2150        root.appendchild(TextNode("123"))
2151        root.appendchild(met4.dup()
2152                .appendchild(met1.dup().appendchild(TextNode("456"))))
2153        root.appendchild(TextNode("789"))
2154        self.dotest(root, False)
2155        ## inside
2156        met5 = MetaNode(self.mkid("id"))
2157        # inserter.insertrange(Range(6-2, 8-2, met5))
2158        inserter.insertrange(Range(6, 8, met5))
2159        root = TreeNode()
2160        root.appendchild(TextNode("123"))
2161        root.appendchild(met4.dup()
2162                .appendchild(met1.dup()
2163                    .appendchild(TextNode("4"))
2164                    .appendchild(met5.dup()
2165                        .appendchild(TextNode("56")))))
2166        root.appendchild(TextNode("789"))
2167        self.dotest(root, False)
2168
2169    def test_range2(self):
2170        inserter = RangeInserter(self.__class__.xDoc)
2171        text = TextNode("123456789")
2172        inserter.insertrange(Range(0, 0, text))
2173        met1 = MetaNode(self.mkid("id"))
2174        inserter.insertrange(Range(1, 8, met1))
2175        met2 = MetaNode(self.mkid("id"))
2176        # inserter.insertrange(Range(3-1, 8-1, met2))
2177        inserter.insertrange(Range(3, 8, met2))
2178        met3 = MetaNode(self.mkid("id"))
2179        # inserter.insertrange(Range(5-2, 8-2, met3))
2180        inserter.insertrange(Range(5, 8, met3))
2181        root = TreeNode()
2182        root.appendchild(TextNode("1"))
2183        root.appendchild(met1.dup()
2184                .appendchild(TextNode("2"))
2185                .appendchild(met2.dup()
2186                    .appendchild(TextNode("3"))
2187                    .appendchild(met3.dup()
2188                        .appendchild(TextNode("456")))
2189                    .appendchild(TextNode("7")))
2190                .appendchild(TextNode("8")))
2191        root.appendchild(TextNode("9"))
2192        self.dotest(root, False)
2193        ## split ruby at every meta start!
2194        rby4 = RubyNode(self.mkname("ruby"))
2195        # inserter.insertrange(Range(0, 7-3, rby4))
2196        inserter.insertrange(Range(0, 7, rby4))
2197        root = TreeNode()
2198        root.appendchild(rby4.dup()
2199                .appendchild(TextNode("1")))
2200        root.appendchild(met1.dup()
2201                .appendchild(rby4.dup()
2202                    .appendchild(TextNode("2")))
2203                .appendchild(met2.dup()
2204                    .appendchild(rby4.dup()
2205                        .appendchild(TextNode("3")))
2206                    .appendchild(met3.dup()
2207                        .appendchild(rby4.dup()
2208                            .appendchild(TextNode("4")))
2209                        .appendchild(TextNode("56")))
2210                    .appendchild(TextNode("7")))
2211                .appendchild(TextNode("8")))
2212        root.appendchild(TextNode("9"))
2213        self.dotest(root, False)
2214        ## split ruby at every meta end!
2215        rby5 = RubyNode(self.mkname("ruby"))
2216        # inserter.insertrange(Range(8-3, 12-3, rby5))
2217        inserter.insertrange(Range(8, 12, rby5))
2218        root = TreeNode()
2219        root.appendchild(rby4.dup()
2220                .appendchild(TextNode("1")))
2221        root.appendchild(met1.dup()
2222                .appendchild(rby4.dup()
2223                    .appendchild(TextNode("2")))
2224                .appendchild(met2.dup()
2225                    .appendchild(rby4.dup()
2226                        .appendchild(TextNode("3")))
2227                    .appendchild(met3.dup()
2228                        .appendchild(rby4.dup()
2229                            .appendchild(TextNode("4")))
2230                        .appendchild(TextNode("5"))
2231                        .appendchild(rby5.dup()
2232                            .appendchild(TextNode("6"))))
2233                    .appendchild(rby5.dup()
2234                        .appendchild(TextNode("7"))))
2235                .appendchild(rby5.dup()
2236                    .appendchild(TextNode("8"))))
2237        root.appendchild(rby5.dup()
2238                .appendchild(TextNode("9")))
2239        self.dotest(root, False)
2240
2241    def test_range3(self):
2242        inserter = RangeInserter(self.__class__.xDoc)
2243        text = TextNode("123456789")
2244        inserter.insertrange(Range(0, 0, text))
2245        rby1 = RubyNode(self.mkname("ruby"))
2246        inserter.insertrange(Range(0, 9, rby1))
2247        met2 = MetaNode(self.mkid("id"))
2248        inserter.insertrange(Range(2, 7, met2))
2249        root = TreeNode()
2250        root.appendchild(rby1.dup()
2251                .appendchild(TextNode("12"))
2252                .appendchild(met2.dup()
2253                    .appendchild(TextNode("34567")))
2254                .appendchild(TextNode("89")))
2255        self.dotest(root, False)
2256        ## overwrite outer ruby, split remains at inner meta!
2257        rby3 = RubyNode(self.mkname("ruby"))
2258        # inserter.insertrange(Range(5-1, 6-1, rby3))
2259        inserter.insertrange(Range(5, 6, rby3))
2260        root = TreeNode()
2261        root.appendchild(rby1.dup()
2262                .appendchild(TextNode("12")))
2263        root.appendchild(met2.dup()
2264                .appendchild(rby1.dup()
2265                    .appendchild(TextNode("34")))
2266                .appendchild(rby3.dup()
2267                    .appendchild(TextNode("5")))
2268                .appendchild(rby1.dup()
2269                    .appendchild(TextNode("67"))))
2270        root.appendchild(rby1.dup()
2271                .appendchild(TextNode("89")))
2272        self.dotest(root, False)
2273
2274    def test_range4(self):
2275        inserter = RangeInserter(self.__class__.xDoc)
2276        text = TextNode("123456789")
2277        inserter.insertrange(Range(0, 0, text))
2278        rby1 = RubyNode(self.mkname("ruby"))
2279        inserter.insertrange(Range(0, 9, rby1))
2280        met2 = MetaNode(self.mkid("id"))
2281        inserter.insertrange(Range(1, 8, met2))
2282        met3 = MetaNode(self.mkid("id"))
2283        # inserter.insertrange(Range(3-1, 8-1, met3))
2284        inserter.insertrange(Range(3, 8, met3))
2285        met4 = MetaNode(self.mkid("id"))
2286        # inserter.insertrange(Range(5-2, 8-2, met4))
2287        inserter.insertrange(Range(5, 8, met4))
2288        root = TreeNode()
2289        root.appendchild(rby1.dup()
2290                .appendchild(TextNode("1"))
2291                .appendchild(met2.dup()
2292                    .appendchild(TextNode("2"))
2293                    .appendchild(met3.dup()
2294                        .appendchild(TextNode("3"))
2295                        .appendchild(met4.dup()
2296                            .appendchild(TextNode("456")))
2297                        .appendchild(TextNode("7")))
2298                    .appendchild(TextNode("8")))
2299                .appendchild(TextNode("9")))
2300        self.dotest(root, False)
2301        ## overwrite outer ruby, split remains at every inner meta!
2302        rby5 = RubyNode(self.mkname("ruby"))
2303        # inserter.insertrange(Range(7-3, 8-3, rby5))
2304        inserter.insertrange(Range(7, 8, rby5))
2305        root = TreeNode()
2306        root.appendchild(rby1.dup()
2307                .appendchild(TextNode("1")))
2308        root.appendchild(met2.dup()
2309                .appendchild(rby1.dup()
2310                    .appendchild(TextNode("2")))
2311                .appendchild(met3.dup()
2312                    .appendchild(rby1.dup()
2313                        .appendchild(TextNode("3")))
2314                    .appendchild(met4.dup()
2315                        .appendchild(rby1.dup()
2316                            .appendchild(TextNode("4")))
2317                        .appendchild(rby5.dup()
2318                            .appendchild(TextNode("5")))
2319                        .appendchild(rby1.dup()
2320                            .appendchild(TextNode("6"))))
2321                    .appendchild(rby1.dup()
2322                        .appendchild(TextNode("7"))))
2323                .appendchild(rby1.dup()
2324                    .appendchild(TextNode("8"))))
2325        root.appendchild(rby1.dup()
2326                .appendchild(TextNode("9")))
2327        self.dotest(root, False)
2328
2329    def test_range5(self):
2330        inserter = RangeInserter(self.__class__.xDoc)
2331        text = TextNode("123456789")
2332        inserter.insertrange(Range(0, 0, text))
2333        rby1 = RubyNode(self.mkname("ruby"))
2334        inserter.insertrange(Range(0, 9, rby1))
2335        met2 = MetaNode(self.mkid("id"))
2336        inserter.insertrange(Range(1, 3, met2))
2337        met3 = MetaNode(self.mkid("id"))
2338        # inserter.insertrange(Range(5-1, 6-1, met3))
2339        inserter.insertrange(Range(5, 6, met3))
2340        met4 = MetaNode(self.mkid("id"))
2341        # inserter.insertrange(Range(8-2, 10-2, met4))
2342        inserter.insertrange(Range(8, 10, met4))
2343        root = TreeNode()
2344        root.appendchild(rby1.dup()
2345                .appendchild(TextNode("1"))
2346                .appendchild(met2.dup().appendchild(TextNode("23")))
2347                .appendchild(TextNode("4"))
2348                .appendchild(met3.dup().appendchild(TextNode("5")))
2349                .appendchild(TextNode("6"))
2350                .appendchild(met4.dup().appendchild(TextNode("78")))
2351                .appendchild(TextNode("9")))
2352        self.dotest(root, False)
2353        ## overwrite outer ruby, but split at inner metas!
2354        rby5 = RubyNode(self.mkname("ruby"))
2355        # inserter.insertrange(Range(3-1, 10-3, rby5))
2356        inserter.insertrange(Range(3, 10, rby5))
2357        root = TreeNode()
2358        root.appendchild(rby1.dup()
2359                .appendchild(TextNode("1")))
2360        root.appendchild(met2.dup()
2361                .appendchild(rby1.dup()
2362                    .appendchild(TextNode("2")))
2363                .appendchild(rby5.dup()
2364                    .appendchild(TextNode("3"))))
2365        root.appendchild(rby5.dup()
2366                .appendchild(TextNode("4"))
2367                .appendchild(met3.dup()
2368                    .appendchild(TextNode("5")))
2369                .appendchild(TextNode("6")))
2370        root.appendchild(met4.dup()
2371                .appendchild(rby5.dup()
2372                    .appendchild(TextNode("7")))
2373                .appendchild(rby1.dup()
2374                    .appendchild(TextNode("8"))))
2375        root.appendchild(rby1.dup()
2376                .appendchild(TextNode("9")))
2377        self.dotest(root, False)
2378
2379    def test_range6(self):
2380        inserter = RangeInserter(self.__class__.xDoc)
2381        text = TextNode("123456789")
2382        inserter.insertrange(Range(0, 0, text))
2383        met1 = MetaNode(self.mkid("id"))
2384        inserter.insertrange(Range(1, 5, met1))
2385        met2 = MetaNode(self.mkid("id"))
2386        # inserter.insertrange(Range(3-1, 6-1, met2))
2387        inserter.insertrange(Range(3, 6, met2))
2388        met3 = MetaNode(self.mkid("id"))
2389        # inserter.insertrange(Range(5-2, 7-2, met3))
2390        inserter.insertrange(Range(5, 7, met3))
2391        root = TreeNode()
2392        root.appendchild(TextNode("1"))
2393        root.appendchild(met1.dup()
2394                .appendchild(TextNode("2"))
2395                .appendchild(met2.dup()
2396                    .appendchild(TextNode("3"))
2397                    .appendchild(met3.dup()
2398                        .appendchild(TextNode("45")))))
2399        root.appendchild(TextNode("6789"))
2400        self.dotest(root, False)
2401        ## split at 3 metas, all at same position
2402        rby4 = RubyNode(self.mkname("ruby"))
2403        # inserter.insertrange(Range(7-3, 10-3, rby4))
2404        inserter.insertrange(Range(7, 10, rby4))
2405        root = TreeNode()
2406        root.appendchild(TextNode("1"))
2407        root.appendchild(met1.dup()
2408                .appendchild(TextNode("2"))
2409                .appendchild(met2.dup()
2410                    .appendchild(TextNode("3"))
2411                    .appendchild(met3.dup()
2412                        .appendchild(TextNode("4"))
2413                        .appendchild(rby4.dup()
2414                            .appendchild(TextNode("5"))))))
2415        root.appendchild(rby4.dup()
2416                .appendchild(TextNode("67")))
2417        root.appendchild(TextNode("89"))
2418        self.dotest(root, False)
2419
2420    def test_range7(self):
2421        inserter = RangeInserter(self.__class__.xDoc)
2422        text = TextNode("123456789")
2423        inserter.insertrange(Range(0, 0, text))
2424        url1 = HyperlinkNode(self.mkname("url"))
2425        inserter.insertrange(Range(1, 5, url1))
2426        met2 = MetaNode(self.mkid("id"))
2427        inserter.insertrange(Range(3, 5, met2))
2428        root = TreeNode()
2429        root.appendchild(TextNode("1"))
2430        root.appendchild(url1.dup()
2431                .appendchild(TextNode("23")))
2432        root.appendchild(met2.dup()
2433                .appendchild(url1.dup()
2434                    .appendchild(TextNode("45"))))
2435        root.appendchild(TextNode("6789"))
2436        self.dotest(root, False)
2437        ## this should result in not splitting the hyperlink, but due to API
2438        ## we can't tell :(
2439        rby3 = RubyNode(self.mkname("ruby"))
2440        # inserter.insertrange(Range(5-1, 8-1, rby3))
2441        inserter.insertrange(Range(5, 8, rby3))
2442        root = TreeNode()
2443        root.appendchild(TextNode("1"))
2444        root.appendchild(url1.dup()
2445                .appendchild(TextNode("23")))
2446        root.appendchild(met2.dup()
2447                .appendchild(url1.dup()
2448                    .appendchild(TextNode("4")))
2449                .appendchild(rby3.dup()
2450                    .appendchild(url1.dup()
2451                        .appendchild(TextNode("5")))))
2452        root.appendchild(rby3.dup()
2453                .appendchild(TextNode("67")))
2454        root.appendchild(TextNode("89"))
2455        self.dotest(root, False)
2456
2457    # TODO: test partial selection, test UNDO/REDO
2458
2459    ##i109601# NestedTextContent and XChild
2460    def test_meta_xchild(self):
2461        xDoc = self.__class__.xDoc
2462        id1 = StringPair("content.xml", self.mkname("id"))
2463        id2 = StringPair("content.xml", self.mkname("id"))
2464        id3 = StringPair("content.xml", self.mkname("id"))
2465        id4 = StringPair("content.xml", self.mkname("id"))
2466        id5 = StringPair("content.xml", self.mkname("id"))
2467        id6 = StringPair("content.xml", self.mkname("id"))
2468        meta1 = MetaNode(id1)
2469        meta2 = MetaNode(id2)
2470        meta3 = MetaFieldNode(id3)
2471        meta4 = MetaNode(id4)
2472        meta5 = MetaNode(id5)
2473        meta6 = MetaFieldNode(id6)
2474        root = TreeNode()
2475        root.appendchild(meta1.dup()
2476                .appendchild(TextNode("1")))
2477        root.appendchild(TextNode("2"))
2478        root.appendchild(meta2.dup()
2479                .appendchild(meta3.dup()
2480                    .appendchild(TextNode("34"))
2481                    .appendchild(meta4.dup()
2482                        .appendchild(TextNode("56")))
2483                    .appendchild(meta5.dup())
2484                    .appendchild(TextNode("7"))))
2485        root.appendchild(TextNode("8"))
2486        root.appendchild(meta6.dup()
2487                .appendchild(TextNode("9")))
2488
2489        inserter = RangeInserter(xDoc)
2490        text = TextNode("123456789")
2491        inserter.insertrange(Range(0, 0, text))
2492        xMeta1 = inserter.insertrange(Range(0, 1, meta1))
2493        xMeta2 = inserter.insertrange(Range(3, 8, meta2))
2494        xMeta3 = inserter.insertrange(Range(4, 9, meta3))
2495        xMeta4 = inserter.insertrange(Range(7, 9, meta4))
2496        xMeta5 = inserter.insertrange(Range(10, 10, meta5))
2497        xMeta6 = inserter.insertrange(Range(13, 14, meta6))
2498
2499        self.dotest(root, False)
2500
2501        xDocText = xDoc.getText()
2502        xDocTextCursor = xDocText.createTextCursor()
2503        xDocTextCursor.gotoNextParagraph(False) # second paragraph
2504        #  X12XX34X56X78X9
2505        #  1  23  4  5  6
2506        #   1       452  6
2507        #             3
2508        nestedTextContent = (
2509            None,
2510            id1,
2511            id1,
2512            None,
2513            id2,
2514            id3,
2515            id3,
2516            id3,
2517            id4,
2518            id4,
2519            id4,
2520            id5,
2521            id3,
2522            None,
2523            id6,
2524            id6)
2525        for i, ntc in enumerate(nestedTextContent):
2526            oNTC = xDocTextCursor.NestedTextContent
2527            if ntc is None:
2528                self.assertIsNone(oNTC,
2529                            "unexpected NestedTextContent at: {}".format(i))
2530            else:
2531                xmlid = oNTC.MetadataReference
2532                self.assertTrue(MetaNode.eq(ntc, xmlid),
2533                            "wrong NestedTextContent at: {}".format(i))
2534            xDocTextCursor.goRight(1, False)
2535
2536        try:
2537            xMeta1.setParent(xMeta4)
2538            fail("setParent(): allowed?")
2539        except NoSupportException:
2540            pass
2541        self.assertIsNone(xMeta1.getParent(), "getParent(): not None")
2542        self.assertIsNone(xMeta2.getParent(), "getParent(): not None")
2543        self.assertIsNone(xMeta6.getParent(), "getParent(): not None")
2544
2545        xParent3 = xMeta3.getParent()
2546        self.assertIsNotNone(xParent3, "getParent(): None")
2547        xmlid = xParent3.MetadataReference
2548        self.assertTrue(MetaNode.eq(xmlid, id2), "getParent(): wrong")
2549
2550        xParent4 = xMeta4.getParent()
2551        self.assertIsNotNone(xParent4, "getParent(): None")
2552        xmlid = xParent4.MetadataReference
2553        self. assertTrue(MetaNode.eq(xmlid, id3), "getParent(): wrong")
2554
2555        xParent5 = xMeta5.getParent()
2556        self.assertIsNotNone(xParent5, "getParent(): None")
2557        xmlid = xParent5.MetadataReference
2558        self.assertTrue(MetaNode.eq(xmlid, id3), "getParent(): wrong")
2559
2560    # test SwXMeta XText interface
2561    def test_meta_xtext(self):
2562        xDoc = self.__class__.xDoc
2563        inserter = RangeInserter(xDoc)
2564        text = TextNode("12AB6789")
2565        inserter.insertrange(Range(0, 0, text))
2566        meta = MetaNode(self.mkid("id"))
2567        xMeta = inserter.makemeta()
2568
2569        xDocText = xDoc.getText()
2570        xDocTextCursor = xDocText.createTextCursor()
2571        xDocTextCursor.goRight(3, False)
2572        xDocTextCursor.goRight(2, True)
2573        xDocText.insertTextContent(xDocTextCursor, xMeta, True)
2574
2575        xMeta.MetadataReference = meta.xmlid
2576        xParentText = xMeta.getText()
2577        self.assertIsNotNone(xParentText, "getText(): no parent")
2578
2579        xStart = xMeta.getStart()
2580        self.assertIsNotNone(xStart, "getStart(): no start")
2581
2582        xEnd = xMeta.getEnd()
2583        self.assertIsNotNone(xEnd, "getEnd(): no end")
2584
2585        xMeta.setString("45")
2586
2587        string = xMeta.getString()
2588        self.assertEqual("45", string, "getString(): invalid string returned")
2589
2590        xTextCursor = xMeta.createTextCursor()
2591        self.assertIsNotNone(xTextCursor, "createTextCursor(): failed")
2592
2593        try:
2594            xMeta.createTextCursorByRange(None)
2595            fail("createTextCursorByRange(): None allowed?")
2596        except RuntimeException:
2597            pass
2598
2599        xTextCursorStart = xMeta.createTextCursorByRange(xStart)
2600        self.assertIsNotNone(xTextCursorStart,
2601                    "createTextCursorByRange(): failed for start")
2602
2603        xTextCursorEnd = xMeta.createTextCursorByRange(xEnd)
2604        self.assertIsNotNone(xTextCursorEnd,
2605                             "createTextCursorByRange(): failed for end")
2606
2607        ## move outside meta
2608        xDocTextCursor.gotoStart(False)
2609
2610        try:
2611            xMeta.insertString(None, "foo", False)
2612            fail("insertString(): None allowed?")
2613        except RuntimeException:
2614            pass
2615
2616        try:
2617            xMeta.insertString(xDocTextCursor, "foo", False)
2618            fail("insertString(): cursor outside allowed?")
2619        except RuntimeException:
2620            pass
2621
2622        xStart = xMeta.getStart()
2623        xMeta.insertString(xStart, "A", False)
2624        string = xMeta.getString()
2625        self.assertEqual("A45", string, "getString(): invalid string returned")
2626
2627        xMeta.insertString(xEnd, "B", False)
2628        string = xMeta.getString()
2629        self.assertEqual("A45B", string, "getString(): invalid string returned")
2630
2631        try:
2632            xMeta.insertControlCharacter(None, HARD_HYPHEN, False)
2633            fail("insertControlCharacter(): None allowed?")
2634        except IllegalArgumentException:
2635            pass
2636
2637        xStart = xMeta.getStart()
2638        try:
2639            xMeta.insertControlCharacter(xDocTextCursor, HARD_HYPHEN, False)
2640            fail("insertControlCharacter(): cursor outside allowed?")
2641        except IllegalArgumentException:
2642            pass
2643
2644        xMeta.insertControlCharacter(xStart, HARD_HYPHEN, False)
2645        string = xMeta.getString()
2646        self.assertEqual('\u2011' + 'A45B', string,
2647                                "getString(): invalid string returned")
2648
2649        xMeta.insertControlCharacter(xEnd, HARD_HYPHEN, False)
2650        string = xMeta.getString()
2651        self.assertEqual('\u2011' + 'A45B' + '\u2011', string,
2652                                "getString(): invalid string returned")
2653
2654        xMeta.setString("45")
2655        try:
2656            xMeta.insertTextContent(None, xMeta, False)
2657            fail("insertTextContent(): None range allowed?")
2658        except IllegalArgumentException:
2659            pass
2660
2661        try:
2662            xMeta.insertTextContent(xStart, None, False)
2663            fail("insertTextContent(): None content allowed?")
2664        except IllegalArgumentException:
2665            pass
2666
2667        try:
2668            xMeta.insertTextContent(xDocTextCursor, xMeta, False)
2669            fail("insertTextContent(): cursor outside allowed?")
2670        except IllegalArgumentException:
2671            pass
2672
2673        field1 = TextFieldNode("f1")
2674        field2 = TextFieldNode("f2")
2675        xField1 = inserter.maketextfield(field1.content)
2676        xField2 = inserter.maketextfield(field2.content)
2677
2678        xStart = xMeta.getStart()
2679        xMeta.insertTextContent(xStart, xField1, False)
2680
2681        root = TreeNode()
2682        root.appendchild(TextNode("12"))
2683        root.appendchild(meta.dup()
2684                .appendchild(field1.dup())
2685                .appendchild(TextNode("45")))
2686        root.appendchild(TextNode("6789"))
2687        self.dotest(root, False)
2688
2689        xMeta.insertTextContent(xEnd, xField2, False)
2690
2691        root = TreeNode()
2692        root.appendchild(TextNode("12"))
2693        root.appendchild(meta.dup()
2694                .appendchild(field1.dup())
2695                .appendchild(TextNode("45"))
2696                .appendchild(field2.dup()))
2697        root.appendchild(TextNode("6789"))
2698        self.dotest(root, False)
2699
2700        try:
2701            xMeta.removeTextContent(None)
2702            fail("removeTextContent(): None content allowed?")
2703        except RuntimeException:
2704            pass
2705
2706        xMeta.removeTextContent(xField1)
2707
2708        xAnchor = xMeta.getAnchor()
2709        self.assertIsNotNone(xAnchor, "getAnchor(): None")
2710
2711        ## evil test case: insert ruby around meta
2712        ruby = RubyNode(self.mkname("ruby"))
2713        inserter.insertrange(Range(2, 6, ruby))
2714
2715        ## prevent caching...
2716        # root = TreeNode()
2717        # root.appendchild(TextNode("12"))
2718        # root.appendchild(ruby.dup()
2719                # .appendchild(meta.dup()
2720                    # .appendchild(TextNode("45"))
2721                    # .appendchild(field2.dup())))
2722        # root.appendchild(TextNode("6789"))
2723        # self.dotest(root, False)
2724
2725        xEnum = xMeta.createEnumeration()
2726        self.assertIsNotNone("createEnumeration(): returns None", xEnum)
2727
2728        self.assertTrue(xEnum.hasMoreElements(),"hasNext(): first missing")
2729        xPortion = xEnum.nextElement()
2730        type_ = xPortion.TextPortionType
2731        self.assertEqual("Text", type_, "first: not text")
2732        txt = xPortion.getString()
2733        self.assertEqual("45", txt, "first: text differs")
2734
2735        self.assertTrue(xEnum.hasMoreElements(),"hasNext(): second missing")
2736        xPortion = xEnum.nextElement()
2737        type_ = xPortion.TextPortionType
2738        self.assertEqual("TextField", type_, "second: not text")
2739
2740        ## no ruby end here!!!
2741        self.assertFalse(xEnum.hasMoreElements(), "hasNext(): more elements?")
2742
2743        xMeta.dispose()
2744
2745        try:
2746            xCursor = xMeta.createTextCursor()
2747            self.assertIsNone(xCursor,
2748                        "createTextCursor(): succeeds on disposed object?")
2749        except RuntimeException:
2750            pass
2751
2752    # check that cursor move methods move to positions in the meta,
2753    # but do not move to positions outside the meta.
2754    def test_meta_xtextcursor(self):
2755        xDoc = self.__class__.xDoc
2756        inserter = RangeInserter(xDoc)
2757        text = TextNode("Text. 12 More text here.")
2758        inserter.insertrange(Range(0, 0, text))
2759        met1 = MetaNode(self.mkid("id"))
2760        xMeta = inserter.makemeta()
2761
2762        xDocText = xDoc.getText()
2763        xDocTextCursor = xDocText.createTextCursor()
2764        xDocTextCursor.goRight(7, False)
2765        xDocTextCursor.goRight(2, True)
2766        xDocText.insertTextContent(xDocTextCursor, xMeta, True)
2767        xDocTextCursor.gotoStart(True)
2768
2769        xMeta.MetadataReference = met1.xmlid
2770        xStart = xMeta.getStart()
2771        self.assertIsNotNone(xStart, "getStart(): no start")
2772        xEnd = xMeta.getEnd()
2773        self.assertIsNotNone(xEnd, "getEnd(): no end")
2774
2775        ## XTextCursor
2776        xMetaCursor = xMeta.createTextCursor()
2777        self.assertIsNotNone(xMetaCursor, "createTextCursor(): no cursor")
2778        bSuccess = False
2779        xMetaCursor.gotoStart(False)
2780        xMetaCursor.gotoEnd(False)
2781        bSuccess = xMetaCursor.goLeft(1, False)
2782        self.assertTrue(bSuccess, "goLeft(): failed")
2783        bSuccess = xMetaCursor.goLeft(1000, False)
2784        self.assertFalse(bSuccess, "goLeft(): succeeded")
2785        bSuccess = xMetaCursor.goRight(1, False)
2786        self.assertTrue(bSuccess, "goRight(): failed")
2787        bSuccess = xMetaCursor.goRight(1000, False)
2788        self.assertFalse(bSuccess, "goRight(): succeeded")
2789        xMetaCursor.gotoRange(xStart, False)
2790        xMetaCursor.gotoRange(xEnd, False)
2791        try:
2792            xMetaCursor.gotoRange(xDocTextCursor, False)
2793            fail("gotoRange(): succeeded")
2794        except RuntimeException:
2795            pass
2796
2797        ## XWordCursor
2798        xMeta.setString("Two words")
2799        xMetaCursor.gotoStart(False)
2800
2801        bSuccess = xMetaCursor.gotoNextWord(True)  # at start of "words"
2802        self.assertTrue(bSuccess, "gotoNextWord(): failed")
2803
2804        string = xMetaCursor.getString()
2805        self.assertEqual("Two ", string, "gotoNextWord(): wrong string")
2806
2807        bSuccess = xMetaCursor.gotoNextWord(False)  # at end of "words", cannot leave metafield
2808        self.assertFalse(bSuccess,"gotoNextWord(): succeeded")
2809        xMetaCursor.collapseToEnd()
2810        bSuccess = xMetaCursor.gotoPreviousWord(True)  # at start of "words"
2811        self.assertTrue(bSuccess, "gotoPreviousWord(): failed")
2812
2813        string = xMetaCursor.getString()
2814        self.assertEqual("words", string, "gotoPreviousWord(): wrong string")
2815
2816        bSuccess = xMetaCursor.gotoPreviousWord(False)  # at start of "Two"
2817        self.assertTrue(bSuccess, "gotoPreviousWord(): failed")
2818
2819        bSuccess = xMetaCursor.gotoPreviousWord(False)  # cannot leave metafield
2820        self.assertFalse(bSuccess, "gotoPreviousWord(): succeeded")
2821
2822        bSuccess = xMetaCursor.gotoEndOfWord(True)  # at end of "Two"
2823        self.assertTrue(bSuccess, "gotoEndOfWord(): failed")
2824
2825        string = xMetaCursor.getString()
2826        self.assertEqual("Two", string, "gotoEndOfWord(): wrong string")
2827
2828        xMetaCursor.gotoEnd(False)
2829        bSuccess = xMetaCursor.gotoStartOfWord(True)
2830        self.assertTrue(bSuccess, "gotoStartOfWord(): failed")
2831
2832        string = xMetaCursor.getString()
2833        self.assertEqual("words", string, "gotoStartOfWord(): wrong string")
2834
2835        xMeta.setString("")
2836        bSuccess = xMetaCursor.gotoEndOfWord(False)
2837        self.assertFalse(bSuccess, "gotoEndOfWord(): succeeded")
2838        bSuccess = xMetaCursor.gotoStartOfWord(False)
2839        self.assertFalse(bSuccess, "gotoStartOfWord(): succeeded")
2840
2841        ## XSentenceCursor
2842        xMeta.setString("This is a sentence. Another sentence.")
2843        xMetaCursor.gotoStart(False)
2844
2845        bSuccess = xMetaCursor.gotoNextSentence(True)
2846        self.assertTrue(bSuccess,"gotoNextSentence(): failed")
2847
2848        string = xMetaCursor.getString()
2849        self.assertEqual("This is a sentence. ", string,
2850                            "gotoNextSentence(): wrong string")
2851
2852        bSuccess = xMetaCursor.gotoNextSentence(False)
2853        self.assertFalse(bSuccess, "gotoNextSentence(): succeeded")
2854        ## FIXME:
2855        ## the sentence cursor seems to work differently than the word cursor
2856        xMeta.setString("This is a sentence. Another sentence. Sentence 3.")
2857        xMetaCursor.gotoEnd(False)
2858        bSuccess = xMetaCursor.gotoPreviousSentence(True)
2859        self.assertTrue(bSuccess, "gotoPreviousSentence(): failed")
2860
2861        string = xMetaCursor.getString()
2862        self.assertEqual("Another sentence. Sentence 3.", string,
2863                                "gotoPreviousSentence(): wrong string")
2864
2865        bSuccess = xMetaCursor.gotoPreviousSentence(False)
2866        self.assertFalse(bSuccess, "gotoPreviousSentence(): succeeded")
2867        bSuccess = xMetaCursor.gotoEndOfSentence(True)
2868        self.assertTrue(bSuccess, "gotoEndOfSentence(): failed")
2869
2870        string = xMetaCursor.getString()
2871        self.assertEqual("This is a sentence.", string,
2872                            "gotoEndOfSentence(): wrong string")
2873
2874        xMetaCursor.gotoEnd(False)
2875        bSuccess = xMetaCursor.gotoStartOfSentence(True)
2876        self.assertTrue(bSuccess,"gotoStartOfSentence(): failed")
2877
2878        string = xMetaCursor.getString()
2879        self.assertEqual("Sentence 3.", string,
2880                         "gotoStartOfSentence(): wrong string")
2881
2882        xMeta.setString("")
2883        bSuccess = xMetaCursor.gotoEndOfSentence(False)
2884        self.assertFalse(bSuccess, "gotoEndOfSentence(): succeeded")
2885        bSuccess = xMetaCursor.gotoStartOfSentence(False)
2886        self.assertFalse(bSuccess, "gotoStartOfSentence(): succeeded")
2887
2888        ## XParagraphCursor (does not make sense)
2889        bSuccess = xMetaCursor.gotoNextParagraph(False)
2890        self.assertFalse(bSuccess, "gotoNextParagraph(): succeeded")
2891        bSuccess = xMetaCursor.gotoPreviousParagraph(False)
2892        self.assertFalse(bSuccess, "gotoPreviousParagraph(): succeeded")
2893        bSuccess = xMetaCursor.gotoStartOfParagraph(False)
2894        self.assertFalse(bSuccess, "gotoStartOfParagraph(): succeeded")
2895        bSuccess = xMetaCursor.gotoEndOfParagraph(False)
2896        self.assertFalse(bSuccess, "gotoEndOfParagraph(): succeeded")
2897
2898    # See https://bugs.libreoffice.org/show_bug.cgi?id=49629
2899    # ensure that gotoEndOfWord does not fail when footnote is at word end
2900    def test_xtextcursor(self):
2901        xDoc = self.__class__.xDoc
2902        inserter = RangeInserter(xDoc)
2903        xDocText = xDoc.getText()
2904        xDocTextCursor = xDocText.createTextCursor()
2905        xDocTextCursor.gotoNextParagraph(False)
2906        inserter.inserttext(xDocTextCursor, "Text")
2907        xDocTextCursor.gotoEndOfWord(False)
2908        inserter.insertfootnote(xDocTextCursor, "footnote")
2909        xDocTextCursor.gotoStartOfParagraph(False)
2910        bSuccess = xDocTextCursor.gotoEndOfWord(True)
2911        self.assertTrue(bSuccess, "gotoEndOfWord(): failed")
2912        string = xDocTextCursor.getString()
2913        self.assertEqual("Text", string, "gotoEndOfWord(): wrong string")
2914        self.assertNotEqual("a","b")
2915
2916    class AttachHelper():
2917        def isattribute(self): pass
2918        def mktreenode(self): pass
2919        def mktextcontent(self, inserter, node): pass
2920        def postinserted(self, node, xContent): pass
2921
2922    def test_meta_xtextattach_toxmark(self):
2923        class Helper(self.AttachHelper):
2924            def isattribute(_):
2925                return True
2926            def mktreenode(_):
2927                return DocumentIndexMarkNode(self.mkname("toxmark"))
2928            def mktextcontent(_, inserter, node):
2929                return inserter.makedocumentindexmark(node.name)
2930        self.do_meta_xtextattach(Helper())
2931
2932    def test_meta_xtextattach_refmark(self):
2933        class Helper(self.AttachHelper):
2934            def isattribute(_):
2935                return True
2936            def mktreenode(_):
2937                return ReferenceMarkNode(self.mkname("refmark"))
2938            def mktextcontent(_, inserter, node):
2939                return inserter.makereferencemark(node.name)
2940        self.do_meta_xtextattach(Helper())
2941
2942    def test_meta_xtextattach_textfield(self):
2943        class Helper(self.AttachHelper):
2944            def isattribute(_):
2945                return False
2946            def mktreenode(_):
2947                return TextFieldNode(self.mkname("field"))
2948            def mktextcontent(_, inserter, node):
2949                return inserter.maketextfield(node.content)
2950        self.do_meta_xtextattach(Helper())
2951
2952    def test_meta_xtextattach_footnote(self):
2953        class Helper(self.AttachHelper):
2954            def isattribute(_):
2955                return False
2956            def mktreenode(_):
2957                return FootnoteNode(self.mkname("ftn"))
2958            def mktextcontent(_, inserter, node):
2959                return inserter.makefootnote(node.label)
2960        self.do_meta_xtextattach(Helper())
2961
2962    def test_meta_xtextattach_meta(self):
2963        class Helper(self.AttachHelper):
2964            def isattribute(_):
2965                return True
2966            def mktreenode(_):
2967                return MetaNode(self.mkid("id"))
2968            def mktextcontent(_, inserter, node):
2969                return inserter.makemeta()
2970            def postinserted(_, node, xContent):
2971                xContent.MetadataReference = node.xmlid
2972        self.do_meta_xtextattach(Helper())
2973
2974    def do_meta_xtextattach(self, helper):
2975        xDoc = self.__class__.xDoc
2976        inserter = RangeInserter(xDoc)
2977        text = TextNode("12AB6789")
2978        inserter.insertrange(Range(0, 0, text))
2979        met1 = MetaNode(self.mkid("id"))
2980        xMeta = inserter.makemeta()
2981
2982        xDocText = xDoc.getText()
2983        xDocTextCursor = xDocText.createTextCursor()
2984        xDocTextCursor.goRight(3, False)
2985        xDocTextCursor.goRight(2, True)
2986        xDocText.insertTextContent(xDocTextCursor, xMeta, True)
2987
2988        xMeta.MetadataReference = met1.xmlid
2989        xStart = None
2990        xEnd = None
2991
2992        xStart = xMeta.getStart()
2993        xEnd = xMeta.getEnd()
2994
2995        nod1 = helper.mktreenode()
2996        nod2 = helper.mktreenode()
2997        xContent1 = helper.mktextcontent(inserter, nod1)
2998        xContent2 = helper.mktextcontent(inserter, nod2)
2999
3000        ## insertTextContent with meta getStart()/getEnd()
3001        xMeta.insertTextContent(xStart, xContent1, False)
3002        xMeta.insertTextContent(xEnd, xContent2, False)
3003
3004        helper.postinserted(nod1, xContent1)
3005        helper.postinserted(nod2, xContent2)
3006
3007        root = TreeNode()
3008        root.appendchild(TextNode("12"))
3009        root.appendchild(met1.dup()
3010                .appendchild(nod1.dup())
3011                .appendchild(TextNode("AB"))
3012                .appendchild(nod2.dup()))
3013        root.appendchild(TextNode("6789"))
3014        self.dotest(root, False)
3015
3016        xMeta.setString("AB")
3017        xStart = xMeta.getStart()
3018        xEnd = xMeta.getEnd()
3019
3020        nod1 = helper.mktreenode()
3021        nod2 = helper.mktreenode()
3022        xContent1 = helper.mktextcontent(inserter, nod1)
3023        xContent2 = helper.mktextcontent(inserter, nod2)
3024
3025        xTextCursor = xMeta.createTextCursor()
3026        xTextCursor.gotoStart(False)
3027
3028        ## insertTextContent with meta cursor
3029        xMeta.insertTextContent(xTextCursor, xContent1, False)
3030        xTextCursor.gotoEnd(False)
3031        xMeta.insertTextContent(xTextCursor, xContent2, False)
3032
3033        helper.postinserted(nod1, xContent1)
3034        helper.postinserted(nod2, xContent2)
3035
3036        root = TreeNode()
3037        root.appendchild(TextNode("12"))
3038        root.appendchild(met1.dup()
3039                .appendchild(nod1.dup())
3040                .appendchild(TextNode("AB"))
3041                .appendchild(nod2.dup()))
3042        root.appendchild(TextNode("6789"))
3043        self.dotest(root, False)
3044
3045        if not helper.isattribute():
3046            # xMeta.setString("AB")
3047            xStart = xMeta.getStart()
3048            xEnd = xMeta.getEnd()
3049
3050            nod1 = helper.mktreenode()
3051            nod2 = helper.mktreenode()
3052            xContent1 = helper.mktextcontent(inserter, nod1)
3053            xContent2 = helper.mktextcontent(inserter, nod2)
3054
3055            xTextCursor = xMeta.createTextCursor()
3056            xTextCursor.gotoStart(False)
3057            xTextCursor.goRight(1, True)
3058
3059            ## insertTextContent with meta cursor and absorb
3060            xMeta.insertTextContent(xTextCursor, xContent1, True)
3061            xTextCursor.gotoEnd(False)
3062            xTextCursor.goLeft(1, True)
3063            xMeta.insertTextContent(xTextCursor, xContent2, True)
3064
3065            helper.postinserted(nod1, xContent1)
3066            helper.postinserted(nod2, xContent2)
3067
3068            root = TreeNode()
3069            root.appendchild(TextNode("12"))
3070            root.appendchild(met1.dup()
3071                    .appendchild(nod1.dup())
3072                    .appendchild(TextNode("AB"))
3073                    .appendchild(nod2.dup()))
3074            root.appendchild(TextNode("6789"))
3075            self.dotest(root, False)
3076
3077            xMeta.setString("AB")
3078            xStart = xMeta.getStart()
3079            xEnd = xMeta.getEnd()
3080
3081            nod1 = helper.mktreenode()
3082            nod2 = helper.mktreenode()
3083            xContent1 = helper.mktextcontent(inserter, nod1)
3084            xContent2 = helper.mktextcontent(inserter, nod2)
3085
3086            xDocTextCursor.gotoRange(xStart, False)
3087
3088            ## insertTextContent with document cursor
3089            xMeta.insertTextContent(xDocTextCursor, xContent1, False)
3090            xDocTextCursor.gotoRange(xEnd, False)
3091            xMeta.insertTextContent(xDocTextCursor, xContent2, False)
3092
3093            helper.postinserted(nod1, xContent1)
3094            helper.postinserted(nod2, xContent2)
3095
3096            root = TreeNode()
3097            root.appendchild(TextNode("12"))
3098            root.appendchild(met1.dup()
3099                    .appendchild(nod1.dup())
3100                    .appendchild(TextNode("AB"))
3101                    .appendchild(nod2.dup()))
3102            root.appendchild(TextNode("6789"))
3103            self.dotest(root, False)
3104
3105        if not helper.isattribute():
3106            xStart = xMeta.getStart()
3107            xEnd = xMeta.getEnd()
3108
3109            nod1 = helper.mktreenode()
3110            nod2 = helper.mktreenode()
3111            xContent1 = helper.mktextcontent(inserter, nod1)
3112            xContent2 = helper.mktextcontent(inserter, nod2)
3113
3114            xDocTextCursor.gotoRange(xStart, False)
3115            xDocTextCursor.goRight(1, True)
3116
3117            ## insertTextContent with document cursor and absorb
3118            xMeta.insertTextContent(xDocTextCursor, xContent1, True)
3119            xDocTextCursor.gotoRange(xEnd, False)
3120            xDocTextCursor.goLeft(1, True)
3121            xMeta.insertTextContent(xDocTextCursor, xContent2, True)
3122
3123            helper.postinserted(nod1, xContent1)
3124            helper.postinserted(nod2, xContent2)
3125
3126            root = TreeNode()
3127            root.appendchild(TextNode("12"))
3128            root.appendchild(met1.dup()
3129                    .appendchild(nod1.dup())
3130                    .appendchild(TextNode("AB"))
3131                    .appendchild(nod2.dup()))
3132            root.appendchild(TextNode("6789"))
3133            self.dotest(root, False)
3134
3135            xMeta.setString("AB")
3136            xStart = xMeta.getStart()
3137            xEnd = xMeta.getEnd()
3138
3139            nod1 = helper.mktreenode()
3140            nod2 = helper.mktreenode()
3141            xContent1 = helper.mktextcontent(inserter, nod1)
3142            xContent2 = helper.mktextcontent(inserter, nod2)
3143
3144            ## attach to range from meta getStart()/getEnd()
3145            xContent1.attach(xStart)
3146            xContent2.attach(xEnd)
3147
3148            helper.postinserted(nod1, xContent1)
3149            helper.postinserted(nod2, xContent2)
3150
3151            root = TreeNode()
3152            root.appendchild(TextNode("12"))
3153            root.appendchild(met1.dup()
3154                    .appendchild(nod1.dup())
3155                    .appendchild(TextNode("AB"))
3156                    .appendchild(nod2.dup()))
3157            root.appendchild(TextNode("6789"))
3158            self.dotest(root, False)
3159
3160            xMeta.setString("AB")
3161            xStart = xMeta.getStart()
3162            xEnd = xMeta.getEnd()
3163
3164            nod1 = helper.mktreenode()
3165            nod2 = helper.mktreenode()
3166            xContent1 = helper.mktextcontent(inserter, nod1)
3167            xContent2 = helper.mktextcontent(inserter, nod2)
3168
3169            xTextCursor = xMeta.createTextCursor()
3170            xTextCursor.gotoStart(False)
3171
3172            ## attach to cursor from meta XText
3173            xContent1.attach(xTextCursor)
3174            xTextCursor.gotoEnd(False)
3175            xContent2.attach(xTextCursor)
3176
3177            helper.postinserted(nod1, xContent1)
3178            helper.postinserted(nod2, xContent2)
3179
3180            root = TreeNode()
3181            root.appendchild(TextNode("12"))
3182            root.appendchild(met1.dup()
3183                    .appendchild(nod1.dup())
3184                    .appendchild(TextNode("AB"))
3185                    .appendchild(nod2.dup()))
3186            root.appendchild(TextNode("6789"))
3187            self.dotest(root, False)
3188
3189    def test_metafield_xtextfield(self):
3190        xDoc = self.__class__.xDoc
3191        smgr = self.__class__._uno.xContext.ServiceManager
3192        xRepo = xDoc.getRDFRepository()
3193        ## for testing just add it to the first graph
3194        Graphs = xRepo.getGraphNames()
3195        xGraph = xRepo.getGraph(Graphs[0])
3196        xOdfPrefix = smgr.createInstance("com.sun.star.rdf.URI")
3197        xOdfPrefix.initialize((ODF_PREFIX,))
3198        xOdfSuffix = smgr.createInstance("com.sun.star.rdf.URI")
3199        xOdfSuffix.initialize((ODF_SUFFIX,))
3200
3201        xPrefix = smgr.createInstance("com.sun.star.rdf.Literal")
3202        xPrefix.initialize(("foo",))
3203        xSuffix = smgr.createInstance("com.sun.star.rdf.Literal")
3204        xSuffix.initialize(("bar",))
3205
3206        inserter = RangeInserter(xDoc)
3207        text = TextNode("abc")
3208        inserter.insertrange(Range(0, 0, text))
3209        xDocText = xDoc.getText()
3210        xDocTextCursor = xDocText.createTextCursor()
3211        xDocTextCursor.goRight(1, False)
3212        xDocTextCursor.goRight(3, True)
3213
3214        xMetaField = inserter.makemetafield()
3215
3216        xDocText.insertTextContent(xDocTextCursor, xMetaField, True)
3217
3218        xMetaField.ensureMetadataReference
3219
3220        xGraph.addStatement(xMetaField, xOdfPrefix, xPrefix)
3221        xGraph.addStatement(xMetaField, xOdfSuffix, xSuffix)
3222        self.assertEqual("fooabcbar", xMetaField.getPresentation(False),
3223                         "getPresentation(): wrong")
3224        inserter.insertrange(Range(0, 0, text))
3225
3226    def test_metafield_xpropertyset(self):
3227        xDoc = self.__class__.xDoc
3228        inserter = RangeInserter(xDoc)
3229        text = TextNode("123")
3230        inserter.insertrange(Range(0, 0, text))
3231        xDocText = xDoc.getText()
3232        xDocTextCursor = xDocText.createTextCursor()
3233        xDocTextCursor.goRight(1, False)
3234        xDocTextCursor.goRight(3, True)
3235
3236        xMetaField = inserter.makemetafield()
3237
3238        xDocText.insertTextContent(xDocTextCursor, xMetaField, True)
3239
3240        self.assertIsNotNone(xMetaField, "PropertySet: not supported?")
3241        xPropertySetInfo = xMetaField.getPropertySetInfo()
3242        self.assertTrue(xPropertySetInfo.hasPropertyByName("NumberFormat"),
3243                        'hasPropertyByName("NumberFormat"):')
3244        self.assertTrue(xPropertySetInfo.hasPropertyByName("IsFixedLanguage"),
3245                        'hasPropertyByName("IsFixedLanguage"):')
3246
3247        def_ = xMetaField.NumberFormat
3248        print("NumberFormat: default is {}".format(def_))
3249        xMetaField.NumberFormat = NUMBER_INT
3250        xMetaField.IsFixedLanguage = True
3251        format = xMetaField.NumberFormat
3252        self.assertEqual(NUMBER_INT, format, "NumberFormat: failed")
3253        isfixed = xMetaField.IsFixedLanguage
3254        self.assertTrue(isfixed, "IsFixedLanguage: failed")
3255
3256    def dostore(self, xComp, file):
3257        print("Storing test document...")
3258        file = uno.systemPathToFileUrl(file)
3259        xComp.storeToURL(file, ())
3260        print("...done")
3261
3262    def doload(self, file):
3263        xComp = None
3264        print("Loading test document...")
3265        xComp = self.__class__._uno.openDocFromAbsolutePath(file)
3266        self.assertIsNotNone(xComp, "cannot load: {}".format(file))
3267        print("...done")
3268        return xComp
3269
3270    def close(self, i_comp):
3271        try:
3272            if i_comp:
3273                i_comp.close(True)
3274        except Exception as e:
3275            pass
3276
3277    def test_load_store(self):
3278        xComp = None
3279        filename = "TESTMETA.odt"
3280        try:
3281            xComp = self.__class__._uno.openDocFromTDOC(filename)
3282            if xComp:
3283                self.checkloadmeta(xComp)
3284                with TemporaryDirectory() as tempdir:
3285                    if os.altsep: # we need URL so replace "\" with "/"
3286                        tempdir = tempdir.replace(os.sep, os.altsep)
3287                    file = tempdir + "/" + filename
3288                    self.dostore(xComp, file)
3289                    self.close(xComp)
3290                    xComp2 = None
3291                    try:
3292                        xComp2 = self.doload(file)
3293                        self.checkloadmeta(xComp2)
3294                    finally:
3295                        self.close(xComp2)
3296        finally:
3297            self.close(xComp)
3298
3299    def checkloadmeta(self, xTextDoc):
3300        xText = xTextDoc.getText()
3301        print("Checking meta(-field)s in loaded test document...")
3302        root = TreeNode()
3303        root.appendchild(RubyNode("ruby1")
3304                .appendchild(TextNode("1")))
3305        root.appendchild(MetaNode(self.mkid_("id1"))
3306                .appendchild(TextNode("2")))
3307        root.appendchild(MetaFieldNode(self.mkid_("id2"))
3308                .appendchild(TextNode("3")))
3309        root.appendchild(RubyNode("ruby2")
3310                .appendchild(MetaNode(self.mkid_("id3"))
3311                    .appendchild(TextNode("4"))))
3312        root.appendchild(RubyNode("ruby3")
3313                .appendchild(MetaFieldNode(self.mkid_("id4"))
3314                    .appendchild(TextNode("5"))))
3315        root.appendchild(MetaNode(self.mkid_("id5"))
3316                .appendchild(RubyNode("ruby4")
3317                    .appendchild(TextNode("6"))))
3318        root.appendchild(MetaFieldNode(self.mkid_("id6"))
3319                .appendchild(RubyNode("ruby5")
3320                    .appendchild(TextNode("7"))))
3321        root.appendchild(MetaNode(self.mkid_("id7"))
3322                .appendchild(MetaNode(self.mkid_("id8"))
3323                    .appendchild(TextNode("8"))))
3324        root.appendchild(MetaNode(self.mkid_("id9"))
3325                .appendchild(MetaFieldNode(self.mkid_("id10"))
3326                    .appendchild(TextNode("9"))))
3327        root.appendchild(MetaFieldNode(self.mkid_("id11"))
3328                .appendchild(MetaNode(self.mkid_("id12"))
3329                    .appendchild(TextNode("10"))))
3330        root.appendchild(MetaFieldNode(self.mkid_("id13"))
3331                .appendchild(MetaFieldNode(self.mkid_("id14"))
3332                    .appendchild(TextNode("11"))))
3333        root.appendchild(MetaNode(self.mkid_("id15"))
3334                .appendchild(RubyNode("ruby6")
3335                    .appendchild(MetaFieldNode(self.mkid_("id16"))
3336                        .appendchild(TextNode("12")))))
3337
3338        class MetaNode_(MetaNode):
3339            def __init__(self, id):
3340                super().__init__(id)
3341            def __eq__(self, other):
3342                return isinstance(other, MetaNode)
3343        root.appendchild(MetaNode_(self.mkid_(""))
3344                .appendchild(TextNode("13")))
3345        root.appendchild(TextNode(" X X "))
3346        self._dotest(xTextDoc, root, False)
3347        print("...done")
3348
3349    def test_load_store_xmlid(self):
3350        xComp = None
3351        filename = "TESTXMLID.odt"
3352        try:
3353            xComp = self.__class__._uno.openDocFromTDOC(filename)
3354            if xComp:
3355                self.checkloadxmlid(xComp)
3356                with TemporaryDirectory() as tempdir:
3357                    if os.altsep: # we need URL so replace "\" with "/"
3358                        tempdir = tempdir.replace(os.sep, os.altsep)
3359                    file = tempdir + "/" + filename
3360                    self.dostore(xComp, file)
3361                    self.close(xComp)
3362                    xComp2 = None
3363                    try:
3364                        xComp2 = self.doload(file)
3365                        self.checkloadxmlid(xComp2)
3366                    finally:
3367                        self.close(xComp2)
3368        finally:
3369            self.close(xComp)
3370
3371    def checkloadxmlid(self, xTextDoc):
3372        xText = xTextDoc.getText()
3373        xRepo = xTextDoc.getRDFRepository()
3374
3375        print("Checking bookmarks in loaded test document...")
3376        xBookmarks = xTextDoc.getBookmarks()
3377        xMark1 = xBookmarks["mk1"]
3378        self.assertTrue(self.eq(xMark1.MetadataReference,
3379                                StringPair("content.xml", "id90")), "mark1")
3380        xMark2 = xBookmarks["mk2"]
3381        result = xRepo.getStatementRDFa(xMark2)
3382        self.assertTrue(len(result.First) == 1 and
3383                        result.First[0].Subject.StringValue == "uri:foo" and
3384                        result.First[0].Predicate.StringValue == "uri:bar" and
3385                        result.First[0].Object.Value == "a fooish bar",
3386                        "mark2")
3387        xMark3 = xBookmarks["mk3"]
3388        self.assertTrue(self.eq(xMark3.MetadataReference,
3389                        StringPair("content.xml", "id91")), "mark3")
3390        print("...done")
3391
3392        print("Checking sections in loaded test document...")
3393        xSections = xTextDoc.getTextSections()
3394        xSection1 = xSections["Section 1"]
3395        self.assertTrue(self.eq(xSection1.MetadataReference,
3396                        StringPair("content.xml", "idSection1")), "idsection1")
3397        xSection2 = xSections["Section 2"]
3398        self.assertTrue(self.eq(xSection2.MetadataReference,
3399                        StringPair("content.xml", "idSection2")),"idSection2")
3400        xSection3 = xSections["Table of Contents1_Head"]
3401        self.assertTrue(self.eq(xSection3.MetadataReference,
3402                        StringPair("content.xml", "idTOCTitle")), "idTOCTitle")
3403        xSection4 = xSections["Alphabetical Index1_Head"]
3404        self.assertTrue(self.eq(xSection4.MetadataReference,
3405                        StringPair("content.xml", "idAITitle")), "idAITitle")
3406        xSection5 = xSections["Illustration Index1_Head"]
3407        self.assertTrue(self.eq(xSection5.MetadataReference,
3408                        StringPair("content.xml", "idIITitle")), "idIITitle")
3409        xSection6 = xSections["Index of Tables1_Head"]
3410        self.assertTrue(self.eq(xSection6.MetadataReference,
3411                        StringPair("content.xml", "idIOTTitle")), "idIOTTitle")
3412        xSection7 = xSections["User-Defined1_Head"]
3413        self.assertTrue(self.eq(xSection7.MetadataReference,
3414                        StringPair("content.xml", "idUDTitle")), "idUDTitle")
3415        xSection8 = xSections["Table of Objects1_Head"]
3416        self.assertTrue(self.eq(xSection8.MetadataReference,
3417                        StringPair("content.xml", "idTOOTitle")), "idTOOTitle")
3418        xSection9 = xSections["Bibliography1_Head"]
3419        self.assertTrue(self.eq(xSection9.MetadataReference,
3420                        StringPair("content.xml", "idBibTitle")), "idBibTitle")
3421        print("...done")
3422
3423        print("Checking indexes in loaded test document...")
3424        xIndexes = xTextDoc.getDocumentIndexes()
3425        xIndex1 = xIndexes["Table of Contents1"]
3426        self.assertTrue(self.eq(xIndex1.MetadataReference,
3427                        StringPair("content.xml", "idTOC")), "idTOC")
3428        xIndex1s = xSections["Table of Contents1"]
3429        self.assertTrue(self.eq(xIndex1s.MetadataReference,
3430                        StringPair("content.xml", "idTOC")), "idTOC")
3431        xIndex2 = xIndexes["Alphabetical Index1"]
3432        self.assertTrue(self.eq(xIndex2.MetadataReference,
3433                        StringPair("content.xml", "idAI")), "idAI")
3434        xIndex2s = xSections["Alphabetical Index1"]
3435        self.assertTrue(self.eq(xIndex2s.MetadataReference,
3436                        StringPair("content.xml", "idAI")), "idAI")
3437        xIndex3 = xIndexes["Illustration Index1"]
3438        self.assertTrue(self.eq(xIndex3.MetadataReference,
3439                        StringPair("content.xml", "idII")), "idII")
3440        xIndex3s = xSections["Table of Figures1"]
3441        self.assertTrue(self.eq(xIndex3s.MetadataReference,
3442                        StringPair("content.xml", "idII")), "idII")
3443        xIndex4 = xIndexes["Index of Tables1"]
3444        self.assertTrue(self.eq(xIndex4.MetadataReference,
3445                        StringPair("content.xml", "idIOT")), "idIOT")
3446        xIndex4s = xSections["Index of Tables1"]
3447        self.assertTrue(self.eq(xIndex4s.MetadataReference,
3448                        StringPair("content.xml", "idIOT")), "idIOT")
3449        xIndex5 = xIndexes["User-Defined1"]
3450        self.assertTrue(self.eq(xIndex5.MetadataReference,
3451                        StringPair("content.xml", "idUD")), "idUD")
3452        xIndex5s = xSections["User-Defined1"]
3453        self.assertTrue(self.eq(xIndex5s.MetadataReference,
3454                        StringPair("content.xml", "idUD")), "idUD")
3455        xIndex6 = xIndexes["Table of Objects1"]
3456        self.assertTrue(self.eq(xIndex6.MetadataReference,
3457                        StringPair("content.xml", "idTOO")), "idTOO")
3458        xIndex6s = xSections["Table of Objects1"]
3459        self.assertTrue(self.eq(xIndex6s.MetadataReference,
3460                        StringPair("content.xml", "idTOO")), "idTOO")
3461        xIndex7 = xIndexes["Bibliography1"]
3462        self.assertTrue(self.eq(xIndex7.MetadataReference,
3463                        StringPair("content.xml", "idBib")), "idBib")
3464        xIndex7s = xSections["Bibliography1"]
3465        self.assertTrue(self.eq(xIndex7s.MetadataReference,
3466                        StringPair("content.xml", "idBib")), "idBib")
3467        print("...done")
3468
3469    def dotest(self, intree, insert=True):
3470        xDoc = self.__class__.xDoc
3471        self._dotest(xDoc, intree, insert)
3472
3473    def _dotest(self, xDoc, intree, insert):
3474        self._dumptree(intree, "I: ")
3475        if insert:
3476            TreeInserter(xDoc).inserttree(intree)
3477        xText = xDoc.getText()
3478        xTextEnum = xText.createEnumeration()
3479        ## skip to right paragraph
3480        xTextEnum.nextElement(); # skip first -- always empty!
3481        xElement = xTextEnum.nextElement() # second contains test case
3482        xEnum = xElement.createEnumeration()
3483        outtree = EnumConverter().convert(xEnum)
3484        self._dumptree(outtree, "O: ")
3485        FuzzyTester().dotest(intree, outtree)
3486
3487    def _dumptree(self, tree, prefix):
3488        print('{}{}'.format(prefix, str(tree)))
3489        children = tree.createenumeration()
3490        for node in children:
3491            self._dumptree(node, "{}  ".format(prefix))
3492
3493    def mkname(self, prefix):
3494        self.__class__.count += 1
3495        return "{}{}".format(prefix, self.__class__.count)
3496
3497    def mkid(self, prefix):
3498        id = self.mkname(prefix)
3499        return StringPair("content.xml", self.mkname(prefix))
3500
3501    def mkid_(self, id):
3502        return StringPair("content.xml", id)
3503
3504    def eq(self, left, right):
3505        return (left.First == right.First and
3506                left.Second == right.Second)
3507
3508
3509if __name__ == '__main__':
3510    unittest.main()
3511