1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 package complex.writer;
29 
30 import com.sun.star.uno.UnoRuntime;
31 import com.sun.star.uno.AnyConverter;
32 import com.sun.star.uno.XComponentContext;
33 import com.sun.star.lang.XMultiServiceFactory;
34 import com.sun.star.lang.XComponent;
35 import com.sun.star.lang.XServiceInfo;
36 import com.sun.star.lang.NoSupportException;
37 import com.sun.star.beans.XPropertySet;
38 import com.sun.star.beans.XPropertySetInfo;
39 import com.sun.star.beans.PropertyValue;
40 import com.sun.star.beans.Pair;
41 import com.sun.star.beans.StringPair;
42 import com.sun.star.container.XNamed;
43 import com.sun.star.container.XChild;
44 import com.sun.star.container.XIndexAccess;
45 import com.sun.star.container.XNameAccess;
46 import com.sun.star.container.XContentEnumerationAccess;
47 import com.sun.star.container.XEnumerationAccess;
48 import com.sun.star.container.XEnumeration;
49 import com.sun.star.container.NoSuchElementException;
50 import com.sun.star.frame.XStorable;
51 import com.sun.star.util.XCloseable;
52 import com.sun.star.text.XText;
53 import com.sun.star.text.XTextContent;
54 import com.sun.star.text.XTextDocument;
55 import com.sun.star.text.XTextRange;
56 import com.sun.star.text.XTextCursor;
57 import com.sun.star.text.XWordCursor;
58 import com.sun.star.text.XSentenceCursor;
59 import com.sun.star.text.XParagraphCursor;
60 import com.sun.star.text.XFootnote;
61 import com.sun.star.text.XTextField;
62 import com.sun.star.text.XBookmarksSupplier;
63 import com.sun.star.text.XTextSectionsSupplier;
64 import com.sun.star.text.XDocumentIndexesSupplier;
65 import com.sun.star.text.TextContentAnchorType;
66 import static com.sun.star.text.TextContentAnchorType.*;
67 import static com.sun.star.text.ControlCharacter.*;
68 import com.sun.star.rdf.XMetadatable;
69 import com.sun.star.rdf.Statement;
70 import com.sun.star.rdf.XDocumentRepository;
71 import com.sun.star.rdf.XRepositorySupplier;
72 import org.junit.After;
73 import org.junit.AfterClass;
74 import org.junit.Before;
75 import org.junit.BeforeClass;
76 import org.junit.Test;
77 import org.openoffice.test.OfficeConnection;
78 import static org.junit.Assert.*;
79 
80 import java.util.Map;
81 import java.util.HashMap;
82 import java.util.List;
83 import java.util.ArrayList;
84 import java.util.Stack;
85 
86 
87 class TreeNodeEnum
88 {
89     private Object[] m_Elements;
90     private int m_Pos;
91     TreeNodeEnum(Object[] Elements) { m_Elements = Elements; m_Pos = 0; }
92     boolean hasNext() { return m_Pos < m_Elements.length; }
93     TreeNode next() { return (TreeNode) m_Elements[m_Pos++]; }
94 }
95 
96 /** base class for tree nodes. only instance: root of tree. */
97 class TreeNode
98 {
99     private List<TreeNode> m_Children;
100     String getType() { return "__ROOT__"; }
101     boolean hasContent() { return false; }
102     boolean isNesting() { return false; }
103     TreeNode dup() { throw new RuntimeException("TreeNode.dup"); }
104     TreeNodeEnum createEnumeration() {
105         return new TreeNodeEnum(m_Children.toArray());
106     }
107     TreeNode() { m_Children = new ArrayList<TreeNode>(); }
108     TreeNode appendChild(TreeNode child)
109     { m_Children.add(child); return this; }
110     public String toString() {
111         return "<" + getType() + ">";
112     }
113 }
114 
115 abstract class MarkNode extends TreeNode
116 {
117     boolean m_isPoint;
118     boolean m_isStart = false;
119     String m_Name;
120     boolean isPoint() { return m_isPoint; }
121     boolean isStart() { return m_isStart; }
122     String getName() { return m_Name; }
123     MarkNode(String name) { m_Name = name; m_isPoint = true; }
124     boolean equals(MarkNode other) {
125         return (other.m_Name.equals(m_Name)) && (other.m_isPoint == m_isPoint)
126             && (other.m_isStart == m_isStart);
127     }
128     public String toString() {
129         return super.toString() + "\tname: " + m_Name +
130             "\tisPoint: " + m_isPoint + "\tisStart: " + m_isStart;
131     }
132 }
133 
134 class BookmarkNode extends MarkNode
135 {
136     private StringPair m_XmlId;
137     StringPair getXmlId() { return m_XmlId; }
138     BookmarkNode dup() { return new BookmarkNode(getName(), getXmlId()); }
139     BookmarkNode(String name) { this(name, new StringPair());  }
140     BookmarkNode(String name, StringPair xmlid) {
141         super(name); m_XmlId = xmlid;
142     }
143     String getType() { return "Bookmark"; }
144     public boolean equals(Object other) {
145         return (other instanceof BookmarkNode)
146             && super.equals((MarkNode) other)
147             && MetaNode.eq(((BookmarkNode) other).m_XmlId, m_XmlId);
148     }
149     public String toString() {
150         return super.toString()
151             + "\txmlid: " + m_XmlId.First + "#" + m_XmlId.Second;
152     }
153 }
154 
155 class BookmarkStartNode extends BookmarkNode
156 {
157     BookmarkStartNode dup() { return new BookmarkStartNode(getName()); }
158     BookmarkStartNode(String name) { this(name, new StringPair()); }
159     BookmarkStartNode(String name, StringPair xmlid) {
160         super(name, xmlid); m_isPoint = false; m_isStart = true;
161     }
162 }
163 
164 class BookmarkEndNode extends BookmarkNode
165 {
166     BookmarkEndNode dup() { return new BookmarkEndNode(getName()); }
167     BookmarkEndNode(String name) { this(name, new StringPair()); }
168     BookmarkEndNode(String name, StringPair xmlid) {
169         super(name, xmlid); m_isPoint = false; m_isStart = false;
170     }
171 }
172 
173 class ReferenceMarkNode extends MarkNode
174 {
175     ReferenceMarkNode dup() { return new ReferenceMarkNode(getName()); }
176     ReferenceMarkNode(String name) { super(name); }
177     String getType() { return "ReferenceMark"; }
178     public boolean equals(Object other) {
179         return (other instanceof ReferenceMarkNode)
180             && super.equals((MarkNode) other);
181     }
182 }
183 
184 class ReferenceMarkStartNode extends ReferenceMarkNode
185 {
186     ReferenceMarkStartNode dup()
187     { return new ReferenceMarkStartNode(getName()); }
188     ReferenceMarkStartNode(String name) {
189         super(name); m_isPoint = false; m_isStart = true;
190     }
191 }
192 
193 class ReferenceMarkEndNode extends ReferenceMarkNode
194 {
195     ReferenceMarkEndNode dup()
196     { return new ReferenceMarkEndNode(getName()); }
197     ReferenceMarkEndNode(String name) {
198         super(name); m_isPoint = false; m_isStart = false;
199     }
200 }
201 
202 class DocumentIndexMarkNode extends MarkNode
203 {
204     DocumentIndexMarkNode dup()
205     { return new DocumentIndexMarkNode(getName()); }
206     DocumentIndexMarkNode(String name) { super(name); }
207     String getType() { return "DocumentIndexMark"; }
208     public boolean equals(Object other) {
209         return (other instanceof DocumentIndexMarkNode)
210             && super.equals((MarkNode) other);
211     }
212 }
213 
214 class DocumentIndexMarkStartNode extends DocumentIndexMarkNode
215 {
216     DocumentIndexMarkStartNode dup()
217     { return new DocumentIndexMarkStartNode(getName()); }
218     DocumentIndexMarkStartNode(String name) {
219         super(name); m_isPoint = false; m_isStart = true;
220     }
221 }
222 
223 class DocumentIndexMarkEndNode extends DocumentIndexMarkNode
224 {
225     DocumentIndexMarkEndNode dup()
226     { return new DocumentIndexMarkEndNode(getName()); }
227     DocumentIndexMarkEndNode(String name) {
228         super(name); m_isPoint = false; m_isStart = false;
229     }
230 }
231 
232 abstract class ContentNode extends TreeNode
233 {
234     private String m_Content;
235     String getContent() { return m_Content; }
236     boolean hasContent() { return true; }
237     ContentNode(String content) {
238         m_Content = content;
239     }
240     TreeNode appendChild(TreeNode t) {
241         throw new RuntimeException("ContentNode.appendChild");
242     }
243     public String toString() {
244         return super.toString() + "\tcontent: " + m_Content;
245     }
246     boolean equals(ContentNode other) {
247         return (other.m_Content.equals(m_Content));
248     }
249 }
250 
251 class TextNode extends ContentNode
252 {
253     TextNode dup() { return new TextNode(getContent()); }
254     TextNode(String content) { super(content); }
255     String getType() { return "Text"; }
256     public boolean equals(Object other) {
257         return (other instanceof TextNode) && super.equals((ContentNode) other);
258     }
259 }
260 
261 class TextFieldNode extends ContentNode
262 {
263     TextFieldNode dup() { return new TextFieldNode(getContent()); }
264     TextFieldNode(String content) { super(content); }
265     String getType() { return "TextField"; }
266     public boolean equals(Object other) {
267         return (other instanceof TextFieldNode)
268             && super.equals((ContentNode) other);
269     }
270 }
271 
272 class FrameNode extends TreeNode
273 {
274     private String m_Name;
275     private TextContentAnchorType m_Anchor;
276     String getName() { return m_Name; }
277     TextContentAnchorType getAnchor() { return m_Anchor; }
278     FrameNode dup() { return new FrameNode(getName(), getAnchor()); }
279     FrameNode(String name, TextContentAnchorType anchor) {
280         m_Name = name; m_Anchor = anchor;
281     }
282     String getType() { return "Frame"; }
283     public boolean equals(Object other) {
284         return (other instanceof FrameNode)
285             && (((FrameNode) other).m_Name.equals(m_Name))
286             && (((FrameNode) other).m_Anchor == m_Anchor);
287     }
288     public String toString() {
289         return super.toString()
290             + "\tname: " + m_Name + "\tanchor: " + toString(m_Anchor);
291     }
292     static String toString(TextContentAnchorType anchor) {
293         switch (anchor.getValue()) {
294             case AS_CHARACTER_value: return "AS_CHARACTER";
295             case AT_CHARACTER_value: return "AT_CHARACTER";
296             case AT_PARAGRAPH_value: return "AT_PARAGRAPH";
297             case AT_PAGE_value:      return "AT_PAGE";
298             case AT_FRAME_value:     return "AT_FRAME";
299             default: throw new RuntimeException("unknown anchor");
300         }
301     }
302 }
303 
304 class FootnoteNode extends TreeNode
305 {
306     private String m_Label;
307     String getLabel() { return m_Label; }
308     FootnoteNode dup() { return new FootnoteNode(getLabel()); }
309     FootnoteNode(String label) { m_Label = label; }
310     String getType() { return "Footnote"; }
311     public boolean equals(Object other) {
312         return (other instanceof FootnoteNode)
313             && (((FootnoteNode) other).m_Label.equals(m_Label));
314     }
315     public String toString() {
316         return super.toString() + "\tlabel: " + m_Label;
317     }
318 }
319 
320 class ControlCharacterNode extends TreeNode
321 {
322     private short m_Char;
323     short getChar() { return m_Char; }
324     ControlCharacterNode dup() { return new ControlCharacterNode(getChar()); }
325     ControlCharacterNode(short c) { m_Char = c; }
326     String getType() { return "ControlCharacter"; }
327     public boolean equals(Object other) {
328         return (other instanceof ControlCharacterNode)
329             && (((ControlCharacterNode) other).m_Char == m_Char);
330     }
331     public String toString() {
332         return super.toString() + "\tchar: " + m_Char;
333     }
334 }
335 
336 class SoftPageBreakNode extends TreeNode
337 {
338     String getType() { return "SoftPageBreak"; }
339     public boolean equals(Object other) {
340         return (other instanceof SoftPageBreakNode);
341     }
342 }
343 
344 class HyperlinkNode extends TreeNode
345 {
346     private String m_URL;
347     String getURL() { return m_URL; }
348     HyperlinkNode dup() { return new HyperlinkNode(getURL()); }
349     HyperlinkNode(String url) {
350         if (url.length() == 0) throw new RuntimeException("HyperlinkNode");
351         m_URL = url;
352     }
353     String getType() { return "Hyperlink"; }
354     boolean isNesting() { return true; }
355     public boolean equals(Object other) {
356         return (other instanceof HyperlinkNode)
357             && (((HyperlinkNode) other).m_URL.equals(m_URL));
358     }
359     public String toString() {
360         return super.toString() + "\turl: " + m_URL;
361     }
362 }
363 
364 class RubyNode extends TreeNode
365 {
366     private String m_Ruby;
367     String getRubyText() { return m_Ruby; }
368     RubyNode dup() { return new RubyNode(getRubyText()); }
369     RubyNode(String ruby) {
370         if (ruby.length() == 0) throw new RuntimeException("RubyNode");
371         m_Ruby = ruby;
372     }
373     String getType() { return "Ruby"; }
374     boolean isNesting() { return true; }
375     public boolean equals(Object other) {
376         return (other instanceof RubyNode)
377             && (((RubyNode) other).m_Ruby.equals(m_Ruby));
378     }
379     public String toString() {
380         return super.toString() + "\trubytext: " + m_Ruby;
381     }
382 }
383 
384 class MetaNode extends TreeNode
385 {
386     private StringPair m_XmlId;
387     StringPair getXmlId() { return m_XmlId; }
388     MetaNode dup() { return new MetaNode(getXmlId()); }
389     MetaNode (StringPair xmlid) { m_XmlId = xmlid; }
390     String getType() { return "InContentMetadata"; }
391     boolean isNesting() { return true; }
392     public boolean equals(Object other) {
393         return (other instanceof MetaNode)
394             && eq(((MetaNode) other).m_XmlId, m_XmlId);
395     }
396     static boolean eq(StringPair left, StringPair right)
397     {
398         return left.First.equals(right.First)
399             && left.Second.equals(right.Second);
400     }
401     public String toString() {
402         return super.toString()
403             + "\txmlid: " + m_XmlId.First + "#" + m_XmlId.Second;
404     }
405 }
406 
407 class MetaFieldNode extends MetaNode
408 {
409     MetaFieldNode dup() { return new MetaFieldNode(getXmlId()); }
410     MetaFieldNode (StringPair xmlid) { super(xmlid); }
411     String getType() { return "MetadataField"; }
412 }
413 
414 class Range
415 {
416     private short m_Start;
417     private short m_End;
418     private TreeNode m_Node;
419     short getStart()    { return m_Start; }
420     short getEnd()      { return m_End  ; }
421     short getExtent()   { return (short) (m_End - m_Start); }
422     TreeNode getNode()  { return m_Node; }
423     Range(int start, int end, TreeNode node)
424     { m_Start = (short) start; m_End = (short) end; m_Node = node; }
425 }
426 
427 //----------------------------------------------------------------------
428 
429 /* this is where we nail the pudding to the wall */
430 class FuzzyTester
431 {
432     private long m_DiffContent = 0;
433     private long m_DiffMissing = 0;
434     private long m_DiffNesting = 0;
435     private long m_DiffSpuriousEmptyText = 0;
436     private long m_DiffSequence = 0; // ignored?
437     private Stack<Pair<TreeNode, TreeNodeEnum>> m_StackExpected;
438     private Stack<Pair<TreeNode, TreeNodeEnum>> m_StackActual;
439     private List<TreeNode> m_BufferExpected;
440     private List<TreeNode> m_BufferActual;
441 
442     FuzzyTester() {
443         m_BufferExpected = new ArrayList<TreeNode>();
444         m_BufferActual = new ArrayList<TreeNode>();
445         m_StackExpected = new Stack<Pair<TreeNode, TreeNodeEnum>>();
446         m_StackActual = new Stack<Pair<TreeNode, TreeNodeEnum>>();
447     }
448 
449     /** idea: traverse both trees, enumerate nodes, stopping at content nodes.
450         then compare buffers. */
451     void doTest(TreeNode expected, TreeNode actual)
452     {
453         assertEquals("__ROOT__", expected.getType());
454         assertEquals("__ROOT__", actual.getType());
455         m_StackExpected.push(new Pair(expected, expected.createEnumeration()));
456         m_StackActual.push(new Pair(actual, actual.createEnumeration()));
457         do {
458             traverse(m_StackExpected, m_BufferExpected);
459             traverse(m_StackActual, m_BufferActual);
460         //??? root?
461             testBuffer();
462         } while (!m_StackExpected.empty() || !m_StackActual.empty());
463         if (m_DiffSequence != 0) {
464             System.out.println("warning: " +  m_DiffSequence
465                     + " differences in sequence");
466         }
467         if (m_DiffSpuriousEmptyText != 0) {
468             System.out.println("warning: " +  m_DiffSpuriousEmptyText
469                     + " spurious empty text nodes");
470         }
471         if (m_DiffNesting != 0) {
472             System.out.println("WARNING: " +  m_DiffNesting
473                     + " differences in nesting");
474         }
475         assertEquals(0, m_DiffContent);
476         assertEquals(0, m_DiffMissing);
477     }
478 
479     private void traverse(Stack<Pair<TreeNode, TreeNodeEnum>> stack,
480                   List<TreeNode> buffer)
481     {
482         while (!stack.empty()) {
483             TreeNodeEnum topEnum = stack.peek().Second;
484             if (topEnum.hasNext()) {
485                 TreeNode node = topEnum.next();
486                 buffer.add(node);
487                 TreeNodeEnum nodeEnum = node.createEnumeration();
488                 if (nodeEnum.hasNext()) {
489                     stack.push(new Pair(node, nodeEnum));
490                 }
491                 if (node.hasContent()) {
492                     if (!((node instanceof TextNode) // spurious empty text?
493                         && ((TextNode) node).getContent().length() == 0)) {
494                             return; // break here
495                     }
496                 }
497             } else {
498                 buffer.add(stack.peek().First);
499                 stack.pop();
500             }
501         }
502     }
503 
504     private void testTerminatingNode()
505     {
506         int lenExpected = m_BufferExpected.size();
507         int lenActual   = m_BufferActual.size();
508         if (lenExpected == 0 || lenActual == 0)
509             return;
510         TreeNode expected = m_BufferExpected.get(lenExpected - 1);
511         TreeNode actual = m_BufferActual.get(lenActual - 1);
512 
513         boolean eRoot = expected.getType().equals("__ROOT__");
514         boolean aRoot = actual.getType().equals("__ROOT__");
515         if (eRoot || aRoot) {
516             if (!(eRoot && aRoot)) {
517                 if (aRoot) printMissing(expected);
518                 else       printUnexpected(actual);
519                 m_DiffMissing++;
520             }
521             return;
522         }
523 
524         testContentNode((ContentNode) expected, (ContentNode) actual);
525 
526         m_BufferExpected.set(lenExpected - 1, null);
527         m_BufferActual.set(lenActual - 1, null);
528     }
529 
530     private void testContentNode(ContentNode expected, ContentNode actual)
531     {
532         String contentExpected = expected.getContent();
533         String contentActual = actual.getContent();
534         if (!expected.equals(actual)) {
535             printDiff("text content differs", contentExpected, contentActual);
536             m_DiffContent++;
537         }
538     }
539 
540     private void testBuffer()
541     {
542         int lenExpected = m_BufferExpected.size();
543         int lenActual   = m_BufferActual.size();
544         for (int i = 0; i < lenExpected - 1; i++ )
545         {
546             TreeNode node = m_BufferExpected.get(i);
547             int j = m_BufferActual.indexOf(node);
548             if (j >= 0) {
549                 TreeNode other = m_BufferActual.get(j);
550                 if (j != i)
551                 {
552                     //FIXME how bad is this?
553                     printDiff("position differs",
554                             String.valueOf(i), String.valueOf(j));
555                     // a hacky hack
556                     int min = Math.min(i,j);
557                     int max = Math.max(Math.min(lenActual - 1, i),j);
558                     for (int k = min; k != max; k ++) {
559                         TreeNode tmp = m_BufferActual.get(k);
560                         if (tmp != null && tmp.isNesting()) {
561                             printNesting(node, tmp);
562                             m_DiffNesting++;
563                         }
564                     }
565                     m_DiffSequence++;
566                 }
567                 m_BufferActual.set(j, null);
568             } else {
569 //System.out.println("testBuffer:");
570                 printMissing(node);
571                 m_DiffMissing++;
572             }
573         }
574         for (int j = 0; j < lenActual - 1; j++)
575         {
576             TreeNode node = m_BufferActual.get(j);
577             if (node != null)
578             {
579 //System.out.println("testBuffer:");
580                 printUnexpected(node);
581                 if ((node instanceof TextNode) &&
582                         ((TextNode) node).getContent().length() == 0) {
583                     m_DiffSpuriousEmptyText++;
584                 } else {
585                     m_DiffMissing++;
586                 }
587             }
588         }
589         testTerminatingNode();
590         m_BufferExpected.clear();
591         m_BufferActual.clear();
592     }
593 
594     void printDiff(String prefix, String expected, String actual)
595     {
596         System.out.println(prefix +
597                 ":\texpected: " + expected + "\tactual: " + actual);
598     }
599 
600     void printNesting(TreeNode node, TreeNode nesting)
601     {
602         System.out.println("node: " + node.toString()
603                 + " possibly moved across nesting " + nesting.toString());
604     }
605 
606     void printMissing(TreeNode node)
607     {
608         System.out.println("   missing node: " + node.toString());
609 
610     }
611 
612     void printUnexpected(TreeNode node)
613     {
614         System.out.println("unexpected node: " + node.toString());
615 
616     }
617 }
618 
619 
620 //----------------------------------------------------------------------
621 
622 class EnumConverter
623 {
624     private Stack<TreeNode> m_Stack;
625 
626     EnumConverter() {
627         m_Stack = new Stack<TreeNode>();
628     }
629 
630     TreeNode convert(XEnumeration xEnum) throws Exception
631     {
632         TreeNode root = new TreeNode();
633         m_Stack.push(root);
634         TreeNode ret = convertChildren(xEnum);
635         assertTrue("EnumConverter.convert: stack", m_Stack.empty());
636         return ret;
637     }
638 
639     TreeNode convertChildren(XEnumeration xEnum) throws Exception
640     {
641         while (xEnum.hasMoreElements()) {
642             TreeNode node;
643             Object xElement = xEnum.nextElement();
644             XTextRange xPortion = (XTextRange)
645                 UnoRuntime.queryInterface(XTextRange.class, xElement);
646             XPropertySet xPropSet = (XPropertySet)
647                 UnoRuntime.queryInterface(XPropertySet.class, xPortion);
648             String type = (String) xPropSet.getPropertyValue("TextPortionType");
649             if (type.equals("Text")) {
650                 String text = xPortion.getString();
651                 node = new TextNode(text);
652                 String url = (String) xPropSet.getPropertyValue("HyperLinkURL");
653                 if (url.length() > 0) {
654                     TreeNode temp = node;
655                     node = new HyperlinkNode(url);
656                     node.appendChild(temp);
657                 }
658             } else if (type.equals("TextField")) {
659                 Object xField = xPropSet.getPropertyValue("TextField");
660                 XServiceInfo xService = (XServiceInfo)
661                     UnoRuntime.queryInterface(XServiceInfo.class, xField);
662                 if (xService.supportsService(
663                         "com.sun.star.text.textfield.MetadataField"))
664                 {
665                     XMetadatable xMeta = (XMetadatable)
666                         UnoRuntime.queryInterface(XMetadatable.class, xField);
667                     StringPair xmlid = xMeta.getMetadataReference();
668                     node = new MetaFieldNode(xmlid);
669                     m_Stack.push(node);
670                     XEnumerationAccess xEA = (XEnumerationAccess)
671                         UnoRuntime.queryInterface(XEnumerationAccess.class,
672                         xMeta);
673                     XEnumeration xEnumChildren = xEA.createEnumeration();
674                     TreeNode node2 = convertChildren(xEnumChildren);
675                     assertSame("stack error: meta-field", node2, node);
676                 } else {
677                     XPropertySet xFieldPropSet = (XPropertySet)
678                         UnoRuntime.queryInterface(XPropertySet.class, xField);
679                     String content = (String)
680                         xFieldPropSet.getPropertyValue("Content");
681                     boolean isFixed = (Boolean)
682                         xFieldPropSet.getPropertyValue("IsFixed");
683                     assertTrue("field not fixed?", isFixed);
684                     node = new TextFieldNode(content);
685                 }
686             } else if (type.equals("Footnote")) {
687                 Object xNote = xPropSet.getPropertyValue("Footnote");
688                 XFootnote xFootnote = (XFootnote)
689                     UnoRuntime.queryInterface(XFootnote.class, xNote);
690                 String label = xFootnote.getLabel();
691                 node = new FootnoteNode(label);
692             } else if (type.equals("Frame")) {
693                 XContentEnumerationAccess xCEA = (XContentEnumerationAccess)
694                     UnoRuntime.queryInterface(XContentEnumerationAccess.class,
695                         xPortion);
696                 XEnumeration xContentEnum = xCEA.createContentEnumeration("");
697                 while (xContentEnum.hasMoreElements()) {
698                     Object xFrame = xContentEnum.nextElement();
699                     XPropertySet xFramePropSet = (XPropertySet)
700                         UnoRuntime.queryInterface(XPropertySet.class, xFrame);
701                     TextContentAnchorType anchor = (TextContentAnchorType)
702                         xFramePropSet.getPropertyValue("AnchorType");
703                     XNamed xNamed = (XNamed)
704                         UnoRuntime.queryInterface(XNamed.class, xFrame);
705                     String name = xNamed.getName();
706                     node = new FrameNode(name, anchor);
707                     m_Stack.peek().appendChild(node);
708                 }
709                 continue;
710             } else if (type.equals("ControlCharacter")) {
711                 short c = (Short)
712                     xPropSet.getPropertyValue("ControlCharacter");
713                 node = new ControlCharacterNode(c);
714             } else if (type.equals("Bookmark")) {
715                 Object xMark = xPropSet.getPropertyValue("Bookmark");
716                 XNamed xNamed = (XNamed)
717                     UnoRuntime.queryInterface(XNamed.class, xMark);
718                 String name = xNamed.getName();
719                 XMetadatable xMetadatable = (XMetadatable)
720                     UnoRuntime.queryInterface(XMetadatable.class, xMark);
721                 StringPair xmlid = xMetadatable.getMetadataReference();
722                 boolean isCollapsed = (Boolean)
723                     xPropSet.getPropertyValue("IsCollapsed");
724                 if (isCollapsed) {
725                     node = new BookmarkNode(name, xmlid);
726                 } else {
727                     boolean isStart = (Boolean)
728                         xPropSet.getPropertyValue("IsStart");
729                     if (isStart) {
730                         node = new BookmarkStartNode(name, xmlid);
731                     } else {
732                         node = new BookmarkEndNode(name, xmlid);
733                     }
734                 }
735             } else if (type.equals("ReferenceMark")) {
736                 Object xMark = xPropSet.getPropertyValue("ReferenceMark");
737                 XNamed xNamed = (XNamed)
738                     UnoRuntime.queryInterface(XNamed.class, xMark);
739                 String name = xNamed.getName();
740                 boolean isCollapsed = (Boolean)
741                     xPropSet.getPropertyValue("IsCollapsed");
742                 if (isCollapsed) {
743                     node = new ReferenceMarkNode(name);
744                 } else {
745                     boolean isStart = (Boolean)
746                         xPropSet.getPropertyValue("IsStart");
747                     if (isStart) {
748                         node = new ReferenceMarkStartNode(name);
749                     } else {
750                         node = new ReferenceMarkEndNode(name);
751                     }
752                 }
753             } else if (type.equals("DocumentIndexMark")) {
754                 Object xMark = xPropSet.getPropertyValue("DocumentIndexMark");
755                 XPropertySet xMarkSet = (XPropertySet)
756                     UnoRuntime.queryInterface(XPropertySet.class, xMark);
757                 String name = (String) xMarkSet.getPropertyValue("PrimaryKey");
758                 boolean isCollapsed = (Boolean)
759                     xPropSet.getPropertyValue("IsCollapsed");
760                 if (isCollapsed) {
761                     node = new DocumentIndexMarkNode(name);
762                 } else {
763                     boolean isStart = (Boolean)
764                         xPropSet.getPropertyValue("IsStart");
765                     if (isStart) {
766                         node = new DocumentIndexMarkStartNode(name);
767                     } else {
768                         node = new DocumentIndexMarkEndNode(name);
769                     }
770                 }
771             } else if (type.equals("SoftPageBreak")) {
772                 node = new SoftPageBreakNode();
773             } else if (type.equals("Ruby")) {
774                 boolean isStart = (Boolean)
775                     xPropSet.getPropertyValue("IsStart");
776                 if (isStart) {
777                     // ARRGH!!! stupid api...
778                     // the text is ONLY at the start!
779                     String ruby = (String)
780                         xPropSet.getPropertyValue("RubyText");
781                     node = new RubyNode(ruby);
782                     m_Stack.push(node);
783                     continue;
784                 } else {
785                     node = m_Stack.pop();
786                     assertTrue("stack error: Ruby expected; is: " +
787                                node.toString(), node instanceof RubyNode);
788                 }
789             } else if (type.equals("InContentMetadata")) {
790                 Object xMeta = xPropSet.getPropertyValue("InContentMetadata");
791                 XMetadatable xMetadatable = (XMetadatable)
792                     UnoRuntime.queryInterface(XMetadatable.class, xMeta);
793                 StringPair xmlid = xMetadatable.getMetadataReference();
794                 node = new MetaNode(xmlid);
795                 m_Stack.push(node);
796                 XEnumerationAccess xEA = (XEnumerationAccess)
797                     UnoRuntime.queryInterface(XEnumerationAccess.class, xMeta);
798                 XEnumeration xEnumChildren = xEA.createEnumeration();
799                 TreeNode node2 = convertChildren(xEnumChildren);
800                 assertSame("stack error: meta", node2, node);
801             } else {
802                 throw new RuntimeException("unexpected type: " + type);
803             }
804             m_Stack.peek().appendChild(node);
805         }
806         TreeNode ret = m_Stack.pop();
807         return ret;
808     }
809 }
810 
811 
812 //----------------------------------------------------------------------
813 
814 abstract class Inserter
815 {
816     private XMultiServiceFactory m_xDocFactory;
817     XText m_xText;
818     XParagraphCursor m_xCursor;
819 
820     Inserter(XTextDocument xDoc)
821     {
822         m_xDocFactory = (XMultiServiceFactory)
823             UnoRuntime.queryInterface(XMultiServiceFactory.class, xDoc);
824         m_xText = xDoc.getText();
825         XTextCursor xCursor = m_xText.createTextCursor();
826         m_xCursor = (XParagraphCursor)
827             UnoRuntime.queryInterface(XParagraphCursor.class, xCursor);
828     }
829 
830     void initParagraph() throws Exception
831     {
832         m_xCursor.gotoStartOfParagraph(false);
833         m_xText.insertControlCharacter(m_xCursor, PARAGRAPH_BREAK, false);
834         // we split the first (empty) paragraph, and then insert into the
835         // second (empty) paragraph; this ensures first is always empty!
836     }
837 
838     void insertControlCharacter(XTextCursor xCursor, short cchar)
839         throws Exception
840     {
841         m_xText.insertControlCharacter(xCursor, cchar, false);
842     }
843 
844     void insertText(XTextCursor xCursor, String text)
845     {
846         xCursor.setString(text);
847     }
848 
849     void insertTextField(XTextCursor xCursor, String content) throws Exception
850     {
851         XTextContent xContent = makeTextField(content);
852         xContent.attach(xCursor);
853     }
854 
855     XTextContent makeTextField(String content)
856         throws Exception
857     {
858         Object xField =
859             m_xDocFactory.createInstance("com.sun.star.text.textfield.Author");
860         XTextContent xContent = (XTextContent)
861             UnoRuntime.queryInterface(XTextContent.class, xField);
862         XPropertySet xPropSet = (XPropertySet)
863             UnoRuntime.queryInterface(XPropertySet.class, xField);
864         xPropSet.setPropertyValue("IsFixed", true);
865         xPropSet.setPropertyValue("FullName", false);
866         xPropSet.setPropertyValue("Content", content);
867         return xContent;
868     }
869 
870     void insertFrame(XTextRange xCursor, String name,
871             TextContentAnchorType anchor) throws Exception
872     {
873         XTextContent xContent = makeFrame(name, anchor);
874         xContent.attach(xCursor);
875     }
876 
877     XTextContent makeFrame(String name, TextContentAnchorType anchor)
878         throws Exception
879     {
880         Object xFrame =
881             m_xDocFactory.createInstance("com.sun.star.text.TextFrame");
882         XTextContent xContent = (XTextContent)
883             UnoRuntime.queryInterface(XTextContent.class, xFrame);
884         XPropertySet xPropSet = (XPropertySet)
885             UnoRuntime.queryInterface(XPropertySet.class, xFrame);
886         xPropSet.setPropertyValue("AnchorType", anchor);
887         XNamed xNamed = (XNamed)
888             UnoRuntime.queryInterface(XNamed.class, xContent);
889         xNamed.setName(name);
890         return xContent;
891     }
892 
893     void insertFootnote(XTextCursor xCursor, String label) throws Exception
894     {
895         XTextContent xContent = makeFootnote(label);
896         xContent.attach(xCursor);
897     }
898 
899     XTextContent makeFootnote(String label) throws Exception
900     {
901         Object xNote =
902             m_xDocFactory.createInstance("com.sun.star.text.Footnote");
903         XTextContent xContent = (XTextContent)
904             UnoRuntime.queryInterface(XTextContent.class, xNote);
905         XFootnote xFootnote = (XFootnote)
906             UnoRuntime.queryInterface(XFootnote.class, xNote);
907         xFootnote.setLabel(label);
908         return xContent;
909     }
910 
911     void insertBookmark(XTextCursor xCursor, String name, StringPair xmlid)
912         throws Exception
913     {
914         XTextContent xContent = makeBookmark(name);
915         xContent.attach(xCursor);
916         if (!xmlid.First.equals(""))
917         {
918             XMetadatable xMetadatable = (XMetadatable)
919                 UnoRuntime.queryInterface(XMetadatable.class, xContent);
920             xMetadatable.setMetadataReference(xmlid);
921         }
922     }
923 
924     XTextContent makeBookmark(String name) throws Exception
925     {
926         Object xBookmark =
927             m_xDocFactory.createInstance("com.sun.star.text.Bookmark");
928         XTextContent xContent = (XTextContent)
929             UnoRuntime.queryInterface(XTextContent.class, xBookmark);
930         XNamed xNamed = (XNamed)
931             UnoRuntime.queryInterface(XNamed.class, xContent);
932         xNamed.setName(name);
933         return xContent;
934     }
935 
936     void insertReferenceMark(XTextCursor xCursor, String name) throws Exception
937     {
938         XTextContent xContent = makeReferenceMark(name);
939         xContent.attach(xCursor);
940     }
941 
942     XTextContent makeReferenceMark(String name) throws Exception
943     {
944         Object xMark =
945             m_xDocFactory.createInstance("com.sun.star.text.ReferenceMark");
946         XTextContent xContent = (XTextContent)
947             UnoRuntime.queryInterface(XTextContent.class, xMark);
948         XNamed xNamed = (XNamed)
949             UnoRuntime.queryInterface(XNamed.class, xContent);
950         xNamed.setName(name);
951         return xContent;
952     }
953 
954     void insertDocumentIndexMark(XTextCursor xCursor, String key)
955         throws Exception
956     {
957         XTextContent xContent = makeDocumentIndexMark(key);
958         xContent.attach(xCursor);
959     }
960 
961     XTextContent makeDocumentIndexMark(String key) throws Exception
962     {
963         Object xMark =
964             m_xDocFactory.createInstance("com.sun.star.text.DocumentIndexMark");
965         XTextContent xContent = (XTextContent)
966             UnoRuntime.queryInterface(XTextContent.class, xMark);
967         XPropertySet xPropSet = (XPropertySet)
968             UnoRuntime.queryInterface(XPropertySet.class, xMark);
969         xPropSet.setPropertyValue("PrimaryKey", key);
970         return xContent;
971     }
972 
973     void insertHyperlink(XTextCursor xCursor, String url) throws Exception
974     {
975         XPropertySet xPropSet = (XPropertySet)
976             UnoRuntime.queryInterface(XPropertySet.class, xCursor);
977         xPropSet.setPropertyValue("HyperLinkURL", url);
978     }
979 
980     void insertRuby(XTextCursor xCursor, String rubytext) throws Exception
981     {
982         XPropertySet xPropSet = (XPropertySet)
983             UnoRuntime.queryInterface(XPropertySet.class, xCursor);
984         xPropSet.setPropertyValue("RubyText", rubytext);
985     }
986 
987     XTextContent insertMeta(XTextCursor xCursor, StringPair xmlid)
988         throws Exception
989     {
990         XTextContent xContent = makeMeta();
991         xContent.attach(xCursor);
992         XMetadatable xMetadatable = (XMetadatable)
993             UnoRuntime.queryInterface(XMetadatable.class, xContent);
994         xMetadatable.setMetadataReference(xmlid);
995         return xContent;
996     }
997 
998     XTextContent makeMeta() throws Exception
999     {
1000         Object xMeta = m_xDocFactory.createInstance(
1001                 "com.sun.star.text.InContentMetadata");
1002         XTextContent xContent = (XTextContent)
1003             UnoRuntime.queryInterface(XTextContent.class, xMeta);
1004         return xContent;
1005     }
1006 
1007     XTextField insertMetaField(XTextCursor xCursor, StringPair xmlid)
1008         throws Exception
1009     {
1010         XTextField xContent = makeMetaField();
1011         xContent.attach(xCursor);
1012         XMetadatable xMetadatable = (XMetadatable)
1013             UnoRuntime.queryInterface(XMetadatable.class, xContent);
1014         xMetadatable.setMetadataReference(xmlid);
1015         return xContent;
1016     }
1017 
1018     XTextField makeMetaField() throws Exception
1019     {
1020         Object xMeta = m_xDocFactory.createInstance(
1021                 "com.sun.star.text.textfield.MetadataField");
1022         XTextField xContent = (XTextField)
1023             UnoRuntime.queryInterface(XTextField.class, xMeta);
1024         return xContent;
1025     }
1026 
1027 }
1028 
1029 class TreeInserter extends Inserter
1030 {
1031     private Map<String, XTextRange> m_BookmarkStarts;
1032     private Map<String, XTextRange> m_ReferenceMarkStarts;
1033     private Map<String, XTextRange> m_DocumentIndexMarkStarts;
1034     private List<Pair<XTextRange, FrameNode>> m_FrameHints;
1035 
1036     TreeInserter(XTextDocument xDoc)
1037     {
1038         super(xDoc);
1039         m_BookmarkStarts = new HashMap<String, XTextRange>();
1040         m_ReferenceMarkStarts = new HashMap<String, XTextRange>();
1041         m_DocumentIndexMarkStarts = new HashMap<String, XTextRange>();
1042         m_FrameHints = new ArrayList<Pair<XTextRange, FrameNode>>();
1043     }
1044 
1045     void insertTree(TreeNode tree) throws Exception
1046     {
1047         if (!tree.getType().equals("__ROOT__"))
1048             throw new RuntimeException("insertTree: test error: no root");
1049         initParagraph();
1050         insertChildren(tree.createEnumeration());
1051         for (int i = 0; i < m_FrameHints.size(); ++i) {
1052             Pair<XTextRange, FrameNode> p = m_FrameHints.get(i);
1053             insertFrame(p.First, p.Second.getName(), p.Second.getAnchor());
1054         }
1055     }
1056 
1057     void insertChildren(TreeNodeEnum children) throws Exception
1058     {
1059         while (children.hasNext()) {
1060             m_xCursor.gotoEndOfParagraph(false);
1061             TreeNode node = children.next();
1062             String type = node.getType();
1063             if (type.equals("Bookmark")) {
1064                 BookmarkNode bkmk = (BookmarkNode) node;
1065                 String name = bkmk.getName();
1066                 StringPair id = bkmk.getXmlId();
1067                 if (bkmk.isPoint()) {
1068                     insertBookmark(m_xCursor, name, id);
1069                 } else if (bkmk.isStart()) {
1070                     m_BookmarkStarts.put(name, m_xCursor.getStart());
1071                 } else {
1072                     XTextRange xRange = m_BookmarkStarts.get(name);
1073                     XParagraphCursor xCursor = mkCursor(xRange);
1074                     insertBookmark(xCursor, name, id);
1075                 }
1076             } else if (type.equals("ReferenceMark")) {
1077                 ReferenceMarkNode mark = (ReferenceMarkNode) node;
1078                 String name = mark.getName();
1079                 if (mark.isPoint()) {
1080                     insertReferenceMark(m_xCursor, name);
1081                 } else if (mark.isStart()) {
1082                     m_ReferenceMarkStarts.put(name, m_xCursor.getStart());
1083                 } else {
1084                     XTextRange xRange = m_ReferenceMarkStarts.get(name);
1085                     XParagraphCursor xCursor = mkCursor(xRange);
1086                     insertReferenceMark(xCursor, name);
1087                 }
1088             } else if (type.equals("DocumentIndexMark")) {
1089                 DocumentIndexMarkNode mark = (DocumentIndexMarkNode) node;
1090                 String name = mark.getName();
1091                 if (mark.isPoint()) {
1092                     insertDocumentIndexMark(m_xCursor, name);
1093                 } else if (mark.isStart()) {
1094                     m_DocumentIndexMarkStarts.put(name, m_xCursor.getStart());
1095                 } else {
1096                     XTextRange xRange = m_DocumentIndexMarkStarts.get(name);
1097                     XParagraphCursor xCursor = mkCursor(xRange);
1098                     insertDocumentIndexMark(xCursor, name);
1099                 }
1100             } else if (type.equals("Hyperlink")) {
1101                 HyperlinkNode href = (HyperlinkNode) node;
1102                 XTextRange xRange = m_xCursor.getStart();
1103                 insertChildren(href.createEnumeration());
1104                 XParagraphCursor xCursor = mkCursor(xRange);
1105                 insertHyperlink(xCursor, href.getURL());
1106             } else if (type.equals("Ruby")) {
1107                 RubyNode ruby = (RubyNode) node;
1108                 XTextRange xRange = m_xCursor.getStart();
1109                 insertChildren(ruby.createEnumeration());
1110                 XParagraphCursor xCursor = mkCursor(xRange);
1111                 insertRuby(xCursor, ruby.getRubyText());
1112             } else if (type.equals("InContentMetadata")) {
1113                 MetaNode meta = (MetaNode) node;
1114                 XTextRange xRange = m_xCursor.getStart();
1115                 insertChildren(meta.createEnumeration());
1116                 XParagraphCursor xCursor = mkCursor(xRange);
1117                 insertMeta(xCursor, meta.getXmlId());
1118             } else if (type.equals("MetadataField")) {
1119                 MetaFieldNode meta = (MetaFieldNode) node;
1120                 XTextRange xRange = m_xCursor.getStart();
1121                 insertChildren(meta.createEnumeration());
1122                 XParagraphCursor xCursor = mkCursor(xRange);
1123                 insertMetaField(xCursor, meta.getXmlId());
1124             } else if (type.equals("Text")) {
1125                 TextNode text = (TextNode) node;
1126                 insertText(m_xCursor, text.getContent());
1127             } else if (type.equals("TextField")) {
1128                 TextFieldNode field = (TextFieldNode) node;
1129                 insertTextField(m_xCursor, field.getContent());
1130             } else if (type.equals("Footnote")) {
1131                 FootnoteNode note = (FootnoteNode) node;
1132                 insertFootnote(m_xCursor, note.getLabel());
1133             } else if (type.equals("Frame")) {
1134                 FrameNode frame = (FrameNode) node;
1135                 if (frame.getAnchor() == AT_CHARACTER) {
1136                     m_FrameHints.add( new Pair(m_xCursor.getStart(), frame) );
1137                 } else {
1138                     insertFrame(m_xCursor, frame.getName(), frame.getAnchor());
1139                 }
1140             } else if (type.equals("ControlCharacter")) {
1141                 ControlCharacterNode cchar = (ControlCharacterNode) node;
1142                 insertControlCharacter(m_xCursor, cchar.getChar());
1143             } else if (type.equals("SoftPageBreak")) {
1144                 SoftPageBreakNode spbk = (SoftPageBreakNode) node;
1145                 throw new RuntimeException("sorry, cannot test SoftPageBreak");
1146             } else {
1147                 throw new RuntimeException("unexpected type: " + type);
1148             }
1149         }
1150     }
1151 
1152     XParagraphCursor mkCursor(XTextRange xRange)
1153     {
1154         XTextCursor xCursor = m_xText.createTextCursorByRange(xRange);
1155         XParagraphCursor xParaCursor = (XParagraphCursor)
1156             UnoRuntime.queryInterface(XParagraphCursor.class, xCursor);
1157         xParaCursor.gotoEndOfParagraph(true);
1158         return xParaCursor;
1159     }
1160 }
1161 
1162 
1163 // FIXME: this does not account for inserted dummy characters!
1164 class RangeInserter extends Inserter
1165 {
1166     RangeInserter(XTextDocument xDoc) throws Exception
1167     { super(xDoc); initParagraph(); }
1168 
1169     /*
1170     void insertText(int pos, String text)
1171     {
1172         m_xCursor.gotoStartOfParagraph(false);
1173         m_xCursor.goRight((short) pos, false);
1174         insertText(m_xCursor, text);
1175     }
1176     */
1177 
1178     XTextContent insertRange(Range range) throws Exception
1179     {
1180         m_xCursor.gotoStartOfParagraph(false);
1181         m_xCursor.goRight(range.getStart(), false);
1182         m_xCursor.goRight(range.getExtent(), true);
1183         return insertNode(m_xCursor, range.getNode());
1184     }
1185 
1186     XTextContent insertNode(XParagraphCursor xCursor, TreeNode node)
1187         throws Exception
1188     {
1189         String type = node.getType();
1190         if (type.equals("Bookmark")) {
1191             BookmarkNode bkmk = (BookmarkNode) node;
1192             if (bkmk.isPoint()) throw new RuntimeException("range only");
1193             insertBookmark(xCursor, bkmk.getName(), bkmk.getXmlId());
1194         } else if (type.equals("ReferenceMark")) {
1195             ReferenceMarkNode mark = (ReferenceMarkNode) node;
1196             if (mark.isPoint()) throw new RuntimeException("range only");
1197             insertReferenceMark(xCursor, mark.getName());
1198         } else if (type.equals("DocumentIndexMark")) {
1199             DocumentIndexMarkNode mark = (DocumentIndexMarkNode) node;
1200             if (mark.isPoint()) throw new RuntimeException("range only");
1201             insertDocumentIndexMark(xCursor, mark.getName());
1202         } else if (type.equals("Hyperlink")) {
1203             HyperlinkNode href = (HyperlinkNode) node;
1204             insertHyperlink(xCursor, href.getURL());
1205         } else if (type.equals("Ruby")) {
1206             RubyNode ruby = (RubyNode) node;
1207             insertRuby(xCursor, ruby.getRubyText());
1208         } else if (type.equals("InContentMetadata")) {
1209             MetaNode meta = (MetaNode) node;
1210             return insertMeta(xCursor, meta.getXmlId());
1211         } else if (type.equals("MetadataField")) {
1212             MetaFieldNode meta = (MetaFieldNode) node;
1213             return insertMetaField(xCursor, meta.getXmlId());
1214         } else if (type.equals("Text")) {
1215             TextNode text = (TextNode) node;
1216             insertText(xCursor, text.getContent());
1217         } else if (type.equals("TextField")) {
1218             TextFieldNode field = (TextFieldNode) node;
1219             insertTextField(m_xCursor, field.getContent());
1220         } else if (type.equals("Footnote")) {
1221             FootnoteNode note = (FootnoteNode) node;
1222             insertFootnote(m_xCursor, note.getLabel());
1223         } else if (type.equals("Frame")) {
1224             FrameNode frame = (FrameNode) node;
1225             insertFrame(xCursor, frame.getName(), frame.getAnchor());
1226         } else if (type.equals("ControlCharacter")) {
1227             ControlCharacterNode cchar = (ControlCharacterNode) node;
1228             insertControlCharacter(m_xCursor, cchar.getChar());
1229         } else if (type.equals("SoftPageBreak")) {
1230             throw new RuntimeException("sorry, cannot test SoftPageBreak");
1231         } else {
1232             throw new RuntimeException("unexpected type: " + type);
1233         }
1234         return null;
1235     }
1236 }
1237 
1238 
1239 //----------------------------------------------------------------------
1240 
1241 public class TextPortionEnumerationTest
1242 {
1243     private XMultiServiceFactory m_xMSF = null;
1244     private XComponentContext m_xContext = null;
1245     private XTextDocument m_xDoc = null;
1246     private String m_TmpDir = null;
1247 
1248     private int m_Count = 1;
1249 
1250     @Before public void before() throws Exception
1251     {
1252         m_xMSF = UnoRuntime.queryInterface(
1253             XMultiServiceFactory.class,
1254             connection.getComponentContext().getServiceManager());
1255         XPropertySet xPropertySet = (XPropertySet)
1256             UnoRuntime.queryInterface(XPropertySet.class, m_xMSF);
1257         Object defaultCtx = xPropertySet.getPropertyValue("DefaultContext");
1258         m_xContext = (XComponentContext)
1259             UnoRuntime.queryInterface(XComponentContext.class, defaultCtx);
1260         assertNotNull("could not get component context.", m_xContext);
1261         m_xDoc = util.WriterTools.createTextDoc(m_xMSF);
1262         m_TmpDir = util.utils.getOfficeTemp/*Dir*/(m_xMSF);
1263         System.out.println("tempdir: " + m_TmpDir);
1264     }
1265 
1266     @After public void after()
1267     {
1268         util.DesktopTools.closeDoc(m_xDoc);
1269     }
1270 
1271     @Test public void testText() throws Exception
1272     {
1273         TreeNode root = new TreeNode();
1274         TreeNode text = new TextNode("abc");
1275         root.appendChild(text);
1276         doTest(root);
1277     }
1278 
1279     @Test public void testTextField() throws Exception
1280     {
1281         String name = mkName("ruby");
1282         TreeNode root = new TreeNode();
1283         TreeNode txtf = new TextFieldNode("abc");
1284         root.appendChild(txtf);
1285         doTest(root);
1286     }
1287 
1288     /*@Test*/ public void testControlChar() throws Exception
1289     {
1290 //FIXME this is converted to a text portion: ControlCharacter is obsolete
1291         TreeNode root = new TreeNode();
1292         TreeNode cchr = new ControlCharacterNode(HARD_HYPHEN);
1293         root.appendChild(cchr);
1294         doTest(root);
1295     }
1296 
1297     /*@Test*/ public void testSoftPageBreak() throws Exception
1298     {
1299 //FIXME: insert a soft page break: not done
1300         TreeNode root = new TreeNode();
1301         TreeNode spbk = new SoftPageBreakNode();
1302         TreeNode text = new TextNode("abc");
1303         root.appendChild(spbk);
1304         root.appendChild(text);
1305         doTest(root);
1306     }
1307 
1308     @Test public void testFootnote() throws Exception
1309     {
1310         String name = mkName("ftn");
1311         TreeNode root = new TreeNode();
1312         TreeNode ftnd = new FootnoteNode(name);
1313         root.appendChild(ftnd);
1314         doTest(root);
1315     }
1316 
1317     @Test public void testFrameAs() throws Exception
1318     {
1319         String name = mkName("frame");
1320         TreeNode root = new TreeNode();
1321         TreeNode fram = new FrameNode(name, AS_CHARACTER);
1322         root.appendChild(fram);
1323         doTest(root);
1324     }
1325 
1326     @Test public void testFrameAt() throws Exception
1327     {
1328         String name = mkName("frame");
1329         TreeNode root = new TreeNode();
1330 //        TreeNode text = new TextNode(""); // necessary?
1331         TreeNode fram = new FrameNode(name, AT_CHARACTER);
1332 //        root.appendChild(text);
1333         root.appendChild(fram);
1334         doTest(root);
1335     }
1336 
1337     @Test public void testBookmarkPoint() throws Exception
1338     {
1339         String name = mkName("mark");
1340         TreeNode root = new TreeNode();
1341         TreeNode bkmk = new BookmarkNode(name);
1342         TreeNode text = new TextNode("abc");
1343         root.appendChild(bkmk);
1344         root.appendChild(text);
1345         doTest(root);
1346     }
1347 
1348     @Test public void testBookmark() throws Exception
1349     {
1350         String name = mkName("mark");
1351         TreeNode root = new TreeNode();
1352         TreeNode bkm1 = new BookmarkStartNode(name);
1353         TreeNode text = new TextNode("abc");
1354         TreeNode bkm2 = new BookmarkEndNode(name);
1355         root.appendChild(bkm1);
1356         root.appendChild(text);
1357         root.appendChild(bkm2);
1358         doTest(root);
1359     }
1360 
1361     @Test public void testBookmarkPointXmlId() throws Exception
1362     {
1363         String name = mkName("mark");
1364         StringPair id = mkId("id");
1365         TreeNode root = new TreeNode();
1366         TreeNode bkmk = new BookmarkNode(name, id);
1367         TreeNode text = new TextNode("abc");
1368         root.appendChild(bkmk);
1369         root.appendChild(text);
1370         doTest(root);
1371     }
1372 
1373     @Test public void testBookmarkXmlId() throws Exception
1374     {
1375         String name = mkName("mark");
1376         StringPair id = mkId("id");
1377         TreeNode root = new TreeNode();
1378         TreeNode bkm1 = new BookmarkStartNode(name, id);
1379         TreeNode text = new TextNode("abc");
1380         TreeNode bkm2 = new BookmarkEndNode(name, id);
1381         root.appendChild(bkm1);
1382         root.appendChild(text);
1383         root.appendChild(bkm2);
1384         doTest(root);
1385     }
1386 
1387     @Test public void testRefmarkPoint() throws Exception
1388     {
1389         String name = mkName("refmark");
1390         TreeNode root = new TreeNode();
1391         TreeNode rfmk = new ReferenceMarkNode(name);
1392         TreeNode text = new TextNode("abc");
1393         root.appendChild(rfmk);
1394         root.appendChild(text);
1395         doTest(root);
1396     }
1397 
1398     @Test public void testRefmark() throws Exception
1399     {
1400         String name = mkName("refmark");
1401         TreeNode root = new TreeNode();
1402         TreeNode rfm1 = new ReferenceMarkStartNode(name);
1403         TreeNode text = new TextNode("abc");
1404         TreeNode rfm2 = new ReferenceMarkEndNode(name);
1405         root.appendChild(rfm1);
1406         root.appendChild(text);
1407         root.appendChild(rfm2);
1408         doTest(root);
1409     }
1410 
1411     @Test public void testToxmarkPoint() throws Exception
1412     {
1413         String name = mkName("toxmark");
1414         TreeNode root = new TreeNode();
1415         TreeNode txmk = new DocumentIndexMarkNode(name);
1416         TreeNode text = new TextNode("abc");
1417         root.appendChild(txmk);
1418         root.appendChild(text);
1419         doTest(root);
1420     }
1421 
1422     @Test public void testToxmark() throws Exception
1423     {
1424         String name = mkName("toxmark");
1425         TreeNode root = new TreeNode();
1426         TreeNode txm1 = new DocumentIndexMarkStartNode(name);
1427         TreeNode text = new TextNode("abc");
1428         TreeNode txm2 = new DocumentIndexMarkEndNode(name);
1429         root.appendChild(txm1);
1430         root.appendChild(text);
1431         root.appendChild(txm2);
1432         doTest(root);
1433     }
1434 
1435     @Test public void testHyperlink() throws Exception
1436     {
1437         String name = mkName("url");
1438         TreeNode root = new TreeNode();
1439         TreeNode href = new HyperlinkNode(name);
1440         TreeNode text = new TextNode("abc");
1441         href.appendChild(text);
1442         root.appendChild(href);
1443         doTest(root);
1444     }
1445 
1446     @Test public void testHyperlinkEmpty() throws Exception
1447     {
1448         String name = mkName("url");
1449         TreeNode root = new TreeNode();
1450         TreeNode href = new HyperlinkNode(name);
1451         TreeNode text = new TextNode("");
1452         href.appendChild(text);
1453         root.appendChild(href);
1454         doTest(root);
1455     }
1456 
1457     @Test public void testRuby() throws Exception
1458     {
1459         String name = mkName("ruby");
1460         TreeNode root = new TreeNode();
1461         TreeNode ruby = new RubyNode(name);
1462         TreeNode text = new TextNode("abc");
1463         ruby.appendChild(text);
1464         root.appendChild(ruby);
1465         doTest(root);
1466     }
1467 
1468     @Test public void testRubyEmpty() throws Exception
1469     {
1470         // BUG: #i91534#
1471         String name = mkName("ruby");
1472         TreeNode root = new TreeNode();
1473         TreeNode ruby = new RubyNode(name);
1474         root.appendChild(ruby);
1475         doTest(root);
1476     }
1477 
1478     @Test public void testMeta() throws Exception
1479     {
1480         StringPair id = new StringPair("content.xml", mkName("id"));
1481         TreeNode root = new TreeNode();
1482         TreeNode meta = new MetaNode(id);
1483         TreeNode text = new TextNode("abc");
1484         root.appendChild(new TextNode("123"));
1485         meta.appendChild(text);
1486         root.appendChild(meta);
1487         doTest(root);
1488     }
1489 
1490     @Test public void testMetaEmpty() throws Exception
1491     {
1492         StringPair id = new StringPair("content.xml", mkName("id"));
1493         TreeNode root = new TreeNode();
1494         TreeNode meta = new MetaNode(id);
1495 //        TreeNode text = new TextNode("");
1496 //        meta.appendChild(text);
1497         root.appendChild(meta);
1498         doTest(root);
1499     }
1500 
1501     @Test public void testMetaField() throws Exception
1502     {
1503         StringPair id = new StringPair("content.xml", mkName("id"));
1504         TreeNode root = new TreeNode();
1505         TreeNode meta = new MetaFieldNode(id);
1506         TreeNode text = new TextNode("abc");
1507         root.appendChild(new TextNode("123"));
1508         meta.appendChild(text);
1509         root.appendChild(meta);
1510         doTest(root);
1511     }
1512 
1513     @Test public void testMetaFieldEmpty() throws Exception
1514     {
1515         StringPair id = new StringPair("content.xml", mkName("id"));
1516         TreeNode root = new TreeNode();
1517         TreeNode meta = new MetaFieldNode(id);
1518 //        TreeNode text = new TextNode("");
1519 //        meta.appendChild(text);
1520         root.appendChild(meta);
1521         doTest(root);
1522     }
1523 
1524     @Test public void testBookmark1() throws Exception
1525     {
1526         String name1 = mkName("mark");
1527         String name2 = mkName("mark");
1528         String name3 = mkName("mark");
1529         TreeNode root = new TreeNode();
1530         root.appendChild( new BookmarkStartNode(name1) );
1531         root.appendChild( new BookmarkNode(name2) );
1532         root.appendChild( new BookmarkStartNode(name3) );
1533         root.appendChild( new TextNode("abc") );
1534         root.appendChild( new BookmarkEndNode(name1) );
1535         root.appendChild( new TextNode("de") );
1536         root.appendChild( new BookmarkEndNode(name3) );
1537         doTest(root);
1538     }
1539 
1540     @Test public void testBookmark2() throws Exception
1541     {
1542         String name1 = mkName("mark");
1543         String name2 = mkName("mark");
1544         String name3 = mkName("mark");
1545         TreeNode root = new TreeNode();
1546         root.appendChild( new BookmarkStartNode(name1) );
1547         root.appendChild( new TextNode("abc") );
1548         root.appendChild( new BookmarkNode(name2) );
1549         root.appendChild( new BookmarkStartNode(name3) );
1550         root.appendChild( new BookmarkEndNode(name1) );
1551         root.appendChild( new TextNode("de") );
1552         root.appendChild( new BookmarkEndNode(name3) );
1553         doTest(root);
1554     }
1555 
1556     @Test public void testRefMark2() throws Exception
1557     {
1558         String name1 = mkName("refmark");
1559         TreeNode root = new TreeNode();
1560         root.appendChild( new ReferenceMarkStartNode(name1) );
1561         root.appendChild( new TextNode("abc") );
1562         // BUG: #i102541# (this is actually not unoportenum's fault)
1563         root.appendChild( new ReferenceMarkEndNode(name1) );
1564         root.appendChild( new TextNode("de") );
1565         doTest(root);
1566     }
1567 
1568     @Test public void testRefMark3() throws Exception
1569     {
1570         // BUG: #i107672# (non-deterministic; depends on pointer ordering)
1571         String name1 = mkName("refmark");
1572         String name2 = mkName("refmark");
1573         String name3 = mkName("refmark");
1574         String name4 = mkName("refmark");
1575         String name5 = mkName("refmark");
1576         String name6 = mkName("refmark");
1577         String name7 = mkName("refmark");
1578         TreeNode root = new TreeNode();
1579         root.appendChild( new ReferenceMarkStartNode(name1) );
1580         root.appendChild( new ReferenceMarkStartNode(name2) );
1581         root.appendChild( new ReferenceMarkStartNode(name3) );
1582         root.appendChild( new ReferenceMarkStartNode(name4) );
1583         root.appendChild( new ReferenceMarkStartNode(name5) );
1584         root.appendChild( new ReferenceMarkStartNode(name6) );
1585         root.appendChild( new ReferenceMarkStartNode(name7) );
1586         root.appendChild( new TextNode("abc") );
1587         root.appendChild( new ReferenceMarkEndNode(name7) );
1588         root.appendChild( new ReferenceMarkEndNode(name6) );
1589         root.appendChild( new ReferenceMarkEndNode(name5) );
1590         root.appendChild( new ReferenceMarkEndNode(name4) );
1591         root.appendChild( new ReferenceMarkEndNode(name3) );
1592         root.appendChild( new ReferenceMarkEndNode(name2) );
1593         root.appendChild( new ReferenceMarkEndNode(name1) );
1594         root.appendChild( new TextNode("de") );
1595         doTest(root);
1596     }
1597 
1598     @Test public void testToxMark2() throws Exception
1599     {
1600         String name1 = mkName("toxmark");
1601         TreeNode root = new TreeNode();
1602         root.appendChild( new DocumentIndexMarkStartNode(name1) );
1603         root.appendChild( new TextNode("abc") );
1604         root.appendChild( new DocumentIndexMarkEndNode(name1) );
1605         root.appendChild( new TextNode("de") );
1606         doTest(root);
1607     }
1608 
1609     @Test public void testToxMark3() throws Exception
1610     {
1611         // BUG: #i107672# (non-deterministic; depends on pointer ordering)
1612         String name1 = mkName("toxmark");
1613         String name2 = mkName("toxmark");
1614         String name3 = mkName("toxmark");
1615         String name4 = mkName("toxmark");
1616         String name5 = mkName("toxmark");
1617         String name6 = mkName("toxmark");
1618         String name7 = mkName("toxmark");
1619         TreeNode root = new TreeNode();
1620         root.appendChild( new DocumentIndexMarkStartNode(name1) );
1621         root.appendChild( new DocumentIndexMarkStartNode(name2) );
1622         root.appendChild( new DocumentIndexMarkStartNode(name3) );
1623         root.appendChild( new DocumentIndexMarkStartNode(name4) );
1624         root.appendChild( new DocumentIndexMarkStartNode(name5) );
1625         root.appendChild( new DocumentIndexMarkStartNode(name6) );
1626         root.appendChild( new DocumentIndexMarkStartNode(name7) );
1627         root.appendChild( new TextNode("abc") );
1628         root.appendChild( new DocumentIndexMarkEndNode(name7) );
1629         root.appendChild( new DocumentIndexMarkEndNode(name6) );
1630         root.appendChild( new DocumentIndexMarkEndNode(name5) );
1631         root.appendChild( new DocumentIndexMarkEndNode(name4) );
1632         root.appendChild( new DocumentIndexMarkEndNode(name3) );
1633         root.appendChild( new DocumentIndexMarkEndNode(name2) );
1634         root.appendChild( new DocumentIndexMarkEndNode(name1) );
1635         root.appendChild( new TextNode("de") );
1636         doTest(root);
1637     }
1638 
1639     @Test public void testMarks1() throws Exception
1640     {
1641         String name1 = mkName("bookmark");
1642         String name2 = mkName("toxmark");
1643         String name3 = mkName("refmark");
1644         String name4 = mkName("toxmark");
1645         TreeNode root = new TreeNode();
1646         root.appendChild( new BookmarkStartNode(name1) );
1647         root.appendChild( new DocumentIndexMarkNode(name2) );
1648         root.appendChild( new ReferenceMarkStartNode(name3) );
1649         root.appendChild( new TextNode("abc") );
1650         root.appendChild( new BookmarkEndNode(name1) );
1651         root.appendChild( new DocumentIndexMarkStartNode(name4) );
1652         root.appendChild( new TextNode("de") );
1653         root.appendChild( new DocumentIndexMarkEndNode(name4) );
1654         root.appendChild( new ReferenceMarkEndNode(name3) );
1655         doTest(root);
1656     }
1657 
1658     @Test public void testMarks2() throws Exception
1659     {
1660         String name1 = mkName("bookmark");
1661         String name2 = mkName("refmark");
1662         String name3 = mkName("refmark");
1663         String name4 = mkName("toxmark");
1664         String name5 = mkName("refmark");
1665         TreeNode root = new TreeNode();
1666         root.appendChild( new BookmarkStartNode(name1) );
1667         root.appendChild( new ReferenceMarkNode(name2) );
1668         root.appendChild( new ReferenceMarkStartNode(name3) );
1669         root.appendChild( new TextNode("abc") );
1670         root.appendChild( new DocumentIndexMarkStartNode(name4) );
1671         root.appendChild( new ReferenceMarkStartNode(name5) );
1672         // BUG: #i102541# (this is actually not unoportenum's fault)
1673         root.appendChild( new ReferenceMarkEndNode(name3) );
1674         root.appendChild( new TextNode("de") );
1675         root.appendChild( new DocumentIndexMarkEndNode(name4) );
1676         root.appendChild( new BookmarkEndNode(name1) );
1677         root.appendChild( new ReferenceMarkEndNode(name5) );
1678         doTest(root);
1679     }
1680 
1681     @Test public void testMarks3() throws Exception
1682     {
1683         String name1 = mkName("bookmark");
1684         String name2 = mkName("refmark");
1685         String name3 = mkName("refmark");
1686         String name4 = mkName("toxmark");
1687         String name5 = mkName("refmark");
1688         TreeNode root = new TreeNode();
1689         root.appendChild( new BookmarkStartNode(name1) );
1690         root.appendChild( new DocumentIndexMarkNode(name2) );
1691         root.appendChild( new DocumentIndexMarkStartNode(name3) );
1692         root.appendChild( new TextNode("abc") );
1693         root.appendChild( new ReferenceMarkStartNode(name4) );
1694         root.appendChild( new DocumentIndexMarkStartNode(name5) );
1695         root.appendChild( new DocumentIndexMarkEndNode(name3) );
1696         root.appendChild( new TextNode("de") );
1697         root.appendChild( new ReferenceMarkEndNode(name4) );
1698         root.appendChild( new BookmarkEndNode(name1) );
1699         root.appendChild( new DocumentIndexMarkEndNode(name5) );
1700         doTest(root);
1701     }
1702 
1703     @Test public void testFrameMark1() throws Exception
1704     {
1705         String name1 = mkName("bookmark");
1706         String name2 = mkName("frame");
1707         TreeNode root = new TreeNode();
1708         root.appendChild( new TextNode("abc") );
1709         root.appendChild( new BookmarkNode(name1) );
1710         root.appendChild( new TextNode("de") );
1711         root.appendChild( new FrameNode(name2, AS_CHARACTER) );
1712         doTest(root);
1713     }
1714 
1715     @Test public void testFrameMark2() throws Exception
1716     {
1717         // BUG: #i98530#
1718         String name1 = mkName("bookmark");
1719         String name2 = mkName("frame");
1720         TreeNode root = new TreeNode();
1721         root.appendChild( new TextNode("abc") );
1722         root.appendChild( new BookmarkNode(name1) );
1723         root.appendChild( new TextNode("de") );
1724         root.appendChild( new FrameNode(name2, AT_CHARACTER) );
1725         doTest(root);
1726     }
1727 
1728     @Test public void testFrameMark3() throws Exception
1729     {
1730         String name1 = mkName("frame");
1731         String name2 = mkName("bookmark");
1732         TreeNode root = new TreeNode();
1733         root.appendChild( new TextNode("abc") );
1734         root.appendChild( new FrameNode(name1, AS_CHARACTER) );
1735         root.appendChild( new TextNode("de") );
1736         root.appendChild( new BookmarkNode(name2) );
1737         doTest(root);
1738     }
1739 
1740     @Test public void testFrameMark4() throws Exception
1741     {
1742         String name1 = mkName("frame");
1743         String name2 = mkName("bookmark");
1744         TreeNode root = new TreeNode();
1745         root.appendChild( new TextNode("abc") );
1746         root.appendChild( new FrameNode(name1, AT_CHARACTER) );
1747         root.appendChild( new TextNode("de") );
1748         root.appendChild( new BookmarkNode(name2) );
1749         doTest(root);
1750     }
1751 
1752     @Test public void testFrames1() throws Exception
1753     {
1754         String name1 = mkName("frame");
1755         String name2 = mkName("frame");
1756         String name3 = mkName("frame");
1757         TreeNode root = new TreeNode();
1758         root.appendChild( new FrameNode(name1, AT_CHARACTER) );
1759         root.appendChild( new FrameNode(name2, AT_CHARACTER) );
1760         root.appendChild( new FrameNode(name3, AT_CHARACTER) );
1761         doTest(root);
1762     }
1763 
1764     @Test public void testFrames2() throws Exception
1765     {
1766         String name1 = mkName("frame");
1767         String name2 = mkName("frame");
1768         String name3 = mkName("frame");
1769         TreeNode root = new TreeNode();
1770         root.appendChild( new FrameNode(name1, AS_CHARACTER) );
1771         root.appendChild( new FrameNode(name2, AS_CHARACTER) );
1772         root.appendChild( new FrameNode(name3, AS_CHARACTER) );
1773         doTest(root);
1774     }
1775 
1776     @Test public void testFrames3() throws Exception
1777     {
1778         String name1 = mkName("frame");
1779         String name2 = mkName("frame");
1780         String name3 = mkName("frame");
1781         TreeNode root = new TreeNode();
1782         root.appendChild( new FrameNode(name1, AT_CHARACTER) );
1783         root.appendChild( new FrameNode(name2, AS_CHARACTER) );
1784         root.appendChild( new FrameNode(name3, AT_CHARACTER) );
1785         doTest(root);
1786     }
1787 
1788     @Test public void testFrames4() throws Exception
1789     {
1790         String name1 = mkName("frame");
1791         String name2 = mkName("frame");
1792         String name3 = mkName("frame");
1793         TreeNode root = new TreeNode();
1794         root.appendChild( new FrameNode(name1, AT_CHARACTER) );
1795         root.appendChild( new FrameNode(name2, AT_CHARACTER) );
1796         root.appendChild( new FrameNode(name3, AS_CHARACTER) );
1797         doTest(root);
1798     }
1799 
1800     @Test public void testFrames5() throws Exception
1801     {
1802         String name1 = mkName("frame");
1803         String name2 = mkName("frame");
1804         String name3 = mkName("frame");
1805         TreeNode root = new TreeNode();
1806         root.appendChild( new FrameNode(name1, AS_CHARACTER) );
1807         root.appendChild( new FrameNode(name2, AT_CHARACTER) );
1808         root.appendChild( new FrameNode(name3, AT_CHARACTER) );
1809         doTest(root);
1810     }
1811 
1812     @Test public void testRubyHyperlink1() throws Exception
1813     {
1814         String name1 = mkName("ruby");
1815         String name2 = mkName("url");
1816         TreeNode root = new TreeNode();
1817         TreeNode ruby = new RubyNode(name1);
1818         TreeNode href = new HyperlinkNode(name2);
1819         href.appendChild( new TextNode("abc") );
1820         ruby.appendChild(href);
1821         root.appendChild(ruby);
1822         doTest(root);
1823     }
1824 
1825     @Test public void testRubyHyperlink2() throws Exception
1826     {
1827         String name1 = mkName("url");
1828         String name2 = mkName("ruby");
1829         TreeNode root = new TreeNode();
1830         TreeNode href = new HyperlinkNode(name1);
1831         TreeNode ruby = new RubyNode(name2);
1832         ruby.appendChild( new TextNode("abc") );
1833         href.appendChild(ruby);
1834         root.appendChild(href);
1835         doTest(root);
1836     }
1837 
1838     @Test public void testEnd1() throws Exception
1839     {
1840         String name1 = mkName("bookmark");
1841         String name2 = mkName("toxmark");
1842         String name3 = mkName("refmark");
1843         TreeNode root = new TreeNode();
1844         root.appendChild( new TextNode("abc") );
1845         root.appendChild( new BookmarkNode(name1) );
1846         root.appendChild( new DocumentIndexMarkNode(name2) );
1847         root.appendChild( new ReferenceMarkNode(name3) );
1848         doTest(root);
1849     }
1850 
1851     @Test public void testEnd2() throws Exception
1852     {
1853         String name1 = mkName("bookmark");
1854         String name2 = mkName("frame");
1855         String name3 = mkName("refmark");
1856         String name4 = mkName("frame");
1857         String name5 = mkName("frame");
1858         TreeNode root = new TreeNode();
1859         root.appendChild( new TextNode("abc") );
1860         root.appendChild( new BookmarkNode(name1) );
1861         root.appendChild( new FrameNode(name2, AT_CHARACTER) );
1862         root.appendChild( new ReferenceMarkNode(name3) );
1863         root.appendChild( new FrameNode(name4, AT_CHARACTER) );
1864         root.appendChild( new FrameNode(name5, AT_CHARACTER) );
1865         doTest(root);
1866     }
1867 
1868     @Test public void testEnd3() throws Exception
1869     {
1870         String name1 = mkName("ftn");
1871         String name2 = mkName("toxmark");
1872         TreeNode root = new TreeNode();
1873         root.appendChild( new TextNode("abc") );
1874         root.appendChild( new FootnoteNode(name1) );
1875         root.appendChild( new DocumentIndexMarkNode(name2) );
1876         doTest(root);
1877     }
1878 
1879     @Test public void testEnd4() throws Exception
1880     {
1881         String name1 = mkName("bookmark");
1882         String name2 = mkName("frame");
1883         TreeNode root = new TreeNode();
1884         root.appendChild( new BookmarkStartNode(name1) );
1885         root.appendChild( new TextNode("abc") );
1886         root.appendChild( new FrameNode(name2, AS_CHARACTER) );
1887         root.appendChild( new BookmarkEndNode(name1) );
1888         doTest(root);
1889     }
1890 
1891     @Test public void testEnd5() throws Exception
1892     {
1893         String name1 = mkName("refmark");
1894         String name2 = mkName("ruby");
1895         TreeNode root = new TreeNode();
1896         root.appendChild( new ReferenceMarkStartNode(name1) );
1897         root.appendChild( new TextNode("abc") );
1898         TreeNode ruby = new RubyNode(name2);
1899         ruby.appendChild( new TextFieldNode("de") );
1900         root.appendChild(ruby);
1901         root.appendChild( new ReferenceMarkEndNode(name1) );
1902         doTest(root);
1903     }
1904 
1905     @Test public void testEmpty1() throws Exception
1906     {
1907         String name1 = mkName("refmark");
1908         String name2 = mkName("toxmark");
1909         String name3 = mkName("bookmark");
1910         String name4 = mkName("frame");
1911         String name7 = mkName("refmark");
1912         String name8 = mkName("toxmark");
1913         String name9 = mkName("bookmark");
1914         String nameA = mkName("frame");
1915         TreeNode root = new TreeNode();
1916         root.appendChild( new ReferenceMarkNode(name1) );
1917         root.appendChild( new DocumentIndexMarkNode(name2) );
1918         root.appendChild( new BookmarkNode(name3) );
1919         root.appendChild( new FrameNode(name4, AT_CHARACTER) );
1920         root.appendChild( new ReferenceMarkNode(name7) );
1921         root.appendChild( new DocumentIndexMarkNode(name8) );
1922         root.appendChild( new BookmarkNode(name9) );
1923         root.appendChild( new FrameNode(nameA, AT_CHARACTER) );
1924         doTest(root);
1925     }
1926 
1927     @Test public void testEmpty2() throws Exception
1928     {
1929         String name3 = mkName("bookmark");
1930         String name4 = mkName("frame");
1931         String name9 = mkName("bookmark");
1932         String nameA = mkName("frame");
1933         TreeNode root = new TreeNode();
1934         root.appendChild( new BookmarkNode(name3) );
1935         root.appendChild( new FrameNode(name4, AT_CHARACTER) );
1936         root.appendChild( new BookmarkNode(name9) );
1937         root.appendChild( new FrameNode(nameA, AT_CHARACTER) );
1938         doTest(root);
1939     }
1940 
1941     @Test public void testEmpty3() throws Exception
1942     {
1943         String name1 = mkName("refmark");
1944         String name2 = mkName("toxmark");
1945         String name3 = mkName("bookmark");
1946         String name4 = mkName("frame");
1947         String name5 = mkName("url");
1948         String name6 = mkName("ruby");
1949         String name7 = mkName("refmark");
1950         String name8 = mkName("toxmark");
1951         String name9 = mkName("bookmark");
1952         String nameA = mkName("frame");
1953         TreeNode root = new TreeNode();
1954         root.appendChild( new ReferenceMarkNode(name1) );
1955         root.appendChild( new DocumentIndexMarkNode(name2) );
1956         root.appendChild( new BookmarkNode(name3) );
1957         root.appendChild( new FrameNode(name4, AT_CHARACTER) );
1958         /* currently empty hyperlinks may get eaten...
1959         TreeNode href = new HyperlinkNode(name5);
1960         href.appendChild( new TextNode("") );
1961         root.appendChild(href);
1962         */
1963         TreeNode ruby = new RubyNode(name6);
1964         root.appendChild(ruby);
1965         root.appendChild( new ReferenceMarkNode(name7) );
1966         root.appendChild( new DocumentIndexMarkNode(name8) );
1967         root.appendChild( new BookmarkNode(name9) );
1968         root.appendChild( new FrameNode(nameA, AT_CHARACTER) );
1969         doTest(root);
1970     }
1971 
1972     @Test public void test1() throws Exception
1973     {
1974         String name1 = mkName("frame");
1975         String name2 = mkName("bookmark");
1976         String name3 = mkName("ruby");
1977         String name4 = mkName("ftn");
1978         String name5 = mkName("frame");
1979         TreeNode root = new TreeNode();
1980         root.appendChild( new FrameNode(name1, AT_CHARACTER) );
1981         root.appendChild( new BookmarkStartNode(name2) );
1982         root.appendChild( new TextNode("abc") );
1983         TreeNode ruby = new RubyNode(name3);
1984         ruby.appendChild( new TextNode("de") );
1985         ruby.appendChild( new FootnoteNode(name4) );
1986         ruby.appendChild( new BookmarkEndNode(name2) );
1987         root.appendChild(ruby);
1988         root.appendChild( new TextNode("fg") );
1989         root.appendChild( new FrameNode(name5, AT_CHARACTER) );
1990         root.appendChild( new TextFieldNode("h") );
1991         doTest(root);
1992     }
1993 
1994     /* some range tests for the insertion: these are for the current
1995        API which treats hyperlinks and rubys not as entities, but as formatting
1996        attributes; if these ever become entities, they should not be split!
1997      */
1998 
1999     @Test public void testRange1() throws Exception
2000     {
2001         String name1 = mkName("url");
2002         RangeInserter inserter = new RangeInserter(m_xDoc);
2003         TreeNode text = new TextNode("12345");
2004         inserter.insertRange(new Range(0, 0, text));
2005         TreeNode url1 = new HyperlinkNode(name1);
2006         Range range1 = new Range(0, 5, url1);
2007         inserter.insertRange(range1);
2008         TreeNode root = new TreeNode();
2009         root.appendChild( url1 );
2010         url1.appendChild( text );
2011         doTest(root, false);
2012     }
2013 
2014     @Test public void testRangeHyperlinkHyperlink() throws Exception
2015     {
2016         RangeInserter inserter = new RangeInserter(m_xDoc);
2017         TreeNode text = new TextNode("123456789");
2018         inserter.insertRange( new Range(0, 0, text) );
2019         TreeNode url1 = new HyperlinkNode( mkName("url") );
2020         inserter.insertRange( new Range(1, 4, url1) );
2021         // overlap left
2022         TreeNode url2 = new HyperlinkNode( mkName("url") );
2023         inserter.insertRange( new Range(0, 2, url2) );
2024         TreeNode root = new TreeNode()
2025             .appendChild( url2.dup().appendChild( new TextNode("12") ) )
2026             .appendChild( url1.dup().appendChild( new TextNode("34") ) )
2027             .appendChild( new TextNode("56789") );
2028         doTest(root, false);
2029         // overlap right
2030         TreeNode url3 = new HyperlinkNode( mkName("url") );
2031         inserter.insertRange( new Range(3, 7, url3) );
2032         root = new TreeNode()
2033             .appendChild( url2.dup().appendChild( new TextNode("12") ) )
2034             .appendChild( url1.dup().appendChild( new TextNode("3") ) )
2035             .appendChild( url3.dup().appendChild( new TextNode("4567") ) )
2036             .appendChild( new TextNode("89") );
2037         doTest(root, false);
2038         // around
2039         TreeNode url4 = new HyperlinkNode( mkName("url") );
2040         inserter.insertRange( new Range(3, 7, url4) );
2041         root = new TreeNode()
2042             .appendChild( url2.dup().appendChild( new TextNode("12") ) )
2043             .appendChild( url1.dup().appendChild( new TextNode("3") ) )
2044             .appendChild( url4.dup().appendChild( new TextNode("4567") ) )
2045             .appendChild( new TextNode("89") );
2046         doTest(root, false);
2047         // inside
2048         TreeNode url5 = new HyperlinkNode( mkName("url") );
2049         inserter.insertRange( new Range(4, 6, url5) );
2050         root = new TreeNode()
2051             .appendChild( url2.dup().appendChild( new TextNode("12") ) )
2052             .appendChild( url1.dup().appendChild( new TextNode("3") ) )
2053             .appendChild( url4.dup().appendChild( new TextNode("4") ) )
2054             .appendChild( url5.dup().appendChild( new TextNode("56") ) )
2055             .appendChild( url4.dup().appendChild( new TextNode("7") ) )
2056             .appendChild( new TextNode("89") );
2057         doTest(root, false);
2058         // empty
2059         TreeNode url6 = new HyperlinkNode( mkName("url") );
2060         inserter.insertRange( new Range(7, 7, url6) );
2061         root = new TreeNode()
2062             .appendChild( url2.dup().appendChild( new TextNode("12") ) )
2063             .appendChild( url1.dup().appendChild( new TextNode("3") ) )
2064             .appendChild( url4.dup().appendChild( new TextNode("4") ) )
2065             .appendChild( url5.dup().appendChild( new TextNode("56") ) )
2066             .appendChild( url4.dup().appendChild( new TextNode("7") ) )
2067 // this one gets eaten, but we still need to test inserting it (#i106930#)
2068 //            .appendChild( url6.dup().appendChild( new TextNode("") ) )
2069             .appendChild( new TextNode("89") );
2070         doTest(root, false);
2071     }
2072 
2073     @Test public void testRangeHyperlinkRuby() throws Exception
2074     {
2075         RangeInserter inserter = new RangeInserter(m_xDoc);
2076         TreeNode text = new TextNode("123456789");
2077         inserter.insertRange( new Range(0, 0, text) );
2078         TreeNode url1 = new HyperlinkNode( mkName("url") );
2079         inserter.insertRange( new Range(1, 4, url1) );
2080         // overlap left
2081         TreeNode rby2 = new RubyNode( mkName("ruby") );
2082         inserter.insertRange( new Range(0, 2, rby2) );
2083         TreeNode root = new TreeNode()
2084             .appendChild( rby2.dup()
2085                 .appendChild( new TextNode("1") )
2086                 .appendChild( url1.dup().appendChild( new TextNode("2") ) ) )
2087             .appendChild( url1.dup().appendChild( new TextNode("34") ) )
2088             .appendChild( new TextNode("56789") );
2089         doTest(root, false);
2090         // overlap right
2091         TreeNode rby3 = new RubyNode( mkName("ruby") );
2092         inserter.insertRange( new Range(3, 5, rby3) );
2093         root = new TreeNode()
2094             .appendChild( rby2.dup()
2095                 .appendChild( new TextNode("1") )
2096                 .appendChild( url1.dup().appendChild( new TextNode("2") ) ) )
2097             .appendChild( url1.dup().appendChild( new TextNode("3") ) )
2098             .appendChild( rby3.dup()
2099                 .appendChild( url1.dup().appendChild( new TextNode("4") ) )
2100                 .appendChild( new TextNode("5") ) )
2101             .appendChild( new TextNode("6789") );
2102         doTest(root, false);
2103         // around
2104         TreeNode rby4 = new RubyNode( mkName("ruby") );
2105         inserter.insertRange( new Range(2, 3, rby4) );
2106         root = new TreeNode()
2107             .appendChild( rby2.dup()
2108                 .appendChild( new TextNode("1") )
2109                 .appendChild( url1.dup().appendChild( new TextNode("2") ) ) )
2110             .appendChild( rby4.dup()
2111                 .appendChild( url1.dup().appendChild( new TextNode("3") ) ) )
2112             .appendChild( rby3.dup()
2113                 .appendChild( url1.dup().appendChild( new TextNode("4") ) )
2114                 .appendChild( new TextNode("5") ) )
2115             .appendChild( new TextNode("6789") );
2116         doTest(root, false);
2117         // inside
2118         TreeNode url5 = new HyperlinkNode( mkName("url") );
2119         inserter.insertRange( new Range(6, 9, url5) );
2120         TreeNode rby6 = new RubyNode( mkName("ruby") );
2121         inserter.insertRange( new Range(7, 8, rby6) );
2122         root = new TreeNode()
2123             .appendChild( rby2.dup()
2124                 .appendChild( new TextNode("1") )
2125                 .appendChild( url1.dup().appendChild( new TextNode("2") ) ) )
2126             .appendChild( rby4.dup()
2127                 .appendChild( url1.dup().appendChild( new TextNode("3") ) ) )
2128             .appendChild( rby3.dup()
2129                 .appendChild( url1.dup().appendChild( new TextNode("4") ) )
2130                 .appendChild( new TextNode("5") ) )
2131             .appendChild( new TextNode("6") )
2132             .appendChild( url5.dup().appendChild( new TextNode("7") ) )
2133             .appendChild( rby6.dup()
2134                 .appendChild( url5.dup().appendChild( new TextNode("8") ) ) )
2135             .appendChild( url5.dup().appendChild( new TextNode("9") ) );
2136         doTest(root, false);
2137     }
2138 
2139     @Test public void testRangeRubyHyperlink() throws Exception
2140     {
2141         RangeInserter inserter = new RangeInserter(m_xDoc);
2142         TreeNode text = new TextNode("123456789");
2143         inserter.insertRange( new Range(0, 0, text) );
2144         TreeNode rby1 = new RubyNode( mkName("ruby") );
2145         inserter.insertRange( new Range(1, 6, rby1) );
2146         // overlap left
2147         TreeNode url2 = new HyperlinkNode( mkName("url") );
2148         inserter.insertRange( new Range(0, 3, url2) );
2149         TreeNode root = new TreeNode()
2150             .appendChild( url2.dup().appendChild( new TextNode("1") ) )
2151             .appendChild( rby1.dup()
2152                 .appendChild( url2.dup().appendChild( new TextNode("23") ) )
2153                 .appendChild( new TextNode("456") ) )
2154             .appendChild( new TextNode("789") );
2155         doTest(root, false);
2156         // overlap right
2157         TreeNode url3 = new HyperlinkNode( mkName("url") );
2158         inserter.insertRange( new Range(5, 7, url3) );
2159         root = new TreeNode()
2160             .appendChild( url2.dup().appendChild( new TextNode("1") ) )
2161             .appendChild( rby1.dup()
2162                 .appendChild( url2.dup().appendChild( new TextNode("23") ) )
2163                 .appendChild( new TextNode("45") )
2164                 .appendChild( url3.dup().appendChild( new TextNode("6") ) ) )
2165             .appendChild( url3.dup().appendChild( new TextNode("7") ) )
2166             .appendChild( new TextNode("89") );
2167         doTest(root, false);
2168         // around (not quite, due to API)
2169         TreeNode url4 = new HyperlinkNode( mkName("url") );
2170         inserter.insertRange( new Range(1, 8, url4) );
2171         root = new TreeNode()
2172             .appendChild( url2.dup().appendChild( new TextNode("1") ) )
2173             .appendChild( rby1.dup()
2174                 .appendChild( url4.dup()
2175                     .appendChild( new TextNode("23456") ) ) )
2176             .appendChild( url4.dup().appendChild( new TextNode("78") ) )
2177             .appendChild( new TextNode("9") );
2178         doTest(root, false);
2179         // inside
2180         TreeNode url5 = new HyperlinkNode( mkName("url") );
2181         inserter.insertRange( new Range(3, 5, url5) );
2182         root = new TreeNode()
2183             .appendChild( url2.dup().appendChild( new TextNode("1") ) )
2184             .appendChild( rby1.dup()
2185                 .appendChild( url4.dup()
2186                     .appendChild( new TextNode("23") ) )
2187                 .appendChild( url5.dup()
2188                     .appendChild( new TextNode("45") ) )
2189                 .appendChild( url4.dup()
2190                     .appendChild( new TextNode("6") ) ) )
2191             .appendChild( url4.dup().appendChild( new TextNode("78") ) )
2192             .appendChild( new TextNode("9") );
2193         doTest(root, false);
2194     }
2195 
2196     @Test public void testRangeRubyRuby() throws Exception
2197     {
2198         RangeInserter inserter = new RangeInserter(m_xDoc);
2199         TreeNode text = new TextNode("123456789");
2200         inserter.insertRange( new Range(0, 0, text) );
2201         TreeNode rby1 = new RubyNode( mkName("ruby") );
2202         inserter.insertRange( new Range(1, 4, rby1) );
2203         // overlap left
2204         TreeNode rby2 = new RubyNode( mkName("ruby") );
2205         inserter.insertRange( new Range(0, 2, rby2) );
2206         TreeNode root = new TreeNode()
2207             .appendChild( rby2.dup().appendChild( new TextNode("12") ) )
2208             .appendChild( rby1.dup().appendChild( new TextNode("34") ) )
2209             .appendChild( new TextNode("56789") );
2210         doTest(root, false);
2211         // overlap right
2212         TreeNode rby3 = new RubyNode( mkName("ruby") );
2213         inserter.insertRange( new Range(3, 7, rby3) );
2214         root = new TreeNode()
2215             .appendChild( rby2.dup().appendChild( new TextNode("12") ) )
2216             .appendChild( rby1.dup().appendChild( new TextNode("3") ) )
2217             .appendChild( rby3.dup().appendChild( new TextNode("4567") ) )
2218             .appendChild( new TextNode("89") );
2219         doTest(root, false);
2220         // around
2221         TreeNode rby4 = new RubyNode( mkName("ruby") );
2222         inserter.insertRange( new Range(3, 7, rby4) );
2223         root = new TreeNode()
2224             .appendChild( rby2.dup().appendChild( new TextNode("12") ) )
2225             .appendChild( rby1.dup().appendChild( new TextNode("3") ) )
2226             .appendChild( rby4.dup().appendChild( new TextNode("4567") ) )
2227             .appendChild( new TextNode("89") );
2228         doTest(root, false);
2229         // inside
2230         TreeNode rby5 = new RubyNode( mkName("ruby") );
2231         inserter.insertRange( new Range(4, 6, rby5) );
2232         root = new TreeNode()
2233             .appendChild( rby2.dup().appendChild( new TextNode("12") ) )
2234             .appendChild( rby1.dup().appendChild( new TextNode("3") ) )
2235             .appendChild( rby4.dup().appendChild( new TextNode("4") ) )
2236             .appendChild( rby5.dup().appendChild( new TextNode("56") ) )
2237             .appendChild( rby4.dup().appendChild( new TextNode("7") ) )
2238             .appendChild( new TextNode("89") );
2239         doTest(root, false);
2240     }
2241 
2242     @Test public void testRangeHyperlinkMeta() throws Exception
2243     {
2244         RangeInserter inserter = new RangeInserter(m_xDoc);
2245         TreeNode text = new TextNode("123456789");
2246         inserter.insertRange( new Range(0, 0, text) );
2247         TreeNode url1 = new HyperlinkNode( mkName("url") );
2248         inserter.insertRange( new Range(1, 4, url1) );
2249         // overlap left
2250         TreeNode met2 = new MetaNode( mkId("id") );
2251         inserter.insertRange( new Range(0, 2, met2) );
2252         TreeNode root = new TreeNode()
2253             .appendChild( met2.dup()
2254                 .appendChild( new TextNode("1") )
2255                 .appendChild( url1.dup().appendChild( new TextNode("2") ) ) )
2256             .appendChild( url1.dup().appendChild( new TextNode("34") ) )
2257             .appendChild( new TextNode("56789") );
2258         doTest(root, false);
2259         // overlap right
2260         TreeNode met3 = new MetaNode( mkId("id") );
2261         inserter.insertRange( new Range(4/*-1*/, 6/*-1*/, met3) );
2262         root = new TreeNode()
2263             .appendChild( met2.dup()
2264                 .appendChild( new TextNode("1") )
2265                 .appendChild( url1.dup().appendChild( new TextNode("2") ) ) )
2266             .appendChild( url1.dup().appendChild( new TextNode("3") ) )
2267             .appendChild( met3.dup()
2268                 .appendChild( url1.dup().appendChild( new TextNode("4") ) )
2269                 .appendChild( new TextNode("5") ) )
2270             .appendChild( new TextNode("6789") );
2271         doTest(root, false);
2272         // around
2273         TreeNode met4 = new MetaNode( mkId("id") );
2274         inserter.insertRange( new Range(3/*-1*/, 4/*-1*/, met4) );
2275         root = new TreeNode()
2276             .appendChild( met2.dup()
2277                 .appendChild( new TextNode("1") )
2278                 .appendChild( url1.dup().appendChild( new TextNode("2") ) ) )
2279             .appendChild( met4.dup()
2280                 .appendChild( url1.dup().appendChild( new TextNode("3") ) ) )
2281             .appendChild( met3.dup()
2282                 .appendChild( url1.dup().appendChild( new TextNode("4") ) )
2283                 .appendChild( new TextNode("5") ) )
2284             .appendChild( new TextNode("6789") );
2285         doTest(root, false);
2286         // inside
2287         TreeNode url5 = new HyperlinkNode( mkName("url") );
2288         inserter.insertRange( new Range(9/*-3*/, 12/*-3*/, url5) );
2289         TreeNode met6 = new MetaNode( mkId("id") );
2290         inserter.insertRange( new Range(10/*-3*/, 11/*-3*/, met6) );
2291         root = new TreeNode()
2292             .appendChild( met2.dup()
2293                 .appendChild( new TextNode("1") )
2294                 .appendChild( url1.dup().appendChild( new TextNode("2") ) ) )
2295             .appendChild( met4.dup()
2296                 .appendChild( url1.dup().appendChild( new TextNode("3") ) ) )
2297             .appendChild( met3.dup()
2298                 .appendChild( url1.dup().appendChild( new TextNode("4") ) )
2299                 .appendChild( new TextNode("5") ) )
2300             .appendChild( new TextNode("6") )
2301             .appendChild( url5.dup().appendChild( new TextNode("7") ) )
2302             .appendChild( met6.dup()
2303                 .appendChild( url5.dup().appendChild( new TextNode("8") ) ) )
2304             .appendChild( url5.dup().appendChild( new TextNode("9") ) );
2305         doTest(root, false);
2306     }
2307 
2308     @Test public void testRangeRubyMeta() throws Exception
2309     {
2310         RangeInserter inserter = new RangeInserter(m_xDoc);
2311         TreeNode text = new TextNode("123456789");
2312         inserter.insertRange( new Range(0, 0, text) );
2313         TreeNode rby1 = new RubyNode( mkName("ruby") );
2314         inserter.insertRange( new Range(1, 4, rby1) );
2315         // overlap left
2316         TreeNode met2 = new MetaNode( mkId("id") );
2317         inserter.insertRange( new Range(0, 2, met2) );
2318         TreeNode root = new TreeNode()
2319             .appendChild( met2.dup()
2320                 .appendChild( new TextNode("1") )
2321                 .appendChild( rby1.dup().appendChild( new TextNode("2") ) ) )
2322             .appendChild( rby1.dup().appendChild( new TextNode("34") ) )
2323             .appendChild( new TextNode("56789") );
2324         doTest(root, false);
2325         // overlap right
2326         TreeNode met3 = new MetaNode( mkId("id") );
2327         inserter.insertRange( new Range(4/*-1*/, 6/*-1*/, met3) );
2328         root = new TreeNode()
2329             .appendChild( met2.dup()
2330                 .appendChild( new TextNode("1") )
2331                 .appendChild( rby1.dup().appendChild( new TextNode("2") ) ) )
2332             .appendChild( rby1.dup().appendChild( new TextNode("3") ) )
2333             .appendChild( met3.dup()
2334                 .appendChild( rby1.dup().appendChild( new TextNode("4") ) )
2335                 .appendChild( new TextNode("5") ) )
2336             .appendChild( new TextNode("6789") );
2337         doTest(root, false);
2338         // around
2339         TreeNode met4 = new MetaNode( mkId("id") );
2340         inserter.insertRange( new Range(3/*-1*/, 4/*-1*/, met4) );
2341         root = new TreeNode()
2342             .appendChild( met2.dup()
2343                 .appendChild( new TextNode("1") )
2344                 .appendChild( rby1.dup().appendChild( new TextNode("2") ) ) )
2345             .appendChild( met4.dup()
2346                 .appendChild( rby1.dup().appendChild( new TextNode("3") ) ) )
2347             .appendChild( met3.dup()
2348                 .appendChild( rby1.dup().appendChild( new TextNode("4") ) )
2349                 .appendChild( new TextNode("5") ) )
2350             .appendChild( new TextNode("6789") );
2351         doTest(root, false);
2352         // inside
2353         TreeNode rby5 = new RubyNode( mkName("ruby") );
2354         inserter.insertRange( new Range(9/*-3*/, 12/*-3*/, rby5) );
2355         TreeNode met6 = new MetaNode( mkId("id") );
2356         inserter.insertRange( new Range(10/*-3*/, 11/*-3*/, met6) );
2357         root = new TreeNode()
2358             .appendChild( met2.dup()
2359                 .appendChild( new TextNode("1") )
2360                 .appendChild( rby1.dup().appendChild( new TextNode("2") ) ) )
2361             .appendChild( met4.dup()
2362                 .appendChild( rby1.dup().appendChild( new TextNode("3") ) ) )
2363             .appendChild( met3.dup()
2364                 .appendChild( rby1.dup().appendChild( new TextNode("4") ) )
2365                 .appendChild( new TextNode("5") ) )
2366             .appendChild( new TextNode("6") )
2367             .appendChild( rby5.dup()
2368                 .appendChild( new TextNode("7") )
2369                 .appendChild( met6.dup()
2370                     .appendChild( new TextNode("8") ) )
2371                 .appendChild( new TextNode("9") ) );
2372         doTest(root, false);
2373     }
2374 
2375     @Test public void testRangeMetaHyperlink() throws Exception
2376     {
2377         RangeInserter inserter = new RangeInserter(m_xDoc);
2378         TreeNode text = new TextNode("123456789");
2379         inserter.insertRange( new Range(0, 0, text) );
2380         TreeNode met1 = new MetaNode( mkId("id") );
2381         inserter.insertRange( new Range(1, 6, met1) );
2382         // overlap left
2383         TreeNode url2 = new HyperlinkNode( mkName("url") );
2384         inserter.insertRange( new Range(0, 4/*-1*/, url2) );
2385         TreeNode root = new TreeNode()
2386             .appendChild( url2.dup().appendChild( new TextNode("1") ) )
2387             .appendChild( met1.dup()
2388                 .appendChild( url2.dup().appendChild( new TextNode("23") ) )
2389                 .appendChild( new TextNode("456") ) )
2390             .appendChild( new TextNode("789") );
2391         doTest(root, false);
2392         // overlap right
2393         TreeNode url3 = new HyperlinkNode( mkName("url") );
2394         inserter.insertRange( new Range(6/*-1*/, 8/*-1*/, url3) );
2395         root = new TreeNode()
2396             .appendChild( url2.dup().appendChild( new TextNode("1") ) )
2397             .appendChild( met1.dup()
2398                 .appendChild( url2.dup().appendChild( new TextNode("23") ) )
2399                 .appendChild( new TextNode("45") )
2400                 .appendChild( url3.dup().appendChild( new TextNode("6") ) ) )
2401             .appendChild( url3.dup().appendChild( new TextNode("7") ) )
2402             .appendChild( new TextNode("89") );
2403         doTest(root, false);
2404         // around (not quite, due to API)
2405         TreeNode url4 = new HyperlinkNode( mkName("url") );
2406         inserter.insertRange( new Range(1, 9/*-1*/, url4) );
2407         root = new TreeNode()
2408             .appendChild( url2.dup().appendChild( new TextNode("1") ) )
2409             .appendChild( met1.dup()
2410                 .appendChild( url4.dup()
2411                     .appendChild( new TextNode("23456") ) ) )
2412             .appendChild( url4.dup().appendChild( new TextNode("78") ) )
2413             .appendChild( new TextNode("9") );
2414         doTest(root, false);
2415         // inside
2416         TreeNode url5 = new HyperlinkNode( mkName("url") );
2417         inserter.insertRange( new Range(4/*-1*/, 6/*-1*/, url5) );
2418         root = new TreeNode()
2419             .appendChild( url2.dup().appendChild( new TextNode("1") ) )
2420             .appendChild( met1.dup()
2421                 .appendChild( url4.dup()
2422                     .appendChild( new TextNode("23") ) )
2423                 .appendChild( url5.dup()
2424                     .appendChild( new TextNode("45") ) )
2425                 .appendChild( url4.dup()
2426                     .appendChild( new TextNode("6") ) ) )
2427             .appendChild( url4.dup().appendChild( new TextNode("78") ) )
2428             .appendChild( new TextNode("9") );
2429         doTest(root, false);
2430     }
2431 
2432     @Test public void testRangeMetaRuby() throws Exception
2433     {
2434         RangeInserter inserter = new RangeInserter(m_xDoc);
2435         TreeNode text = new TextNode("123456789");
2436         inserter.insertRange( new Range(0, 0, text) );
2437         TreeNode met1 = new MetaNode( mkId("id") );
2438         inserter.insertRange( new Range(1, 5, met1) );
2439         // overlap left
2440         TreeNode rby2 = new RubyNode( mkName("ruby") );
2441         inserter.insertRange( new Range(0, 3/*-1*/, rby2) );
2442         TreeNode root = new TreeNode()
2443             .appendChild( rby2.dup().appendChild( new TextNode("1") ) )
2444             .appendChild( met1.dup()
2445                 .appendChild( rby2.dup().appendChild( new TextNode("2") ) )
2446                 .appendChild( new TextNode("345") ) )
2447             .appendChild( new TextNode("6789") );
2448         doTest(root, false);
2449         // overlap right
2450         TreeNode rby3 = new RubyNode( mkName("ruby") );
2451         inserter.insertRange( new Range(5/*-1*/, 7/*-1*/, rby3) );
2452         root = new TreeNode()
2453             .appendChild( rby2.dup().appendChild( new TextNode("1") ) )
2454             .appendChild( met1.dup()
2455                 .appendChild( rby2.dup().appendChild( new TextNode("2") ) )
2456                 .appendChild( new TextNode("34") )
2457                 .appendChild( rby3.dup().appendChild( new TextNode("5") ) ) )
2458             .appendChild( rby3.dup().appendChild( new TextNode("6") ) )
2459             .appendChild( new TextNode("789") );
2460         doTest(root, false);
2461         // around
2462         TreeNode rby4 = new RubyNode( mkName("ruby") );
2463         inserter.insertRange( new Range(1, 7/*-1*/, rby4) );
2464         root = new TreeNode()
2465             .appendChild( rby2.dup().appendChild( new TextNode("1") ) )
2466             .appendChild( rby4.dup()
2467                 .appendChild( met1.dup()
2468                     .appendChild( new TextNode("2345") ) )
2469                 .appendChild( new TextNode("6") ) )
2470             .appendChild( new TextNode("789") );
2471         doTest(root, false);
2472         // inside
2473         TreeNode met5 = new MetaNode( mkId("id") );
2474         inserter.insertRange( new Range(7/*-1*/, 9/*-1*/, met5) );
2475         TreeNode rby6 = new RubyNode( mkName("ruby") );
2476         inserter.insertRange( new Range(9/*-2*/, 10/*-2*/, rby6) );
2477         root = new TreeNode()
2478             .appendChild( rby2.dup().appendChild( new TextNode("1") ) )
2479             .appendChild( rby4.dup()
2480                 .appendChild( met1.dup()
2481                     .appendChild( new TextNode("2345") ) )
2482                 .appendChild( new TextNode("6") ) )
2483             .appendChild( met5.dup()
2484                 .appendChild( new TextNode("7") )
2485                 .appendChild( rby6.dup()
2486                     .appendChild( new TextNode("8") ) ) )
2487             .appendChild( new TextNode("9") );
2488         doTest(root, false);
2489         // inside, with invalid range that includes the dummy char
2490         TreeNode rby7 = new RubyNode( mkName("ruby") );
2491         inserter.insertRange( new Range(7/*-1*/, 9/*-2*/, rby7) );
2492         root = new TreeNode()
2493             .appendChild( rby2.dup().appendChild( new TextNode("1") ) )
2494             .appendChild( rby4.dup()
2495                 .appendChild( met1.dup()
2496                     .appendChild( new TextNode("2345") ) )
2497                 .appendChild( new TextNode("6") ) )
2498             .appendChild( met5.dup()
2499                 .appendChild( rby7.dup()
2500                     .appendChild( new TextNode("7") ) )
2501                 .appendChild( rby6.dup()
2502                     .appendChild( new TextNode("8") ) ) )
2503             .appendChild( new TextNode("9") );
2504         doTest(root, false);
2505         // around, at same position as meta
2506         TreeNode rby8 = new RubyNode( mkName("ruby") );
2507         inserter.insertRange( new Range(7/*-1*/, 10/*-2*/, rby8) );
2508         root = new TreeNode()
2509             .appendChild( rby2.dup().appendChild( new TextNode("1") ) )
2510             .appendChild( rby4.dup()
2511                 .appendChild( met1.dup()
2512                     .appendChild( new TextNode("2345") ) )
2513                 .appendChild( new TextNode("6") ) )
2514             .appendChild( rby8.dup()
2515                 .appendChild( met5.dup()
2516                     .appendChild( new TextNode("78") ) ) )
2517             .appendChild( new TextNode("9") );
2518         doTest(root, false);
2519     }
2520 
2521     @Test public void testRangeMetaMeta() throws Exception
2522     {
2523         RangeInserter inserter = new RangeInserter(m_xDoc);
2524         TreeNode text = new TextNode("123456789");
2525         inserter.insertRange( new Range(0, 0, text) );
2526         TreeNode met1 = new MetaNode( mkId("id") );
2527         inserter.insertRange( new Range(3, 6, met1) );
2528         // overlap left
2529         TreeNode met2 = new MetaNode( mkId("id") );
2530         try {
2531             inserter.insertRange( new Range(0, 4, met2) );
2532             fail("testRangeMetaMeta: overlap left allowed");
2533         } catch (com.sun.star.lang.IllegalArgumentException e) { /* ignore */ }
2534         TreeNode root = new TreeNode()
2535             .appendChild( new TextNode("123") )
2536             .appendChild( met1.dup().appendChild( new TextNode("456") ) )
2537             .appendChild( new TextNode("789") );
2538         doTest(root, false);
2539         // overlap right
2540         TreeNode met3 = new MetaNode( mkId("id") );
2541         try {
2542             inserter.insertRange( new Range(5/*-1*/, 8/*-1*/, met3) );
2543             fail("testRangeMetaMeta: overlap right allowed");
2544         } catch (com.sun.star.lang.IllegalArgumentException e) { /* ignore */ }
2545         root = new TreeNode()
2546             .appendChild( new TextNode("123") )
2547             .appendChild( met1.dup().appendChild( new TextNode("456") ) )
2548             .appendChild( new TextNode("789") );
2549         doTest(root, false);
2550         // around
2551         TreeNode met4 = new MetaNode( mkId("id") );
2552         inserter.insertRange( new Range(3, 7/*-1*/, met4) );
2553         root = new TreeNode()
2554             .appendChild( new TextNode("123") )
2555             .appendChild( met4.dup()
2556                 .appendChild( met1.dup().appendChild( new TextNode("456") ) ) )
2557             .appendChild( new TextNode("789") );
2558         doTest(root, false);
2559         // inside
2560         TreeNode met5 = new MetaNode( mkId("id") );
2561         inserter.insertRange( new Range(6/*-2*/, 8/*-2*/, met5) );
2562         root = new TreeNode()
2563             .appendChild( new TextNode("123") )
2564             .appendChild( met4.dup()
2565                 .appendChild( met1.dup()
2566                     .appendChild( new TextNode("4") )
2567                     .appendChild( met5.dup()
2568                         .appendChild( new TextNode("56") ) ) ) )
2569             .appendChild( new TextNode("789") );
2570         doTest(root, false);
2571     }
2572 
2573     @Test public void testRange2() throws Exception
2574     {
2575         RangeInserter inserter = new RangeInserter(m_xDoc);
2576         TreeNode text = new TextNode("123456789");
2577         inserter.insertRange( new Range(0, 0, text) );
2578         TreeNode met1 = new MetaNode( mkId("id") );
2579         inserter.insertRange( new Range(1, 8, met1) );
2580         TreeNode met2 = new MetaNode( mkId("id") );
2581         inserter.insertRange( new Range(3/*-1*/, 8/*-1*/, met2) );
2582         TreeNode met3 = new MetaNode( mkId("id") );
2583         inserter.insertRange( new Range(5/*-2*/, 8/*-2*/, met3) );
2584         TreeNode root = new TreeNode()
2585             .appendChild( new TextNode("1") )
2586             .appendChild( met1.dup()
2587                 .appendChild( new TextNode("2") )
2588                 .appendChild( met2.dup()
2589                     .appendChild( new TextNode("3") )
2590                     .appendChild( met3.dup()
2591                         .appendChild( new TextNode("456") ) )
2592                     .appendChild( new TextNode("7") ) )
2593                 .appendChild( new TextNode("8") ) )
2594             .appendChild( new TextNode("9") );
2595         doTest(root, false);
2596         // split ruby at every meta start!
2597         TreeNode rby4 = new RubyNode( mkName("ruby") );
2598         inserter.insertRange( new Range(0, 7/*-3*/, rby4) );
2599         root = new TreeNode()
2600             .appendChild( rby4.dup()
2601                 .appendChild( new TextNode("1") ) )
2602             .appendChild( met1.dup()
2603                 .appendChild( rby4.dup()
2604                     .appendChild( new TextNode("2") ) )
2605                 .appendChild( met2.dup()
2606                     .appendChild( rby4.dup()
2607                         .appendChild( new TextNode("3") ) )
2608                     .appendChild( met3.dup()
2609                         .appendChild( rby4.dup()
2610                             .appendChild( new TextNode("4") ) )
2611                         .appendChild( new TextNode("56") ) )
2612                     .appendChild( new TextNode("7") ) )
2613                 .appendChild( new TextNode("8") ) )
2614             .appendChild( new TextNode("9") );
2615         doTest(root, false);
2616         // split ruby at every meta end!
2617         TreeNode rby5 = new RubyNode( mkName("ruby") );
2618         inserter.insertRange( new Range(8/*-3*/, 12/*-3*/, rby5) );
2619         root = new TreeNode()
2620             .appendChild( rby4.dup()
2621                 .appendChild( new TextNode("1") ) )
2622             .appendChild( met1.dup()
2623                 .appendChild( rby4.dup()
2624                     .appendChild( new TextNode("2") ) )
2625                 .appendChild( met2.dup()
2626                     .appendChild( rby4.dup()
2627                         .appendChild( new TextNode("3") ) )
2628                     .appendChild( met3.dup()
2629                         .appendChild( rby4.dup()
2630                             .appendChild( new TextNode("4") ) )
2631                         .appendChild( new TextNode("5") )
2632                         .appendChild( rby5.dup()
2633                             .appendChild( new TextNode("6") ) ) )
2634                     .appendChild( rby5.dup()
2635                         .appendChild( new TextNode("7") ) ) )
2636                 .appendChild( rby5.dup()
2637                     .appendChild( new TextNode("8") ) ) )
2638             .appendChild( rby5.dup()
2639                 .appendChild( new TextNode("9") ) );
2640         doTest(root, false);
2641     }
2642 
2643     @Test public void testRange3() throws Exception
2644     {
2645         RangeInserter inserter = new RangeInserter(m_xDoc);
2646         TreeNode text = new TextNode("123456789");
2647         inserter.insertRange( new Range(0, 0, text) );
2648         TreeNode rby1 = new RubyNode( mkName("ruby") );
2649         inserter.insertRange( new Range(0, 9, rby1) );
2650         TreeNode met2 = new MetaNode( mkId("id") );
2651         inserter.insertRange( new Range(2, 7, met2) );
2652         TreeNode root = new TreeNode()
2653             .appendChild( rby1.dup()
2654                 .appendChild( new TextNode("12") )
2655                 .appendChild( met2.dup()
2656                     .appendChild( new TextNode("34567") ) )
2657                 .appendChild( new TextNode("89") ) );
2658         doTest(root, false);
2659         // overwrite outer ruby, split remains at inner meta!
2660         TreeNode rby3 = new RubyNode( mkName("ruby") );
2661         inserter.insertRange( new Range(5/*-1*/, 6/*-1*/, rby3) );
2662         root = new TreeNode()
2663             .appendChild( rby1.dup()
2664                 .appendChild( new TextNode("12") ) )
2665             .appendChild( met2.dup()
2666                 .appendChild( rby1.dup()
2667                     .appendChild( new TextNode("34") ) )
2668                 .appendChild( rby3.dup()
2669                     .appendChild( new TextNode("5") ) )
2670                 .appendChild( rby1.dup()
2671                     .appendChild( new TextNode("67") ) ) )
2672             .appendChild( rby1.dup()
2673                 .appendChild( new TextNode("89") ) );
2674         doTest(root, false);
2675     }
2676 
2677     @Test public void testRange4() throws Exception
2678     {
2679         RangeInserter inserter = new RangeInserter(m_xDoc);
2680         TreeNode text = new TextNode("123456789");
2681         inserter.insertRange( new Range(0, 0, text) );
2682         TreeNode rby1 = new RubyNode( mkName("ruby") );
2683         inserter.insertRange( new Range(0, 9, rby1) );
2684         TreeNode met2 = new MetaNode( mkId("id") );
2685         inserter.insertRange( new Range(1, 8, met2) );
2686         TreeNode met3 = new MetaNode( mkId("id") );
2687         inserter.insertRange( new Range(3/*-1*/, 8/*-1*/, met3) );
2688         TreeNode met4 = new MetaNode( mkId("id") );
2689         inserter.insertRange( new Range(5/*-2*/, 8/*-2*/, met4) );
2690         TreeNode root = new TreeNode()
2691             .appendChild( rby1.dup()
2692                 .appendChild( new TextNode("1") )
2693                 .appendChild( met2.dup()
2694                     .appendChild( new TextNode("2") )
2695                     .appendChild( met3.dup()
2696                         .appendChild( new TextNode("3") )
2697                         .appendChild( met4.dup()
2698                             .appendChild( new TextNode("456") ) )
2699                         .appendChild( new TextNode("7") ) )
2700                     .appendChild( new TextNode("8") ) )
2701                 .appendChild( new TextNode("9") ) );
2702         doTest(root, false);
2703         // overwrite outer ruby, split remains at every inner meta!
2704         TreeNode rby5 = new RubyNode( mkName("ruby") );
2705         inserter.insertRange( new Range(7/*-3*/, 8/*-3*/, rby5) );
2706         root = new TreeNode()
2707             .appendChild( rby1.dup()
2708                 .appendChild( new TextNode("1") ) )
2709             .appendChild( met2.dup()
2710                 .appendChild( rby1.dup()
2711                     .appendChild( new TextNode("2") ) )
2712                 .appendChild( met3.dup()
2713                     .appendChild( rby1.dup()
2714                         .appendChild( new TextNode("3") ) )
2715                     .appendChild( met4.dup()
2716                         .appendChild( rby1.dup()
2717                             .appendChild( new TextNode("4") ) )
2718                         .appendChild( rby5.dup()
2719                             .appendChild( new TextNode("5") ) )
2720                         .appendChild( rby1.dup()
2721                             .appendChild( new TextNode("6") ) ) )
2722                     .appendChild( rby1.dup()
2723                         .appendChild( new TextNode("7") ) ) )
2724                 .appendChild( rby1.dup()
2725                     .appendChild( new TextNode("8") ) ) )
2726             .appendChild( rby1.dup()
2727                 .appendChild( new TextNode("9") ) );
2728         doTest(root, false);
2729     }
2730 
2731     @Test public void testRange5() throws Exception
2732     {
2733         RangeInserter inserter = new RangeInserter(m_xDoc);
2734         TreeNode text = new TextNode("123456789");
2735         inserter.insertRange( new Range(0, 0, text) );
2736         TreeNode rby1 = new RubyNode( mkName("ruby") );
2737         inserter.insertRange( new Range(0, 9, rby1) );
2738         TreeNode met2 = new MetaNode( mkId("id") );
2739         inserter.insertRange( new Range(1, 3, met2) );
2740         TreeNode met3 = new MetaNode( mkId("id") );
2741         inserter.insertRange( new Range(5/*-1*/, 6/*-1*/, met3) );
2742         TreeNode met4 = new MetaNode( mkId("id") );
2743         inserter.insertRange( new Range(8/*-2*/, 10/*-2*/, met4) );
2744         TreeNode root = new TreeNode()
2745             .appendChild( rby1.dup()
2746                 .appendChild( new TextNode("1") )
2747                 .appendChild( met2.dup().appendChild( new TextNode("23") ) )
2748                 .appendChild( new TextNode("4") )
2749                 .appendChild( met3.dup().appendChild( new TextNode("5") ) )
2750                 .appendChild( new TextNode("6") )
2751                 .appendChild( met4.dup().appendChild( new TextNode("78") ) )
2752                 .appendChild( new TextNode("9") ) );
2753         doTest(root, false);
2754         // overwrite outer ruby, but split at inner metas!
2755         TreeNode rby5 = new RubyNode( mkName("ruby") );
2756         inserter.insertRange( new Range(3/*-1*/, 10/*-3*/, rby5) );
2757         root = new TreeNode()
2758             .appendChild( rby1.dup()
2759                 .appendChild( new TextNode("1") ) )
2760             .appendChild( met2.dup()
2761                 .appendChild( rby1.dup()
2762                     .appendChild( new TextNode("2") ) )
2763                 .appendChild( rby5.dup()
2764                     .appendChild( new TextNode("3") ) ) )
2765             .appendChild( rby5.dup()
2766                 .appendChild( new TextNode("4") )
2767                 .appendChild( met3.dup()
2768                     .appendChild( new TextNode("5") ) )
2769                 .appendChild( new TextNode("6") ) )
2770             .appendChild( met4.dup()
2771                 .appendChild( rby5.dup()
2772                     .appendChild( new TextNode("7") ) )
2773                 .appendChild( rby1.dup()
2774                     .appendChild( new TextNode("8") ) ) )
2775             .appendChild( rby1.dup()
2776                 .appendChild( new TextNode("9") ) );
2777         doTest(root, false);
2778     }
2779 
2780     @Test public void testRange6() throws Exception
2781     {
2782         RangeInserter inserter = new RangeInserter(m_xDoc);
2783         TreeNode text = new TextNode("123456789");
2784         inserter.insertRange( new Range(0, 0, text) );
2785         TreeNode met1 = new MetaNode( mkId("id") );
2786         inserter.insertRange( new Range(1, 5, met1) );
2787         TreeNode met2 = new MetaNode( mkId("id") );
2788         inserter.insertRange( new Range(3/*-1*/, 6/*-1*/, met2) );
2789         TreeNode met3 = new MetaNode( mkId("id") );
2790         inserter.insertRange( new Range(5/*-2*/, 7/*-2*/, met3) );
2791         TreeNode root = new TreeNode()
2792             .appendChild( new TextNode("1") )
2793             .appendChild( met1.dup()
2794                 .appendChild( new TextNode("2") )
2795                 .appendChild( met2.dup()
2796                     .appendChild( new TextNode("3") )
2797                     .appendChild( met3.dup()
2798                         .appendChild( new TextNode("45") ) ) ) )
2799             .appendChild( new TextNode("6789") );
2800         doTest(root, false);
2801         // split at 3 metas, all at same position
2802         TreeNode rby4 = new RubyNode( mkName("ruby") );
2803         inserter.insertRange( new Range(7/*-3*/, 10/*-3*/, rby4) );
2804         root = new TreeNode()
2805             .appendChild( new TextNode("1") )
2806             .appendChild( met1.dup()
2807                 .appendChild( new TextNode("2") )
2808                 .appendChild( met2.dup()
2809                     .appendChild( new TextNode("3") )
2810                     .appendChild( met3.dup()
2811                         .appendChild( new TextNode("4") )
2812                         .appendChild( rby4.dup()
2813                             .appendChild( new TextNode("5") ) ) ) ) )
2814             .appendChild( rby4.dup()
2815                 .appendChild( new TextNode("67") ) )
2816             .appendChild( new TextNode("89") );
2817         doTest(root, false);
2818     }
2819 
2820     @Test public void testRange7() throws Exception
2821     {
2822         RangeInserter inserter = new RangeInserter(m_xDoc);
2823         TreeNode text = new TextNode("123456789");
2824         inserter.insertRange( new Range(0, 0, text) );
2825         TreeNode url1 = new HyperlinkNode( mkName("url") );
2826         inserter.insertRange( new Range(1, 5, url1) );
2827         TreeNode met2 = new MetaNode( mkId("id") );
2828         inserter.insertRange( new Range(3, 5, met2) );
2829         TreeNode root = new TreeNode()
2830             .appendChild( new TextNode("1") )
2831             .appendChild( url1.dup()
2832                 .appendChild( new TextNode("23") ) )
2833             .appendChild( met2.dup()
2834                 .appendChild( url1.dup()
2835                     .appendChild( new TextNode("45") ) ) )
2836             .appendChild( new TextNode("6789") );
2837         doTest(root, false);
2838         // this should result in not splitting the hyperlink, but due to API
2839         // we can't tell :(
2840         TreeNode rby3 = new RubyNode( mkName("ruby") );
2841         inserter.insertRange( new Range(5/*-1*/, 8/*-1*/, rby3) );
2842         root = new TreeNode()
2843             .appendChild( new TextNode("1") )
2844             .appendChild( url1.dup()
2845                 .appendChild( new TextNode("23") ) )
2846             .appendChild( met2.dup()
2847                 .appendChild( url1.dup()
2848                     .appendChild( new TextNode("4") ) )
2849                 .appendChild( rby3.dup()
2850                     .appendChild( url1.dup()
2851                         .appendChild( new TextNode("5") ) ) ) )
2852             .appendChild( rby3.dup()
2853                 .appendChild( new TextNode("67") ) )
2854             .appendChild( new TextNode("89") );
2855         doTest(root, false);
2856     }
2857 
2858     /* TODO: test partial selection, test UNDO/REDO */
2859 
2860     // #i109601# NestedTextContent and XChild
2861     @Test public void testMetaXChild() throws Exception
2862     {
2863         StringPair id1 = new StringPair("content.xml", mkName("id"));
2864         StringPair id2 = new StringPair("content.xml", mkName("id"));
2865         StringPair id3 = new StringPair("content.xml", mkName("id"));
2866         StringPair id4 = new StringPair("content.xml", mkName("id"));
2867         StringPair id5 = new StringPair("content.xml", mkName("id"));
2868         StringPair id6 = new StringPair("content.xml", mkName("id"));
2869         TreeNode meta1 = new MetaNode(id1);
2870         TreeNode meta2 = new MetaNode(id2);
2871         TreeNode meta3 = new MetaFieldNode(id3);
2872         TreeNode meta4 = new MetaNode(id4);
2873         TreeNode meta5 = new MetaNode(id5);
2874         TreeNode meta6 = new MetaFieldNode(id6);
2875         TreeNode root = new TreeNode()
2876             .appendChild( meta1.dup()
2877                 .appendChild( new TextNode("1") ) )
2878             .appendChild( new TextNode("2") )
2879             .appendChild( meta2.dup()
2880                 .appendChild( meta3.dup()
2881                     .appendChild( new TextNode("34") )
2882                     .appendChild( meta4.dup()
2883                         .appendChild( new TextNode("56") ) )
2884                     .appendChild( meta5.dup() )
2885                     .appendChild( new TextNode("7") ) ) )
2886             .appendChild( new TextNode("8") )
2887             .appendChild( meta6.dup()
2888                 .appendChild( new TextNode("9") ) );
2889 
2890         RangeInserter inserter = new RangeInserter(m_xDoc);
2891         TreeNode text = new TextNode("123456789");
2892         inserter.insertRange( new Range(0, 0, text) );
2893         XTextContent xMeta1 = inserter.insertRange( new Range(0, 1, meta1) );
2894         XTextContent xMeta2 = inserter.insertRange( new Range(3, 8, meta2) );
2895         XTextContent xMeta3 = inserter.insertRange( new Range(4, 9, meta3) );
2896         XTextContent xMeta4 = inserter.insertRange( new Range(7, 9, meta4) );
2897         XTextContent xMeta5 = inserter.insertRange( new Range(10, 10, meta5) );
2898         XTextContent xMeta6 = inserter.insertRange( new Range(13, 14, meta6) );
2899 
2900         doTest(root, false);
2901 
2902         XText xDocText = m_xDoc.getText();
2903         XTextCursor xDocTextCursor = xDocText.createTextCursor();
2904         XParagraphCursor xParagraphCursor = (XParagraphCursor)
2905             UnoRuntime.queryInterface(XParagraphCursor.class, xDocTextCursor);
2906         xParagraphCursor.gotoNextParagraph(false); // second paragraph
2907         // X12XX34X56X78X9
2908         // 1  23  4  5  6
2909         //  1       452  6
2910         //            3
2911         StringPair [] nestedTextContent = new StringPair[] {
2912             null,
2913             id1,
2914             id1,
2915             null,
2916             id2,
2917             id3,
2918             id3,
2919             id3,
2920             id4,
2921             id4,
2922             id4,
2923             id5,
2924             id3,
2925             null,
2926             id6,
2927             id6,
2928         };
2929         XPropertySet xPropertySet = (XPropertySet)
2930             UnoRuntime.queryInterface(XPropertySet.class, xDocTextCursor);
2931         for (int i = 0; i < nestedTextContent.length; ++i) {
2932             Object oNTC = xPropertySet.getPropertyValue("NestedTextContent");
2933             XTextContent xNTC = (XTextContent)
2934                 UnoRuntime.queryInterface(XTextContent.class, oNTC);
2935             if (null == nestedTextContent[i]) {
2936                 assertNull("unexpected NestedTextContent at: " + i, xNTC);
2937             } else {
2938                 XMetadatable xMetadatable = (XMetadatable)
2939                     UnoRuntime.queryInterface(XMetadatable.class, xNTC);
2940                 StringPair xmlid = xMetadatable.getMetadataReference();
2941                 assertTrue("wrong NestedTextContent at: " + i,
2942                     MetaNode.eq(nestedTextContent[i], xmlid));
2943             }
2944             xDocTextCursor.goRight((short)1, false);
2945         }
2946 
2947         XChild xChild1 = (XChild)
2948             UnoRuntime.queryInterface(XChild.class, xMeta1);
2949         XChild xChild2 = (XChild)
2950             UnoRuntime.queryInterface(XChild.class, xMeta2);
2951         XChild xChild3 = (XChild)
2952             UnoRuntime.queryInterface(XChild.class, xMeta3);
2953         XChild xChild4 = (XChild)
2954             UnoRuntime.queryInterface(XChild.class, xMeta4);
2955         XChild xChild5 = (XChild)
2956             UnoRuntime.queryInterface(XChild.class, xMeta5);
2957         XChild xChild6 = (XChild)
2958             UnoRuntime.queryInterface(XChild.class, xMeta6);
2959         try {
2960             xChild1.setParent(xChild4);
2961             fail("setParent(): allowed?");
2962         } catch (NoSupportException e) { /* expected */ }
2963         assertNull("getParent(): not null", xChild1.getParent());
2964         assertNull("getParent(): not null", xChild2.getParent());
2965         assertNull("getParent(): not null", xChild6.getParent());
2966         {
2967             Object xParent3 = xChild3.getParent();
2968             assertNotNull("getParent(): null", xParent3);
2969             XMetadatable xMetadatable = (XMetadatable)
2970                 UnoRuntime.queryInterface(XMetadatable.class, xParent3);
2971             StringPair xmlid = xMetadatable.getMetadataReference();
2972             assertTrue("getParent(): wrong", MetaNode.eq(xmlid, id2));
2973         }{
2974             Object xParent4 = xChild4.getParent();
2975             assertNotNull("getParent(): null", xParent4);
2976             XMetadatable xMetadatable = (XMetadatable)
2977                 UnoRuntime.queryInterface(XMetadatable.class, xParent4);
2978             StringPair xmlid = xMetadatable.getMetadataReference();
2979             assertTrue("getParent(): wrong", MetaNode.eq(xmlid, id3));
2980         }{
2981             Object xParent5 = xChild5.getParent();
2982             assertNotNull("getParent(): null", xParent5);
2983             XMetadatable xMetadatable = (XMetadatable)
2984                 UnoRuntime.queryInterface(XMetadatable.class, xParent5);
2985             StringPair xmlid = xMetadatable.getMetadataReference();
2986             assertTrue("getParent(): wrong", MetaNode.eq(xmlid, id3));
2987         }
2988     }
2989 
2990     /** test SwXMeta XText interface */
2991     @Test public void testMetaXText() throws Exception
2992     {
2993         RangeInserter inserter = new RangeInserter(m_xDoc);
2994         TreeNode text = new TextNode("12AB6789");
2995         inserter.insertRange( new Range(0, 0, text) );
2996         MetaNode meta = new MetaNode( mkId("id") );
2997 //        inserter.insertRange( new Range(3, 5, met2) );
2998         XTextContent xMeta = inserter.makeMeta();
2999 
3000         XText xDocText = m_xDoc.getText();
3001         XTextCursor xDocTextCursor = xDocText.createTextCursor();
3002         xDocTextCursor.goRight((short)3, false);
3003         xDocTextCursor.goRight((short)2, true);
3004         xDocText.insertTextContent(xDocTextCursor, xMeta, true);
3005 //        xMeta.attach(xDocTextCursor);
3006 
3007         XMetadatable xMetadatable = (XMetadatable)
3008             UnoRuntime.queryInterface(XMetadatable.class, xMeta);
3009         xMetadatable.setMetadataReference(meta.getXmlId());
3010         XText xText = (XText) UnoRuntime.queryInterface(XText.class, xMeta);
3011 
3012         XText xParentText = xText.getText();
3013         assertNotNull("getText(): no parent", xParentText);
3014 
3015         XTextRange xStart = xText.getStart();
3016         assertNotNull("getStart(): no start", xStart);
3017 
3018         XTextRange xEnd = xText.getEnd();
3019         assertNotNull("getEnd(): no end", xEnd);
3020 
3021         xText.setString("45");
3022 
3023         {
3024             String string = xText.getString();
3025             assertEquals("getString(): invalid string returned",
3026                          "45", string);
3027         }
3028 
3029         XTextCursor xTextCursor = xText.createTextCursor();
3030         assertNotNull("createTextCursor(): failed", xTextCursor);
3031 
3032         try {
3033             xText.createTextCursorByRange(null);
3034             fail("createTextCursorByRange(): null allowed?");
3035         } catch (RuntimeException e) { /* expected */ }
3036 
3037         XTextCursor xTextCursorStart = xText.createTextCursorByRange(xStart);
3038         assertNotNull("createTextCursorByRange(): failed for start",
3039                       xTextCursorStart);
3040 
3041         XTextCursor xTextCursorEnd = xText.createTextCursorByRange(xEnd);
3042         assertNotNull("createTextCursorByRange(): failed for end",
3043                       xTextCursorEnd);
3044 
3045         // move outside meta
3046         xDocTextCursor.gotoStart(false);
3047 
3048         try {
3049             xText.insertString(null, "foo", false);
3050             fail("insertString(): null allowed?");
3051         } catch (RuntimeException e) { /* expected */ }
3052 
3053         try {
3054             xText.insertString(xDocTextCursor, "foo", false);
3055             fail("insertString(): cursor outside allowed?");
3056         } catch (RuntimeException e) { /* expected */ }
3057 
3058         xStart = xText.getStart();
3059         xText.insertString(xStart, "A", false);
3060         {
3061             String string = xText.getString();
3062             assertEquals("getString(): invalid string returned",
3063                          "A45", string);
3064         }
3065 
3066         xText.insertString(xEnd, "B", false);
3067         {
3068             String string = xText.getString();
3069             assertEquals("getString(): invalid string returned",
3070                          "A45B", string);
3071         }
3072 
3073         try {
3074             xText.insertControlCharacter(null, HARD_HYPHEN, false);
3075             fail("insertControlCharacter(): null allowed?");
3076         } catch (com.sun.star.lang.IllegalArgumentException e) { /* ignore */ }
3077 
3078         xStart = xText.getStart();
3079         try {
3080             xText.insertControlCharacter(xDocTextCursor, HARD_HYPHEN, false);
3081             fail("insertControlCharacter(): cursor outside allowed?");
3082         } catch (com.sun.star.lang.IllegalArgumentException e) { /* ignore */ }
3083 
3084         xText.insertControlCharacter(xStart, HARD_HYPHEN, false);
3085         {
3086             String string = xText.getString();
3087             assertEquals("getString(): invalid string returned",
3088                          '\u2011' + "A45B", string);
3089         }
3090 
3091         xText.insertControlCharacter(xEnd, HARD_HYPHEN, false);
3092         {
3093             String string = xText.getString();
3094             assertEquals("getString(): invalid string returned",
3095                          '\u2011' + "A45B" + '\u2011', string);
3096         }
3097 
3098         xText.setString("45");
3099 
3100         try {
3101             xText.insertTextContent(null, xMeta, false);
3102             fail("insertTextContent(): null range allowed?");
3103         } catch (com.sun.star.lang.IllegalArgumentException e) { /* ignore */ }
3104 
3105         try {
3106             xText.insertTextContent(xStart, null, false);
3107             fail("insertTextContent(): null content allowed?");
3108         } catch (com.sun.star.lang.IllegalArgumentException e) { /* ignore */ }
3109 
3110         try {
3111             xText.insertTextContent(xDocTextCursor, xMeta, false);
3112             fail("insertTextContent(): cursor outside allowed?");
3113         } catch (com.sun.star.lang.IllegalArgumentException e) { /* ignore */ }
3114 
3115         TextFieldNode field1 = new TextFieldNode( "f1" );
3116         TextFieldNode field2 = new TextFieldNode( "f2" );
3117         XTextContent xField1 = inserter.makeTextField(field1.getContent());
3118         XTextContent xField2 = inserter.makeTextField(field2.getContent());
3119 
3120         xStart = xText.getStart();
3121         xText.insertTextContent(xStart, xField1, false);
3122 
3123         TreeNode root = new TreeNode()
3124             .appendChild( new TextNode("12") )
3125             .appendChild( meta.dup()
3126                 .appendChild( field1.dup() )
3127                 .appendChild( new TextNode("45") ) )
3128             .appendChild( new TextNode("6789") );
3129         doTest(root, false);
3130 
3131         xText.insertTextContent(xEnd, xField2, false);
3132 
3133         root = new TreeNode()
3134             .appendChild( new TextNode("12") )
3135             .appendChild( meta.dup()
3136                 .appendChild( field1.dup() )
3137                 .appendChild( new TextNode("45") )
3138                 .appendChild( field2.dup() ) )
3139             .appendChild( new TextNode("6789") );
3140         doTest(root, false);
3141 
3142         try {
3143             xText.removeTextContent(null);
3144             fail("removeTextContent(): null content allowed?");
3145         } catch (RuntimeException e) { /* expected */ }
3146 
3147         xText.removeTextContent(xField1);
3148 
3149         XTextRange xAnchor = xMeta.getAnchor();
3150         assertNotNull("getAnchor(): null", xAnchor);
3151 
3152         // evil test case: insert ruby around meta
3153         RubyNode ruby = new RubyNode( mkName("ruby") );
3154         inserter.insertRange( new Range(2, 6, ruby) );
3155 
3156         /* prevent caching...
3157         root = new TreeNode()
3158             .appendChild( new TextNode("12") )
3159             .appendChild( ruby.dup()
3160                 .appendChild( meta.dup()
3161                     .appendChild( new TextNode("45") )
3162                     .appendChild( field2.dup() ) ) )
3163             .appendChild( new TextNode("6789") );
3164         doTest(root, false);
3165         */
3166 
3167         XEnumerationAccess xEA = (XEnumerationAccess)
3168             UnoRuntime.queryInterface(XEnumerationAccess.class, xMeta);
3169         XEnumeration xEnum = xEA.createEnumeration();
3170         assertNotNull("createEnumeration(): returns null", xEnum);
3171         {
3172             assertTrue("hasNext(): first missing", xEnum.hasMoreElements());
3173             Object xElement = xEnum.nextElement();
3174             XTextRange xPortion = (XTextRange)
3175                 UnoRuntime.queryInterface(XTextRange.class, xElement);
3176             XPropertySet xPropSet = (XPropertySet)
3177                 UnoRuntime.queryInterface(XPropertySet.class, xPortion);
3178             String type = (String) xPropSet.getPropertyValue("TextPortionType");
3179             assertEquals("first: not text", "Text", type);
3180             String txt = xPortion.getString();
3181             assertEquals("first: text differs", "45", txt);
3182         }
3183         {
3184             assertTrue("hasNext(): second missing", xEnum.hasMoreElements());
3185             Object xElement = xEnum.nextElement();
3186             XTextRange xPortion = (XTextRange)
3187                 UnoRuntime.queryInterface(XTextRange.class, xElement);
3188             XPropertySet xPropSet = (XPropertySet)
3189                 UnoRuntime.queryInterface(XPropertySet.class, xPortion);
3190             String type = (String) xPropSet.getPropertyValue("TextPortionType");
3191             assertEquals("second: not text", "TextField", type);
3192         }
3193         // no ruby end here!!!
3194         assertFalse("hasNext(): more elements?", xEnum.hasMoreElements());
3195 
3196         XComponent xComponent = (XComponent)
3197             UnoRuntime.queryInterface(XComponent.class, xMeta);
3198         xComponent.dispose();
3199 
3200         try {
3201             XTextCursor xCursor = xText.createTextCursor();
3202             assertNull("createTextCursor(): succeeds on disposed object?",
3203                        xCursor);
3204         } catch (RuntimeException e) { /* expected */ }
3205     }
3206 
3207     /** check that cursor move methods move to positions in the meta,
3208         but do not move to positions outside the meta. */
3209     @Test public void testMetaXTextCursor() throws Exception
3210     {
3211         RangeInserter inserter = new RangeInserter(m_xDoc);
3212         TreeNode text = new TextNode("Text. 12 More text here.");
3213         inserter.insertRange( new Range(0, 0, text) );
3214         MetaNode met1 = new MetaNode( mkId("id") );
3215         XTextContent xMeta = inserter.makeMeta();
3216 
3217         XText xDocText = m_xDoc.getText();
3218         XTextCursor xDocTextCursor = xDocText.createTextCursor();
3219         xDocTextCursor.goRight((short)7, false);
3220         xDocTextCursor.goRight((short)2, true);
3221         xDocText.insertTextContent(xDocTextCursor, xMeta, true);
3222         xDocTextCursor.gotoStart(true);
3223 
3224         XMetadatable xMetadatable = (XMetadatable)
3225             UnoRuntime.queryInterface(XMetadatable.class, xMeta);
3226         xMetadatable.setMetadataReference(met1.getXmlId());
3227         XText xText = (XText) UnoRuntime.queryInterface(XText.class, xMeta);
3228 
3229         XTextRange xStart = xText.getStart();
3230         assertNotNull("getStart(): no start", xStart);
3231         XTextRange xEnd = xText.getEnd();
3232         assertNotNull("getEnd(): no end", xEnd);
3233 
3234         XTextCursor xTextCursor = xText.createTextCursor();
3235         assertNotNull("createTextCursor(): no cursor", xTextCursor);
3236 
3237         // XTextCursor
3238         boolean bSuccess = false;
3239         xTextCursor.gotoStart(false);
3240         xTextCursor.gotoEnd(false);
3241         bSuccess = xTextCursor.goLeft((short)1, false);
3242         assertTrue("goLeft(): failed", bSuccess);
3243         bSuccess = xTextCursor.goLeft((short)1000, false);
3244         assertFalse("goLeft(): succeeded", bSuccess);
3245         bSuccess = xTextCursor.goRight((short)1, false);
3246         assertTrue("goRight(): failed", bSuccess);
3247         bSuccess = xTextCursor.goRight((short)1000, false);
3248         assertFalse("goRight(): succeeded", bSuccess);
3249         xTextCursor.gotoRange(xStart, false);
3250         xTextCursor.gotoRange(xEnd, false);
3251         try {
3252             xTextCursor.gotoRange(xDocTextCursor, false);
3253             fail("gotoRange(): succeeded");
3254         } catch (RuntimeException e) { /* expected */ }
3255 
3256         // XWordCursor
3257         xText.setString("Two words");
3258         xTextCursor.gotoStart(false);
3259         XWordCursor xWordCursor = (XWordCursor)
3260             UnoRuntime.queryInterface(XWordCursor.class, xTextCursor);
3261 
3262         bSuccess = xWordCursor.gotoNextWord(true);
3263         assertTrue("gotoNextWord(): failed", bSuccess);
3264         {
3265             String string = xTextCursor.getString();
3266             assertEquals("gotoNextWord(): wrong string",
3267                          "Two ", string);
3268         }
3269         bSuccess = xWordCursor.gotoNextWord(false);
3270         assertFalse("gotoNextWord(): succeeded", bSuccess);
3271         xTextCursor.collapseToEnd();
3272         bSuccess = xWordCursor.gotoPreviousWord(true);
3273         assertTrue("gotoPreviousWord(): failed", bSuccess);
3274         {
3275             String string = xTextCursor.getString();
3276             assertEquals("gotoPreviousWord(): wrong string",
3277                          "words", string);
3278         }
3279         bSuccess = xWordCursor.gotoPreviousWord(false);
3280         assertFalse("gotoPreviousWord(): succeeded", bSuccess);
3281         bSuccess = xWordCursor.gotoEndOfWord(true);
3282         assertTrue("gotoEndOfWord(): failed", bSuccess);
3283         {
3284             String string = xTextCursor.getString();
3285             assertEquals("gotoEndOfWord(): wrong string",
3286                          "Two", string);
3287         }
3288         xTextCursor.gotoEnd(false);
3289         bSuccess = xWordCursor.gotoStartOfWord(true);
3290         assertTrue("gotoStartOfWord(): failed", bSuccess);
3291         {
3292             String string = xTextCursor.getString();
3293             assertEquals("gotoStartOfWord(): wrong string",
3294                          "words", string);
3295         }
3296         xText.setString("");
3297         bSuccess = xWordCursor.gotoEndOfWord(false);
3298         assertFalse("gotoEndOfWord(): succeeded", bSuccess);
3299         bSuccess = xWordCursor.gotoStartOfWord(false);
3300         assertFalse("gotoStartOfWord(): succeeded", bSuccess);
3301 
3302         // XSentenceCursor
3303         xText.setString("This is a sentence. Another sentence.");
3304         xTextCursor.gotoStart(false);
3305         XSentenceCursor xSentenceCursor = (XSentenceCursor)
3306             UnoRuntime.queryInterface(XSentenceCursor.class, xTextCursor);
3307 
3308         bSuccess = xSentenceCursor.gotoNextSentence(true);
3309         assertTrue("gotoNextSentence(): failed", bSuccess);
3310         {
3311             String string = xTextCursor.getString();
3312             assertEquals("gotoNextSentence(): wrong string",
3313                          "This is a sentence. ", string);
3314         }
3315         bSuccess = xSentenceCursor.gotoNextSentence(false);
3316         assertFalse("gotoNextSentence(): succeeded", bSuccess);
3317         // FIXME:
3318         // the sentence cursor seems to work differently than the word cursor
3319         xText.setString("This is a sentence. Another sentence. Sentence 3.");
3320         xTextCursor.gotoEnd(false);
3321         bSuccess = xSentenceCursor.gotoPreviousSentence(true);
3322         assertTrue("gotoPreviousSentence(): failed", bSuccess);
3323         {
3324             String string = xTextCursor.getString();
3325             assertEquals("gotoPreviousSentence(): wrong string",
3326                          "Another sentence. Sentence 3.", string);
3327         }
3328         bSuccess = xSentenceCursor.gotoPreviousSentence(false);
3329         assertFalse("gotoPreviousSentence(): succeeded", bSuccess);
3330         bSuccess = xSentenceCursor.gotoEndOfSentence(true);
3331         assertTrue("gotoEndOfSentence(): failed", bSuccess);
3332         {
3333             String string = xTextCursor.getString();
3334             assertEquals("gotoEndOfSentence(): wrong string",
3335                          "This is a sentence.", string);
3336         }
3337         xTextCursor.gotoEnd(false);
3338         bSuccess = xSentenceCursor.gotoStartOfSentence(true);
3339         assertTrue("gotoStartOfSentence(): failed", bSuccess);
3340         {
3341             String string = xTextCursor.getString();
3342             assertEquals("gotoStartOfSentence(): wrong string",
3343                          "Sentence 3.", string);
3344         }
3345         xText.setString("");
3346         bSuccess = xSentenceCursor.gotoEndOfSentence(false);
3347         assertFalse("gotoEndOfSentence(): succeeded", bSuccess);
3348         bSuccess = xSentenceCursor.gotoStartOfSentence(false);
3349         assertFalse("gotoStartOfSentence(): succeeded", bSuccess);
3350 
3351         XParagraphCursor xParagraphCursor = (XParagraphCursor)
3352             UnoRuntime.queryInterface(XParagraphCursor.class, xTextCursor);
3353 
3354         // XParagraphCursor (does not make sense)
3355         bSuccess = xParagraphCursor.gotoNextParagraph(false);
3356         assertFalse("gotoNextParagraph(): succeeded", bSuccess);
3357         bSuccess = xParagraphCursor.gotoPreviousParagraph(false);
3358         assertFalse("gotoPreviousParagraph(): succeeded", bSuccess);
3359         bSuccess = xParagraphCursor.gotoStartOfParagraph(false);
3360         assertFalse("gotoStartOfParagraph(): succeeded", bSuccess);
3361         bSuccess = xParagraphCursor.gotoEndOfParagraph(false);
3362         assertFalse("gotoEndOfParagraph(): succeeded", bSuccess);
3363     }
3364 
3365 
3366     abstract class AttachHelper
3367     {
3368         abstract boolean isAttribute();
3369         abstract TreeNode mkTreeNode();
3370         abstract XTextContent mkTextContent(Inserter inserter, TreeNode node)
3371             throws Exception;
3372         void postInserted(TreeNode node, XTextContent xContent)
3373             throws Exception { }
3374     }
3375 
3376     @Test public void testMetaXTextAttachToxMark() throws Exception
3377     {
3378         doMetaXTextAttach( new AttachHelper()
3379             {
3380                 boolean isAttribute() { return true; }
3381                 TreeNode mkTreeNode() {
3382                     return new DocumentIndexMarkNode( mkName("toxmark") );
3383                 }
3384                 XTextContent mkTextContent(Inserter inserter, TreeNode node)
3385                         throws Exception {
3386                     return inserter.makeDocumentIndexMark(
3387                         ((DocumentIndexMarkNode)node).getName());
3388                 }
3389             });
3390     }
3391 
3392     @Test public void testMetaXTextAttachRefMark() throws Exception
3393     {
3394         doMetaXTextAttach( new AttachHelper()
3395             {
3396                 boolean isAttribute() { return true; }
3397                 TreeNode mkTreeNode() {
3398                     return new ReferenceMarkNode( mkName("refmark") );
3399                 }
3400                 XTextContent mkTextContent(Inserter inserter, TreeNode node)
3401                         throws Exception {
3402                     return inserter.makeReferenceMark(
3403                         ((ReferenceMarkNode)node).getName());
3404                 }
3405             });
3406     }
3407 
3408     @Test public void testMetaXTextAttachTextField() throws Exception
3409     {
3410         doMetaXTextAttach( new AttachHelper()
3411             {
3412                 boolean isAttribute() { return false; }
3413                 TreeNode mkTreeNode() {
3414                     return new TextFieldNode( mkName("field") );
3415                 }
3416                 XTextContent mkTextContent(Inserter inserter, TreeNode node)
3417                         throws Exception {
3418                     return inserter.makeTextField(
3419                         ((TextFieldNode)node).getContent());
3420                 }
3421             });
3422     }
3423 
3424     @Test public void testMetaXTextAttachFootnote() throws Exception
3425     {
3426         doMetaXTextAttach( new AttachHelper()
3427             {
3428                 boolean isAttribute() { return false; }
3429                 TreeNode mkTreeNode() {
3430                     return new FootnoteNode( mkName("ftn") );
3431                 }
3432                 XTextContent mkTextContent(Inserter inserter, TreeNode node)
3433                         throws Exception {
3434                     return inserter.makeFootnote(
3435                         ((FootnoteNode)node).getLabel());
3436                 }
3437             });
3438     }
3439 
3440     @Test public void testMetaXTextAttachMeta() throws Exception
3441     {
3442         doMetaXTextAttach( new AttachHelper()
3443             {
3444                 boolean isAttribute() { return true; }
3445                 TreeNode mkTreeNode() {
3446                     return new MetaNode( mkId("id") );
3447                 }
3448                 XTextContent mkTextContent(Inserter inserter, TreeNode node)
3449                         throws Exception {
3450                     return inserter.makeMeta();
3451                 }
3452                 void postInserted(TreeNode node, XTextContent xContent)
3453                         throws Exception {
3454                     XMetadatable xMetadatable = (XMetadatable)
3455                         UnoRuntime.queryInterface(XMetadatable.class, xContent);
3456                     xMetadatable.setMetadataReference(
3457                             ((MetaNode)node).getXmlId());
3458                 }
3459             });
3460     }
3461 
3462     void doMetaXTextAttach(AttachHelper helper) throws Exception
3463     {
3464         RangeInserter inserter = new RangeInserter(m_xDoc);
3465         TreeNode text = new TextNode("12AB6789");
3466         inserter.insertRange( new Range(0, 0, text) );
3467         MetaNode met1 = new MetaNode( mkId("id") );
3468         XTextContent xMeta = inserter.makeMeta();
3469 
3470         XText xDocText = m_xDoc.getText();
3471         XTextCursor xDocTextCursor = xDocText.createTextCursor();
3472         xDocTextCursor.goRight((short)3, false);
3473         xDocTextCursor.goRight((short)2, true);
3474         xDocText.insertTextContent(xDocTextCursor, xMeta, true);
3475 
3476         XMetadatable xMetadatable = (XMetadatable)
3477             UnoRuntime.queryInterface(XMetadatable.class, xMeta);
3478         xMetadatable.setMetadataReference(met1.getXmlId());
3479         XText xText = (XText) UnoRuntime.queryInterface(XText.class, xMeta);
3480         XTextRange xStart = null;
3481         XTextRange xEnd = null;
3482 
3483         {
3484             xStart = xText.getStart();
3485             xEnd = xText.getEnd();
3486 
3487             TreeNode nod1 = helper.mkTreeNode();
3488             TreeNode nod2 = helper.mkTreeNode();
3489             XTextContent xContent1 = helper.mkTextContent(inserter, nod1);
3490             XTextContent xContent2 = helper.mkTextContent(inserter, nod2);
3491 
3492             // insertTextContent with meta getStart()/getEnd()
3493             xText.insertTextContent(xStart, xContent1, false);
3494             xText.insertTextContent(xEnd  , xContent2, false);
3495 
3496             helper.postInserted(nod1, xContent1);
3497             helper.postInserted(nod2, xContent2);
3498 
3499             TreeNode root = new TreeNode()
3500                 .appendChild( new TextNode("12") )
3501                 .appendChild( met1.dup()
3502                     .appendChild( nod1.dup() )
3503                     .appendChild( new TextNode("AB") )
3504                     .appendChild( nod2.dup() ) )
3505                 .appendChild( new TextNode("6789") );
3506             doTest(root, false);
3507         }
3508         {
3509             xText.setString("AB");
3510             xStart = xText.getStart();
3511             xEnd = xText.getEnd();
3512 
3513             TreeNode nod1 = helper.mkTreeNode();
3514             TreeNode nod2 = helper.mkTreeNode();
3515             XTextContent xContent1 = helper.mkTextContent(inserter, nod1);
3516             XTextContent xContent2 = helper.mkTextContent(inserter, nod2);
3517 
3518             XTextCursor xTextCursor = xText.createTextCursor();
3519             xTextCursor.gotoStart(false);
3520 
3521             // insertTextContent with meta cursor
3522             xText.insertTextContent(xTextCursor, xContent1, false);
3523             xTextCursor.gotoEnd(false);
3524             xText.insertTextContent(xTextCursor, xContent2, false);
3525 
3526             helper.postInserted(nod1, xContent1);
3527             helper.postInserted(nod2, xContent2);
3528 
3529             TreeNode root = new TreeNode()
3530                 .appendChild( new TextNode("12") )
3531                 .appendChild( met1.dup()
3532                     .appendChild( nod1.dup() )
3533                     .appendChild( new TextNode("AB") )
3534                     .appendChild( nod2.dup() ) )
3535                 .appendChild( new TextNode("6789") );
3536             doTest(root, false);
3537         }
3538         if (!helper.isAttribute())
3539         {
3540 //            xText.setString("AB");
3541             xStart = xText.getStart();
3542             xEnd = xText.getEnd();
3543 
3544             TreeNode nod1 = helper.mkTreeNode();
3545             TreeNode nod2 = helper.mkTreeNode();
3546             XTextContent xContent1 = helper.mkTextContent(inserter, nod1);
3547             XTextContent xContent2 = helper.mkTextContent(inserter, nod2);
3548 
3549             XTextCursor xTextCursor = xText.createTextCursor();
3550             xTextCursor.gotoStart(false);
3551             xTextCursor.goRight((short)1, true);
3552 
3553             // insertTextContent with meta cursor and absorb
3554             xText.insertTextContent(xTextCursor, xContent1, true);
3555             xTextCursor.gotoEnd(false);
3556             xTextCursor.goLeft((short)1, true);
3557             xText.insertTextContent(xTextCursor, xContent2, true);
3558 
3559             helper.postInserted(nod1, xContent1);
3560             helper.postInserted(nod2, xContent2);
3561 
3562             TreeNode root = new TreeNode()
3563                 .appendChild( new TextNode("12") )
3564                 .appendChild( met1.dup()
3565                     .appendChild( nod1.dup() )
3566                     .appendChild( new TextNode("AB") )
3567                     .appendChild( nod2.dup() ) )
3568                 .appendChild( new TextNode("6789") );
3569             doTest(root, false);
3570         }
3571         {
3572             xText.setString("AB");
3573             xStart = xText.getStart();
3574             xEnd = xText.getEnd();
3575 
3576             TreeNode nod1 = helper.mkTreeNode();
3577             TreeNode nod2 = helper.mkTreeNode();
3578             XTextContent xContent1 = helper.mkTextContent(inserter, nod1);
3579             XTextContent xContent2 = helper.mkTextContent(inserter, nod2);
3580 
3581             xDocTextCursor.gotoRange(xStart, false);
3582 
3583             // insertTextContent with document cursor
3584             xText.insertTextContent(xDocTextCursor, xContent1, false);
3585             xDocTextCursor.gotoRange(xEnd, false);
3586             xText.insertTextContent(xDocTextCursor, xContent2, false);
3587 
3588             helper.postInserted(nod1, xContent1);
3589             helper.postInserted(nod2, xContent2);
3590 
3591             TreeNode root = new TreeNode()
3592                 .appendChild( new TextNode("12") )
3593                 .appendChild( met1.dup()
3594                     .appendChild( nod1.dup() )
3595                     .appendChild( new TextNode("AB") )
3596                     .appendChild( nod2.dup() ) )
3597                 .appendChild( new TextNode("6789") );
3598             doTest(root, false);
3599         }
3600         if (!helper.isAttribute())
3601         {
3602 //            xText.setString("AB");
3603             xStart = xText.getStart();
3604             xEnd = xText.getEnd();
3605 
3606             TreeNode nod1 = helper.mkTreeNode();
3607             TreeNode nod2 = helper.mkTreeNode();
3608             XTextContent xContent1 = helper.mkTextContent(inserter, nod1);
3609             XTextContent xContent2 = helper.mkTextContent(inserter, nod2);
3610 
3611             xDocTextCursor.gotoRange(xStart, false);
3612             xDocTextCursor.goRight((short)1, true);
3613 
3614             // insertTextContent with document cursor and absorb
3615             xText.insertTextContent(xDocTextCursor, xContent1, true);
3616             xDocTextCursor.gotoRange(xEnd, false);
3617             xDocTextCursor.goLeft((short)1, true);
3618             xText.insertTextContent(xDocTextCursor, xContent2, true);
3619 
3620             helper.postInserted(nod1, xContent1);
3621             helper.postInserted(nod2, xContent2);
3622 
3623             TreeNode root = new TreeNode()
3624                 .appendChild( new TextNode("12") )
3625                 .appendChild( met1.dup()
3626                     .appendChild( nod1.dup() )
3627                     .appendChild( new TextNode("AB") )
3628                     .appendChild( nod2.dup() ) )
3629                 .appendChild( new TextNode("6789") );
3630             doTest(root, false);
3631         }
3632         {
3633             xText.setString("AB");
3634             xStart = xText.getStart();
3635             xEnd = xText.getEnd();
3636 
3637             TreeNode nod1 = helper.mkTreeNode();
3638             TreeNode nod2 = helper.mkTreeNode();
3639             XTextContent xContent1 = helper.mkTextContent(inserter, nod1);
3640             XTextContent xContent2 = helper.mkTextContent(inserter, nod2);
3641 
3642             // attach to range from meta getStart()/getEnd()
3643             xContent1.attach(xStart);
3644             xContent2.attach(xEnd);
3645 
3646             helper.postInserted(nod1, xContent1);
3647             helper.postInserted(nod2, xContent2);
3648 
3649             TreeNode root = new TreeNode()
3650                 .appendChild( new TextNode("12") )
3651                 .appendChild( met1.dup()
3652                     .appendChild( nod1.dup() )
3653                     .appendChild( new TextNode("AB") )
3654                     .appendChild( nod2.dup() ) )
3655                 .appendChild( new TextNode("6789") );
3656             doTest(root, false);
3657         }
3658         {
3659             xText.setString("AB");
3660             xStart = xText.getStart();
3661             xEnd = xText.getEnd();
3662 
3663             TreeNode nod1 = helper.mkTreeNode();
3664             TreeNode nod2 = helper.mkTreeNode();
3665             XTextContent xContent1 = helper.mkTextContent(inserter, nod1);
3666             XTextContent xContent2 = helper.mkTextContent(inserter, nod2);
3667 
3668             XTextCursor xTextCursor = xText.createTextCursor();
3669             xTextCursor.gotoStart(false);
3670 
3671             // attach to cursor from meta XText
3672             xContent1.attach(xTextCursor);
3673             xTextCursor.gotoEnd(false);
3674             xContent2.attach(xTextCursor);
3675 
3676             helper.postInserted(nod1, xContent1);
3677             helper.postInserted(nod2, xContent2);
3678 
3679             TreeNode root = new TreeNode()
3680                 .appendChild( new TextNode("12") )
3681                 .appendChild( met1.dup()
3682                     .appendChild( nod1.dup() )
3683                     .appendChild( new TextNode("AB") )
3684                     .appendChild( nod2.dup() ) )
3685                 .appendChild( new TextNode("6789") );
3686             doTest(root, false);
3687         }
3688     }
3689 
3690     @Test public void testMetaFieldXTextField() throws Exception
3691     {
3692         com.sun.star.rdf.XRepositorySupplier xModel =
3693             (com.sun.star.rdf.XRepositorySupplier) UnoRuntime.queryInterface(
3694                 com.sun.star.rdf.XRepositorySupplier.class, m_xDoc);
3695         com.sun.star.rdf.XRepository xRepo = xModel.getRDFRepository();
3696         // for testing just add it to the first graph
3697         com.sun.star.rdf.XURI[] Graphs = xRepo.getGraphNames();
3698         com.sun.star.rdf.XNamedGraph xGraph = xRepo.getGraph(Graphs[0]);
3699         com.sun.star.rdf.XURI xOdfPrefix =
3700             com.sun.star.rdf.URI.createKnown(m_xContext,
3701                 com.sun.star.rdf.URIs.ODF_PREFIX);
3702         com.sun.star.rdf.XURI xOdfSuffix =
3703             com.sun.star.rdf.URI.createKnown(m_xContext,
3704                 com.sun.star.rdf.URIs.ODF_SUFFIX);
3705         com.sun.star.rdf.XNode xPrefix =
3706             com.sun.star.rdf.Literal.create(m_xContext, "foo");
3707         com.sun.star.rdf.XNode xSuffix =
3708             com.sun.star.rdf.Literal.create(m_xContext, "bar");
3709 
3710         RangeInserter inserter = new RangeInserter(m_xDoc);
3711         TreeNode text = new TextNode("abc");
3712         inserter.insertRange( new Range(0, 0, text) );
3713         XText xDocText = m_xDoc.getText();
3714         XTextCursor xDocTextCursor = xDocText.createTextCursor();
3715         xDocTextCursor.goRight((short)1, false);
3716         xDocTextCursor.goRight((short)3, true);
3717 
3718         XTextField xMetaField = inserter.makeMetaField();
3719 
3720         xDocText.insertTextContent(xDocTextCursor, xMetaField, true);
3721 
3722         XMetadatable xMetadatable = (XMetadatable)
3723             UnoRuntime.queryInterface(XMetadatable.class, xMetaField);
3724         xMetadatable.ensureMetadataReference();
3725 
3726         xGraph.addStatement(xMetadatable, xOdfPrefix, xPrefix);
3727         xGraph.addStatement(xMetadatable, xOdfSuffix, xSuffix);
3728         assertEquals("getPresentation(): wrong",
3729                      "fooabcbar", xMetaField.getPresentation(false));
3730         inserter.insertRange( new Range(0, 0, text) );
3731     }
3732 
3733     @Test public void testMetaFieldXPropertySet() throws Exception
3734     {
3735         RangeInserter inserter = new RangeInserter(m_xDoc);
3736         TreeNode text = new TextNode("123");
3737         inserter.insertRange( new Range(0, 0, text) );
3738         XText xDocText = m_xDoc.getText();
3739         XTextCursor xDocTextCursor = xDocText.createTextCursor();
3740         xDocTextCursor.goRight((short)1, false);
3741         xDocTextCursor.goRight((short)3, true);
3742 
3743         XTextField xMetaField = inserter.makeMetaField();
3744 
3745         xDocText.insertTextContent(xDocTextCursor, xMetaField, true);
3746 
3747         XPropertySet xPropertySet = (XPropertySet)
3748             UnoRuntime.queryInterface(XPropertySet.class, xMetaField);
3749         assertNotNull("PropertySet: not supported?", xPropertySet);
3750         XPropertySetInfo xPropertySetInfo = xPropertySet.getPropertySetInfo();
3751         assertTrue("hasPropertyByName(\"NumberFormat\"):",
3752                    xPropertySetInfo.hasPropertyByName("NumberFormat"));
3753         assertTrue("hasPropertyByName(\"IsFixedLanguage\"):",
3754                    xPropertySetInfo.hasPropertyByName("IsFixedLanguage"));
3755 
3756         int def = (Integer) xPropertySet.getPropertyValue("NumberFormat");
3757         System.out.println("NumberFormat: default is " + def);
3758         short INT = com.sun.star.i18n.NumberFormatIndex.NUMBER_INT;
3759         xPropertySet.setPropertyValue("NumberFormat", INT);
3760         xPropertySet.setPropertyValue("IsFixedLanguage", true);
3761         int format = (Integer) xPropertySet.getPropertyValue("NumberFormat");
3762         assertEquals("NumberFormat: failed", INT, format);
3763         boolean isFixed = (Boolean)
3764             xPropertySet.getPropertyValue("IsFixedLanguage");
3765         assertTrue("IsFixedLanguage: failed", isFixed);
3766     }
3767 
3768     @Test public void testLoadStore() throws Exception
3769     {
3770         XTextDocument xComp = null;
3771         String filename = "TESTMETA.odt";
3772         String file;
3773         try {
3774             file = TestDocument.getUrl(filename);
3775             xComp = doLoad(file);
3776             if (xComp != null)
3777             {
3778                 checkLoadMeta(xComp);
3779                 file = m_TmpDir + filename;
3780                 doStore(xComp, file);
3781                 close(xComp);
3782                 xComp = doLoad(file);
3783                 checkLoadMeta(xComp);
3784             }
3785         } finally {
3786             close(xComp);
3787         }
3788     }
3789 
3790     private void doStore(XComponent xComp, String file) throws Exception
3791     {
3792         System.out.println("Storing test document...");
3793 
3794         XStorable xStor = (XStorable) UnoRuntime.queryInterface(
3795                     XStorable.class, xComp);
3796 
3797         xStor.storeToURL(file, new PropertyValue[0]);
3798 
3799         System.out.println("...done");
3800     }
3801 
3802     public XTextDocument doLoad(String file) throws Exception
3803     {
3804         XComponent xComp = null;
3805 
3806         System.out.println("Loading test document...");
3807 
3808         PropertyValue[] loadProps = new PropertyValue[1];
3809         loadProps[0] = new PropertyValue();
3810         loadProps[0].Name = "Hidden";
3811         loadProps[0].Value = new Boolean(true);
3812 
3813         xComp = util.DesktopTools.loadDoc(m_xMSF, file, loadProps);
3814 //        xComp =  util.DesktopTools.getCLoader(m_xMSF).loadComponentFromURL(file, "_blank", 0, loadProps);
3815 
3816         XTextDocument xTextDoc = (XTextDocument)
3817             UnoRuntime.queryInterface(XTextDocument.class, xComp);
3818 
3819         assertNotNull("cannot load: " + file, xTextDoc);
3820 
3821         System.out.println("...done");
3822 
3823         return xTextDoc;
3824     }
3825 
3826     public void checkLoadMeta(XTextDocument xTextDoc) throws Exception
3827     {
3828         XText xText = xTextDoc.getText();
3829 
3830         System.out.println("Checking meta(-field)s in loaded test document...");
3831 
3832         TreeNode root = new TreeNode()
3833             .appendChild( new RubyNode("ruby1")
3834                 .appendChild( new TextNode("1") ) )
3835             .appendChild( new MetaNode(mkId_("id1"))
3836                 .appendChild( new TextNode("2") ) )
3837             .appendChild( new MetaFieldNode(mkId_("id2"))
3838                 .appendChild( new TextNode("3") ) )
3839             .appendChild( new RubyNode("ruby2")
3840                 .appendChild( new MetaNode(mkId_("id3"))
3841                     .appendChild( new TextNode("4") ) ) )
3842             .appendChild( new RubyNode("ruby3")
3843                 .appendChild( new MetaFieldNode(mkId_("id4"))
3844                     .appendChild( new TextNode("5") ) ) )
3845             .appendChild( new MetaNode(mkId_("id5"))
3846                 .appendChild( new RubyNode("ruby4")
3847                     .appendChild( new TextNode("6") ) ) )
3848             .appendChild( new MetaFieldNode(mkId_("id6"))
3849                 .appendChild( new RubyNode("ruby5")
3850                     .appendChild( new TextNode("7") ) ) )
3851             .appendChild( new MetaNode(mkId_("id7"))
3852                 .appendChild( new MetaNode(mkId_("id8"))
3853                     .appendChild( new TextNode("8") ) ) )
3854             .appendChild( new MetaNode(mkId_("id9"))
3855                 .appendChild( new MetaFieldNode(mkId_("id10"))
3856                     .appendChild( new TextNode("9") ) ) )
3857             .appendChild( new MetaFieldNode(mkId_("id11"))
3858                 .appendChild( new MetaNode(mkId_("id12"))
3859                     .appendChild( new TextNode("10") ) ) )
3860             .appendChild( new MetaFieldNode(mkId_("id13"))
3861                 .appendChild( new MetaFieldNode(mkId_("id14"))
3862                     .appendChild( new TextNode("11") ) ) )
3863             .appendChild( new MetaNode(mkId_("id15"))
3864                 .appendChild( new RubyNode("ruby6")
3865                     .appendChild( new MetaFieldNode(mkId_("id16"))
3866                         .appendChild( new TextNode("12") ) ) ) )
3867             .appendChild( new MetaNode(mkId_("")) {
3868                                 public boolean equals(Object other) {
3869                                     return (other instanceof MetaNode);
3870                                 } }
3871                 .appendChild( new TextNode("13") ) )
3872             .appendChild( new TextNode(" X X ") );
3873         doTest(xTextDoc, root, false);
3874 
3875         System.out.println("...done");
3876     }
3877 
3878     @Test public void testLoadStoreXmlid() throws Exception
3879     {
3880         XTextDocument xComp = null;
3881         String filename = "TESTXMLID.odt";
3882         String file;
3883         try {
3884             file = TestDocument.getUrl(filename);
3885             xComp = doLoad(file);
3886             if (xComp != null)
3887             {
3888                 checkLoadXmlId(xComp);
3889                 file = m_TmpDir + filename;
3890                 doStore(xComp, file);
3891                 close(xComp);
3892                 xComp = doLoad(file);
3893                 checkLoadXmlId(xComp);
3894             }
3895         } finally {
3896             close(xComp);
3897         }
3898     }
3899 
3900     public void checkLoadXmlId(XTextDocument xTextDoc) throws Exception
3901     {
3902         XText xText = xTextDoc.getText();
3903 
3904         System.out.println("Checking bookmarks in loaded test document...");
3905 
3906         XRepositorySupplier xRS = (XRepositorySupplier)
3907             UnoRuntime.queryInterface(XRepositorySupplier.class, xTextDoc);
3908         XDocumentRepository xRepo = (XDocumentRepository)
3909             UnoRuntime.queryInterface(XDocumentRepository.class,
3910                 xRS.getRDFRepository());
3911         XBookmarksSupplier xBMS = (XBookmarksSupplier)
3912             UnoRuntime.queryInterface(XBookmarksSupplier.class, xTextDoc);
3913         XNameAccess xBookmarks = xBMS.getBookmarks();
3914         XMetadatable xMark1 = (XMetadatable) UnoRuntime.queryInterface(
3915                 XMetadatable.class, xBookmarks.getByName("mk1"));
3916         assertTrue("mark1",
3917                 eq(xMark1.getMetadataReference(),
3918                     new StringPair("content.xml", "id90")));
3919 
3920         XMetadatable xMark2 = (XMetadatable) UnoRuntime.queryInterface(
3921                 XMetadatable.class, xBookmarks.getByName("mk2"));
3922         Pair<Statement[], Boolean> result = xRepo.getStatementRDFa(xMark2);
3923         assertTrue("mark2", (result.First.length == 1)
3924             && result.First[0].Subject.getStringValue().equals("uri:foo")
3925             && result.First[0].Predicate.getStringValue().equals("uri:bar")
3926             && result.First[0].Object.getStringValue().contains("a fooish bar")
3927             );
3928 
3929         XMetadatable xMark3 = (XMetadatable) UnoRuntime.queryInterface(
3930                 XMetadatable.class, xBookmarks.getByName("mk3"));
3931         assertTrue("mark3",
3932                 eq(xMark3.getMetadataReference(),
3933                     new StringPair("content.xml", "id91")));
3934 
3935         System.out.println("...done");
3936 
3937         System.out.println("Checking sections in loaded test document...");
3938 
3939         XTextSectionsSupplier xTSS = (XTextSectionsSupplier)
3940             UnoRuntime.queryInterface(XTextSectionsSupplier.class, xTextDoc);
3941 
3942         XNameAccess xSections = xTSS.getTextSections();
3943 
3944         XMetadatable xSection1 = (XMetadatable) UnoRuntime.queryInterface(
3945                 XMetadatable.class, xSections.getByName("Section 1"));
3946         assertTrue("idsection1", eq(xSection1.getMetadataReference(),
3947                     new StringPair("content.xml", "idSection1")));
3948 
3949         XMetadatable xSection2 = (XMetadatable) UnoRuntime.queryInterface(
3950                 XMetadatable.class, xSections.getByName("Section 2"));
3951         assertTrue("idSection2", eq(xSection2.getMetadataReference(),
3952                     new StringPair("content.xml", "idSection2")));
3953 
3954         XMetadatable xSection3 = (XMetadatable) UnoRuntime.queryInterface(
3955                 XMetadatable.class,
3956                 xSections.getByName("Table of Contents1_Head"));
3957         assertTrue("idTOCTitle", eq(xSection3.getMetadataReference(),
3958                     new StringPair("content.xml", "idTOCTitle")));
3959 
3960         XMetadatable xSection4 = (XMetadatable) UnoRuntime.queryInterface(
3961                 XMetadatable.class,
3962                 xSections.getByName("Alphabetical Index1_Head"));
3963         assertTrue("idAITitle", eq(xSection4.getMetadataReference(),
3964                     new StringPair("content.xml", "idAITitle")));
3965 
3966         XMetadatable xSection5 = (XMetadatable) UnoRuntime.queryInterface(
3967                 XMetadatable.class,
3968                 xSections.getByName("Illustration Index1_Head"));
3969         assertTrue("idIITitle", eq(xSection5.getMetadataReference(),
3970                     new StringPair("content.xml", "idIITitle")));
3971 
3972         XMetadatable xSection6 = (XMetadatable) UnoRuntime.queryInterface(
3973                 XMetadatable.class,
3974                 xSections.getByName("Index of Tables1_Head"));
3975         assertTrue("idIOTTitle", eq(xSection6.getMetadataReference(),
3976                     new StringPair("content.xml", "idIOTTitle")));
3977 
3978         XMetadatable xSection7 = (XMetadatable) UnoRuntime.queryInterface(
3979                 XMetadatable.class,
3980                 xSections.getByName("User-Defined1_Head"));
3981         assertTrue("idUDTitle", eq(xSection7.getMetadataReference(),
3982                     new StringPair("content.xml", "idUDTitle")));
3983 
3984         XMetadatable xSection8 = (XMetadatable) UnoRuntime.queryInterface(
3985                 XMetadatable.class,
3986                 xSections.getByName("Table of Objects1_Head"));
3987         assertTrue("idTOOTitle", eq(xSection8.getMetadataReference(),
3988                     new StringPair("content.xml", "idTOOTitle")));
3989 
3990         XMetadatable xSection9 = (XMetadatable) UnoRuntime.queryInterface(
3991                 XMetadatable.class, xSections.getByName("Bibliography1_Head"));
3992         assertTrue("idBibTitle", eq(xSection9.getMetadataReference(),
3993                     new StringPair("content.xml", "idBibTitle")));
3994 
3995         System.out.println("...done");
3996 
3997         System.out.println("Checking indexes in loaded test document...");
3998 
3999         XDocumentIndexesSupplier xDIS = (XDocumentIndexesSupplier)
4000             UnoRuntime.queryInterface(XDocumentIndexesSupplier.class, xTextDoc);
4001         XIndexAccess xIndexesIA = xDIS.getDocumentIndexes();
4002         XNameAccess xIndexes =
4003             UnoRuntime.queryInterface(XNameAccess.class, xIndexesIA);
4004 
4005         XMetadatable xIndex1 = (XMetadatable) UnoRuntime.queryInterface(
4006                 XMetadatable.class, xIndexes.getByName("Table of Contents1"));
4007         assertTrue("idTOC", eq(xIndex1.getMetadataReference(),
4008                     new StringPair("content.xml", "idTOC")));
4009         XMetadatable xIndex1s = (XMetadatable) UnoRuntime.queryInterface(
4010                 XMetadatable.class, xSections.getByName("Table of Contents1"));
4011         assertTrue("idTOC", eq(xIndex1s.getMetadataReference(),
4012                     new StringPair("content.xml", "idTOC")));
4013 
4014         XMetadatable xIndex2 = (XMetadatable) UnoRuntime.queryInterface(
4015                 XMetadatable.class, xIndexes.getByName("Alphabetical Index1"));
4016         assertTrue("idAI", eq(xIndex2.getMetadataReference(),
4017                     new StringPair("content.xml", "idAI")));
4018         XMetadatable xIndex2s = (XMetadatable) UnoRuntime.queryInterface(
4019                 XMetadatable.class, xSections.getByName("Alphabetical Index1"));
4020         assertTrue("idAI", eq(xIndex2s.getMetadataReference(),
4021                     new StringPair("content.xml", "idAI")));
4022 
4023         XMetadatable xIndex3 = (XMetadatable) UnoRuntime.queryInterface(
4024                 XMetadatable.class, xIndexes.getByName("Illustration Index1"));
4025         assertTrue("idII", eq(xIndex3.getMetadataReference(),
4026                     new StringPair("content.xml", "idII")));
4027         XMetadatable xIndex3s = (XMetadatable) UnoRuntime.queryInterface(
4028                 XMetadatable.class, xSections.getByName("Illustration Index1"));
4029         assertTrue("idII", eq(xIndex3s.getMetadataReference(),
4030                     new StringPair("content.xml", "idII")));
4031 
4032         XMetadatable xIndex4 = (XMetadatable) UnoRuntime.queryInterface(
4033                 XMetadatable.class, xIndexes.getByName("Index of Tables1"));
4034         assertTrue("idIOT", eq(xIndex4.getMetadataReference(),
4035                     new StringPair("content.xml", "idIOT")));
4036         XMetadatable xIndex4s = (XMetadatable) UnoRuntime.queryInterface(
4037                 XMetadatable.class, xSections.getByName("Index of Tables1"));
4038         assertTrue("idIOT", eq(xIndex4s.getMetadataReference(),
4039                     new StringPair("content.xml", "idIOT")));
4040 
4041         XMetadatable xIndex5 = (XMetadatable) UnoRuntime.queryInterface(
4042                 XMetadatable.class, xIndexes.getByName("User-Defined1"));
4043         assertTrue("idUD", eq(xIndex5.getMetadataReference(),
4044                     new StringPair("content.xml", "idUD")));
4045         XMetadatable xIndex5s = (XMetadatable) UnoRuntime.queryInterface(
4046                 XMetadatable.class, xSections.getByName("User-Defined1"));
4047         assertTrue("idUD", eq(xIndex5s.getMetadataReference(),
4048                     new StringPair("content.xml", "idUD")));
4049 
4050         XMetadatable xIndex6 = (XMetadatable) UnoRuntime.queryInterface(
4051                 XMetadatable.class, xIndexes.getByName("Table of Objects1"));
4052         assertTrue("idTOO", eq(xIndex6.getMetadataReference(),
4053                     new StringPair("content.xml", "idTOO")));
4054         XMetadatable xIndex6s = (XMetadatable) UnoRuntime.queryInterface(
4055                 XMetadatable.class, xSections.getByName("Table of Objects1"));
4056         assertTrue("idTOO", eq(xIndex6s.getMetadataReference(),
4057                     new StringPair("content.xml", "idTOO")));
4058 
4059         XMetadatable xIndex7 = (XMetadatable) UnoRuntime.queryInterface(
4060                 XMetadatable.class, xIndexes.getByName("Bibliography1"));
4061         assertTrue("idBib", eq(xIndex7.getMetadataReference(),
4062                     new StringPair("content.xml", "idBib")));
4063         XMetadatable xIndex7s = (XMetadatable) UnoRuntime.queryInterface(
4064                 XMetadatable.class, xSections.getByName("Bibliography1"));
4065         assertTrue("idBib", eq(xIndex7s.getMetadataReference(),
4066                     new StringPair("content.xml", "idBib")));
4067 
4068         System.out.println("...done");
4069     }
4070 
4071     static void close(XComponent i_comp)
4072     {
4073         try {
4074             XCloseable xClos = (XCloseable) UnoRuntime.queryInterface(
4075                         XCloseable.class, i_comp);
4076             if (xClos != null) xClos.close(true);
4077         } catch (Exception e) {
4078         }
4079     }
4080 
4081     private void doTest(TreeNode intree) throws Exception
4082     {
4083         doTest(m_xDoc, intree, true);
4084     }
4085 
4086     private void doTest(TreeNode intree, boolean insert) throws Exception
4087     {
4088         doTest(m_xDoc, intree, insert);
4089     }
4090 
4091     private void doTest(XTextDocument xDoc, TreeNode intree,
4092             boolean insert) throws Exception
4093     {
4094         dumpTree(intree, "I: ");
4095 
4096         if (insert) {
4097             new TreeInserter(xDoc).insertTree(intree);
4098         }
4099 
4100 //Thread.sleep(10000);
4101 
4102         XText xText = xDoc.getText();
4103         XEnumerationAccess xTextEA = (XEnumerationAccess)
4104             UnoRuntime.queryInterface(XEnumerationAccess.class, xText);
4105         XEnumeration xTextEnum = xTextEA.createEnumeration();
4106         // skip to right paragraph
4107         xTextEnum.nextElement(); // skip first -- always empty!
4108         Object xElement = xTextEnum.nextElement(); // second contains test case
4109         XEnumerationAccess xEA = (XEnumerationAccess)
4110             UnoRuntime.queryInterface(XEnumerationAccess.class, xElement);
4111         XEnumeration xEnum = xEA.createEnumeration();
4112         TreeNode outtree = new EnumConverter().convert(xEnum);
4113 
4114         dumpTree(outtree, "O: ");
4115 
4116         new FuzzyTester().doTest(intree, outtree);
4117     }
4118 
4119     private void dumpTree(TreeNode tree) { dumpTree(tree, "> "); }
4120 
4121     private void dumpTree(TreeNode tree, String prefix)
4122     {
4123         System.out.println(prefix + tree.toString());
4124         TreeNodeEnum children = tree.createEnumeration();
4125         while (children.hasNext()) {
4126             TreeNode node = children.next();
4127             dumpTree(node, prefix + "  ");
4128         }
4129     }
4130 
4131     private String mkName(String prefix)
4132     {
4133         return prefix + String.valueOf(m_Count++);
4134     }
4135 
4136     private StringPair mkId(String prefix)
4137     {
4138         return new StringPair("content.xml", mkName(prefix));
4139     }
4140 
4141     private StringPair mkId_(String id)
4142     {
4143         return new StringPair("content.xml", id);
4144     }
4145 
4146     static boolean eq(StringPair i_Left, StringPair i_Right)
4147     {
4148         return ((i_Left.First).equals(i_Right.First)) &&
4149             ((i_Left.Second).equals(i_Right.Second));
4150     }
4151 
4152     @BeforeClass public static void setUpConnection() throws Exception {
4153         connection.setUp();
4154     }
4155 
4156     @AfterClass public static void tearDownConnection()
4157         throws InterruptedException, com.sun.star.uno.Exception
4158     {
4159         connection.tearDown();
4160     }
4161 
4162     private static final OfficeConnection connection = new OfficeConnection();
4163 }
4164 
4165