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