From 4060110e74410a6f26a64b5a947ae61338d3a84f Mon Sep 17 00:00:00 2001 From: flywire Date: Tue, 26 Oct 2021 18:22:31 +1100 Subject: [PATCH] Part 2 layout --- docs/01-Concepts.md | 9 +- docs/03-Examining.md | 8 +- docs/04-Listening.md | 2 +- docs/05-Text_API_Overview.md | 2053 ++++++++++++++------------- docs/06-Text_Styles.md | 1893 +++++++++++++------------ docs/07-Non-text_Content.md | 2146 ++++++++++++++-------------- docs/08-Graphic_Content.md | 1574 +++++++++++---------- docs/09-Text_Search.md | 721 +++++----- docs/10-Linguistics.md | 2560 +++++++++++++++++----------------- docs/images/bulb_on.png | Bin 0 -> 4647 bytes docs/index.md | 2 + 11 files changed, 5594 insertions(+), 5374 deletions(-) create mode 100644 docs/images/bulb_on.png diff --git a/docs/01-Concepts.md b/docs/01-Concepts.md index dc05078..c2de746 100644 --- a/docs/01-Concepts.md +++ b/docs/01-Concepts.md @@ -429,9 +429,6 @@ Figures 5 and 6). The Frame-Controller-Model (FCM) relationship (or design pattern) is a part of Office which programmers will encounter frequently. It appears in the API as connections between the XFrame, XController, and XModel interfaces, as shown in - -![](images/01-Concepts-11.png) - Figure 11. @@ -475,17 +472,13 @@ interfaces, lets me give a more detailed definition of a component. Back in sect (and in Figure 4), I said a component was an implemented service. Another way of understanding a component is in terms of how much of the FCM relationship it supports, which allows the 'component' idea to be divided into three: -1. A component that supports both the XModel and XController interfaces is usually +1. A component that supports both the XModel and XController interfaces is usually an Office document. - 2. A component with a controller but no model is typically used to implement library - functionality that doesn't need to load data. Examples include the spell checker, and Office tools for creating database forms. - 3. A component with no model or controller (i.e. just an XWindow object) is used - for simple GUI elements, such as Office's help windows. Of these three types, the component-as-document (number 1) is the most important diff --git a/docs/03-Examining.md b/docs/03-Examining.md index ee87437..e7220ea 100644 --- a/docs/03-Examining.md +++ b/docs/03-Examining.md @@ -93,8 +93,8 @@ class. One way of finding the most current list is to browse main.xcd in Path settings store directory locations for parts of the Office installation, such as the whereabouts of the gallery and spellchecker files. A partial list of predefined paths is -accessible from within LibreOffice, via the Tools menu: Tools > Options > -LibreOffice > Paths. But the best source of information is the developer's guide, in the +accessible from within LibreOffice, via the Tools menu: Tools, Options, +LibreOffice, Paths. But the best source of information is the developer's guide, in the "Path Organization" section of chapter 6, or at https://wiki.openoffice.org/wiki/Documentation/DevGuide/OfficeDev/Path_Organizat ion, which can be accessed using: @@ -437,7 +437,7 @@ MRI can be downloaded from http://extensions.services.openoffice.org/en/project/mri-uno-object-inspection-tool, depositing an OXT file (probably called "MRI-1.3.3.oxt") on your machine. -Installing an extension is done through the Tools > Extension Manager menu item. +Installing an extension is done through the Tools, Extension Manager menu item. Click on the "Add" button and select the OXT file. Or you may be able to simply double click on the file to start the installation. @@ -449,7 +449,7 @@ v.1.2.4, at OpenOffice's extension's website (http://extensions.services.openoffice.org/en/project/mri-uno-object-inspection-tool) did install successfully. -Once installed, MRI can be accessed through Office's GUI via the Tools > Add-ons +Once installed, MRI can be accessed through Office's GUI via the Tools, Add-ons menu item, or programmatically as in my ExamineDoc.java example: ```java diff --git a/docs/04-Listening.md b/docs/04-Listening.md index 97416c7..ea98099 100644 --- a/docs/04-Listening.md +++ b/docs/04-Listening.md @@ -421,7 +421,7 @@ its close box. Another menu-related approach to controlling Office is to programmatically send menu shortcut key strokes to the currently active window. For example, a loaded Impress document is often displayed with a slide selection pane. This can be closed -using the menu item View > Slide Pane, which is assigned the shortcut keys ALT-v +using the menu item View, Slide Pane, which is assigned the shortcut keys ALT-v ALT-l. toggleSlidePane() 'types' these key strokes with the help of Java's Robot class: diff --git a/docs/05-Text_API_Overview.md b/docs/05-Text_API_Overview.md index 5d3c4aa..449edb8 100644 --- a/docs/05-Text_API_Overview.md +++ b/docs/05-Text_API_Overview.md @@ -1,1050 +1,1129 @@ -# Chapter 5. Text API Overview +# Chapter 5. Text API Overview !!! note "Topics" - API Overview; - Text Cursors; Extracting - Text; Cursor Iteration; - Creating Cursors; - Creating a Document; - Using and Comparing - Text Cursors; - Inserting/Changing Text - in a Document; Text - Enumeration; - Appending Documents - - Example folders: "Text - Tests" and "Utils" - - -The next few chapters look at programming with the text -document part of the Office API. This chapter begins with -a quick overview of the text API, then a detailed look at -text cursors for moving about in a document, extracting -text, and adding/inserting new text. - -Text cursors aren't the only way to move around inside a -document; it's also possible to iterate over a document by -treating it as a sequence of paragraphs. - -The chapter finishes with a look at how two (or more) text -documents can be appended. - -The online Developer's Guide begins text document programming at -https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Text_Documents -(the easiest way of accessing that page is to type loguide writer). It corresponds to -Chapter 7 in the printed guide (available at -https://wiki.openoffice.org/w/images/d/d9/DevelopersGuide_OOo3.1.0.pdf), but the -Web material is better structured and formatted. - -The guide's text programming examples are in TextDocuments.java, downloadable -from http://api.libreoffice.org/examples/DevelopersGuide/examples.html#Text. - -Although the code is long, it's well-organized. Some smaller text processing examples -are available at http://api.libreoffice.org/examples/examples.html#Java_examples. - -This chapter (and later ones) assume that you're familiar with Writer, including text -concepts such as paragraph styles. If you're not, then I recommend the "Writer + API Overview; + Text Cursors; Extracting + Text; Cursor Iteration; + Creating Cursors; + Creating a Document; + Using and Comparing + Text Cursors; + Inserting/Changing Text + in a Document; Text + Enumeration; + Appending Documents + + Example folders: "Text + Tests" and "Utils" + + +The next few chapters look at programming with the text +document part of the Office API. This chapter begins with +a quick overview of the text API, then a detailed look at +text cursors for moving about in a document, extracting +text, and adding/inserting new text. + +Text cursors aren't the only way to move around inside a +document; it's also possible to iterate over a document by +treating it as a sequence of paragraphs. + +The chapter finishes with a look at how two (or more) text +documents can be appended. + +The online Developer's Guide begins text document programming at +https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Text_Documents +(the easiest way of accessing that page is to type loguide writer). It corresponds to +Chapter 7 in the printed guide (available at +https://wiki.openoffice.org/w/images/d/d9/DevelopersGuide_OOo3.1.0.pdf), but the +Web material is better structured and formatted. + +The guide's text programming examples are in TextDocuments.java, downloadable +from http://api.libreoffice.org/examples/DevelopersGuide/examples.html#Text. + +Although the code is long, it's well-organized. Some smaller text processing examples +are available at http://api.libreoffice.org/examples/examples.html#Java_examples. + +This chapter (and later ones) assume that you're familiar with Writer, including text +concepts such as paragraph styles. If you're not, then I recommend the "Writer Guide", a user manual, available at http://www.libreoffice.org/get- -help/documentation/ or -https://wiki.documentfoundation.org/Documentation/Publications. +help/documentation/ or +https://wiki.documentfoundation.org/Documentation/Publications. - -## 1. An Overview of the Text Document API -The API is centered around four text document services which subclass -OfficeDocument, as shown in Figure 1. +## 1. An Overview of the Text Document API + +The API is centered around four text document services which subclass +OfficeDocument, as shown in Figure 1. - ![](images/05-Text_API_Overview-1.png) -Figure 1. The Text Document Services. +Figure 1. The Text Document Services. + - -I'll be concentrating on the TextDocument service, which is documented at +I'll be concentrating on the TextDocument service, which is documented at http://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1text_1_1TextD -ocument.html. Or you can type lodoc TextDocument service, which unfortunately -takes you to the IDL page for TextDocument; to reach the documentation, click on -"TextDocument" under the "Classes" heading. +ocument.html. Or you can type `lodoc TextDocument service`, which unfortunately +takes you to the IDL page for TextDocument; to reach the documentation, click on +"TextDocument" under the "Classes" heading. -The GlobalDocument service in Figure 1 is employed by master documents, such as a -book or thesis. A master document is typically made up of links to files holding its -parts, such as chapters, bibliography, and appendices. +The GlobalDocument service in Figure 1 is employed by master documents, such as a +book or thesis. A master document is typically made up of links to files holding its +parts, such as chapters, bibliography, and appendices. -The WebDocument service in Figure 1 is for manipulating web pages, although its -also possible to generate HTML files with the TextDocument service. +The WebDocument service in Figure 1 is for manipulating web pages, although its +also possible to generate HTML files with the TextDocument service. -TextDocument, GlobalDocument, and WebDocument are mostly empty because those -services don't define any interfaces or properties. The GenericTextDocument service -is where the action takes place, as summarized in Figure 2. +TextDocument, GlobalDocument, and WebDocument are mostly empty because those +services don't define any interfaces or properties. The GenericTextDocument service +is where the action takes place, as summarized in Figure 2. - ![](images/05-Text_API_Overview-2.png) -Figure 2. The Text Document Services, and some Interfaces. +Figure 2. The Text Document Services, and some Interfaces. + - -The numerous 'Supplier' interfaces in Figure 2 are Office's way of accessing different -elements in a document. For example, XStyleFamiliesSupplier manages character, -paragraph, and other styles, while XTextTableSupplier deals with tables. +The numerous 'Supplier' interfaces in Figure 2 are Office's way of accessing different +elements in a document. For example, XStyleFamiliesSupplier manages character, +paragraph, and other styles, while XTextTableSupplier deals with tables. -I'll be looking at these suppliers in later chapters, which is why they're highlighted, -but for now let's only consider the XTextDocument interface at the top right of the -GenericTextDocument service box in Figure 2 -XTextDocument has a getText() method for returning an XText object. XText -supports functionality related to text ranges and positions, cursors, and text contents. +I'll be looking at these suppliers in later chapters, which is why they're highlighted, +but for now let's only consider the XTextDocument interface at the top right of the +GenericTextDocument service box in Figure 2 +XTextDocument has a getText() method for returning an XText object. XText +supports functionality related to text ranges and positions, cursors, and text contents. -It inherits XSimpleText and XTextRange, as indicated in Figure 3. +It inherits XSimpleText and XTextRange, as indicated in Figure 3. - ![](images/05-Text_API_Overview-3.png) -Figure 3. XText and its Superclasses. +Figure 3. XText and its Superclasses. - -Text content covers a multitude, such as embedded images, tables, footnotes, and text -fields. Many of the suppliers shown in Figure 2 (e.g. XTextTablesSupplier) are for -iterating through text content (e.g. accessing the document's tables). -I'll concentrate on ordinary text in this chapter, and look at more esoteric content -forms in Chapters 7 and 8. +Text content covers a multitude, such as embedded images, tables, footnotes, and text +fields. Many of the suppliers shown in Figure 2 (e.g. XTextTablesSupplier) are for +iterating through text content (e.g. accessing the document's tables). -A text document can utilize eight different cursors, which fall into two groups, as in +I'll concentrate on ordinary text in this chapter, and look at more esoteric content +forms in Chapters 7 and 8. + +A text document can utilize eight different cursors, which fall into two groups, as in ![](images/05-Text_API_Overview-4.png) -Figure 4. +Figure 4. - ![](images/05-Text_API_Overview-4.png) -Figure 4. Types of Cursor. - - -XTextCursor contains methods for moving around the document, and an instance is -often called a model cursor because of its close links to the document's data. A -program can create multiple XTextCursor objects if it wants, and can convert an -XTextCursor into XParagraphCursor, XSentenceCursor, or XWordCursor. The -differences are that while an XTextCursor moves through a document character by -character, the others travel in units of paragraphs, sentences, and words. - -A program may employ a single XTextViewCursor cursor, to represent the cursor the -user sees in the Writer application window; for this reason, it's often called the view -cursor. XTextViewCursor can be converted into a XLineCursor, XPageCursor, or -XScreenCursor object, which allows it to move in terms of lines, pages, or screens. - -A cursor's location is specified using a text range, which can be the currently selected -text, or a position in the document. A text position is a text range that begins and ends -at the same point. - - - -## 2. Extracting Text from a Document - -The ExtractText.java example opens a document using Lo.openOffice(), and tries to -print its text: - -public static void main(String[] args) -{ - if (args.length != 1) { - System.out.println("Usage: ExtractText fnm"); - return; - } - - XComponentLoader loader = Lo.loadOffice(); - XComponent doc = Lo.openDoc(args[0], loader); - if (doc == null) { - System.out.println("Could not open " + args[0]); - Lo.closeOffice(); - return; - } - - if (Info.isDocType(doc, Lo.WRITER_SERVICE)) { - XTextDocument textDoc = Write.getTextDoc(doc); - XTextCursor cursor = Write.getCursor(textDoc); - String text = Write.getAllText(cursor); - System.out.println("--------- Text Content --------"); - System.out.println(text); - System.out.println("-------------------------------"); - } - else - System.out.println("Extraction unsupported for this doc type"); - - Lo.closeDoc(doc); - Lo.closeOffice(); -} // end of main() - -Info.isDocType() tests the document's type by casting it into an XServiceInfo -interface. Then it calls XServiceInfo.supportsService() to check the document's -service capabilities: - -public static boolean isDocType(Object doc, String docType) -// in the Info utility class -{ XServiceInfo si = Lo.qi(XServiceInfo.class, doc); - return si.supportsService(docType); -} - -The argument type of the document is Object rather than XComponent so that a wider -range of objects can be passed to the function for testing. - -The service names for documents are hard to remember, so they're defined as -constants in my Lo class: - -// in the Lo class -public static final String WRITER_SERVICE = - "com.sun.star.text.TextDocument"; -public static final String BASE_SERVICE = - "com.sun.star.sdb.OfficeDatabaseDocument"; -public static final String CALC_SERVICE = - "com.sun.star.sheet.SpreadsheetDocument"; -public static final String DRAW_SERVICE = - "com.sun.star.drawing.DrawingDocument"; -public static final String IMPRESS_SERVICE = - "com.sun.star.presentation.PresentationDocument"; -public static final String MATH_SERVICE = - "com.sun.star.formula.FormulaProperties"; - -Write.getTextDoc() uses Lo.qi() to cast the document's XComponent interface into an -XTextDocument: -XTextDocument textDoc = Lo.qi(XTextDocument.class, doc); -This may fail (i.e. return null) if the loaded document isn't an instance of the -TextDocument service. - -The casting 'power' of Lo.qi() is confusing – it depends on the document's service -type. All text documents are instances of the TextDocument service (see Figure 2). - -This means that Lo.qi() can 'switch' between any of the interfaces defined by -TextDocument or its superclasses (i.e. the interfaces in GenericTextDocument or -OfficeDocument). For instance, the following cast is fine: - -XStyleFamiliesSupplier xSupplier = - Lo.qi(XStyleFamiliesSupplier.class, doc); - -This changes the instance into an XStyleFamiliesSupplier, which can access the -document's styles. - -Alternatively, the following converts the instance into a supplier defined in -OfficeDocument: -XDocumentPropertiesSupplier xSupplier = - Lo.qi(XDocumentPropertiesSupplier.class, doc); - -Most of the examples in this chapter and the next few cast the document to -XTextDocument since that interface can access the document's contents as an XText -object: - -XTextDocument textDoc = Lo.qi(XTextDocument.class, doc); -XText xText = textDoc.getText(); - -The XText instance can access all the capabilities shown in Figure 3. - -A common next step is to create a cursor for moving around the document. This is -easy since XText inherits XSimpleText which has a createTextCursor() method: -XTextCursor textCursor = xText.createTextCursor(); -These few lines are so useful that I've put them inside a Write.getCursor() method: - -public static XTextCursor getCursor(XTextDocument textDoc) -// get cursor from a text document -{ - XText xText = textDoc.getText(); - if (xText == null) { - System.out.println("Text not found in document"); - return null; - } - else - return xText.createTextCursor(); -} // end of getCursor() - -An XTextCursor can be converted into other kinds of model cursors (e.g. - -XParagraphCursor, XSentenceCursor, XWordCursor; see Figure 4). That's not -necessary in for the ExtractText.java example; instead, the XTextCursor is passed to -Write.getAllText() to access the text as a sequence of characters: - -public static String getAllText(XTextCursor cursor) -{ - cursor.gotoStart(false); - cursor.gotoEnd(true); - String text = cursor.getString(); - cursor.gotoEnd(false); // to deselect everything in the doc - return text; -} // end of getAllText() - -All cursor movement operations take a boolean argument which specifies whether the -movement should also select the text. For example, in getAllText(), -cursor.gotoStart(false) shifts the cursor to the start of the text without selecting -anything. The subsequent call to cursor.gotoEnd(true) moves the cursor to the end of -the text and selects all the text moved over. The call to getString() on the third line -returns the selection (i.e. all the text in the document). - -Two other useful XTextCursor methods are: -boolean goLeft(short charCount, boolean isSelected) -boolean goRight(short charCount, boolean isSelected) -They move the cursor left or right by a given number of characters, and the boolean -argument specifies whether the text moved over is selected. - -All cursor methods return a boolean result which indicates if the move (and optional -selection) was successful. - -Another method worth knowing is: -boolean gotoRange(XTextRange textRange, boolean isSelected) -gotoRange() takes an XTextRange argument, which represents a selected region or -position where the cursor should be moved to. For example, it's possible to find a -bookmark in a document, extract its text range/position, and move the cursor to that -location with gotoRange(). I'll show code for doing this in Chapter 7. - -The XTextCursor methods are documented at +Figure 4. Types of Cursor. + + +XTextCursor contains methods for moving around the document, and an instance is +often called a model cursor because of its close links to the document's data. A +program can create multiple XTextCursor objects if it wants, and can convert an +XTextCursor into XParagraphCursor, XSentenceCursor, or XWordCursor. The +differences are that while an XTextCursor moves through a document character by +character, the others travel in units of paragraphs, sentences, and words. + +A program may employ a single XTextViewCursor cursor, to represent the cursor the +user sees in the Writer application window; for this reason, it's often called the view +cursor. XTextViewCursor can be converted into a XLineCursor, XPageCursor, or +XScreenCursor object, which allows it to move in terms of lines, pages, or screens. + +A cursor's location is specified using a text range, which can be the currently selected +text, or a position in the document. A text position is a text range that begins and ends +at the same point. + + +## 2. Extracting Text from a Document + +The ExtractText.java example opens a document using Lo.openOffice(), and tries to +print its text: + +```java +public static void main(String[] args) +{ + if (args.length != 1) { + System.out.println("Usage: ExtractText fnm"); + return; + } + + XComponentLoader loader = Lo.loadOffice(); + XComponent doc = Lo.openDoc(args[0], loader); + if (doc == null) { + System.out.println("Could not open " + args[0]); + Lo.closeOffice(); + return; + } + + if (Info.isDocType(doc, Lo.WRITER_SERVICE)) { + XTextDocument textDoc = Write.getTextDoc(doc); + XTextCursor cursor = Write.getCursor(textDoc); + String text = Write.getAllText(cursor); + System.out.println("--------- Text Content --------"); + System.out.println(text); + System.out.println("-------------------------------"); + } + else + System.out.println("Extraction unsupported for this doc type"); + + Lo.closeDoc(doc); + Lo.closeOffice(); +} // end of main() +``` + +Info.isDocType() tests the document's type by casting it into an XServiceInfo +interface. Then it calls XServiceInfo.supportsService() to check the document's +service capabilities: + +```java +public static boolean isDocType(Object doc, String docType) +// in the Info utility class +{ XServiceInfo si = Lo.qi(XServiceInfo.class, doc); + return si.supportsService(docType); +} +``` + +The argument type of the document is Object rather than XComponent so that a wider +range of objects can be passed to the function for testing. + +The service names for documents are hard to remember, so they're defined as +constants in my Lo class: + +```java +// in the Lo class +public static final String WRITER_SERVICE = + "com.sun.star.text.TextDocument"; +public static final String BASE_SERVICE = + "com.sun.star.sdb.OfficeDatabaseDocument"; +public static final String CALC_SERVICE = + "com.sun.star.sheet.SpreadsheetDocument"; +public static final String DRAW_SERVICE = + "com.sun.star.drawing.DrawingDocument"; +public static final String IMPRESS_SERVICE = + "com.sun.star.presentation.PresentationDocument"; +public static final String MATH_SERVICE = + "com.sun.star.formula.FormulaProperties"; +``` + +Write.getTextDoc() uses Lo.qi() to cast the document's XComponent interface into an +XTextDocument: + +```java +// in the Lo class +public static final String WRITER_SERVICE = + "com.sun.star.text.TextDocument"; +public static final String BASE_SERVICE = + "com.sun.star.sdb.OfficeDatabaseDocument"; +public static final String CALC_SERVICE = + "com.sun.star.sheet.SpreadsheetDocument"; +public static final String DRAW_SERVICE = + "com.sun.star.drawing.DrawingDocument"; +public static final String IMPRESS_SERVICE = + "com.sun.star.presentation.PresentationDocument"; +public static final String MATH_SERVICE = + "com.sun.star.formula.FormulaProperties"; +``` + +TextDocument textDoc = Lo.qi(XTextDocument.class, doc); +This may fail (i.e. return null) if the loaded document isn't an instance of the +TextDocument service. + +The casting 'power' of Lo.qi() is confusing – it depends on the document's service +type. All text documents are instances of the TextDocument service (see Figure 2). +This means that Lo.qi() can 'switch' between any of the interfaces defined by +TextDocument or its superclasses (i.e. the interfaces in GenericTextDocument or +OfficeDocument). For instance, the following cast is fine: + +```java +XStyleFamiliesSupplier xSupplier = + Lo.qi(XStyleFamiliesSupplier.class, doc); +``` + +This changes the instance into an XStyleFamiliesSupplier, which can access the +document's styles. + +Alternatively, the following converts the instance into a supplier defined in +OfficeDocument: + +```java +XDocumentPropertiesSupplier xSupplier = + Lo.qi(XDocumentPropertiesSupplier.class, doc); +``` + +Most of the examples in this chapter and the next few cast the document to +XTextDocument since that interface can access the document's contents as an XText +object: + +```java +XTextDocument textDoc = Lo.qi(XTextDocument.class, doc); +XText xText = textDoc.getText(); +``` + +The XText instance can access all the capabilities shown in Figure 3. + +A common next step is to create a cursor for moving around the document. This is +easy since XText inherits XSimpleText which has a createTextCursor() method: + +```java +XTextCursor textCursor = xText.createTextCursor(); +``` + +These few lines are so useful that I've put them inside a Write.getCursor() method: + +```java +public static XTextCursor getCursor(XTextDocument textDoc) +// get cursor from a text document +{ + XText xText = textDoc.getText(); + if (xText == null) { + System.out.println("Text not found in document"); + return null; + } + else + return xText.createTextCursor(); +} // end of getCursor() +``` + +An XTextCursor can be converted into other kinds of model cursors (e.g. + +XParagraphCursor, XSentenceCursor, XWordCursor; see Figure 4). That's not +necessary in for the ExtractText.java example; instead, the XTextCursor is passed to +Write.getAllText() to access the text as a sequence of characters: + +```java +public static String getAllText(XTextCursor cursor) +{ + cursor.gotoStart(false); + cursor.gotoEnd(true); + String text = cursor.getString(); + cursor.gotoEnd(false); // to deselect everything in the doc + return text; +} // end of getAllText() +``` + +All cursor movement operations take a boolean argument which specifies whether the +movement should also select the text. For example, in getAllText(), +cursor.gotoStart(false) shifts the cursor to the start of the text without selecting +anything. The subsequent call to cursor.gotoEnd(true) moves the cursor to the end of +the text and selects all the text moved over. The call to getString() on the third line +returns the selection (i.e. all the text in the document). + +Two other useful XTextCursor methods are: + +```java +boolean goLeft(short charCount, boolean isSelected) +boolean goRight(short charCount, boolean isSelected) +``` + +They move the cursor left or right by a given number of characters, and the boolean +argument specifies whether the text moved over is selected. + +All cursor methods return a boolean result which indicates if the move (and optional +selection) was successful. + +Another method worth knowing is: + +```java +boolean gotoRange(XTextRange textRange, boolean isSelected) +``` + +gotoRange() takes an XTextRange argument, which represents a selected region or +position where the cursor should be moved to. For example, it's possible to find a +bookmark in a document, extract its text range/position, and move the cursor to that +location with gotoRange(). I'll show code for doing this in Chapter 7. + +The XTextCursor methods are documented at http://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1text_1_1XTex -tCursor.html (or type lodoc XTextCursor). - - -A Problem with Write.getAllText() -Write.getAllText() may fail if supplied with a very large document because -XTextCursor.getString() might be unable to construct a big enough String object. For -that reason, it's better to iterate over large documents returning a paragraph of text at a -time. These iteration techniques are described next. - - -## 3. Cursor Iteration - -My HighlightText.java example uses paragraph and word cursors (i.e. the -XParagraphCursor and XWordCursor interfaces in Figure 4). It also employs the view -cursor, an XTextViewCursor instance, to control the Writer application's visible -cursor. - -The main() function of HighlightText: - -public static void main(String args[]) -{ - if (args.length < 1) { - System.out.println("Usage: run HighlightText "); - return; - } - - XComponentLoader loader = Lo.loadOffice(); - XTextDocument doc = Write.openDoc(args[0], loader); - if (doc == null) { - System.out.println("Could not open " + args[0]); - Lo.closeOffice(); - return; - } - - GUI.setVisible(doc, true); // make doc visible on-screen - - showParagraphs(doc); - System.out.println("Word count: " + countWords(doc)); - showLines(doc); - - Lo.closeDoc(doc); - Lo.closeOffice(); -} // end of main() - -main() calls Write.openDoc() to return the opened document as an XTextDocument -instance. If you recall, the previous ExtractText.java example started with an -XComponent instance by calling Lo.openDoc(), and then converted it to -XTextDocument. Write.openDoc() returns the XTextDocument reference in one go. - -showParagraphs() moves the visible on-screen cursor through the document, -highlighting a paragraph at a time. This requires two cursors – an instance of -XTextViewCursor and a separate XParagraphCursor. The paragraph cursor is capable -of moving through the document paragraph-by-paragraph, but it's a model cursor, so -invisible to the user looking at the document on-screen. showParagraphs() extracts the -start and end positions of each paragraph and uses them to move the view cursor, -which is visible. - -The code for showParagraphs(): - -private static void showParagraphs(XTextDocument doc) -{ - XTextViewCursor tvc = Write.getViewCursor(doc); - XParagraphCursor paraCursor = Write.getParagraphCursor(doc); - paraCursor.gotoStart(false); - // go to start of text; no selection - do { - paraCursor.gotoEndOfParagraph(true); // select 1 paragraph - String currPara = paraCursor.getString(); - if (currPara.length() > 0) { - // move view cursor to highlight current paragraph - tvc.gotoRange( paraCursor.getStart(), false); - tvc.gotoRange( paraCursor.getEnd(), true); - - Lo.wait(500); // slow down paragraph changing speed - } - } while (paraCursor.gotoNextParagraph(false)); -} // end of showParagraphs() - -The code utilizes two Write utility functions (Write.getViewCursor() and -Write.getParagraphCursor()) to create the cursors. The subsequent do-while loop is a -common coding pattern for iterating over a text document: - -paraCursor.gotoStart(false); -do { - paraCursor.gotoEndOfParagraph(true); // select 1 paragraph - - // do something to the selected text range... - -} while (paraCursor.gotoNextParagraph(false)); - -gotoNextParagraph() tries to move the cursor to the beginning of the next paragraph. - -If the moves fails (i.e. when the cursor has reached the end of the document), the -function returns false, and the loop terminates. - -The call to gotoEndOfParagraph() at the beginning of the loop moves the cursor to the -end of the paragraph and selects its text. Since the cursor was originally at the start of -the paragraph, the selection will span that paragraph. - -XParagraphCursor and the sentence and word cursors inherit XTextCursor, as shown -in Figure 5. - - +tCursor.html (or type `lodoc XTextCursor`). + + +#### A Problem with Write.getAllText() + +Write.getAllText() may fail if supplied with a very large document because +XTextCursor.getString() might be unable to construct a big enough String object. For +that reason, it's better to iterate over large documents returning a paragraph of text at a +time. These iteration techniques are described next. + + +## 3. Cursor Iteration + +My HighlightText.java example uses paragraph and word cursors (i.e. the +XParagraphCursor and XWordCursor interfaces in Figure 4). It also employs the view +cursor, an XTextViewCursor instance, to control the Writer application's visible +cursor. + +```java +The main() function of HighlightText: + +public static void main(String args[]) +{ + if (args.length < 1) { + System.out.println("Usage: run HighlightText "); + return; + } + + XComponentLoader loader = Lo.loadOffice(); + XTextDocument doc = Write.openDoc(args[0], loader); + if (doc == null) { + System.out.println("Could not open " + args[0]); + Lo.closeOffice(); + return; + } + + GUI.setVisible(doc, true); // make doc visible on-screen + + showParagraphs(doc); + System.out.println("Word count: " + countWords(doc)); + showLines(doc); + + Lo.closeDoc(doc); + Lo.closeOffice(); +} // end of main() +``` + +main() calls Write.openDoc() to return the opened document as an XTextDocument +instance. If you recall, the previous ExtractText.java example started with an +XComponent instance by calling Lo.openDoc(), and then converted it to +XTextDocument. Write.openDoc() returns the XTextDocument reference in one go. + +showParagraphs() moves the visible on-screen cursor through the document, +highlighting a paragraph at a time. This requires two cursors – an instance of +XTextViewCursor and a separate XParagraphCursor. The paragraph cursor is capable +of moving through the document paragraph-by-paragraph, but it's a model cursor, so +invisible to the user looking at the document on-screen. showParagraphs() extracts the +start and end positions of each paragraph and uses them to move the view cursor, +which is visible. + +The code for showParagraphs(): + +```java +private static void showParagraphs(XTextDocument doc) +{ + XTextViewCursor tvc = Write.getViewCursor(doc); + XParagraphCursor paraCursor = Write.getParagraphCursor(doc); + paraCursor.gotoStart(false); + // go to start of text; no selection + do { + paraCursor.gotoEndOfParagraph(true); // select 1 paragraph + String currPara = paraCursor.getString(); + if (currPara.length() > 0) { + // move view cursor to highlight current paragraph + tvc.gotoRange( paraCursor.getStart(), false); + tvc.gotoRange( paraCursor.getEnd(), true); + + Lo.wait(500); // slow down paragraph changing speed + } + } while (paraCursor.gotoNextParagraph(false)); +} // end of showParagraphs() +``` + +The code utilizes two Write utility functions (Write.getViewCursor() and +Write.getParagraphCursor()) to create the cursors. The subsequent do-while loop is a +common coding pattern for iterating over a text document: + +```java +paraCursor.gotoStart(false); +do { + paraCursor.gotoEndOfParagraph(true); // select 1 paragraph + + // do something to the selected text range... + +} while (paraCursor.gotoNextParagraph(false)); +``` + +gotoNextParagraph() tries to move the cursor to the beginning of the next paragraph. + +If the moves fails (i.e. when the cursor has reached the end of the document), the +function returns false, and the loop terminates. + +The call to gotoEndOfParagraph() at the beginning of the loop moves the cursor to the +end of the paragraph and selects its text. Since the cursor was originally at the start of +the paragraph, the selection will span that paragraph. + +XParagraphCursor and the sentence and word cursors inherit XTextCursor, as shown +in Figure 5. + ![](images/05-Text_API_Overview-5.png) -Figure 5. The Model Cursors Inheritance Hierarchy. - - -Since all these cursors also inherit XTextRange, they can easily access and change -their text selections/positions. In the showParagraphs() method above, the two ends of -the paragraph are obtained by calling the inherited XTextRange.getStart() and -XTextRange.getEnd(), and the positions are used to move the view cursor: - -XTextViewCursor tvc = Write.getViewCursor(doc); - : -tvc.gotoRange(paraCursor.getStart(), false); -tvc.gotoRange(paraCursor.getEnd(), true); - -gotoRange() sets the text range/position of the view cursor: the first call moves the -cursor to the paragraph's starting position without selecting anything, and the second -moves it to the end position, selecting all the text in between. Since this is a view -cursor, the selection is visible on-screen, as illustrated in Figure 6. - - - +Figure 5. The Model Cursors Inheritance Hierarchy. + + +Since all these cursors also inherit XTextRange, they can easily access and change +their text selections/positions. In the showParagraphs() method above, the two ends of +the paragraph are obtained by calling the inherited XTextRange.getStart() and +XTextRange.getEnd(), and the positions are used to move the view cursor: + +```java +XTextViewCursor tvc = Write.getViewCursor(doc); + : +tvc.gotoRange(paraCursor.getStart(), false); +tvc.gotoRange(paraCursor.getEnd(), true); +``` + +gotoRange() sets the text range/position of the view cursor: the first call moves the +cursor to the paragraph's starting position without selecting anything, and the second +moves it to the end position, selecting all the text in between. Since this is a view +cursor, the selection is visible on-screen, as illustrated in Figure 6. + ![](images/05-Text_API_Overview-6.png) -Figure 6. A Selected Paragraph. +Figure 6. A Selected Paragraph. + - -Note that getStart() and getEnd() do not return integers but collapsed text ranges, -which is Office-lingo for a range that starts and ends at the same cursor position. +Note that getStart() and getEnd() do not return integers but collapsed text ranges, +which is Office-lingo for a range that starts and ends at the same cursor position. -Somewhat confusingly, the XTextViewCursor interface inherits XTextCursor (as -shown in Figure 7). This only means that XTextViewCursor supports the same -character-based movement and text range operations as the model-based cursor. +Somewhat confusingly, the XTextViewCursor interface inherits XTextCursor (as +shown in Figure 7). This only means that XTextViewCursor supports the same +character-based movement and text range operations as the model-based cursor. - ![](images/05-Text_API_Overview-7.png) -Figure 7. The XTextViewCursor Inheritance Hierarchy. - - - -## 4. Creating Cursors - -An XTextCursor is created by calling Write.getCursor(), which can then be converted -into a paragraph, sentence, or word cursor by using Lo.qi(). For example, the Write -utility class defines getParagraphCursor() as: - -public static XParagraphCursor getParagraphCursor( - XTextDocument textDoc) -{ XTextCursor cursor = getCursor(textDoc); - if (cursor == null) { - System.out.println("Text cursor is null"); - return null; - } - else - return Lo.qi(XParagraphCursor.class, cursor); -} // end of getParagraphCursor() - -Obtaining the view cursor is a little more tricky since it's only accessible via the -document's controller. - -As described in Chapter 1, section 5 about the FCM relationship, the controller is -reached via the document's model, as shown in the first two lines of -Write.getViewCursor(): - -public static XTextViewCursor getViewCursor(XTextDocument textDoc) -{ - XModel model = Lo.qi(XModel.class, textDoc); - XController xController = model.getCurrentController(); - - // the controller supplies the TextViewCursor - XTextViewCursorSupplier supplier = Lo.qi( - XTextViewCursorSupplier.class, xController); - return supplier.getViewCursor(); -} // end of getViewCursor() - -The view cursor isn't directly accessible from the controller; a supplier must be -queried, even though there's only one view cursor per document. - - -### 4.1. Counting Words - -countWords() in HightlightText.java shows how to iterate over the document using a -word cursor: - -private static int countWords(XTextDocument doc) -{ - XWordCursor wordCursor = Write.getWordCursor(doc); - wordCursor.gotoStart(false); // go to start of text - - int wordCount = 0; - String currWord; - do { - wordCursor.gotoEndOfWord(true); - currWord = wordCursor.getString(); - if (currWord.length() > 0) - wordCount++; - } while( wordCursor.gotoNextWord(false)); - return wordCount; -} // end of countWords() - -This uses the same kind of do-while loop as showParagraphs() except that the -XTextWordCursor methods gotoEndOfWord() and gotoNextWord() control the -iteration. Also, there's no need for an XTextViewCursor instance since the selected -words aren't shown on the screen. - - -### 4.2. Displaying Lines - -showLines() in HightlightText.java iterates over the document highlighting a line at a -time. Don't confuse this with sentence selection because a sentence may consist of -several lines on the screen. A sentence is part of the text's organization (i.e. in terms -of words, sentences, and paragraphs) while a line is part of the document view (i.e. - -line, page, screen). This means that XLineCursor is a view cursor, which is obtained -by converting XTextViewCursor with Lo.qi(): - -XTextViewCursor tvc = Write.getViewCursor(doc); -XLineCursor lineCursor = Lo.qi(XLineCursor.class, tvc); - -The line cursor has limited functionality compared to the model cursors (paragraph, -sentence, word). In particular, there's no "next' function for moving to the next line -(unlike gotoNextParagraph() or gotoNextWord()). The screen cursor also lacks this -ability, but the page cursor offers jumpToNextPage(). - -One way of getting around the absence of a 'next' operation is shown in showLines(): - -private static void showLines(XTextDocument doc) -{ - XTextViewCursor tvc = Write.getViewCursor(doc); - tvc.gotoStart(false); // go to start of text - - XLineCursor lineCursor = - Lo.qi(XLineCursor.class, tvc); - - boolean haveText = true; - do { - lineCursor.gotoStartOfLine(false); - lineCursor.gotoEndOfLine(true); // select 1 line - - Lo.wait(500); // slow down the line changing speed - tvc.collapseToEnd(); // make selection disappear - - haveText = tvc.goRight((short) 1, true); // move 1 char - } while (haveText); -} // end of showLines() - -The view cursor is manipulated using the XTextViewCursor object and the -XLineCursor line cursor. This is possible since the two references point to the same -on-screen cursor. Either one can move it around the display. - -Inside the loop, XLineCursor's gotoStartOfLine() and gotoEndOfLine() highlight a -single line. Then the XTextViewCursor instance deselects the line, by moving the -cursor to the end of the selection with collapseToEnd(). At the end of the loop, -goRight() tries to move the cursor one character to the right. If goRight() succeeds -then the cursor is shifted one position to the first character of the next line. When the -loop repeats, this line will be selected. If doRight() fails, then there are no more -characters to be read from the document, and the loop finishes. - - - -## 5. Creating a Document - -All the examples so far have involved the manipulation of an existing document. The -HelloText.java example creates a new text document, containing two short -paragraphs, and saves it as "hello.odt". The main() function is: - -public static void main(String[] args) -{ - XComponentLoader loader = Lo.loadOffice(); - XTextDocument doc = Write.createDoc(loader); - - if (doc == null) { - System.out.println("Writer doc creation failed"); - Lo.closeOffice(); - return; - } - - GUI.setVisible(doc, true); // make the document visible - - XTextCursor cursor = Write.getCursor(doc); - cursor.gotoEnd(false); - // move cursor to end of doc before appending - - Write.appendPara(cursor, "Hello LibreOffice.\n"); - Lo.wait(1000); // slow things down so they can be seen - - Write.appendPara(cursor, "How are you?."); - Lo.wait(2000); - - Lo.saveDoc(doc, "hello.odt"); - // or use extension doc, docx, rtf, pdf, txt - Lo.closeDoc(doc); - Lo.closeOffice(); -} // end of main() - -Write.createDoc() calls Lo.createDoc() with the text document service name (the -Lo.WRITER_STR constant is "swriter"). Office creates a TextDocument service with -an XComponent interface, which is cast to the XTextDocument interface, and -returned: - -// in the Write class -public static XTextDocument createDoc(XComponentLoader loader) -{ - XComponent doc = Lo.createDoc(Lo.WRITER_STR, loader); - return Lo.qi(XTextDocument.class, doc); -} - -Text documents are saved using Lo.saveDoc() which was described in Chapter 2. - -saveDoc() examines the filename's extension to determine its type. The known -extensions include doc, docx, rtf, odt, pdf, and txt. - -Back in HelloText.java, a cursor is needed before text can be added; one is created by -calling Write.getCursor(). - -The call to XTextCursor.gotoEnd() isn't really necessary because the new cursor is -pointing to an empty document so is already at its end. It's included to emphasize the -assumption by Write.appendPara() (and other Write.appendXXX() functions) that the -cursor is positioned at the end of the document before new text is added. - -Write.appendPara() calls three other methods in the Write utility class: - -// in the Write class -public static int appendPara(XTextCursor cursor, String text) -{ append(cursor, text); - append(cursor, ControlCharacter.PARAGRAPH_BREAK); - return getPosition(cursor); -} - -The 'append' name is utilized several times in Write – one version takes a string as its -second argument, the other a short representing a control character: - -// in the Write class -public static int append(XTextCursor cursor, String text) -{ cursor.setString(text); - cursor.gotoEnd(false); - return getPosition(cursor); -} // end of append() - - -public static int append(XTextCursor cursor, short ctrlChar) -{ XText xText = cursor.getText(); - xText.insertControlCharacter(cursor, ctrlChar, false); - cursor.gotoEnd(false); - return getPosition(cursor); -} - -The first append() function uses XTextCursor.setString() to add the user-supplied -string. The second function employs XTextCursor.insertControlCharacter(). After the -addition of the text or character, the cursor is moved to the end of the document, and -getPosition() is called to return the cursor's new position as an integer: - -public static int getPosition(XTextCursor cursor) -{ return (cursor.getText().getString()).length(); } - -getPosition() is not very robust – it assumes that getString() will be able to convert the -document's text into a string. This may fail if the document is very big (you may -recall the same problem with my Write.getAllText() described earlier). - -Office deals with this size issue by using XTextRange instances, which encapsulate -text ranges and positions. Write.getPosition() returns an integer because its easier to -understand when you're first learning to program with Office. It's better style to use -and compare XTextRanges rather than integer positions, an approach I'll demonstrate -in the next section. - - - -## 6. Using and Comparing Text Cursors - -My TalkingBook.java example utilizes the third-party library FreeTTS -(http://freetts.sourceforge.net/) to convert text into speech. The inner workings of -FreeTTS aren't relevant here, so are hidden inside a support class called Speaker. It -has three public methods: - Speaker(): instantiates a FreeTTS instance – a male voice - say(String s): converts the string s into speech - dispose(): closes down the FreeTTS instance -TalkingBook employs two text cursors: a paragraph cursor that iterates over the -paragraphs in the document, and a sentence cursor that iterates over all the sentences -in the current paragraph and passes each sentence to Speaker.say(). FreeTTS is -capable of speaking long or short sequences of text, but TalkingBook processes a -sentence at a time since this sounds more natural when spoken. - -The crucial function in TalkingBook.java is speakSentences(): - -private static void speakSentences(XTextDocument doc) -{ - Speaker speaker = new Speaker(); // create FreeTTS voice - - XTextViewCursor tvc = Write.getViewCursor(doc); - XParagraphCursor paraCursor = Write.getParagraphCursor(doc); - paraCursor.gotoStart(false); // go to start of text - - // create range comparer for the entire document - XTextRangeCompare comparer = Lo.qi( - XTextRangeCompare.class, doc.getText()); - - String currParaStr, currSentStr; - do { - paraCursor.gotoEndOfParagraph(true); // select 1 paragraph - XTextRange endPara = paraCursor.getEnd(); - - currParaStr = paraCursor.getString(); - - if (currParaStr.length() > 0) { - // set sentence cursor to start of paragraph - XTextCursor cursor = - paraCursor.getText().createTextCursorByRange( - paraCursor.getStart()); - XSentenceCursor sc = Lo.qi( - XSentenceCursor.class, cursor); - sc.gotoStartOfSentence(false); // goto start - do { - sc.gotoEndOfSentence(true); // select 1 sentence - if (comparer.compareRegionEnds(endPara, sc.getEnd()) > 0) - // has sentence cursor passed end of current paragraph? - break; - - // move view cursor to highlight current sentence - tvc.gotoRange(sc.getStart(), false); - tvc.gotoRange(sc.getEnd(), true); - - currSentStr = stripNonWordChars(sc.getString()); - // clean up string to make speech nicer - if (currSentStr.length() > 0) - speaker.say(currSentStr); - } while (sc.gotoNextSentence(false)); - } - } while (paraCursor.gotoNextParagraph(false)); - - speaker.dispose(); -} // end of speakSentences() - -speakSentences() comprises two nested loops: the outer loop iterates through the -paragraphs, and the inner loop through the sentences in the current paragraph. - -The sentence cursor is created like so: - -XTextCursor cursor = - paraCursor.getText().createTextCursorByRange( - paraCursor.getStart()); -XSentenceCursor sc = - Lo.qi(XSentenceCursor.class, cursor); - -The XText reference is returned by paraCursor.getText(), and a text cursor is created. - -createTextCursorByRange() allows the start position of the cursor to be specified. The -text cursor is converted into a sentence cursor with Lo.qi(). - -The tricky aspect of this code is the meaning of paraCursor.getText() which is the -XText object that paraCursor utilizes. This is not a single paragraph but the entire text -document. Remember that the paragraph cursor is created with: -XParagraphCursor paraCursor = Write.getParagraphCursor(doc); -This corresponds to: - -XText xText = doc.getText(); -XTextCursor textCursor = xText.createTextCursor(); -XParagraphCursor paraCursor = Lo.qi( - XParagraphCursor.class, textCursor); - -Both the paragraph and sentence cursors refer to -the entire text document. This means that I cannot -code the inner loop using the coding pattern from -before. That would result in something like the -following: - -// set sentence cursor to point to start -of this paragraph -XTextCursor cursor = paraCursor.getText().createTextCursorByRange( - paraCursor.getStart()) -XSentenceCursor sc = Lo.qi( - XSentenceCursor.class, cursor); - -sc.gotoStartOfSentence(false); // goto start -do { - sc.gotoEndOfSentence(true); // select 1 sentence - // do something with the sentence - // : -} while (sc.gotoNextSentence(false)); - -The problem with the above code fragment is that -XSentenceCursor.gotoNextSentence() will keep moving to the next sentence until it -reaches the end of the text document. This is not the desired behavior – what I want is -for the loop to terminate when the last sentence of the current paragraph has been -processed. - -We need to compare text ranges, in this case the end of the current sentence with the -end of the current paragraph. This capability is handled by the XTextRangeCompare -interface. A comparer object is created at the beginning of speakSentence(), initialized -to compare ranges that can span the entire document: - -XTextRangeCompare comparer = - Lo.qi(XTextRangeCompare.class, doc.getText()); - -This comparer object is utilized inside the sentence-iterating loop to compare the end -of the current paragraph (the endPara text range) with the end of current sentence (the -sc.getEnd() text range): - -if (comparer.compareRegionEnds(endPara, sc.getEnd()) > 0) - break; - -If the sentence ends after the end of the paragraph then compareRegionEnds() returns -a positive number, and the inner loop terminates. - -This approach doesn't suffer from the problem in Write.getPosition() with -XTextRange.getString(), and using its length as a position: -To further confuse matters, a -XText object does not always -correspond to the entire text -document. For example, a text -frame (e.g. like this one) can -return an XText object for the -text only inside the frame. - - -public static int getPosition(XTextCursor cursor) -{ return (cursor.getText().getString()).length(); - -Since there's no String object being created by the comparer, there's no way that the -instantiation can fail due to the size of the text. - - - -## 7. Inserting/Changing Text in a Document - -My ShuffleWords.java example searches a document and changes the words it -encounters. Figure 8 shows the program in progress: "predominates" has been -selected but not yet changed, but all the previous 'big' words have been shuffled. - - - +Figure 7. The XTextViewCursor Inheritance Hierarchy. + + +## 4. Creating Cursors + +An XTextCursor is created by calling Write.getCursor(), which can then be converted +into a paragraph, sentence, or word cursor by using Lo.qi(). For example, the Write +utility class defines getParagraphCursor() as: + +```java +public static XParagraphCursor getParagraphCursor( + XTextDocument textDoc) +{ XTextCursor cursor = getCursor(textDoc); + if (cursor == null) { + System.out.println("Text cursor is null"); + return null; + } + else + return Lo.qi(XParagraphCursor.class, cursor); +} // end of getParagraphCursor() +``` + +Obtaining the view cursor is a little more tricky since it's only accessible via the +document's controller. + +As described in Chapter 1, section 5 about the FCM relationship, the controller is +reached via the document's model, as shown in the first two lines of +Write.getViewCursor(): + +```java +public static XTextViewCursor getViewCursor(XTextDocument textDoc) +{ + XModel model = Lo.qi(XModel.class, textDoc); + XController xController = model.getCurrentController(); + + // the controller supplies the TextViewCursor + XTextViewCursorSupplier supplier = Lo.qi( + XTextViewCursorSupplier.class, xController); + return supplier.getViewCursor(); +} // end of getViewCursor() +``` + +The view cursor isn't directly accessible from the controller; a supplier must be +queried, even though there's only one view cursor per document. + + +### 4.1. Counting Words + +countWords() in HightlightText.java shows how to iterate over the document using a +word cursor: + +```java +private static int countWords(XTextDocument doc) +{ + XWordCursor wordCursor = Write.getWordCursor(doc); + wordCursor.gotoStart(false); // go to start of text + + int wordCount = 0; + String currWord; + do { + wordCursor.gotoEndOfWord(true); + currWord = wordCursor.getString(); + if (currWord.length() > 0) + wordCount++; + } while( wordCursor.gotoNextWord(false)); + return wordCount; +} // end of countWords() +``` + +This uses the same kind of do-while loop as showParagraphs() except that the +XTextWordCursor methods gotoEndOfWord() and gotoNextWord() control the +iteration. Also, there's no need for an XTextViewCursor instance since the selected +words aren't shown on the screen. + + +### 4.2. Displaying Lines + +showLines() in HightlightText.java iterates over the document highlighting a line at a +time. Don't confuse this with sentence selection because a sentence may consist of +several lines on the screen. A sentence is part of the text's organization (i.e. in terms +of words, sentences, and paragraphs) while a line is part of the document view (i.e. + +line, page, screen). This means that XLineCursor is a view cursor, which is obtained +by converting XTextViewCursor with Lo.qi(): + +```java +XTextViewCursor tvc = Write.getViewCursor(doc); +XLineCursor lineCursor = Lo.qi(XLineCursor.class, tvc); +``` + +The line cursor has limited functionality compared to the model cursors (paragraph, +sentence, word). In particular, there's no "next' function for moving to the next line +(unlike gotoNextParagraph() or gotoNextWord()). The screen cursor also lacks this +ability, but the page cursor offers jumpToNextPage(). + +One way of getting around the absence of a 'next' operation is shown in showLines(): + +```java +private static void showLines(XTextDocument doc) +{ + XTextViewCursor tvc = Write.getViewCursor(doc); + tvc.gotoStart(false); // go to start of text + + XLineCursor lineCursor = + Lo.qi(XLineCursor.class, tvc); + + boolean haveText = true; + do { + lineCursor.gotoStartOfLine(false); + lineCursor.gotoEndOfLine(true); // select 1 line + + Lo.wait(500); // slow down the line changing speed + tvc.collapseToEnd(); // make selection disappear + + haveText = tvc.goRight((short) 1, true); // move 1 char + } while (haveText); +} // end of showLines() +``` + +The view cursor is manipulated using the XTextViewCursor object and the +XLineCursor line cursor. This is possible since the two references point to the same +on-screen cursor. Either one can move it around the display. + +Inside the loop, XLineCursor's gotoStartOfLine() and gotoEndOfLine() highlight a +single line. Then the XTextViewCursor instance deselects the line, by moving the +cursor to the end of the selection with collapseToEnd(). At the end of the loop, +goRight() tries to move the cursor one character to the right. If goRight() succeeds +then the cursor is shifted one position to the first character of the next line. When the +loop repeats, this line will be selected. If doRight() fails, then there are no more +characters to be read from the document, and the loop finishes. + + +## 5. Creating a Document + +All the examples so far have involved the manipulation of an existing document. The +HelloText.java example creates a new text document, containing two short +paragraphs, and saves it as "hello.odt". The main() function is: + +```java +public static void main(String[] args) +{ + XComponentLoader loader = Lo.loadOffice(); + XTextDocument doc = Write.createDoc(loader); + + if (doc == null) { + System.out.println("Writer doc creation failed"); + Lo.closeOffice(); + return; + } + + GUI.setVisible(doc, true); // make the document visible + + XTextCursor cursor = Write.getCursor(doc); + cursor.gotoEnd(false); + // move cursor to end of doc before appending + + Write.appendPara(cursor, "Hello LibreOffice.\n"); + Lo.wait(1000); // slow things down so they can be seen + + Write.appendPara(cursor, "How are you?."); + Lo.wait(2000); + + Lo.saveDoc(doc, "hello.odt"); + // or use extension doc, docx, rtf, pdf, txt + Lo.closeDoc(doc); + Lo.closeOffice(); +} // end of main() +``` + +Write.createDoc() calls Lo.createDoc() with the text document service name (the +Lo.WRITER_STR constant is "swriter"). Office creates a TextDocument service with +an XComponent interface, which is cast to the XTextDocument interface, and +returned: + +```java +// in the Write class +public static XTextDocument createDoc(XComponentLoader loader) +{ + XComponent doc = Lo.createDoc(Lo.WRITER_STR, loader); + return Lo.qi(XTextDocument.class, doc); +} +``` + +Text documents are saved using Lo.saveDoc() which was described in Chapter 2. +saveDoc() examines the filename's extension to determine its type. The known +extensions include doc, docx, rtf, odt, pdf, and txt. + +Back in HelloText.java, a cursor is needed before text can be added; one is created by +calling Write.getCursor(). + +The call to XTextCursor.gotoEnd() isn't really necessary because the new cursor is +pointing to an empty document so is already at its end. It's included to emphasize the +assumption by Write.appendPara() (and other Write.appendXXX() functions) that the +cursor is positioned at the end of the document before new text is added. + +Write.appendPara() calls three other methods in the Write utility class: + +```java +// in the Write class +public static int appendPara(XTextCursor cursor, String text) +{ append(cursor, text); + append(cursor, ControlCharacter.PARAGRAPH_BREAK); + return getPosition(cursor); +} +``` + +The 'append' name is utilized several times in Write – one version takes a string as its +second argument, the other a short representing a control character: + +```java +// in the Write class +public static int append(XTextCursor cursor, String text) +{ cursor.setString(text); + cursor.gotoEnd(false); + return getPosition(cursor); +} // end of append() + + +public static int append(XTextCursor cursor, short ctrlChar) +{ XText xText = cursor.getText(); + xText.insertControlCharacter(cursor, ctrlChar, false); + cursor.gotoEnd(false); + return getPosition(cursor); +} +``` + +The first append() function uses XTextCursor.setString() to add the user-supplied +string. The second function employs XTextCursor.insertControlCharacter(). After the +addition of the text or character, the cursor is moved to the end of the document, and +getPosition() is called to return the cursor's new position as an integer: + +```java +public static int getPosition(XTextCursor cursor) +{ return (cursor.getText().getString()).length(); } +``` + +getPosition() is not very robust – it assumes that getString() will be able to convert the +document's text into a string. This may fail if the document is very big (you may +recall the same problem with my Write.getAllText() described earlier). + +Office deals with this size issue by using XTextRange instances, which encapsulate +text ranges and positions. Write.getPosition() returns an integer because its easier to +understand when you're first learning to program with Office. It's better style to use +and compare XTextRanges rather than integer positions, an approach I'll demonstrate +in the next section. + + +## 6. Using and Comparing Text Cursors + +My TalkingBook.java example utilizes the third-party library FreeTTS +(http://freetts.sourceforge.net/) to convert text into speech. The inner workings of +FreeTTS aren't relevant here, so are hidden inside a support class called Speaker. It +has three public methods: + +* Speaker(): instantiates a FreeTTS instance – a male voice +* say(String s): converts the string s into speech +* dispose(): closes down the FreeTTS instance + +TalkingBook employs two text cursors: a paragraph cursor that iterates over the +paragraphs in the document, and a sentence cursor that iterates over all the sentences +in the current paragraph and passes each sentence to Speaker.say(). FreeTTS is +capable of speaking long or short sequences of text, but TalkingBook processes a +sentence at a time since this sounds more natural when spoken. + +The crucial function in TalkingBook.java is speakSentences(): + +```java +private static void speakSentences(XTextDocument doc) +{ + Speaker speaker = new Speaker(); // create FreeTTS voice + + XTextViewCursor tvc = Write.getViewCursor(doc); + XParagraphCursor paraCursor = Write.getParagraphCursor(doc); + paraCursor.gotoStart(false); // go to start of text + + // create range comparer for the entire document + XTextRangeCompare comparer = Lo.qi( + XTextRangeCompare.class, doc.getText()); + + String currParaStr, currSentStr; + do { + paraCursor.gotoEndOfParagraph(true); // select 1 paragraph + XTextRange endPara = paraCursor.getEnd(); + + currParaStr = paraCursor.getString(); + + if (currParaStr.length() > 0) { + // set sentence cursor to start of paragraph + XTextCursor cursor = + paraCursor.getText().createTextCursorByRange( + paraCursor.getStart()); + XSentenceCursor sc = Lo.qi( + XSentenceCursor.class, cursor); + sc.gotoStartOfSentence(false); // goto start + do { + sc.gotoEndOfSentence(true); // select 1 sentence + if (comparer.compareRegionEnds(endPara, sc.getEnd()) > 0) + // has sentence cursor passed end of current paragraph? + break; + + // move view cursor to highlight current sentence + tvc.gotoRange(sc.getStart(), false); + tvc.gotoRange(sc.getEnd(), true); + + currSentStr = stripNonWordChars(sc.getString()); + // clean up string to make speech nicer + if (currSentStr.length() > 0) + speaker.say(currSentStr); + } while (sc.gotoNextSentence(false)); + } + } while (paraCursor.gotoNextParagraph(false)); + + speaker.dispose(); +} // end of speakSentences() +``` + +speakSentences() comprises two nested loops: the outer loop iterates through the +paragraphs, and the inner loop through the sentences in the current paragraph. + +The sentence cursor is created like so: + +```java +XTextCursor cursor = + paraCursor.getText().createTextCursorByRange( + paraCursor.getStart()); +XSentenceCursor sc = + Lo.qi(XSentenceCursor.class, cursor); +``` + +The XText reference is returned by paraCursor.getText(), and a text cursor is created. + +createTextCursorByRange() allows the start position of the cursor to be specified. The +text cursor is converted into a sentence cursor with Lo.qi(). + +The tricky aspect of this code is the meaning of paraCursor.getText() which is the +XText object that paraCursor utilizes. This is not a single paragraph but the entire text +document. Remember that the paragraph cursor is created with: +XParagraphCursor paraCursor = Write.getParagraphCursor(doc); +This corresponds to: + +XText xText = doc.getText(); +XTextCursor textCursor = xText.createTextCursor(); +XParagraphCursor paraCursor = Lo.qi( + XParagraphCursor.class, textCursor); + +Both the paragraph and sentence cursors refer to +the entire text document. This means that I cannot +code the inner loop using the coding pattern from +before. That would result in something like the +following: + +```java +// set sentence cursor to point to start +of this paragraph +XTextCursor cursor = paraCursor.getText().createTextCursorByRange( + paraCursor.getStart()) +XSentenceCursor sc = Lo.qi( + XSentenceCursor.class, cursor); + +sc.gotoStartOfSentence(false); // goto start +do { + sc.gotoEndOfSentence(true); // select 1 sentence + // do something with the sentence + // : +} while (sc.gotoNextSentence(false)); +``` + +!!! Note + To further confuse matters, a + XText object does not always + correspond to the entire text + document. For example, a text + frame (e.g. like this one) can + return an XText object for the + text only inside the frame. + +The problem with the above code fragment is that +XSentenceCursor.gotoNextSentence() will keep moving to the next sentence until it +reaches the end of the text document. This is not the desired behavior – what I want is +for the loop to terminate when the last sentence of the current paragraph has been +processed. + +We need to compare text ranges, in this case the end of the current sentence with the +end of the current paragraph. This capability is handled by the XTextRangeCompare +interface. A comparer object is created at the beginning of speakSentence(), initialized +to compare ranges that can span the entire document: + +```java +XTextRangeCompare comparer = + Lo.qi(XTextRangeCompare.class, doc.getText()); +``` + +This comparer object is utilized inside the sentence-iterating loop to compare the end +of the current paragraph (the endPara text range) with the end of current sentence (the +sc.getEnd() text range): + +```java +if (comparer.compareRegionEnds(endPara, sc.getEnd()) > 0) + break; +``` + +If the sentence ends after the end of the paragraph then compareRegionEnds() returns +a positive number, and the inner loop terminates. + +This approach doesn't suffer from the problem in Write.getPosition() with +XTextRange.getString(), and using its length as a position: + + +```java +public static int getPosition(XTextCursor cursor) +{ return (cursor.getText().getString()).length(); +``` + +Since there's no String object being created by the comparer, there's no way that the +instantiation can fail due to the size of the text. + + +## 7. Inserting/Changing Text in a Document + +My ShuffleWords.java example searches a document and changes the words it +encounters. Figure 8 shows the program in progress: "predominates" has been +selected but not yet changed, but all the previous 'big' words have been shuffled. + ![](images/05-Text_API_Overview-8.png) -Figure 8. Shuffling of Words. - - -A word shuffle is applied to every word of four letters or more, but only involves the -random exchange of the middle letters without changing the first and last characters. - -The applyShuffle() function which iterates through the words in the input file is -similar to countWords() in HighlightText.java. One difference is the use of -XText.insertString(): - -private static void applyShuffle(XTextDocument doc) -{ - XText docText = doc.getText(); - - XWordCursor wordCursor = Write.getWordCursor(doc); - wordCursor.gotoStart(false); // go to start of text - - XTextViewCursor tvc = Write.getViewCursor(doc); - - String currWord; - do { - wordCursor.gotoEndOfWord(true); - - // move the text view cursor, and highlight the current word - tvc.gotoRange(wordCursor.getStart(), false); - tvc.gotoRange(wordCursor.getEnd(), true); - currWord = wordCursor.getString().trim(); - if (currWord.length() > 0) { - Lo.wait(250); - // slow down so user can see selection before change - docText.insertString(wordCursor, midShuffle(currWord), true); - } - } while( wordCursor.gotoNextWord(false)); -} // end of applyShuffle() - -insertString() is located in XSimpleText: -void insertString(XTextRange xRange, String s, boolean willReplace) -The string s is inserted at the cursor's text range position. If willReplace is true then -the string replaces the current selection (which is the case in applyShuffle()). - -midShuffle() shuffles the string in currWord, returning a new word. It doesn't use the -Office API, so I won't explain it here. - - - -## 8. Treating a Document as Paragraphs and Text Portions - -Another approach for moving around a document involves the XEnumerationAccess -interface which treats the document as a series of Paragraph text contents. - -XEnumerationAccess is an interface in the Text service, which means that an XText -reference can be converted into it by using Lo.qi(). These relationships are shown in +Figure 8. Shuffling of Words. -![](images/05-Text_API_Overview-9.png) -Figure 9. +A word shuffle is applied to every word of four letters or more, but only involves the +random exchange of the middle letters without changing the first and last characters. + +The applyShuffle() function which iterates through the words in the input file is +similar to countWords() in HighlightText.java. One difference is the use of +XText.insertString(): + +```java +private static void applyShuffle(XTextDocument doc) +{ + XText docText = doc.getText(); + + XWordCursor wordCursor = Write.getWordCursor(doc); + wordCursor.gotoStart(false); // go to start of text + + XTextViewCursor tvc = Write.getViewCursor(doc); + + String currWord; + do { + wordCursor.gotoEndOfWord(true); + + // move the text view cursor, and highlight the current word + tvc.gotoRange(wordCursor.getStart(), false); + tvc.gotoRange(wordCursor.getEnd(), true); + currWord = wordCursor.getString().trim(); + if (currWord.length() > 0) { + Lo.wait(250); + // slow down so user can see selection before change + docText.insertString(wordCursor, midShuffle(currWord), true); + } + } while( wordCursor.gotoNextWord(false)); +} // end of applyShuffle() +``` + +insertString() is located in XSimpleText: + +```java +void insertString(XTextRange xRange, String s, boolean willReplace) +``` + +The string s is inserted at the cursor's text range position. If willReplace is true then +the string replaces the current selection (which is the case in applyShuffle()). + +midShuffle() shuffles the string in currWord, returning a new word. It doesn't use the +Office API, so I won't explain it here. + + +## 8. Treating a Document as Paragraphs and Text Portions + +Another approach for moving around a document involves the XEnumerationAccess +interface which treats the document as a series of Paragraph text contents. + +XEnumerationAccess is an interface in the Text service, which means that an XText +reference can be converted into it by using Lo.qi(). These relationships are shown in +Figure 9. - ![](images/05-Text_API_Overview-9.png) -Figure 9. The Text Service and its Interfaces. - - -The following code fragment utilizes this technique: - -XText xText = doc.getText(); // get text of document -XEnumerationAccess enumAccess = - Lo.qi(XEnumerationAccess.class, xText); - -XEnumerationAccess contains a single method, createEnumeration() which creates an -enumerator (an instance of XEnumeration). Each element returned from this iterator is -a Paragraph text content: - -// create enumerator over the document text -XEnumeration textEnum = enumAccess.createEnumeration(); - -while (textEnum.hasMoreElements()) { // loop through paragraphs - XTextContent textCon = - Lo.qi(XTextContent.class, textEnum.nextElement()); - // use the Paragraph text content (textCon) in some way... - -} - -Paragraph doesn't support its own interface (i.e. there's no XParagraph), so I've used -Lo.qi() to access its XTextContent interface, which belongs to the TextContent -subclass. The hierarchy is shown in Figure 10. - - +Figure 9. The Text Service and its Interfaces. + + +The following code fragment utilizes this technique: + +```java +XText xText = doc.getText(); // get text of document +XEnumerationAccess enumAccess = + Lo.qi(XEnumerationAccess.class, xText); +``` + +XEnumerationAccess contains a single method, createEnumeration() which creates an +enumerator (an instance of XEnumeration). Each element returned from this iterator is +a Paragraph text content: + +```java +// create enumerator over the document text +XEnumeration textEnum = enumAccess.createEnumeration(); + +while (textEnum.hasMoreElements()) { // loop through paragraphs + XTextContent textCon = + Lo.qi(XTextContent.class, textEnum.nextElement()); + // use the Paragraph text content (textCon) in some way... + +} +``` + +Paragraph doesn't support its own interface (i.e. there's no XParagraph), so I've used +Lo.qi() to access its XTextContent interface, which belongs to the TextContent +subclass. The hierarchy is shown in Figure 10. + ![](images/05-Text_API_Overview-10.png) -Figure 10. The Paragraph Text Content Hierarchy. - - -Iterating over a document to access Paragraph text contents doesn't seem much -different from iterating over a document using a paragraph cursor, except that the -Paragraph service offers a more structured view of a paragraph. - -In particular, you can use another XEnumerationAccess instance to iterate over a -single paragraph, viewing it as a sequence of text portions. - -The following code illustrates the notion, using the textCon text content from the -previous piece of code: - -if (!Info.supportService(textCon, "com.sun.star.text.TextTable")) { - // create enumerator over a paragraph - XEnumerationAccess enumAccess = - Lo.qi(XEnumerationAccess.class, textCon); - XEnumeration paraEnum = enumAccess.createEnumeration(); - - while (paraEnum.hasMoreElements()) { // loop through portions - XTextRange txtRange = - Lo.qi(XTextRange.class, paraEnum.nextElement()); - // use the text portion (txtRange) in some way... - - } -} - -The TextTable service is a subclass of Paragraph, and cannot be enumerated. - -Therefore, I surrounded the paragraph enumerator with an if-test to skip a paragraph if -it's really a table. - -The paragraph enumerator returns text portions, represented by the TextPortion -service. TextPortion contains a lot of useful properties which describe the paragraph, -but it doesn't have its own interface (such as XTextPortion). However, TextPortion -inherits the TextRange service, so I can use Lo.qi() to obtain its XTextRange -interface. This hierarchy is shown in Figure 11. - - +Figure 10. The Paragraph Text Content Hierarchy. + + +Iterating over a document to access Paragraph text contents doesn't seem much +different from iterating over a document using a paragraph cursor, except that the +Paragraph service offers a more structured view of a paragraph. + +In particular, you can use another XEnumerationAccess instance to iterate over a +single paragraph, viewing it as a sequence of text portions. + +The following code illustrates the notion, using the textCon text content from the +previous piece of code: + +```java +if (!Info.supportService(textCon, "com.sun.star.text.TextTable")) { + // create enumerator over a paragraph + XEnumerationAccess enumAccess = + Lo.qi(XEnumerationAccess.class, textCon); + XEnumeration paraEnum = enumAccess.createEnumeration(); + + while (paraEnum.hasMoreElements()) { // loop through portions + XTextRange txtRange = + Lo.qi(XTextRange.class, paraEnum.nextElement()); + // use the text portion (txtRange) in some way... + + } +} +``` + +The TextTable service is a subclass of Paragraph, and cannot be enumerated. + +Therefore, I surrounded the paragraph enumerator with an if-test to skip a paragraph if +it's really a table. + +The paragraph enumerator returns text portions, represented by the TextPortion +service. TextPortion contains a lot of useful properties which describe the paragraph, +but it doesn't have its own interface (such as XTextPortion). However, TextPortion +inherits the TextRange service, so I can use Lo.qi() to obtain its XTextRange +interface. This hierarchy is shown in Figure 11. + ![](images/05-Text_API_Overview-11.png) -Figure 11. The TextPortion Service Hierarchy. - - -TextPortion includes a "TextPortionType" property which identifies the type of the -portion. Other properties access different kinds of portion data, such as a text field or -footnote. - -For instance, the following prints the text portion type and the string inside the -txtRange text portion (txtRange comes from the previous code fragment): - -System.out.println(" " + - Props.getProperty(txtRange, "TextPortionType") + - " = \"" + txtRange.getString() + "\""); - -These code fragments are combined together in my ShowBookText.java example. - -More details on enumerators and text portions are given in the Developers Guide at -https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Iterating_over_Text -(or use loGuide "Iterating over Text"). - - - -## 9. Appending Documents Together - -If you need to write a large multi-part document (e.g. a thesis with chapters, -appendices, contents page, and an index) then you should utilize a master document, -which acts as a repository of links to documents representing the component parts. - -You can find out about master documents in Chapter 13 of the Writers Guide, at -https://wiki.documentfoundation.org/Documentation/Publications. - -However, the complexity of master documents isn't always needed. Often the aim is -simply to append one document to the end of another. In that case, the -XDocumentInsertable interface, and its insertDocumentFromURL() method is more -suitable. - -My DocsAppend.java example uses -XDocumentInsertable.insertDocumentFromURL(). A list of filenames is read from -the command line; the first file is opened, and the other files appended to it by -appendTextFiles(): - -// part of DocsAppend.java -private static void appendTextFiles(XTextDocument doc, String[] args) -{ - XTextCursor cursor = Write.getCursor(doc); - for (int i=1; i < args.length; i++) { - /* start at 1 to skip the first file, which has - been opened as doc */ - try { - cursor.gotoEnd(false); - // Write.pageBreak(cursor); - - System.out.println("Appending " + args[i]); - XDocumentInsertable inserter = - Lo.qi(XDocumentInsertable.class, cursor); - if (inserter == null) - System.out.println("Inserter could not be created"); - else - inserter.insertDocumentFromURL( - FileIO.fnmToURL(args[i]), new PropertyValue[0]); - } - catch (java.lang.Exception e) - { System.out.println("Could not append " + - args[i] + ": " + e); } - } -} // end of appendTextFiles() - -An XDocumentInsertable instance is obtained by converting the text cursor with -UnoRuntime.queryInterface(). - -XDocumentInsertable.insertDocumentFromURL() requires two arguments – the URL -of the file that's being appended, and an empty property value array. - - - - +Figure 11. The TextPortion Service Hierarchy. + + +TextPortion includes a "TextPortionType" property which identifies the type of the +portion. Other properties access different kinds of portion data, such as a text field or +footnote. + +For instance, the following prints the text portion type and the string inside the +txtRange text portion (txtRange comes from the previous code fragment): + +```java +System.out.println(" " + + Props.getProperty(txtRange, "TextPortionType") + + " = \"" + txtRange.getString() + "\""); +``` + +These code fragments are combined together in my ShowBookText.java example. + +More details on enumerators and text portions are given in the Developers Guide at +https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Iterating_over_Text +(or use loGuide "Iterating over Text"). + + + +## 9. Appending Documents Together + +If you need to write a large multi-part document (e.g. a thesis with chapters, +appendices, contents page, and an index) then you should utilize a master document, +which acts as a repository of links to documents representing the component parts. + +You can find out about master documents in Chapter 13 of the Writers Guide, at +https://wiki.documentfoundation.org/Documentation/Publications. + +However, the complexity of master documents isn't always needed. Often the aim is +simply to append one document to the end of another. In that case, the +XDocumentInsertable interface, and its insertDocumentFromURL() method is more +suitable. + +My DocsAppend.java example uses +XDocumentInsertable.insertDocumentFromURL(). A list of filenames is read from +the command line; the first file is opened, and the other files appended to it by +appendTextFiles(): + +```java +// part of DocsAppend.java +private static void appendTextFiles(XTextDocument doc, String[] args) +{ + XTextCursor cursor = Write.getCursor(doc); + for (int i=1; i < args.length; i++) { + /* start at 1 to skip the first file, which has + been opened as doc */ + try { + cursor.gotoEnd(false); + // Write.pageBreak(cursor); + + System.out.println("Appending " + args[i]); + XDocumentInsertable inserter = + Lo.qi(XDocumentInsertable.class, cursor); + if (inserter == null) + System.out.println("Inserter could not be created"); + else + inserter.insertDocumentFromURL( + FileIO.fnmToURL(args[i]), new PropertyValue[0]); + } + catch (java.lang.Exception e) + { System.out.println("Could not append " + + args[i] + ": " + e); } + } +} // end of appendTextFiles() +``` + +An XDocumentInsertable instance is obtained by converting the text cursor with +UnoRuntime.queryInterface(). + +XDocumentInsertable.insertDocumentFromURL() requires two arguments – the URL +of the file that's being appended, and an empty property value array. \ No newline at end of file diff --git a/docs/06-Text_Styles.md b/docs/06-Text_Styles.md index 175fea6..b8aad05 100644 --- a/docs/06-Text_Styles.md +++ b/docs/06-Text_Styles.md @@ -1,1017 +1,1056 @@ -# Chapter 6. Text Styles +# Chapter 6. Text Styles !!! note "Topics" - Five Style - Families; Properties; - Listing Styles; Creating - a Style; Applying Styles; - Paragraph/Word Styles; - Hyperlink Styling; Text - Numbering; Headers and - Footers - - Example folders: "Text - Tests" and "Utils" - - -This chapter focuses on how text documents styles can be -examined and manipulated. This revolves around the -XStyleFamiliesSupplier interface in -GenericTextDocument, which is highlighted in Figure 1 (a -repeat of Figure 2 in Chapter 5). - - + Five Style + Families; Properties; + Listing Styles; Creating + a Style; Applying Styles; + Paragraph/Word Styles; + Hyperlink Styling; Text + Numbering; Headers and + Footers + + Example folders: "Text + Tests" and "Utils" + + +This chapter focuses on how text documents styles can be +examined and manipulated. This revolves around the +XStyleFamiliesSupplier interface in +GenericTextDocument, which is highlighted in Figure 1 (a +repeat of Figure 2 in Chapter 5). + ![](images/06-Text_Styles-1.png) -Figure 1. The Text Document Services, and some Interfaces. +Figure 1. The Text Document Services, and some Interfaces. - -XStyleFamiliesSupplier has a getStyleFamilies() method for returning text style -families. All these families are stored in an XNameAccess object, as depicted in -![](images/06-Text_Styles-2.png) +XStyleFamiliesSupplier has a getStyleFamilies() method for returning text style +families. All these families are stored in an XNameAccess object, as depicted in +Figure 2. -Figure 2. +XNameAccess is one of Office's collection types, and employed when the objects in a +collection have names. There's also an XIndexAccess for collections in index order. -XNameAccess is one of Office's collection types, and employed when the objects in a -collection have names. There's also an XIndexAccess for collections in index order. +XNameContainer and XIndexContainer add the ability to insert and remove objects +from a collection. -XNameContainer and XIndexContainer add the ability to insert and remove objects -from a collection. - ![](images/06-Text_Styles-2.png) -Figure 2. Style Families and their Property Sets. - - -Five style family names are used by text documents: "CharacterStyles", -"FrameStyles", "NumberingStyles", "PageStyles", and "ParagraphStyles". The -XNameAccess families collection can be accessed with one of those names, and -returns a style family. A style family is a modifiable collection of PropertySet objects, -stored in an XNameContainer object. - -Figure 2 shows that if the "ParagraphStyles" family is retrieved, it contains property -sets labelled "Header", "List", "Standard", and quite a few more. Each property set -can format a paragraph, change the text's font, size, and many other attributes. These -property sets are called styles. - -The "CharacterStyles" family is a container of property sets (styles) which affect -selected sentences, words, or characters in the document. The "FrameStyles" -container holds property sets (styles) for formatting graphic and text frames. The -"NumberingStyles" family is for adding numbers or bullets to paragraphs. The -"PageStyles" family is for formatting pages. - -The names of the property sets (styles) in the style families can be listed using -LibreOffice's GUI. If you create a new text document in Writer, a "Styles and -Formatting" dialog window appears when you press F11 (or click on the brown -spanner icon in the "Formatting" toolbar). Within the window you can switch between -five icons representing the five style families. Figure 3 shows the list of property set -(style) names for the paragraph styles family. They corresponds to the property set -names shown in Figure 2. - - - +Figure 2. Style Families and their Property Sets. + + +Five style family names are used by text documents: "CharacterStyles", +"FrameStyles", "NumberingStyles", "PageStyles", and "ParagraphStyles". The +XNameAccess families collection can be accessed with one of those names, and +returns a style family. A style family is a modifiable collection of PropertySet objects, +stored in an XNameContainer object. + +Figure 2 shows that if the "ParagraphStyles" family is retrieved, it contains property +sets labelled "Header", "List", "Standard", and quite a few more. Each property set +can format a paragraph, change the text's font, size, and many other attributes. These +property sets are called styles. + +The "CharacterStyles" family is a container of property sets (styles) which affect +selected sentences, words, or characters in the document. The "FrameStyles" +container holds property sets (styles) for formatting graphic and text frames. The +"NumberingStyles" family is for adding numbers or bullets to paragraphs. The +"PageStyles" family is for formatting pages. + +The names of the property sets (styles) in the style families can be listed using +LibreOffice's GUI. If you create a new text document in Writer, a "Styles and +Formatting" dialog window appears when you press F11 (or click on the brown +spanner icon in the "Formatting" toolbar). Within the window you can switch between +five icons representing the five style families. Figure 3 shows the list of property set +(style) names for the paragraph styles family. They corresponds to the property set +names shown in Figure 2. + ![](images/06-Text_Styles-3.png) -Figure 3. Styles and Formatting Window in Writer. - - -The names listed in the window are the same as the names used in the API, except in -two cases: the "Default Style" name that appears in the GUI window for "Paragraph -Styles" and "Page Styles" is changed to "Standard" in the API. Strangely, the "Default -Style" name for "Character Styles" in the GUI is called "Default Style" in the API. - -Accessing a style (a property set) is a three-step process, shown below. First the style -families, then the style family (e.g. "ParagraphStyle"), and then the style (e.g. - -"Standard"): - -// 1. get the style families -XStyleFamiliesSupplier xSupplier = - Lo.qi(XStyleFamiliesSupplier.class, doc); -XNameAccess nameAcc = xSupplier.getStyleFamilies(); - -// 2. get the paragraph style family -XNameContainer paraStyleCon = Lo.qi(XNameContainer.class, - nameAcc.getByName("ParagraphStyles")); - -// 3. get the 'standard' style (property set) -XPropertySet standardProps = Lo.qi(XPropertySet.class, - paraStyleCon.getByName("Standard")); - -The code that implements this process in the Write utility class is a bit more -complicated since the calls to getByName() may raise exceptions if their string -arguments are incorrect. - -The calls to Lo.qi() cast the object returned from a collection into the correct type. - - - -## 1. What Properties are in a PropertySet? - -The "Standard" name in the "ParagraphStyles" style family refers to a property set -(style). Each set is a collection of name=value pairs, and there are get and set methods -using a name to get/set its value. This is simple enough, but what names should the -programmer use? -Each property set (style) in the same style family contain the same properties, but -with different values. For instance, in Figure 2 the "Header", "Title", "Standard", -"List", and "Table" sets contain the same named properties. - -The names of the properties used by the sets in a style family can be found in the -documentation for their "XXXStyle" service. Table 1 summarizes the mapping. - - -|Style Family Name|Service where Properties are Defined| +Figure 3. Styles and Formatting Window in Writer. + + +The names listed in the window are the same as the names used in the API, except in +two cases: the "Default Style" name that appears in the GUI window for "Paragraph +Styles" and "Page Styles" is changed to "Standard" in the API. Strangely, the "Default +Style" name for "Character Styles" in the GUI is called "Default Style" in the API. + +Accessing a style (a property set) is a three-step process, shown below. First the style +families, then the style family (e.g. "ParagraphStyle"), and then the style (e.g. + +"Standard"): + +```java +// 1. get the style families +XStyleFamiliesSupplier xSupplier = + Lo.qi(XStyleFamiliesSupplier.class, doc); +XNameAccess nameAcc = xSupplier.getStyleFamilies(); + +// 2. get the paragraph style family +XNameContainer paraStyleCon = Lo.qi(XNameContainer.class, + nameAcc.getByName("ParagraphStyles")); + +// 3. get the 'standard' style (property set) +XPropertySet standardProps = Lo.qi(XPropertySet.class, + paraStyleCon.getByName("Standard")); +``` + +The code that implements this process in the Write utility class is a bit more +complicated since the calls to getByName() may raise exceptions if their string +arguments are incorrect. + +The calls to Lo.qi() cast the object returned from a collection into the correct type. + + +## 1. What Properties are in a PropertySet? + +The "Standard" name in the "ParagraphStyles" style family refers to a property set +(style). Each set is a collection of name=value pairs, and there are get and set methods +using a name to get/set its value. This is simple enough, but what names should the +programmer use? +Each property set (style) in the same style family contain the same properties, but +with different values. For instance, in Figure 2 the "Header", "Title", "Standard", +"List", and "Table" sets contain the same named properties. + +The names of the properties used by the sets in a style family can be found in the +documentation for their "XXXStyle" service. Table 1 summarizes the mapping. + + +|Style Family Name|Service where Properties are Defined| |-----------------|------------------------------------| |"CharacterStyles"|CharacterStyle | -|"FrameStyles" |FrameStyle (??) | +|"FrameStyles" |FrameStyle (??) | |"NumberingStyles"|NumberingStyle | |"PageStyles" |PageStyle | |"ParagraphStyles"|ParagraphStyle | -Table 1. Properties Information for Each Style Family. +Table 1. Properties Information for Each Style Family. + - -The easiest way of finding Office documentation for the services in the second -column of Table 1 is with lodoc.bat. For example, the page about CharacterStyle -can be found with lodoc CharacterStyle service. +The easiest way of finding Office documentation for the services in the second +column of Table 1 is with lodoc.bat. For example, the page about CharacterStyle +can be found with `lodoc CharacterStyle service`. -The FrameStyle service (full name: com.sun.star.style.FrameStyle) has a "??" against -it since there's no online documentation for that service, although such a service -exists. +The FrameStyle service (full name: com.sun.star.style.FrameStyle) has a "??" against +it since there's no online documentation for that service, although such a service +exists. -A style's properties are usually defined across several classes in an inheritance -hierarchy. The hierarchies for the five styles are summarized in Figure 4. +A style's properties are usually defined across several classes in an inheritance +hierarchy. The hierarchies for the five styles are summarized in Figure 4. - ![](images/06-Text_Styles-4.png) -Figure 4. The Inheritance Hierarchies for the Style Services. +Figure 4. The Inheritance Hierarchies for the Style Services. - -Figure 4 shows the hierarchies for the five style services: CharacterStyle, FrameStyle, -NumberingStyle, PageStyle, and ParagraphStyle. There's clearly a lot of similarities -between them, so I'll focus on CharacterStyle. -There are three services containing character style properties: CharacterStyle, Style, -and CharacterProperties. If you visit the online documentation for CharacterStyle, the -properties are listed under the heading "Public Attributes", which is shown in Figure -5. +Figure 4 shows the hierarchies for the five style services: CharacterStyle, FrameStyle, +NumberingStyle, PageStyle, and ParagraphStyle. There's clearly a lot of similarities +between them, so I'll focus on CharacterStyle. + +There are three services containing character style properties: CharacterStyle, Style, +and CharacterProperties. If you visit the online documentation for CharacterStyle, the +properties are listed under the heading "Public Attributes", which is shown in Figure +5. - ![](images/06-Text_Styles-5.png) -Figure 5. Part of the Online Documentation for CharacterStyle. +Figure 5. Part of the Online Documentation for CharacterStyle. + - -CharacterStyle defines six properties itself, but there are many more inherited from -the Style and CharacterProperties services. If you click on the triangles next to the -"Public Attributes inherited from" lines, the documentation expands to display those -properties. +CharacterStyle defines six properties itself, but there are many more inherited from +the Style and CharacterProperties services. If you click on the triangles next to the +"Public Attributes inherited from" lines, the documentation expands to display those +properties. -Figure 4 contains two "(??)" strings – one is to indicate that there's no documentation -for FrameStyle, so I'm guessing about its inheritance hierarchy. +Figure 4 contains two "(??)" strings – one is to indicate that there's no documentation +for FrameStyle, so I'm guessing about its inheritance hierarchy. -The other "(??)" is in the ParagraphStyle hierarchy. The documentation for -ParagraphStyle, and the information in the developers guide, indicate that -ParagraphStyle inherits only Style and ParagraphCharacter. I believe this to be -incorrect, based on my coding with ParagraphStyle (some of which you'll see in the -next sections). ParagraphStyle appears to inherits three services: Style, -ParagraphCharacter, and CharacterStyle, as indicated in Figure 6. +The other "(??)" is in the ParagraphStyle hierarchy. The documentation for +ParagraphStyle, and the information in the developers guide, indicate that +ParagraphStyle inherits only Style and ParagraphCharacter. I believe this to be +incorrect, based on my coding with ParagraphStyle (some of which you'll see in the +next sections). ParagraphStyle appears to inherits three services: Style, +ParagraphCharacter, and CharacterStyle, as indicated in Figure 6. - ![](images/06-Text_Styles-6.png) -Figure 6. The Paragraph Service and its Superclasses. - - -For more information of the styles API, start in the development guide in the "Overall -Document Features" section, online at: -https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/ - Overall_Document_Features -or type loGuide "Overall Document Features" -The character and paragraph style properties are explained in the "Formatting" -section: -https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/ - Formatting -or type loGuide writer Formatting - - -## 2. Listing Styles Information - -My StylesInfo.java example illustrates some of the Writer and Info utility functions -for examining style families and their property sets. The main() function starts by -listing the style families names: - -XTextDocument doc = Write.openDoc(args[0], loader); - -// get all the style families for this document -String[] styleFamilies = Info.getStyleFamilyNames(doc); -System.out.println("No. of Style Family Names: " + - styleFamilies.length); -for(String styleFamily : styleFamilies) - System.out.println(" " + styleFamily); -System.out.println(); - -The output lists the five family names: - -No. of Style Family Names: 5 - CharacterStyles - FrameStyles - NumberingStyles - PageStyles - ParagraphStyles - -Info.getStyleFamilyNames() starts by casting the document to an -XStyleFamiliesSupplier interface. Then the style families XNameAccess collection is -obtained by calling XStyleFamiliesSupplier.getStyleFamilies().The family names in -that collection are extracted with XNameAccess.getElementName(): - -public static String[] getStyleFamilyNames(XComponent doc) -// in the Info class -{ - XStyleFamiliesSupplier xSupplier = Lo.qi( - XStyleFamiliesSupplier.class, doc); - XNameAccess nameAcc = xSupplier.getStyleFamilies(); - String[] names = nameAcc.getElementNames(); - Arrays.sort(names); - return names; -} // end of getStyleFamilyNames() - -Back in StylesInfo.java, the main() function continues by looping through the list of -style family names, printing all the style (property set) names in each family: - -// list all the style names for each style family -for(int i=0; i < styleFamilies.length; i++) { - String styleFamily = styleFamilies[i]; - System.out.println((i+1) + ". \"" + styleFamily + - "\" Style Family contains:"); - String[] styleNames = Info.getStyleNames(doc, styleFamily); - Lo.printNames(styleNames); -} - -The output is lengthy, but informative: - -## 1. "CharacterStyles" Style Family contains: - - "Bullet Symbols" "Caption characters" "Citation" "Default Style" - "Definition" "Drop Caps" "Emphasis" "Endnote Symbol" - "Endnote anchor" "Example" "Footnote Symbol" "Footnote anchor" - "Index Link" "Internet link" "Line numbering" "Main index entry" - "Numbering Symbols" "Page Number" "Placeholder" "Rubies" - "Source Text" "Strong Emphasis" "Teletype" "User Entry" - "Variable" "Vertical Numbering Symbols" "Visited Internet Link" - -## 2. "FrameStyles" Style Family contains containers: - - "Formula" "Frame" "Graphics" "Labels" - "Marginalia" "OLE" "Watermark" - -## 3. "NumberingStyles" Style Family contains containers: - - "List 1" "List 2" "List 3" "List 4" - "List 5" "Numbering 1" "Numbering 2" "Numbering 3" - "Numbering 4" "Numbering 5" - -## 4. "PageStyles" Style Family contains containers: - - "Endnote" "Envelope" "First Page" "Footnote" - "HTML" "Index" "Landscape" "Left Page" - "Right Page" "Standard" - -## 5. "ParagraphStyles" Style Family contains containers: - - "Addressee" "Bibliography 1" "Bibliography Heading" "Caption" - "Contents 1" "Contents 10" "Contents 2" "Contents 3" - "Contents 4" "Contents 5" "Contents 6" "Contents 7" - "Contents 8" "Contents 9" "Contents Heading" "Drawing" - "Endnote" "First line indent" "Footer" "Footer left" - "Footer right" "Footnote" "Frame contents" "Hanging indent" - "Header" "Header left" "Header right" "Heading" - "Heading 1" "Heading 10" "Heading 2" "Heading 3" - "Heading 4" "Heading 5" "Heading 6" "Heading 7" - "Heading 8" "Heading 9" "Horizontal Line" "Illustration" - "Illustration Index 1" "Illustration Index Heading" "Index" -"Index 1" - "Index 2" "Index 3" "Index Heading" "Index Separator" - "List" "List 1" "List 1 Cont." "List 1 End" - "List 1 Start" "List 2" "List 2 Cont." "List 2 End" - "List 2 Start" "List 3" "List 3 Cont." "List 3 End" - "List 3 Start" "List 4" "List 4 Cont." "List 4 End" - "List 4 Start" "List 5" "List 5 Cont." "List 5 End" - "List 5 Start" "List Contents" "List Heading" "List Indent" - "Marginalia" "Numbering 1" "Numbering 1 Cont." "Numbering 1 End" - "Numbering 1 Start" "Numbering 2" "Numbering 2 Cont." "Numbering -2 End" - "Numbering 2 Start" "Numbering 3" "Numbering 3 Cont." "Numbering -3 End" - "Numbering 3 Start" "Numbering 4" "Numbering 4 Cont." "Numbering -4 End" - "Numbering 4 Start" "Numbering 5" "Numbering 5 Cont." "Numbering -5 End" - "Numbering 5 Start" "Object index 1" "Object index heading" -"Preformatted - "Quotations" "Salutation" "Sender" "Signature" - "Standard" "Subtitle" "Table" "Table Contents" - "Table Heading" "Table index 1" "Table index heading" "Text" - "Text body" "Text body indent" "Title" "User Index 1" - "User Index 10" "User Index 2" "User Index 3" "User Index 4" - "User Index 5" "User Index 6" "User Index 7" "User Index 8" - "User Index 9" "User Index Heading" - -Info.getStyleNames() retrieves the XNameContainer object for each style family, and -extracts its style (property set) names using getElementNames(): - -public static String[] getStyleNames(XComponent doc, - String familyStyleName) -// in Info.java -{ XNameContainer styleContainer = - getStyleContainer(doc, familyStyleName); - if (styleContainer == null) - return null; - else { - String[] names = styleContainer.getElementNames(); - Arrays.sort(names); - return names; - } -} // end of getStyleNames() - -The last part of StylesInfo.java lists the properties for a specific property set. - -Info.getStyleProps() does that: - -XPropertySet getStyleProps(XComponent doc, - String familyStyleName, String propSetNm) -{ XNameContainer styleContainer = - getStyleContainer(doc, familyStyleName); - // container is a collection of named property sets - if (styleContainer == null) - return null; - else { - XPropertySet nameProps = null; - try { - nameProps = Lo.qi( XPropertySet.class, - styleContainer.getByName(propSetNm)); - } - catch(Exception e) - { System.out.println("Could not access style: " + e); } - return nameProps; - } -} // end of getStyleProps() - -Its arguments are the document, the style family name, and style (property set) name. - -A reference to the property set is returned. Accessing the "Standard" style (property -set) of the "ParagraphStyle" family would require: - -XPropertySet props = - Info.getStyleProps(doc, "ParagraphStyles", "Standard") - -The property set can be nicely printed by calling Props.showProps(): -Props.showProps("ParagraphStyles \"Standard\"", props ); -The output is long, but begins and ends like so: - -ParagraphStyles "Standard" Properties - BorderDistance == 0 - BottomBorder == com.sun.star.table.BorderLine2@1a8a1dc - BottomBorderDistance == 0 - BreakType == com.sun.star.style.BreakType@18e5cde - Category == 0 - CharAutoKerning == true - CharBackColor == -1 - CharBackTransparent == true - : - Rsid == Any[Type[unsigned long], 0] - SnapToGrid == true - StyleInteropGrabBag == [Lcom.sun.star.beans.PropertyValue;@1701da1 - TopBorder == com.sun.star.table.BorderLine2@6f3b58 - TopBorderDistance == 0 - WritingMode == 0 - -This listing, and in fact any listing of a style from "ParagraphStyles", shows that the -properties are a mixture of those defined in the Style, ParagraphProperties, and -CharacterProperties services. - - - -## 3. Creating a New Style - -My StoryCreator.java example adds a new style to the paragraph style family, and -uses it to format the document's paragraphs. - -The new ParagraphStyle service is referenced using one of its interfaces, the usual one -being XStyle since all the different style services support it (as shown in Figure 4). - -For example: - -// create a new paragraph style -XStyle paraStyle = Lo.createInstanceMSF(XStyle.class, - "com.sun.star.style.ParagraphStyle"); - -Lo.createInstanceMSF()'s second argument is the full name of the service, and the -first argument is the interface. All the style services are located in the -"com.sun.star.style" package. - -Since I want to change property in this new style, I cast the XStyle interface to -XPropertySet: -XPropertySet props = Lo.qi(XPropertySet.class, paraStyle); -A property is modified using setPropertyValue(). - - -props.setPropertyValue("ParaBottomMargin", 400); -props.setPropertyValue("CharFontName", "Times New Roman"); -props.setPropertyValue("CharHeight", 12.0f); - -These three properties are defined in one of the 'Properties' classes inherited by -ParagraphStyle (as shown in Figure 4). "ParaBottomMargin" appears in -ParagraphProperties, while "CharFontName" and "CharHeight" come from -CharacterProperties. - -After setting the style's properties, the new style added to the document's paragraph -style family: - -// access the paragraph style family -XNameContainer paraStyles = - Info.getStyleContainer(textDoc, "ParagraphStyles"); - -// store the style in the style family with the name "Foo" -paraStyles.insertByName("Foo", props); - -The style is stored with the name "Foo", but any unique name would be good (perhaps -one a little more descriptive than "Foo" would be better). - -The style creation code in StoryCreator.java is located in createParaStyle() and -follows the code fragment sequence described above: - -public static boolean createParaStyle(XTextDocument textDoc, - String styleName) -// create a new paragraph container/style called styleName -{ - XNameContainer paraStyles = - Info.getStyleContainer(textDoc, "ParagraphStyles"); - if (paraStyles == null) - return false; - - try { - // create new paragraph style properties set - XStyle paraStyle = Lo.createInstanceMSF( - XStyle.class, "com.sun.star.style.ParagraphStyle"); - XPropertySet props = Lo.qi(XPropertySet.class, paraStyle); - - // set some properties - props.setPropertyValue("CharFontName", "Times New Roman"); - props.setPropertyValue("CharHeight", 12.0f); - props.setPropertyValue("ParaBottomMargin", 400); - // 4mm, in 100th mm units - - // set paragraph line spacing to 6mm - LineSpacing lineSpacing = new LineSpacing(); - lineSpacing.Mode = LineSpacingMode.FIX; - lineSpacing.Height = 600; - props.setPropertyValue("ParaLineSpacing", lineSpacing); - - // some more common properties; not all used here - /* props.setPropertyValue("CharWeight", - com.sun.star.awt.FontWeight.BOLD); - props.setPropertyValue("CharAutoKerning", true); - props.setPropertyValue("ParaAdjust", - ParagraphAdjust.CENTER_value); - props.setPropertyValue("ParaFirstLineIndent", 0); - props.setPropertyValue("BreakType", BreakType.PAGE_AFTER); - */ - // store those properties in a container called styleName - paraStyles.insertByName(styleName, props); - return true; - } - catch(com.sun.star.uno.Exception e) - { System.out.println("Could not set paragraph style"); - return false; - } - } // end of createParaStyle() - -The "ParaLineSpacing" property is a little more complex than the others since its -value isn't a basic type, but a LineSpacing object. - -The ParagraphProperties documentation for "ParaLineSpacing" is shown in Figure 7. - - - +Figure 6. The Paragraph Service and its Superclasses. + + +For more information of the styles API, start in the development guide in the "Overall +Document Features" section, online at: https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Overall_Document_Features +or type `loGuide "Overall Document Features"` + +The character and paragraph style properties are explained in the "Formatting" +section: +https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Formatting +or type `loGuide writer Formatting` + + +## 2. Listing Styles Information + +My StylesInfo.java example illustrates some of the Writer and Info utility functions +for examining style families and their property sets. The main() function starts by +listing the style families names: + +```java +XTextDocument doc = Write.openDoc(args[0], loader); + +// get all the style families for this document +String[] styleFamilies = Info.getStyleFamilyNames(doc); +System.out.println("No. of Style Family Names: " + + styleFamilies.length); +for(String styleFamily : styleFamilies) + System.out.println(" " + styleFamily); +System.out.println(); +``` + +The output lists the five family names: + +``` +No. of Style Family Names: 5 + CharacterStyles + FrameStyles + NumberingStyles + PageStyles + ParagraphStyles +``` + +Info.getStyleFamilyNames() starts by casting the document to an +XStyleFamiliesSupplier interface. Then the style families XNameAccess collection is +obtained by calling XStyleFamiliesSupplier.getStyleFamilies().The family names in +that collection are extracted with XNameAccess.getElementName(): + +```java +public static String[] getStyleFamilyNames(XComponent doc) +// in the Info class +{ + XStyleFamiliesSupplier xSupplier = Lo.qi( + XStyleFamiliesSupplier.class, doc); + XNameAccess nameAcc = xSupplier.getStyleFamilies(); + String[] names = nameAcc.getElementNames(); + Arrays.sort(names); + return names; +} // end of getStyleFamilyNames() +``` + +Back in StylesInfo.java, the main() function continues by looping through the list of +style family names, printing all the style (property set) names in each family: + +```java +// list all the style names for each style family +for(int i=0; i < styleFamilies.length; i++) { + String styleFamily = styleFamilies[i]; + System.out.println((i+1) + ". \"" + styleFamily + + "\" Style Family contains:"); + String[] styleNames = Info.getStyleNames(doc, styleFamily); + Lo.printNames(styleNames); +} +``` + +The output is lengthy, but informative: + +``` +## 1. "CharacterStyles" Style Family contains: + "Bullet Symbols" "Caption characters" "Citation" "Default Style" + "Definition" "Drop Caps" "Emphasis" "Endnote Symbol" + "Endnote anchor" "Example" "Footnote Symbol" "Footnote anchor" + "Index Link" "Internet link" "Line numbering" "Main index entry" + "Numbering Symbols" "Page Number" "Placeholder" "Rubies" + "Source Text" "Strong Emphasis" "Teletype" "User Entry" + "Variable" "Vertical Numbering Symbols" "Visited Internet Link" + +## 2. "FrameStyles" Style Family contains containers: + "Formula" "Frame" "Graphics" "Labels" + "Marginalia" "OLE" "Watermark" + +## 3. "NumberingStyles" Style Family contains containers: + "List 1" "List 2" "List 3" "List 4" + "List 5" "Numbering 1" "Numbering 2" "Numbering 3" + "Numbering 4" "Numbering 5" + +## 4. "PageStyles" Style Family contains containers: + "Endnote" "Envelope" "First Page" "Footnote" + "HTML" "Index" "Landscape" "Left Page" + "Right Page" "Standard" + +## 5. "ParagraphStyles" Style Family contains containers: + "Addressee" "Bibliography 1" "Bibliography Heading" "Caption" + "Contents 1" "Contents 10" "Contents 2" "Contents 3" + "Contents 4" "Contents 5" "Contents 6" "Contents 7" + "Contents 8" "Contents 9" "Contents Heading" "Drawing" + "Endnote" "First line indent" "Footer" "Footer left" + "Footer right" "Footnote" "Frame contents" "Hanging indent" + "Header" "Header left" "Header right" "Heading" + "Heading 1" "Heading 10" "Heading 2" "Heading 3" + "Heading 4" "Heading 5" "Heading 6" "Heading 7" + "Heading 8" "Heading 9" "Horizontal Line" "Illustration" + "Illustration Index 1" "Illustration Index Heading" "Index" +"Index 1" + "Index 2" "Index 3" "Index Heading" "Index Separator" + "List" "List 1" "List 1 Cont." "List 1 End" + "List 1 Start" "List 2" "List 2 Cont." "List 2 End" + "List 2 Start" "List 3" "List 3 Cont." "List 3 End" + "List 3 Start" "List 4" "List 4 Cont." "List 4 End" + "List 4 Start" "List 5" "List 5 Cont." "List 5 End" + "List 5 Start" "List Contents" "List Heading" "List Indent" + "Marginalia" "Numbering 1" "Numbering 1 Cont." "Numbering 1 End" + "Numbering 1 Start" "Numbering 2" "Numbering 2 Cont." "Numbering +2 End" + "Numbering 2 Start" "Numbering 3" "Numbering 3 Cont." "Numbering +3 End" + "Numbering 3 Start" "Numbering 4" "Numbering 4 Cont." "Numbering +4 End" + "Numbering 4 Start" "Numbering 5" "Numbering 5 Cont." "Numbering +5 End" + "Numbering 5 Start" "Object index 1" "Object index heading" +"Preformatted + "Quotations" "Salutation" "Sender" "Signature" + "Standard" "Subtitle" "Table" "Table Contents" + "Table Heading" "Table index 1" "Table index heading" "Text" + "Text body" "Text body indent" "Title" "User Index 1" + "User Index 10" "User Index 2" "User Index 3" "User Index 4" + "User Index 5" "User Index 6" "User Index 7" "User Index 8" + "User Index 9" "User Index Heading" +``` + +Info.getStyleNames() retrieves the XNameContainer object for each style family, and +extracts its style (property set) names using getElementNames(): + +```java +public static String[] getStyleNames(XComponent doc, + String familyStyleName) +// in Info.java +{ XNameContainer styleContainer = + getStyleContainer(doc, familyStyleName); + if (styleContainer == null) + return null; + else { + String[] names = styleContainer.getElementNames(); + Arrays.sort(names); + return names; + } +} // end of getStyleNames() +``` + +The last part of StylesInfo.java lists the properties for a specific property set. +Info.getStyleProps() does that: + +```java +XPropertySet getStyleProps(XComponent doc, + String familyStyleName, String propSetNm) +{ XNameContainer styleContainer = + getStyleContainer(doc, familyStyleName); + // container is a collection of named property sets + if (styleContainer == null) + return null; + else { + XPropertySet nameProps = null; + try { + nameProps = Lo.qi( XPropertySet.class, + styleContainer.getByName(propSetNm)); + } + catch(Exception e) + { System.out.println("Could not access style: " + e); } + return nameProps; + } +} // end of getStyleProps() +``` + +Its arguments are the document, the style family name, and style (property set) name. + +A reference to the property set is returned. Accessing the "Standard" style (property +set) of the "ParagraphStyle" family would require: + +```java +XPropertySet props = + Info.getStyleProps(doc, "ParagraphStyles", "Standard") +``` + +The property set can be nicely printed by calling Props.showProps(): + +```java +Props.showProps("ParagraphStyles \"Standard\"", props ); +``` + +The output is long, but begins and ends like so: + +``` +ParagraphStyles "Standard" Properties + BorderDistance == 0 + BottomBorder == com.sun.star.table.BorderLine2@1a8a1dc + BottomBorderDistance == 0 + BreakType == com.sun.star.style.BreakType@18e5cde + Category == 0 + CharAutoKerning == true + CharBackColor == -1 + CharBackTransparent == true + : + Rsid == Any[Type[unsigned long], 0] + SnapToGrid == true + StyleInteropGrabBag == [Lcom.sun.star.beans.PropertyValue;@1701da1 + TopBorder == com.sun.star.table.BorderLine2@6f3b58 + TopBorderDistance == 0 + WritingMode == 0 +``` + +This listing, and in fact any listing of a style from "ParagraphStyles", shows that the +properties are a mixture of those defined in the Style, ParagraphProperties, and +CharacterProperties services. + + +## 3. Creating a New Style + +My StoryCreator.java example adds a new style to the paragraph style family, and +uses it to format the document's paragraphs. + +The new ParagraphStyle service is referenced using one of its interfaces, the usual one +being XStyle since all the different style services support it (as shown in Figure 4). + +For example: + +```java +// create a new paragraph style +XStyle paraStyle = Lo.createInstanceMSF(XStyle.class, + "com.sun.star.style.ParagraphStyle"); +``` + +Lo.createInstanceMSF()'s second argument is the full name of the service, and the +first argument is the interface. All the style services are located in the +"com.sun.star.style" package. + +Since I want to change property in this new style, I cast the XStyle interface to +XPropertySet: + +```java +XPropertySet props = Lo.qi(XPropertySet.class, paraStyle); +``` + +A property is modified using setPropertyValue(). + +```java +props.setPropertyValue("ParaBottomMargin", 400); +props.setPropertyValue("CharFontName", "Times New Roman"); +props.setPropertyValue("CharHeight", 12.0f); +``` + +These three properties are defined in one of the 'Properties' classes inherited by +ParagraphStyle (as shown in Figure 4). "ParaBottomMargin" appears in +ParagraphProperties, while "CharFontName" and "CharHeight" come from +CharacterProperties. + +After setting the style's properties, the new style added to the document's paragraph +style family: + +```java +// access the paragraph style family +XNameContainer paraStyles = + Info.getStyleContainer(textDoc, "ParagraphStyles"); + +// store the style in the style family with the name "Foo" +paraStyles.insertByName("Foo", props); +``` + +The style is stored with the name "Foo", but any unique name would be good (perhaps +one a little more descriptive than "Foo" would be better). + +The style creation code in StoryCreator.java is located in createParaStyle() and +follows the code fragment sequence described above: + +```java +public static boolean createParaStyle(XTextDocument textDoc, + String styleName) +// create a new paragraph container/style called styleName +{ + XNameContainer paraStyles = + Info.getStyleContainer(textDoc, "ParagraphStyles"); + if (paraStyles == null) + return false; + + try { + // create new paragraph style properties set + XStyle paraStyle = Lo.createInstanceMSF( + XStyle.class, "com.sun.star.style.ParagraphStyle"); + XPropertySet props = Lo.qi(XPropertySet.class, paraStyle); + + // set some properties + props.setPropertyValue("CharFontName", "Times New Roman"); + props.setPropertyValue("CharHeight", 12.0f); + props.setPropertyValue("ParaBottomMargin", 400); + // 4mm, in 100th mm units + + // set paragraph line spacing to 6mm + LineSpacing lineSpacing = new LineSpacing(); + lineSpacing.Mode = LineSpacingMode.FIX; + lineSpacing.Height = 600; + props.setPropertyValue("ParaLineSpacing", lineSpacing); + + // some more common properties; not all used here + /* props.setPropertyValue("CharWeight", + com.sun.star.awt.FontWeight.BOLD); + props.setPropertyValue("CharAutoKerning", true); + props.setPropertyValue("ParaAdjust", + ParagraphAdjust.CENTER_value); + props.setPropertyValue("ParaFirstLineIndent", 0); + props.setPropertyValue("BreakType", BreakType.PAGE_AFTER); + */ + // store those properties in a container called styleName + paraStyles.insertByName(styleName, props); + return true; + } + catch(com.sun.star.uno.Exception e) + { System.out.println("Could not set paragraph style"); + return false; + } + } // end of createParaStyle() +``` + +The "ParaLineSpacing" property is a little more complex than the others since its +value isn't a basic type, but a LineSpacing object. + +The ParagraphProperties documentation for "ParaLineSpacing" is shown in Figure 7. + ![](images/06-Text_Styles-7.png) -Figure 7. The ParaLineSpacing Property in the ParagraphProperties Documentation. - - -Clicking on the "com::sun::star::style::LineSpacing" return type will load the -LineSpacing documentation page into the browser. - -In StoryCreator.java, createParaStyle() is called like so: - -XTextDocument doc = Write.createDoc(loader); - -if (!createParaStyle(doc, "adParagraph")){ - System.out.println("Could not create new paragraph style"); - Lo.closeOffice(); - return; -} - -A new style called "adParagraph" is added to the paragraph style family. It uses -Times New Roman 12pt font, and leaves a 6mm space between paragraphs. - - - -## 4. Applying Styles to Paragraphs (and Characters) - -I've added an "adParagraph" style to the paragraph style family, but how do I apply -that style to some paragraphs in the document? -The easiest way is through the document's XTextRange interface. XTextRange is -supported by the TextRange service, which inherits ParagraphProperties and -CharacterProperties (and several other property classes), as illustrated in Figure 8. - - +Figure 7. The ParaLineSpacing Property in the ParagraphProperties Documentation. + + +Clicking on the "com::sun::star::style::LineSpacing" return type will load the +LineSpacing documentation page into the browser. + +In StoryCreator.java, createParaStyle() is called like so: + +```java +XTextDocument doc = Write.createDoc(loader); + +if (!createParaStyle(doc, "adParagraph")){ + System.out.println("Could not create new paragraph style"); + Lo.closeOffice(); + return; +} +``` + +A new style called "adParagraph" is added to the paragraph style family. It uses +Times New Roman 12pt font, and leaves a 6mm space between paragraphs. + + +## 4. Applying Styles to Paragraphs (and Characters) + +I've added an "adParagraph" style to the paragraph style family, but how do I apply +that style to some paragraphs in the document? +The easiest way is through the document's XTextRange interface. XTextRange is +supported by the TextRange service, which inherits ParagraphProperties and +CharacterProperties (and several other property classes), as illustrated in Figure 8. + ![](images/06-Text_Styles-8.png) -Figure 8. The TextRange Service. +Figure 8. The TextRange Service. + - -XTextRange can be cast to XPropertySet to make the properties in -ParagraphProperties and CharacterProperties accessible. An existing (or new) -paragraph style is applied to a text range by setting its "ParaStyleName" property: - -XTextRange xTextRange = doc.getText().getStart(); -XPropertySet props = Lo.qi(XPropertySet.class, xTextRange); -props.setProperty("ParaStyleName", "adParagraph"); - -Using Props.setProperty(), simplifies this to: - -XTextRange xTextRange = doc.getText().getStart(); -Props.setProperty(xTextRange, "ParaStyleName", "adParagraph"); - -The code above obtains the text range at the start of the document, and set its -paragraph style to "adParagraph". Any text added from this position onwards will use -that style. +XTextRange can be cast to XPropertySet to make the properties in +ParagraphProperties and CharacterProperties accessible. An existing (or new) +paragraph style is applied to a text range by setting its "ParaStyleName" property: -This approach is used in StoryCreator.java: the style is set first, then text is added. +```java +XTextRange xTextRange = doc.getText().getStart(); +XPropertySet props = Lo.qi(XPropertySet.class, xTextRange); +props.setProperty("ParaStyleName", "adParagraph"); +``` - - -## 5. Cursors and Text Ranges +Using Props.setProperty(), simplifies this to: -Another technique for applying styles uses a cursor to select a text range. Then the -text's properties are accessed through the cursor. +```java +XTextRange xTextRange = doc.getText().getStart(); +Props.setProperty(xTextRange, "ParaStyleName", "adParagraph"); +``` -All the different kinds of model and view cursor belong to the TextCursor service, -and this inherits TextRange. This allows us to extend Figure 8 to become Figure 9. +The code above obtains the text range at the start of the document, and set its +paragraph style to "adParagraph". Any text added from this position onwards will use +that style. + +This approach is used in StoryCreator.java: the style is set first, then text is added. + + +## 5. Cursors and Text Ranges + +Another technique for applying styles uses a cursor to select a text range. Then the +text's properties are accessed through the cursor. + +All the different kinds of model and view cursor belong to the TextCursor service, +and this inherits TextRange. This allows us to extend Figure 8 to become Figure 9. - ![](images/06-Text_Styles-9.png) -Figure 9. Cursor Access to Text Properties. - - -This hierarchy means that a cursor can access the TextRange service and its text -properties. The following code fragment demonstrates the idea: - -XTextCursor cursor = Write.getCursor(textDoc); -cursor.gotoEnd(true); // select the entire document - -XPropertySet props = Lo.qi(XPropertySet.class, cursor); -props.setProperty("ParaStyleName", "adParagraph"); - -Using Props.setProperty(), simplifies this to: - -XTextCursor cursor = Write.getCursor(textDoc); -cursor.gotoEnd(true); -Props.setProperty(cursor, "ParaStyleName", "adParagraph"); - -This approach is employed in StoryCreator.java when some paragraphs (such as -section headers) need to use a paragraph style other than "adParagraph". I'll supply -details in a moment. - - - -## 6. Building a Story Document - -StoryCreator.java starts by setting the "adParagraph" style, then employs readText() -to read text from a file and add it to the document: - -XTextRange xTextRange = doc.getText().getStart(); -Props.setProperty(xTextRange, "ParaStyleName", "adParagraph"); - -XTextCursor cursor = Write.getCursor(doc); -readText("scandal.txt", cursor); -Write.endParagraph(cursor); - -readText() assumes the text file has a certain format. For example, "scandal.txt" -begins like so: - -Title: A Scandal in Bohemia -Author: Sir Arthur Conan Doyle - -Part I. - - -To Sherlock Holmes she is always THE woman. I have seldom heard -him mention her under any other name. In his eyes she eclipses -and predominates the whole of her sex. - - -It was not that he felt any emotion akin to love for Irene Adler. - -All emotions, and that one particularly, were abhorrent to his -cold, precise but admirably balanced mind. - - -A paragraph is a series of text lines followed by a blank line. But there are exceptions: -lines that starts with "Title: ", "Author: " or "Part " are treated as headings, and styled -differently. When the text above is processed, the resulting document looks like +Figure 9. Cursor Access to Text Properties. + + +This hierarchy means that a cursor can access the TextRange service and its text +properties. The following code fragment demonstrates the idea: + +```java +XTextCursor cursor = Write.getCursor(textDoc); +cursor.gotoEnd(true); // select the entire document + +XPropertySet props = Lo.qi(XPropertySet.class, cursor); +props.setProperty("ParaStyleName", "adParagraph"); +``` + +Using Props.setProperty(), simplifies this to: + +```java +XTextCursor cursor = Write.getCursor(textDoc); +cursor.gotoEnd(true); +Props.setProperty(cursor, "ParaStyleName", "adParagraph"); +``` + +This approach is employed in StoryCreator.java when some paragraphs (such as +section headers) need to use a paragraph style other than "adParagraph". I'll supply +details in a moment. -![](images/06-Text_Styles-10.png) -Figure 10. +## 6. Building a Story Document + +StoryCreator.java starts by setting the "adParagraph" style, then employs readText() +to read text from a file and add it to the document: + +```java +XTextRange xTextRange = doc.getText().getStart(); +Props.setProperty(xTextRange, "ParaStyleName", "adParagraph"); + +XTextCursor cursor = Write.getCursor(doc); +readText("scandal.txt", cursor); +Write.endParagraph(cursor); +``` + +readText() assumes the text file has a certain format. For example, "scandal.txt" +begins like so: + +``` +Title: A Scandal in Bohemia +Author: Sir Arthur Conan Doyle + +Part I. + + +To Sherlock Holmes she is always THE woman. I have seldom heard +him mention her under any other name. In his eyes she eclipses +and predominates the whole of her sex. + + +It was not that he felt any emotion akin to love for Irene Adler. + +All emotions, and that one particularly, were abhorrent to his +cold, precise but admirably balanced mind. +``` + +A paragraph is a series of text lines followed by a blank line. But there are exceptions: +lines that starts with "Title: ", "Author: " or "Part " are treated as headings, and styled +differently. When the text above is processed, the resulting document looks like +Figure 10. - ![](images/06-Text_Styles-10.png) -Figure 10. The Output of StoryCreator.java. - - -readText() is implemented using Java's FileReader and BufferedReader: - -public static void readText(String fnm, XTextCursor cursor) -/* Write the text in fnm into a Office document. - - Use current paragraph styling unless the line starts - with "Title: ", "Author: ", or "Part ". - -*/ -{ - StringBuilder sb = new StringBuilder(0); - BufferedReader br = null; - try { - br = new BufferedReader(new FileReader(fnm)); - System.out.println("Reading text from: " + fnm); - - String line; - while ((line = br.readLine()) != null) { - // System.out.println("<" + line + ">"); - if (line.length() == 0) { - if (sb.length() > 0) - Write.appendPara(cursor, sb.toString()); - sb.setLength(0); - } - else if (line.startsWith("Title: ")) { - Write.appendPara(cursor, line.substring(7)); - Write.stylePrevParagraph(cursor, "Title"); - } - else if (line.startsWith("Author: ")) { - Write.appendPara(cursor, line.substring(8)); - Write.stylePrevParagraph(cursor, "Subtitle"); - } - else if (line.startsWith("Part ")) { - Write.appendPara(cursor, line); - Write.stylePrevParagraph(cursor, "Heading"); - } - else { - sb.append(line + " "); - } - } - if (sb.length() > 0) - Write.appendPara(cursor, sb.toString()); - } - catch (FileNotFoundException ex) - { System.out.println("Could not open: " + fnm); } - catch (IOException ex) - { System.out.println("Read error: " + ex); } - finally { - try { - if (br != null) - br.close(); - } - catch (IOException ex) - { System.out.println("Problem closing " + fnm); } - } -} // end of readText() - -The interesting bits are the calls to Write.appendPara() and -Write.stylePrevParagraph() which add a paragraph to the document and apply a style -to it. For instance: - -else if (line.startsWith("Author: ")) { - Write.appendPara(cursor, line.substring(8)); - Write.stylePrevParagraph(cursor, "Subtitle"); -} - -Write.appendPara() writes the string into the document as a paragraph (the input line -without the "Author: " substring). Write.stylePrevParagraph() changes the paragraph -style from "adParagraph" to "Subtitle". - -The hard part of Write.stylePrevParagraph() is making sure that the style change only -affects the previous paragraph. Text appended after this line should use "adParagraph" -styling. - - -public static void stylePrevParagraph(XTextCursor cursor, - Object propVal) -{ stylePrevParagraph(cursor, "ParaStyleName", propVal); } - - -public static void stylePrevParagraph(XTextCursor cursor, - String propName, Object propVal) -{ - // save current property - Object oldValue = Props.getProperty(cursor, propName); - - // apply property change to previous paragraph - XParagraphCursor paraCursor = Lo.qi( - XParagraphCursor.class, cursor); - paraCursor.gotoPreviousParagraph(true); // select prev para - Props.setProperty(paraCursor, propName, propVal); - - // reset the cursor and property - paraCursor.gotoNextParagraph(false); - Props.setProperty(cursor, propName, oldValue); -} // end of stylePrevParagraph() - -The current "ParaStyleName" value is stored before changing its value in the selected -range. Afterwards, that style name is applied back to the cursor. - -stylePrevParagraph() changes the XTextCursor into a paragraph cursor so that it's -easier to move around across paragraphs. - -readText() calls Write.stylePrevParagraph() with three style names ("Title", -"Subtitle", and "Heading"). I obtained those names from looking at the "Paragraph -Styles" dialog window in Figure 3. - - - -## 7. Style Changes to Words and Phrases - -Aside from changing paragraph styles, it's useful to apply style changes to words or -strings inside a paragraph. For example, to highlight a word in bold, or write several -words in red italics for emphasis. - -This is implemented by Write.styleLeft() using a similar approach to -Write.stylePrevParagraph(). styleLeft() is passed an integer position which lies to the -left of the current cursor position. Character style changes are applied to the text range -defined by that distance: - -public static void styleLeft(XTextCursor cursor, - int pos, String propName, Object propVal) -{ - // save current property - Object oldValue = Props.getProperty(cursor, propName); - - // apply property change to the left of cursor - int currPos = getPosition(cursor); - cursor.goLeft((short)(currPos-pos), true); - Props.setProperty(cursor, propName, propVal); - - // reset the cursor and property - cursor.goRight((short)(currPos-pos), false); - Props.setProperty(cursor, propName, oldValue); -} // end of styleLeft() - -An XTextCursor is used to select the range, and the new style is set. Then the cursor -is moved back to its old position, and the previous style reapplied. - -The Write class contain a few support functions that set common styles using -styleLeft(): - -public static void styleLeftBold(XTextCursor cursor, int pos) -{ styleLeft(cursor, pos, "CharWeight", - com.sun.star.awt.FontWeight.BOLD); } - - -public static void styleLeftItalic(XTextCursor cursor, int pos) -{ styleLeft(cursor, pos, "CharPosture", - com.sun.star.awt.FontSlant.ITALIC); } - - -public static void styleLeftColor(XTextCursor cursor, - int pos, java.awt.Color col) -{ styleLeft(cursor, pos, "CharColor", Lo.getColorInt(col)); } - - -public static void styleLeftCode(XTextCursor cursor, int pos) -// code is text in 10pt Courier New -{ styleLeft(cursor, pos, "CharFontName", "Courier New"); - styleLeft(cursor, pos, "CharHeight", 10); -} - -The position (the pos value) passed to styleLeft() can be obtained from -Write.getPosition(), or as the result of the Write.append() methods. - -My BuildDoc.java example contains several examples of how to use -Write.styleLeft(): - -// code fragment from BuildDoc.java -XTextCursor cursor = Write.getCursor(doc); - -int pos = Write.append(cursor, "Some examples of simple text "); -Write.append(cursor, "styles.\n"); -Write.styleLeftBold(cursor, pos); // bold text back to pos - -pos = Write.getPosition(cursor); -Write.appendPara(cursor, "This line is written in red italics."); -Write.styleLeftColor(cursor, pos, Color.RED); // red -Write.styleLeftItalic(cursor, pos); // italics - -Write.appendPara(cursor, "Back to old style\n"); - -The resulting text in the document looks like Figure 11. - - +Figure 10. The Output of StoryCreator.java. + + +readText() is implemented using Java's FileReader and BufferedReader: + +```java +public static void readText(String fnm, XTextCursor cursor) +/* Write the text in fnm into a Office document. + + Use current paragraph styling unless the line starts + with "Title: ", "Author: ", or "Part ". + +*/ +{ + StringBuilder sb = new StringBuilder(0); + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader(fnm)); + System.out.println("Reading text from: " + fnm); + + String line; + while ((line = br.readLine()) != null) { + // System.out.println("<" + line + ">"); + if (line.length() == 0) { + if (sb.length() > 0) + Write.appendPara(cursor, sb.toString()); + sb.setLength(0); + } + else if (line.startsWith("Title: ")) { + Write.appendPara(cursor, line.substring(7)); + Write.stylePrevParagraph(cursor, "Title"); + } + else if (line.startsWith("Author: ")) { + Write.appendPara(cursor, line.substring(8)); + Write.stylePrevParagraph(cursor, "Subtitle"); + } + else if (line.startsWith("Part ")) { + Write.appendPara(cursor, line); + Write.stylePrevParagraph(cursor, "Heading"); + } + else { + sb.append(line + " "); + } + } + if (sb.length() > 0) + Write.appendPara(cursor, sb.toString()); + } + catch (FileNotFoundException ex) + { System.out.println("Could not open: " + fnm); } + catch (IOException ex) + { System.out.println("Read error: " + ex); } + finally { + try { + if (br != null) + br.close(); + } + catch (IOException ex) + { System.out.println("Problem closing " + fnm); } + } +} // end of readText() +``` + +The interesting bits are the calls to Write.appendPara() and +Write.stylePrevParagraph() which add a paragraph to the document and apply a style +to it. For instance: + +```java +else if (line.startsWith("Author: ")) { + Write.appendPara(cursor, line.substring(8)); + Write.stylePrevParagraph(cursor, "Subtitle"); +} +``` + +Write.appendPara() writes the string into the document as a paragraph (the input line +without the "Author: " substring). Write.stylePrevParagraph() changes the paragraph +style from "adParagraph" to "Subtitle". + +The hard part of Write.stylePrevParagraph() is making sure that the style change only +affects the previous paragraph. Text appended after this line should use "adParagraph" +styling. + +```java +public static void stylePrevParagraph(XTextCursor cursor, + Object propVal) +{ stylePrevParagraph(cursor, "ParaStyleName", propVal); } + + +public static void stylePrevParagraph(XTextCursor cursor, + String propName, Object propVal) +{ + // save current property + Object oldValue = Props.getProperty(cursor, propName); + + // apply property change to previous paragraph + XParagraphCursor paraCursor = Lo.qi( + XParagraphCursor.class, cursor); + paraCursor.gotoPreviousParagraph(true); // select prev para + Props.setProperty(paraCursor, propName, propVal); + + // reset the cursor and property + paraCursor.gotoNextParagraph(false); + Props.setProperty(cursor, propName, oldValue); +} // end of stylePrevParagraph() +``` + +The current "ParaStyleName" value is stored before changing its value in the selected +range. Afterwards, that style name is applied back to the cursor. + +stylePrevParagraph() changes the XTextCursor into a paragraph cursor so that it's +easier to move around across paragraphs. + +readText() calls Write.stylePrevParagraph() with three style names ("Title", +"Subtitle", and "Heading"). I obtained those names from looking at the "Paragraph +Styles" dialog window in Figure 3. + + +## 7. Style Changes to Words and Phrases + +Aside from changing paragraph styles, it's useful to apply style changes to words or +strings inside a paragraph. For example, to highlight a word in bold, or write several +words in red italics for emphasis. + +This is implemented by Write.styleLeft() using a similar approach to +Write.stylePrevParagraph(). styleLeft() is passed an integer position which lies to the +left of the current cursor position. Character style changes are applied to the text range +defined by that distance: + +```java +public static void styleLeft(XTextCursor cursor, + int pos, String propName, Object propVal) +{ + // save current property + Object oldValue = Props.getProperty(cursor, propName); + + // apply property change to the left of cursor + int currPos = getPosition(cursor); + cursor.goLeft((short)(currPos-pos), true); + Props.setProperty(cursor, propName, propVal); + + // reset the cursor and property + cursor.goRight((short)(currPos-pos), false); + Props.setProperty(cursor, propName, oldValue); +} // end of styleLeft() +``` + +An XTextCursor is used to select the range, and the new style is set. Then the cursor +is moved back to its old position, and the previous style reapplied. + +The Write class contain a few support functions that set common styles using +styleLeft(): + +```java +public static void styleLeftBold(XTextCursor cursor, int pos) +{ styleLeft(cursor, pos, "CharWeight", + com.sun.star.awt.FontWeight.BOLD); } + + +public static void styleLeftItalic(XTextCursor cursor, int pos) +{ styleLeft(cursor, pos, "CharPosture", + com.sun.star.awt.FontSlant.ITALIC); } + + +public static void styleLeftColor(XTextCursor cursor, + int pos, java.awt.Color col) +{ styleLeft(cursor, pos, "CharColor", Lo.getColorInt(col)); } + + +public static void styleLeftCode(XTextCursor cursor, int pos) +// code is text in 10pt Courier New +{ styleLeft(cursor, pos, "CharFontName", "Courier New"); + styleLeft(cursor, pos, "CharHeight", 10); +} +``` + +The position (the pos value) passed to styleLeft() can be obtained from +Write.getPosition(), or as the result of the Write.append() methods. + +My BuildDoc.java example contains several examples of how to use +Write.styleLeft(): + +```java +// code fragment from BuildDoc.java +XTextCursor cursor = Write.getCursor(doc); + +int pos = Write.append(cursor, "Some examples of simple text "); +Write.append(cursor, "styles.\n"); +Write.styleLeftBold(cursor, pos); // bold text back to pos + +pos = Write.getPosition(cursor); +Write.appendPara(cursor, "This line is written in red italics."); +Write.styleLeftColor(cursor, pos, Color.RED); // red +Write.styleLeftItalic(cursor, pos); // italics + +Write.appendPara(cursor, "Back to old style\n"); +``` + +The resulting text in the document looks like Figure 11. + ![](images/06-Text_Styles-11.png) -Figure 11. Styled Text. - - -The following fragment from BuildDoc.java applies a 'code' styling to several lines: - -// code fragment from BuildDoc.java -Write.appendPara(cursor, "Here's some code:"); - -pos = Write.getPosition(cursor); -Write.append(cursor, "\npublic class Hello\n"); -Write.append(cursor, "{\n"); -Write.append(cursor, " public static void main(String args[]\n"); -Write.append(cursor, " { System.out.println(\"Hello Andrew\"); }\n"); -Write.appendPara(cursor, "} // end of Hello class\n"); -Write.styleLeftCode(cursor, pos); - -Figure 12 shows the generated document text. - - +Figure 11. Styled Text. + + +The following fragment from BuildDoc.java applies a 'code' styling to several lines: + +```java +// code fragment from BuildDoc.java +Write.appendPara(cursor, "Here's some code:"); + +pos = Write.getPosition(cursor); +Write.append(cursor, "\npublic class Hello\n"); +Write.append(cursor, "{\n"); +Write.append(cursor, " public static void main(String args[]\n"); +Write.append(cursor, " { System.out.println(\"Hello Andrew\"); }\n"); +Write.appendPara(cursor, "} // end of Hello class\n"); +Write.styleLeftCode(cursor, pos); +``` + +Figure 12 shows the generated document text. + ![](images/06-Text_Styles-12.png) -Figure 12. Text with Code Styling. - - -Unfortunately, styleLeft() depend on integer character positions, which are calculated -using Write.getPosition(). As I've mentioned before, this method could fail if asked to -generate too large a string, and this would cause styleLeft() to die. - - - -## 8. Hyperlink Styling - -Text hyperlinks are implemented as styles, using "HyperLinkURL", and perhaps -"HyperLinkName", and "HyperLinkTarget". BuildDoc.java shows how the -"HyperLinkURL" property is set: - -Write.appendPara(cursor, "A link to my JLOP website:"); -String urlStr = "http://fivedots.coe.psu.ac.th/~ad/jlop/"; - -pos = Write.getPosition(cursor); -Write.append(cursor, urlStr); - -Write.styleLeft(cursor, pos, "HyperLinkURL", urlStr); -Write.endParagraph(cursor); - -When the document is viewed, the text is drawn as a link as in Figure 13. - - +Figure 12. Text with Code Styling. + + +Unfortunately, styleLeft() depend on integer character positions, which are calculated +using Write.getPosition(). As I've mentioned before, this method could fail if asked to +generate too large a string, and this would cause styleLeft() to die. + + +## 8. Hyperlink Styling + +Text hyperlinks are implemented as styles, using "HyperLinkURL", and perhaps +"HyperLinkName", and "HyperLinkTarget". BuildDoc.java shows how the +"HyperLinkURL" property is set: + +```java +Write.appendPara(cursor, "A link to my JLOP website:"); +String urlStr = "http://fivedots.coe.psu.ac.th/~ad/jlop/"; + +pos = Write.getPosition(cursor); +Write.append(cursor, urlStr); + +Write.styleLeft(cursor, pos, "HyperLinkURL", urlStr); +Write.endParagraph(cursor); +``` + +When the document is viewed, the text is drawn as a link as in Figure 13. + ![](images/06-Text_Styles-13.png) -Figure 13. Text Containing a Hypertext Link. +Figure 13. Text Containing a Hypertext Link. + + +If the user control-clicks on the link, then the URL value of "HyperLinkURL" will be +loaded into the browser. + +The "HyperLinkName" property specifies a link name, which can be used when +searching a document. "HyperLinkTarget" corresponds to the HTML hypertext target +attribute, and has a default value of "_self". + + - -If the user control-clicks on the link, then the URL value of "HyperLinkURL" will be -loaded into the browser. +## 9. Text Numbering -The "HyperLinkName" property specifies a link name, which can be used when -searching a document. "HyperLinkTarget" corresponds to the HTML hypertext target -attribute, and has a default value of "_self". +It's straightforward to number paragraphs by using Write.styleLeft() and the +"NumberingStyleName" property. The following code from BuildDoc.java, numbers +three paragraphs: - - -## 9. Text Numbering +```java +// code fragment from BuildDoc.java +Write.appendPara(cursor, "The following points are important:"); -It's straightforward to number paragraphs by using Write.styleLeft() and the -"NumberingStyleName" property. The following code from BuildDoc.java, numbers -three paragraphs: - -// code fragment from BuildDoc.java -Write.appendPara(cursor, "The following points are important:"); - -pos = Write.appendPara(cursor, "Have a good breakfast"); -Write.appendPara(cursor, "Have a good lunch"); -Write.appendPara(cursor, "Have a good dinner\n"); -Write.styleLeft(cursor, pos, "NumberingStyleName", "Numbering 1"); - -The result is shown in Figure 14. +pos = Write.appendPara(cursor, "Have a good breakfast"); +Write.appendPara(cursor, "Have a good lunch"); +Write.appendPara(cursor, "Have a good dinner\n"); +Write.styleLeft(cursor, pos, "NumberingStyleName", "Numbering 1"); +``` + +The result is shown in Figure 14. - ![](images/06-Text_Styles-14.png) -Figure 14. Numbered Paragraphs. +Figure 14. Numbered Paragraphs. + + +"NumberingStyleName" is a property in ParagraphProperties, and the "Numbering 1" +style is from the "Paragraph Styles" dialog window in Figure 3. - -"NumberingStyleName" is a property in ParagraphProperties, and the "Numbering 1" -style is from the "Paragraph Styles" dialog window in Figure 3. +Bullets are drawn instead of numbers by changing the style name to "List 1" (see +Figure 15). -Bullets are drawn instead of numbers by changing the style name to "List 1" (see -Figure 15). - ![](images/06-Text_Styles-15.png) -Figure 15. Bulleted Paragraphs. +Figure 15. Bulleted Paragraphs. - -One issue with numbered paragraphs is that their default behavior retains the current -count when numbering another group of text. For example, a second group of -numbered paragraphs appearing in the document after Figure 14 would start at '4'. -This is fixed by setting the "ParaIsNumberingRestart" property to true: -Write.styleLeft(cursor, pos, "ParaIsNumberingRestart", true); - -One large topic I won’t be looking at is document numbering. This includes the -numbering of chapter headings and lines. Chapter and line numbering are dealt with -differently from most document styles. Instead of being accessed via -XStyleFamiliesSupplier, they employ XChapterNumberingSupplier and -XNumberFormatsSupplier. +One issue with numbered paragraphs is that their default behavior retains the current +count when numbering another group of text. For example, a second group of +numbered paragraphs appearing in the document after Figure 14 would start at '4'. +This is fixed by setting the "ParaIsNumberingRestart" property to true: -For more details, see the development guide: -https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Line_Numbering_an -d_Outline_Numbering -or type loGuide "Line Numbering". - - - -10. Other Style Changes -StoryCreator.java illustrates three other styling effects: the creation of a header, -setting the page to A4 format, and employing page numbers in the footer. The -relevant calls are: - -// code fragment in StoryCreator.java -Write.setHeader(doc, "From: " + args[0]); -Write.setA4PageFormat(doc); -Write.setPageNumbers(doc); - -Write.setA4PageFormat() sets the page formatting. Write.setPageNumbers() utilizes -text fields, which I'll examine in the "Text Fields" section in Chapter 7. - -Changing the header in Write.setHeader() requires the setting of the "HeaderIsOn" -boolean in the "Standard" page style. Adding text to the header is done via an XText -reference. The code for Write.setHeader(): - -public static void setHeader(XTextDocument textDoc, String hText) -/* Modify the header via the page style for the document. - - Put the text on the right hand side in the header in - Times New Roman, 10pt */ -{ - XPropertySet props = - Info.getStyleProps(textDoc, "PageStyles", "Standard"); - if (props == null) { - System.out.println("Could not access standard page style"); - return; - } - - try { - props.setPropertyValue("HeaderIsOn", true); - // header is turned on in the document - - // access the header's XText and cursor - XText headerText = Lo.qi(XText.class, - props.getPropertyValue("HeaderText")); - XTextCursor headerCursor = headerText.createTextCursor(); - headerCursor.gotoEnd(false); - - // header is on the rhs and in Times New Roman, 10pt - XPropertySet headerProps = Lo.qi( - XPropertySet.class, headerCursor); - headerProps.setPropertyValue("CharFontName","Times New Roman"); - headerProps.setPropertyValue("CharHeight", 10); - headerProps.setPropertyValue("ParaAdjust",ParagraphAdjust.RIGHT); - - headerText.setString(hText + "\n"); - } - catch (Exception ex) - { System.out.println(ex); } -} // end of setHeader() - -The header's XText reference is retrieved via the page style's "HeaderText" property, -and a cursor is created local to the header: -XTextCursor headerCursor = headerText.createTextCursor(); -This cursor can only move around inside the header not the entire document. - -The properties of the header's XText are changed using the cursor, and then the text is -added. +```java +Write.styleLeft(cursor, pos, "ParaIsNumberingRestart", true); +``` + +One large topic I won’t be looking at is document numbering. This includes the +numbering of chapter headings and lines. Chapter and line numbering are dealt with +differently from most document styles. Instead of being accessed via +XStyleFamiliesSupplier, they employ XChapterNumberingSupplier and +XNumberFormatsSupplier. +For more details, see the development guide: +https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Line_Numbering_an +d_Outline_Numbering +or type `loGuide "Line Numbering"`. + + + +## 10. Other Style Changes + +StoryCreator.java illustrates three other styling effects: the creation of a header, +setting the page to A4 format, and employing page numbers in the footer. The +relevant calls are: + +```java +// code fragment in StoryCreator.java +Write.setHeader(doc, "From: " + args[0]); +Write.setA4PageFormat(doc); +Write.setPageNumbers(doc); +``` + +Write.setA4PageFormat() sets the page formatting. Write.setPageNumbers() utilizes +text fields, which I'll examine in the "Text Fields" section in Chapter 7. + +Changing the header in Write.setHeader() requires the setting of the "HeaderIsOn" +boolean in the "Standard" page style. Adding text to the header is done via an XText +reference. The code for Write.setHeader(): + +```java +public static void setHeader(XTextDocument textDoc, String hText) +/* Modify the header via the page style for the document. + + Put the text on the right hand side in the header in + Times New Roman, 10pt */ +{ + XPropertySet props = + Info.getStyleProps(textDoc, "PageStyles", "Standard"); + if (props == null) { + System.out.println("Could not access standard page style"); + return; + } + + try { + props.setPropertyValue("HeaderIsOn", true); + // header is turned on in the document + + // access the header's XText and cursor + XText headerText = Lo.qi(XText.class, + props.getPropertyValue("HeaderText")); + XTextCursor headerCursor = headerText.createTextCursor(); + headerCursor.gotoEnd(false); + + // header is on the rhs and in Times New Roman, 10pt + XPropertySet headerProps = Lo.qi( + XPropertySet.class, headerCursor); + headerProps.setPropertyValue("CharFontName","Times New Roman"); + headerProps.setPropertyValue("CharHeight", 10); + headerProps.setPropertyValue("ParaAdjust",ParagraphAdjust.RIGHT); + + headerText.setString(hText + "\n"); + } + catch (Exception ex) + { System.out.println(ex); } +} // end of setHeader() +``` + +The header's XText reference is retrieved via the page style's "HeaderText" property, +and a cursor is created local to the header: + +```java +XTextCursor headerCursor = headerText.createTextCursor(); +``` + +This cursor can only move around inside the header not the entire document. + +The properties of the header's XText are changed using the cursor, and then the text is +added. \ No newline at end of file diff --git a/docs/07-Non-text_Content.md b/docs/07-Non-text_Content.md index 5a42bad..9ff5bf2 100644 --- a/docs/07-Non-text_Content.md +++ b/docs/07-Non-text_Content.md @@ -1,1157 +1,1151 @@ -# Chapter 7. Text Content Other than Strings +# Chapter 7. Text Content Other than Strings !!! note "Topics" - Accessing Text - Content; Text Frames; - Embedded Objects - (Math Formulae); Text - Fields; Text Tables; - Bookmarks + Accessing Text + Content; Text Frames; + Embedded Objects + (Math Formulae); Text + Fields; Text Tables; + Bookmarks - Example folders: "Text - Tests" and "Utils" - - -Chapter 5 looked at using text cursors to move around -inside text documents, adding or extracting text strings. + Example folders: "Text + Tests" and "Utils" -That chapter utilized the XText inheritance hierarchy, -which is shown again in Figure 1. - +Chapter 5 looked at using text cursors to move around +inside text documents, adding or extracting text strings. + +That chapter utilized the XText inheritance hierarchy, +which is shown again in Figure 1. + ![](images/07-Non-text_Content-1.png) -Figure 1. XText and its Superclasses. +Figure 1. XText and its Superclasses. - -The documents manipulated in Chapter 5 only contained character-based text, but can -be a lot more varied, including text frames, embedded objects, graphics, shapes, text -fields, tables, bookmarks, text sections, footnotes and endnotes, and more. -From the XText service its possible to access the XTextContent interface (see Figure -1), which belongs to the TextContent service. As Figure 2 indicates, that service is the -parent of many subclasses which represent different kinds of text document content. +The documents manipulated in Chapter 5 only contained character-based text, but can +be a lot more varied, including text frames, embedded objects, graphics, shapes, text +fields, tables, bookmarks, text sections, footnotes and endnotes, and more. + +From the XText service its possible to access the XTextContent interface (see Figure +1), which belongs to the TextContent service. As Figure 2 indicates, that service is the +parent of many subclasses which represent different kinds of text document content. - ![](images/07-Non-text_Content-2.png) -Figure 2. The TextContent Service and Some Subclasses. - - -A more complete hierarchy can be found in the documentation for TextContent -(lodoc TextContent service). - -The two services highlighted in orange relate to graphical content, which is explained -in the next chapter. - -Table 1 summarizes content types in terms of their services and access methods. Most -of the methods are in Supplier interfaces which are part of the GenericTextDocument -or OfficeDocument services in Figure 2 of Chapter 5. - -If the service text is in bold then there's an example of how to manipulate its content -later in this chapter. - - -Content Name -Service for Creating -Content -Access Method in Supplier -Text Frame TextFrame XNameAccess XTextFrameSupplier. - -getTextFrames() -Embedded -Object -TextEmbeddedObject XComponent XTextEmbeddedObjectSupplier2. - -getEmbeddedObject() -Graphic -Object -TextGraphicObject; - -XNameAccess XTextGraphicObjectsSupplier. - -getGraphicObjects() -Shape text.Shape, -drawing.Shape or a -subclass -XDrawPage XDrawPageSupplier. - -getDrawPage() -Text Field TextField XEnumerationAccess XTextFieldsSupplier. - -getTextFields() -Text Table TextTable XNameAccess XTextTablesSupplier. - -getTextTables() -Bookmark Bookmark XNameAccess XBookmarksSupplier. - -getBookmarks() -Paragraph Paragraph XEnumerationAccess on XText -Text Section - -TextSection XNameAccess XTextSectionsSupplier. - -getTextSections() -Footnote Footnote XIndexAccess XFootnotesSupplier. - -getFootnotes() -End Note Endnote XIndexAccess XEndnotesSupplier.getEndnotes() -Reference -Mark -ReferenceMark XNameAccess XReferenceMarksSupplier. - -getReferenceMarks() -Index DocumentIndex XIndexAccess XDocumentIndexesSupplier. - -getDocumentIndexes() -Link Target LinkTarget XNameAccess XLinkTargetSupplier.getLinks() -Redline RedlinePortion XEnumerationAccess XRedlinesSupplier. - -getRedlines() -Content -Metadata -InContentMetaData - -XDocumentMetadataAccess -Table 1. Creating and Accessing Text Content. - - -Rows 3 and 4 are highlighted to indicate that graphical content is discussed in the next -chapter. - - -## 1. How to Access Text Content - -Most of the examples in this chapter create text document content rather than access -it. This is mainly because the different access functions work in a similar way, so you -don’t need many examples to get the general idea. - -First the document is converted into a supplier, then its getXXX() method is called -(see column 3 of Table 1). For example, accessing the graphic objects in a document -(see row 3 of Table 1) requires: - -// get the graphic objects supplier -XTextGraphicObjectsSupplier imsSupplier = - Lo.qi(XTextGraphicObjectsSupplier.class, doc); - -// access the graphic objects collection -XNameAccess xNameAccess = imsSupplier.getGraphicObjects(); - -The names associated with the graphic objects in XNameAccess can be extracted with -XNameAccess.getElementNames(), and printed: - -String[] names = xNameAccess.getElementNames(); -System.out.println("Number of graphic names: " + names.length); -Arrays.sort(names); // sort them, if you want -Lo.printNames(names); // useful for printing long lists - -A particular object in an XNameAccess collection is retrieved with getByName(): -XInterface oGraphic = xNameAccess.getByName("foo"); - // get graphic object called "foo" -XInterface is the Office equivalent of Java's Object: every interface subclasses -XInterface. - -A common next step is to convert the object into a property set, which makes it -possible to lookup the properties stored in the object's service. For instance, the -graphic object’s filename or URL can be retrieved using: - -XPropertySet props = Lo.qi(XPropertySet.class, oGraphic); -String fnm = (String) props.getPropertyValue("GraphicURL"); - -I know the graphic object's URL is stored in the "GraphicURL" property from looking -at the documentation for the TextGraphicObject service. It can be (almost) directly -accessed by typing lodoc TextGraphicObject service; unfortunately -DuckDuckGo chooses the TextGraphicObjects service, and you need to click on the -"TextGraphicObject" link below the title to get to the required page. - -It's possible to call setPropertyValue() to change a property: -props.setPropertyValue("Transparency", (short)50); - -What About the Text Content I Don't Cover? -Table 1 has many rows without bold entries, which means I won't be looking at them. - -except for the very brief descriptions here; for more please consult the Developer's -Guide at +Figure 2. The TextContent Service and Some Subclasses. + + +A more complete hierarchy can be found in the documentation for TextContent +(`lodoc TextContent service`). + +The two services highlighted in orange relate to graphical content, which is explained +in the next chapter. + +Table 1 summarizes content types in terms of their services and access methods. Most +of the methods are in Supplier interfaces which are part of the GenericTextDocument +or OfficeDocument services in Figure 2 of Chapter 5. + +If the service text is in bold then there's an example of how to manipulate its content +later in this chapter. + + +|Content Name |Service for Creating Content|Access Method in Supplier | +| | | | +|Text Frame |TextFrame |XNameAccess XTextFrameSupplier. | +| | |getTextFrames() | +|Embedded Object |TextEmbeddedObject |XComponent XTextEmbeddedObjectSupplier2. | +| | |getEmbeddedObject() | +|Graphic Object |TextGraphicObject; |XNameAccess XTextGraphicObjectsSupplier. | +| | |getGraphicObjects() | +|Shape |text.Shape, drawing.Shape |XDrawPage XDrawPageSupplier. | +| |or a subclass |getDrawPage() | +|Text Field |TextField |XEnumerationAccess XTextFieldsSupplier. | +| | |getTextFields() | +|Text Table |TextTable |XNameAccess XTextTablesSupplier. | +| | |getTextTables() | +|Bookmark |Bookmark |XNameAccess XBookmarksSupplier. | +| | |getBookmarks() | +|Paragraph |Paragraph |XEnumerationAccess on XText | +|Text Section |TextSection |XNameAccess XTextSectionsSupplier. | +| | |getTextSections() | +|Footnote |Footnote |XIndexAccess XFootnotesSupplier. | +| | |getFootnotes() | +|End Note |Endnote |XIndexAccess XEndnotesSupplier.getEndnotes()| +|Reference Mark |ReferenceMark |XNameAccess XReferenceMarksSupplier. | +| | |getReferenceMarks() | +|Index |DocumentIndex |XIndexAccess XDocumentIndexesSupplier. | +| | |getDocumentIndexes() | +|Link Target |LinkTarget |XNameAccess XLinkTargetSupplier.getLinks() | +|Redline |RedlinePortion |XEnumerationAccess XRedlinesSupplier. | +| | |getRedlines() | +|Content Metadata |InContentMetaData |XDocumentMetadataAccess | + +Table 1. Creating and Accessing Text Content. + + +Rows 3 and 4 are highlighted to indicate that graphical content is discussed in the next +chapter. + + +## 1. How to Access Text Content + +Most of the examples in this chapter create text document content rather than access +it. This is mainly because the different access functions work in a similar way, so you +don’t need many examples to get the general idea. + +First the document is converted into a supplier, then its getXXX() method is called +(see column 3 of Table 1). For example, accessing the graphic objects in a document +(see row 3 of Table 1) requires: + +```java +// get the graphic objects supplier +XTextGraphicObjectsSupplier imsSupplier = + Lo.qi(XTextGraphicObjectsSupplier.class, doc); + +// access the graphic objects collection +XNameAccess xNameAccess = imsSupplier.getGraphicObjects(); +``` + +The names associated with the graphic objects in XNameAccess can be extracted with +XNameAccess.getElementNames(), and printed: + +```java +String[] names = xNameAccess.getElementNames(); +System.out.println("Number of graphic names: " + names.length); +Arrays.sort(names); // sort them, if you want +Lo.printNames(names); // useful for printing long lists +``` + +A particular object in an XNameAccess collection is retrieved with getByName(): + +```java +XInterface oGraphic = xNameAccess.getByName("foo"); + // get graphic object called "foo" +``` + +XInterface is the Office equivalent of Java's Object: every interface subclasses +XInterface. + +A common next step is to convert the object into a property set, which makes it +possible to lookup the properties stored in the object's service. For instance, the +graphic object’s filename or URL can be retrieved using: + +```java +XPropertySet props = Lo.qi(XPropertySet.class, oGraphic); +String fnm = (String) props.getPropertyValue("GraphicURL"); +``` + +I know the graphic object's URL is stored in the "GraphicURL" property from looking +at the documentation for the TextGraphicObject service. It can be (almost) directly +accessed by typing `lodoc TextGraphicObject service;` unfortunately +DuckDuckGo chooses the TextGraphicObjects service, and you need to click on the +"TextGraphicObject" link below the title to get to the required page. + +It's possible to call setPropertyValue() to change a property: +`props.setPropertyValue("Transparency", (short)50);` + +**What About the Text Content I Don't Cover?** + +Table 1 has many rows without bold entries, which means I won't be looking at them. + +except for the very brief descriptions here; for more please consult the Developer's +Guide at https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Working_with_Text_ -Documents (or type loGuide "Working with Text Documents"). All the examples -in that section are in TextDocuments.java at -http://api.libreoffice.org/examples/DevelopersGuide/examples.html#Text. - -Text Sections. A text section is a grouping of paragraphs which can be assigned their -own style settings. More usefully, a section may be located in another file, which is -the mechanism underlying master documents. See: -https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Text_Sections (or -type loGuide "Text Sections"). - -Footnotes and Endnotes. Footnotes and endnotes are blocks of text that appear in the -page footers and at the end of a document. They can be treated as XText objects, so -manipulated using the same techniques as the main document text. See: +Documents (or type `loGuide "Working with Text Documents"`). All the examples +in that section are in TextDocuments.java at +http://api.libreoffice.org/examples/DevelopersGuide/examples.html#Text. + +**Text Sections.** A text section is a grouping of paragraphs which can be assigned their +own style settings. More usefully, a section may be located in another file, which is +the mechanism underlying master documents. See: +https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Text_Sections (or +type `loGuide "Text Sections"`). + +**Footnotes and Endnotes.** Footnotes and endnotes are blocks of text that appear in the +page footers and at the end of a document. They can be treated as XText objects, so +manipulated using the same techniques as the main document text. See: https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Footnotes_and_Endn -otes (or type loGuide Footnotes). - -Reference Marks. Reference marks can be inserted throughout a document, and then -jumped to via GetReference text fields: -https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Reference_Marks (or -type loGuide "Reference Marks"). - -Indexes and Index Marks. Index marks, like reference marks, can be inserted -anywhere in a document, but are used to generate indices (collections of information) -inside the document. There are several types of index marks used for generating lists -of chapter headings (i.e. a book's index), lists of key words, illustrations, tables, and a -bibliography. For details see: +otes (or type `loGuide Footnotes`). + +**Reference Marks.** Reference marks can be inserted throughout a document, and then +jumped to via GetReference text fields: +https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Reference_Marks (or +type `loGuide "Reference Marks"`). + +**Indexes and Index Marks.** Index marks, like reference marks, can be inserted +anywhere in a document, but are used to generate indices (collections of information) +inside the document. There are several types of index marks used for generating lists +of chapter headings (i.e. a book's index), lists of key words, illustrations, tables, and a +bibliography. For details see: https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Indexes_and_Index_ -Marks (or type loGuide Indexes). +Marks (or type `loGuide Indexes`). -Link Targets. A link target (sometimes called a jump mark) labels a location inside a -document. These labels can be included as part of a filename so that the document can -be opened at that position. For information, see: -https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Link_Targets (or -type loGuide "Link Targets"). +**Link Targets.** A link target (sometimes called a jump mark) labels a location inside a +document. These labels can be included as part of a filename so that the document can +be opened at that position. For information, see: +https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Link_Targets (or +type `loGuide "Link Targets"`). -Redlines. Redlines are the changes recorded when a user edits a document with track -changes turned on. Each of the changes is saved as a text fragment (also called a text -portion) inside a redline object. See: -https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Redline (or type -loGuide Redline). +**Redlines.** Redlines are the changes recorded when a user edits a document with track +changes turned on. Each of the changes is saved as a text fragment (also called a text +portion) inside a redline object. See: +https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Redline (or type +`loGuide Redline`). - - -## 2. Adding a Text Frame to a Document -The TextFrame service inherits many of its properties and interfaces, so its -inheritance hierarchy is shown in detail in Figure 3. +## 2. Adding a Text Frame to a Document + +The TextFrame service inherits many of its properties and interfaces, so its +inheritance hierarchy is shown in detail in Figure 3. - ![](images/07-Non-text_Content-3.png) -Figure 3. The TextFrame Service Hierarchy. - - -Figure 3 includes two sibling services of TextFrame: TextEmbeddedObject and -TextGraphicObject, which I'll discuss a bit later; infact, I'll only get around to -TextGraphicObject in the next chapter. - -The BaseFrameProperties service contains most of the frame size and positional -properties, such as "Width", "Height", and margin and border distance settings. - -A TextFrame interface can be converted into a text content (i.e. XTextContent) or a -shape (i.e. XShape). Typically, the former is used when adding text to the frame, the -latter when manipulating the shape of the frame. - -In my BuildDoc.java example, text frame creation is done by Write.addTextFrame(), -with BuildDoc supplying the frame's y-axis coordinate position for its anchor: - -// code fragment in BuildDoc.java -XTextViewCursor tvc = Write.getViewCursor(doc); -int yPos = tvc.getPosition().Y; // y-axis anchor position - - : // other unrelated code - -Write.appendPara(cursor, "A text frame"); - -Write.addTextFrame(cursor, yPos, - "This is a newly created text frame.\n - Which is over on the right of the page, next to the code.", - 4000, 1500); // width x height of frame - -An anchor specifies how the text content is positioned relative to the ordinary text -around it. Anchoring can be relative to a character, paragraph, page, or another frame. - -Write.addTextFrame() uses page anchoring, which means that BuildDoc must obtain -a view cursor, so that an on-screen page position can be calculated. As Figure 4 -shows, the text frame is located on the right of the page, with its top edge level with -the start of the code listing. - - - +Figure 3. The TextFrame Service Hierarchy. + + +Figure 3 includes two sibling services of TextFrame: TextEmbeddedObject and +TextGraphicObject, which I'll discuss a bit later; infact, I'll only get around to +TextGraphicObject in the next chapter. + +The BaseFrameProperties service contains most of the frame size and positional +properties, such as "Width", "Height", and margin and border distance settings. + +A TextFrame interface can be converted into a text content (i.e. XTextContent) or a +shape (i.e. XShape). Typically, the former is used when adding text to the frame, the +latter when manipulating the shape of the frame. + +In my BuildDoc.java example, text frame creation is done by Write.addTextFrame(), +with BuildDoc supplying the frame's y-axis coordinate position for its anchor: + +```java +// code fragment in BuildDoc.java +XTextViewCursor tvc = Write.getViewCursor(doc); +int yPos = tvc.getPosition().Y; // y-axis anchor position + + : // other unrelated code + +Write.appendPara(cursor, "A text frame"); + +Write.addTextFrame(cursor, yPos, + "This is a newly created text frame.\n + Which is over on the right of the page, next to the code.", + 4000, 1500); // width x height of frame +``` + +An anchor specifies how the text content is positioned relative to the ordinary text +around it. Anchoring can be relative to a character, paragraph, page, or another frame. + +Write.addTextFrame() uses page anchoring, which means that BuildDoc must obtain +a view cursor, so that an on-screen page position can be calculated. As Figure 4 +shows, the text frame is located on the right of the page, with its top edge level with +the start of the code listing. + ![](images/07-Non-text_Content-4.png) -Figure 4. Text Frame Position in the Document. +Figure 4. Text Frame Position in the Document. + - -In the code fragment above, Write.getViewCursor() creates the view cursor, and +In the code fragment above, Write.getViewCursor() creates the view cursor, and XTextViewCursor.getPosition() returns its (x, y) coordinate on the page. The y- -coordinate is stored in yPos until after the code listing has been inserted into the -document, and then passed to Write.addTextFrame(). - -Write.addTextFrame() is defined as: - -public static void addTextFrame(XTextCursor cursor, int yPos, - String text, int width, int height) -{ - try { - XTextFrame xFrame = Lo.createInstanceMSF(XTextFrame.class, - "com.sun.star.text.TextFrame"); - if (xFrame == null) { - System.out.println("Could not create a text frame"); - return; - } - - // convert frame to a shape interface; set size - XShape tfShape = Lo.qi(XShape.class, xFrame); - tfShape.setSize(new Size(width, height)); - - // get properties of the frame - XPropertySet frameProps = Lo.qi(XPropertySet.class, xFrame); - - // anchor the text frame to the page - frameProps.setPropertyValue("AnchorType", - TextContentAnchorType.AT_PAGE); - frameProps.setPropertyValue("FrameIsAutomaticHeight", true); - // will grow if necessary - - // add a red border around all 4 sides - BorderLine border = new BorderLine(); - border.OuterLineWidth = 1; - border.Color = 0xFF0000; // red - frameProps.setPropertyValue("TopBorder", border); - frameProps.setPropertyValue("BottomBorder", border); - frameProps.setPropertyValue("LeftBorder", border); - frameProps.setPropertyValue("RightBorder", border); - - // make the text frame blue - frameProps.setPropertyValue("BackTransparent", false); - // not transparent - frameProps.setPropertyValue("BackColor", 0xCCCCFF); // blue - - // set the horizontal and vertical position - frameProps.setPropertyValue("HoriOrient", - HoriOrientation.RIGHT); - - frameProps.setPropertyValue("VertOrient", - VertOrientation.NONE); - frameProps.setPropertyValue("VertOrientPosition", yPos); - // down from top - - // insert empty text frame into document - append(cursor, xFrame); - endParagraph(cursor); - - // add text to the text frame - XText xFrameText = xFrame.getText(); - XTextCursor xFrameCursor = xFrameText.createTextCursor(); - xFrameText.insertString(xFrameCursor, text, false); - } - catch (Exception e) { - System.out.println("Insertion of text frame failed: " + e); - } -} // end of addTextFrame() - -addTextFrame() starts by creating a TextFrame service, and accessing its XTextFrame -interface: - -XTextFrame xFrame = Lo.createInstanceMSF(XTextFrame.class, - "com.sun.star.text.TextFrame"); - -The service name for a text frame is listed as "TextFrame" in row 1 of Table 1, but -Lo.createInstanceMSF() requires a fully qualified name. Almost all the text content -services, including TextFrame, are in the com.sun.star.text package. - -The XTextFrame interface is converted into XShape so the frame's dimensions can be -set. The interface is also cast to XPropertySet so that various frame properties can be -initialized; these properties are defined in the TextFrame and BaseFrameProperties -services (see Figure 3). - -The "AnchorType" property uses the AT_PAGE anchor constant to tie the frame to -the page. There are five anchor constants: AT_PARAGRAPH, AT_CHARACTER, -AS_CHARACTER, AT_PAGE, and AT_FRAME, which are defined in the -TextContentAnchorType enumeration. - -The difference between AT_CHARACTER and AS_CHARACTER relates to how -the surrounding text is wrapped around the text content. "AS" means that the text -content is treated as a single (perhaps very large) character inside the text, while "AT" -means that the text frame's upper-left corner is positioned at that character location. - -The frame's page position is dealt with a few lines later by the "HoriOrient" and -"VertOrient" properties. The HoriOrientation and VertOrientation constants are a -convenient way of positioning a frame at the corners or edges of the page. However, I -use "VertOrientPosition" to set the vertical position using the yPos coordinate, and -switch off the "VertOrient" vertical orientation. - -Towards the end of Write.addTextFrame(), the frame is added to the document by -calling a version of Write.append() that expects an XTextContent object: - -// in the Write class -public static int append(XTextCursor cursor, - XTextContent textContent) -// append text content such as a text frame, table, text field -{ - XText xText = cursor.getText(); - xText.insertTextContent(cursor, textContent, false); - cursor.gotoEnd(false); - return getPosition(cursor); -} - -It utilizes the XText.insertTextContent() method. - -The last task of Write.addTextFrame(), is to insert some text into the frame. - -XTextFrame inherits XTextContent, and so has access to the getText() method (see -Figure 3). This means that all the text manipulations possible in a document are also -possible inside a frame. - -The ordering of the tasks at the end of addTextFrame() is important. Office prefers -that an empty text content be added to the document, and the data inserted afterwards. - - - -## 3. Adding a Text Embedded Object to a Document - -Text embedded object content support OLE (Microsoft's Object Linking and -Embedding), and is typically used to create a frame linked to an external Office -document. Probably, its most popular use is to link to a chart, but I'll delay looking at -that until Chapter 33. - -The best way of getting an idea of what OLE objects are available is to go to the -Writer application's Insert menu  Object  "OLE Object" dialog. In my version of -Office, it lists Office spreadsheet, chart, drawing, presentation, and formula -documents, and a range of Microsoft and PDF types. - -Note that text embedded objects arem't utilized for adding graphics to a document. - -That's easier to do using the TextGraphicObject or GraphicObjectShape services, -which I'll describe next. - -In this section I'll explain how to insert mathematical formulae into a text document. - -The example code is in MathQuestions.java, but most of the formula embedding is -performed by Write.addFormula(): - -public static void addFormula(XTextCursor cursor, String formula) -{ - try { - XTextContent embedContent = - Lo.createInstanceMSF(XTextContent.class, - "com.sun.star.text.TextEmbeddedObject"); - if (embedContent == null) { - System.out.println("Could not create a formula"); - return; - } - - // get property set for the embedded object - XPropertySet props = Lo.qi(XPropertySet.class, embedContent); - - // set class ID (type) for object - props.setPropertyValue("CLSID", Lo.MATH_CLSID); - - // anchor object as a character - props.setPropertyValue("AnchorType", - TextContentAnchorType.AS_CHARACTER); - - // insert empty object into document - append(cursor, embedContent); - endLine(cursor); - - // access object's model - XEmbeddedObjectSupplier2 supp = Lo.qi( - XEmbeddedObjectSupplier2.class, embedContent); - XComponent oEmbed = supp.getEmbeddedObject(); - - // insert formula into the object - XPropertySet formulaProps = Lo.qi(XPropertySet.class, oEmbed); - formulaProps.setPropertyValue("Formula", formula); - } - catch (Exception e) { - System.out.println("\"" + formula + "\" failed: " + e); - } -} // end of addFormula() - -A math formula is passed to addFormula() as a string in a format I'll explain shortly. - -The method begins by creating a TextEmbeddedObject service, and referring to it -using the XTextContent interface: - -XTextContent embedContent = - Lo.createInstanceMSF(XTextContent.class, - "com.sun.star.text.TextEmbeddedObject"); - -Details about embedded objects are given in row 2 of Table 1. - -Unlike TextFrame which has an XTextFrame interface, there's no -XTextEmbeddedObject interface for TextEmbeddedObject. This can be confirmed by -looking at the TextFrame inheritance hierarchy in Figure 3. There is an -XEmbeddedObjectSuppler, but that's for accessing objects, not creating them. Instead -I've utilized the XTextContent interface in Lo.createInstanceMSF() because it's the -most specific interface available. - -The XTextContent interface is converted to XPropertySet so the "CLSID" and -"AnchorType" properties can be set. "CLSID" is specific to TextEmbeddedObject – -its value is the OLE class ID for the embedded document. The Lo.java utility contains -the class ID constants for Office's documents: - -// in the Lo class -public static final String WRITER_CLSID = - "8BC6B165-B1B2-4EDD-aa47-dae2ee689dd6"; - -public static final String CALC_CLSID = - "47BBB4CB-CE4C-4E80-a591-42d9ae74950f"; - -public static final String DRAW_CLSID = - "4BAB8970-8A3B-45B3-991c-cbeeac6bd5e3"; - -public static final String IMPRESS_CLSID = - "9176E48A-637A-4D1F-803b-99d9bfac1047"; - -public static final String MATH_CLSID = - "078B7ABA-54FC-457F-8551-6147e776a997"; - -public static final String CHART_CLSID = - "12DCAE26-281F-416F-a234-c3086127382e"; - -The "AnchorType" property is set to AS_CHARACTER so the formula string will be -anchored in the document in the same way as a string of characters. - -As with the text frame in Write.addTextFrame(), an empty text content is added to the -document first, then filled with the formula. - -The embedded object's content is accessed via the XEmbeddedObjectSupplier2 -interface which has a get method for obtaining the object: - -XEmbeddedObjectSupplier2 supp = - Lo.qi(XEmbeddedObjectSupplier2.class, embedContent); -XComponent oEmbed = supp.getEmbeddedObject(); - -The properties for this empty object (oEmbed) are accessed, and the formula string is -assigned to the "Formula" property: - -XPropertySet formulaProps = Lo.qi(XPropertySet.class, oEmbed); -formulaProps.setPropertyValue("Formula", formula); - -### 3.1. What's a Formula String? - -Although I've explained the working of Write.addFormula(), I haven't explained the -format of the formula string that's passed to it. There's a good overview of the -notation in the "Commands Reference" appendix of Office's "Math Guide", available -at https://www.libreoffice.org/get-help/documentation/ -For example, the formula string: -"1 {5}over{9} + 3 {5}over{9} = 5 {1}over{9}" -is rendered as: - - - - - - - - - - - -### 3.2. Building Formulae - -MathQuestions.java is mainly a for-loop for randomly generating numbers and -constructing simple formulae strings. Ten formulae are added to the document, which -is saved as "mathQuestions.pdf". The main() function: - -public static void main(String[] args) -{ - XComponentLoader loader = Lo.loadOffice(); - XTextDocument doc = Write.createDoc(loader); - if (doc == null) { - System.out.println("Writer doc creation failed"); - Lo.closeOffice(); - return; - } - - XTextCursor cursor = Write.getCursor(doc); - Write.appendPara(cursor, "Math Questions"); - Write.stylePrevParagraph(cursor, "Heading 1"); - Write.appendPara(cursor, "Solve the formulae for x:\n"); - - Random r = new Random(); - String formula; - for(int i=0; i < 10; i++) { // generate 10 formulae - int iA = r.nextInt(8)+2; // generate some integers - int iB = r.nextInt(8)+2; - int iC = r.nextInt(9)+1; - int iD = r.nextInt(8)+2; - int iE = r.nextInt(9)+1; - int iF1 = r.nextInt(8)+2; - - int choice = r.nextInt(3); // decide between 3 formulae - if (choice == 0) - formula = "{{{sqrt{" + iA + "x}} over " + iB + "} + - {" + iC + " over " + iD + - "}={" + iE + " over " + iF1 + "}}"; - else if (choice == 1) - formula = "{{{" + iA + "x} over " + iB + "} + - {" + iC + " over " + iD + - "}={" + iE + " over " + iF1 + "}}"; - else - formula = "{" + iA + "x + " + iB + " =" + iC + "}"; - - Write.addFormula(cursor, formula); - Write.endParagraph(cursor); - } - - Write.append(cursor, "Timestamp: " + Lo.getTimeStamp() + "\n"); - Lo.saveDoc(doc, "mathQuestions.pdf"); - Lo.closeDoc(doc); - Lo.closeOffice(); -} // end of main() - -Figure 5 shows a screenshot of part of mathQuestions.pdf. - - +coordinate is stored in yPos until after the code listing has been inserted into the +document, and then passed to Write.addTextFrame(). + +Write.addTextFrame() is defined as: + +```java +public static void addTextFrame(XTextCursor cursor, int yPos, + String text, int width, int height) +{ + try { + XTextFrame xFrame = Lo.createInstanceMSF(XTextFrame.class, + "com.sun.star.text.TextFrame"); + if (xFrame == null) { + System.out.println("Could not create a text frame"); + return; + } + + // convert frame to a shape interface; set size + XShape tfShape = Lo.qi(XShape.class, xFrame); + tfShape.setSize(new Size(width, height)); + + // get properties of the frame + XPropertySet frameProps = Lo.qi(XPropertySet.class, xFrame); + + // anchor the text frame to the page + frameProps.setPropertyValue("AnchorType", + TextContentAnchorType.AT_PAGE); + frameProps.setPropertyValue("FrameIsAutomaticHeight", true); + // will grow if necessary + + // add a red border around all 4 sides + BorderLine border = new BorderLine(); + border.OuterLineWidth = 1; + border.Color = 0xFF0000; // red + frameProps.setPropertyValue("TopBorder", border); + frameProps.setPropertyValue("BottomBorder", border); + frameProps.setPropertyValue("LeftBorder", border); + frameProps.setPropertyValue("RightBorder", border); + + // make the text frame blue + frameProps.setPropertyValue("BackTransparent", false); + // not transparent + frameProps.setPropertyValue("BackColor", 0xCCCCFF); // blue + + // set the horizontal and vertical position + frameProps.setPropertyValue("HoriOrient", + HoriOrientation.RIGHT); + + frameProps.setPropertyValue("VertOrient", + VertOrientation.NONE); + frameProps.setPropertyValue("VertOrientPosition", yPos); + // down from top + + // insert empty text frame into document + append(cursor, xFrame); + endParagraph(cursor); + + // add text to the text frame + XText xFrameText = xFrame.getText(); + XTextCursor xFrameCursor = xFrameText.createTextCursor(); + xFrameText.insertString(xFrameCursor, text, false); + } + catch (Exception e) { + System.out.println("Insertion of text frame failed: " + e); + } +} // end of addTextFrame() +``` + +addTextFrame() starts by creating a TextFrame service, and accessing its XTextFrame +interface: + +```java +XTextFrame xFrame = Lo.createInstanceMSF(XTextFrame.class, + "com.sun.star.text.TextFrame"); +``` + +The service name for a text frame is listed as "TextFrame" in row 1 of Table 1, but +Lo.createInstanceMSF() requires a fully qualified name. Almost all the text content +services, including TextFrame, are in the com.sun.star.text package. + +The XTextFrame interface is converted into XShape so the frame's dimensions can be +set. The interface is also cast to XPropertySet so that various frame properties can be +initialized; these properties are defined in the TextFrame and BaseFrameProperties +services (see Figure 3). + +The "AnchorType" property uses the AT_PAGE anchor constant to tie the frame to +the page. There are five anchor constants: AT_PARAGRAPH, AT_CHARACTER, +AS_CHARACTER, AT_PAGE, and AT_FRAME, which are defined in the +TextContentAnchorType enumeration. + +The difference between AT_CHARACTER and AS_CHARACTER relates to how +the surrounding text is wrapped around the text content. "AS" means that the text +content is treated as a single (perhaps very large) character inside the text, while "AT" +means that the text frame's upper-left corner is positioned at that character location. + +The frame's page position is dealt with a few lines later by the "HoriOrient" and +"VertOrient" properties. The HoriOrientation and VertOrientation constants are a +convenient way of positioning a frame at the corners or edges of the page. However, I +use "VertOrientPosition" to set the vertical position using the yPos coordinate, and +switch off the "VertOrient" vertical orientation. + +Towards the end of Write.addTextFrame(), the frame is added to the document by +calling a version of Write.append() that expects an XTextContent object: + +```java +// in the Write class +public static int append(XTextCursor cursor, + XTextContent textContent) +// append text content such as a text frame, table, text field +{ + XText xText = cursor.getText(); + xText.insertTextContent(cursor, textContent, false); + cursor.gotoEnd(false); + return getPosition(cursor); +} +``` + +It utilizes the XText.insertTextContent() method. + +The last task of Write.addTextFrame(), is to insert some text into the frame. + +XTextFrame inherits XTextContent, and so has access to the getText() method (see +Figure 3). This means that all the text manipulations possible in a document are also +possible inside a frame. + +The ordering of the tasks at the end of addTextFrame() is important. Office prefers +that an empty text content be added to the document, and the data inserted afterwards. + + +## 3. Adding a Text Embedded Object to a Document + +Text embedded object content support OLE (Microsoft's Object Linking and +Embedding), and is typically used to create a frame linked to an external Office +document. Probably, its most popular use is to link to a chart, but I'll delay looking at +that until Chapter 33. + +The best way of getting an idea of what OLE objects are available is to go to the +Writer application's Insert menu, Object, "OLE Object" dialog. In my version of +Office, it lists Office spreadsheet, chart, drawing, presentation, and formula +documents, and a range of Microsoft and PDF types. + +Note that text embedded objects aren't utilized for adding graphics to a document. + +That's easier to do using the TextGraphicObject or GraphicObjectShape services, +which I'll describe next. + +In this section I'll explain how to insert mathematical formulae into a text document. + +The example code is in MathQuestions.java, but most of the formula embedding is +performed by Write.addFormula(): + +```java +public static void addFormula(XTextCursor cursor, String formula) +{ + try { + XTextContent embedContent = + Lo.createInstanceMSF(XTextContent.class, + "com.sun.star.text.TextEmbeddedObject"); + if (embedContent == null) { + System.out.println("Could not create a formula"); + return; + } + + // get property set for the embedded object + XPropertySet props = Lo.qi(XPropertySet.class, embedContent); + + // set class ID (type) for object + props.setPropertyValue("CLSID", Lo.MATH_CLSID); + + // anchor object as a character + props.setPropertyValue("AnchorType", + TextContentAnchorType.AS_CHARACTER); + + // insert empty object into document + append(cursor, embedContent); + endLine(cursor); + + // access object's model + XEmbeddedObjectSupplier2 supp = Lo.qi( + XEmbeddedObjectSupplier2.class, embedContent); + XComponent oEmbed = supp.getEmbeddedObject(); + + // insert formula into the object + XPropertySet formulaProps = Lo.qi(XPropertySet.class, oEmbed); + formulaProps.setPropertyValue("Formula", formula); + } + catch (Exception e) { + System.out.println("\"" + formula + "\" failed: " + e); + } +} // end of addFormula() +``` + +A math formula is passed to addFormula() as a string in a format I'll explain shortly. + +The method begins by creating a TextEmbeddedObject service, and referring to it +using the XTextContent interface: + +```java +XTextContent embedContent = + Lo.createInstanceMSF(XTextContent.class, + "com.sun.star.text.TextEmbeddedObject"); +``` + +Details about embedded objects are given in row 2 of Table 1. + +Unlike TextFrame which has an XTextFrame interface, there's no +XTextEmbeddedObject interface for TextEmbeddedObject. This can be confirmed by +looking at the TextFrame inheritance hierarchy in Figure 3. There is an +XEmbeddedObjectSuppler, but that's for accessing objects, not creating them. Instead +I've utilized the XTextContent interface in Lo.createInstanceMSF() because it's the +most specific interface available. + +The XTextContent interface is converted to XPropertySet so the "CLSID" and +"AnchorType" properties can be set. "CLSID" is specific to TextEmbeddedObject – +its value is the OLE class ID for the embedded document. The Lo.java utility contains +the class ID constants for Office's documents: + +```java +// in the Lo class +public static final String WRITER_CLSID = + "8BC6B165-B1B2-4EDD-aa47-dae2ee689dd6"; + +public static final String CALC_CLSID = + "47BBB4CB-CE4C-4E80-a591-42d9ae74950f"; + +public static final String DRAW_CLSID = + "4BAB8970-8A3B-45B3-991c-cbeeac6bd5e3"; + +public static final String IMPRESS_CLSID = + "9176E48A-637A-4D1F-803b-99d9bfac1047"; + +public static final String MATH_CLSID = + "078B7ABA-54FC-457F-8551-6147e776a997"; + +public static final String CHART_CLSID = + "12DCAE26-281F-416F-a234-c3086127382e"; +``` + +The "AnchorType" property is set to AS_CHARACTER so the formula string will be +anchored in the document in the same way as a string of characters. + +As with the text frame in Write.addTextFrame(), an empty text content is added to the +document first, then filled with the formula. + +The embedded object's content is accessed via the XEmbeddedObjectSupplier2 +interface which has a get method for obtaining the object: + +```java +XEmbeddedObjectSupplier2 supp = + Lo.qi(XEmbeddedObjectSupplier2.class, embedContent); +XComponent oEmbed = supp.getEmbeddedObject(); +``` + +The properties for this empty object (oEmbed) are accessed, and the formula string is +assigned to the "Formula" property: + +XPropertySet formulaProps = Lo.qi(XPropertySet.class, oEmbed); +formulaProps.setPropertyValue("Formula", formula); + +### 3.1. What's a Formula String? + +Although I've explained the working of Write.addFormula(), I haven't explained the +format of the formula string that's passed to it. There's a good overview of the +notation in the "Commands Reference" appendix of Office's "Math Guide", available +at https://www.libreoffice.org/get-help/documentation/ +For example, the formula string: +"1 {5}over{9} + 3 {5}over{9} = 5 {1}over{9}" +is rendered as: + +[*** Formula did not render in MarkDown ***] + + +### 3.2. Building Formulae + +MathQuestions.java is mainly a for-loop for randomly generating numbers and +constructing simple formulae strings. Ten formulae are added to the document, which +is saved as "mathQuestions.pdf". The main() function: + +```java +public static void main(String[] args) +{ + XComponentLoader loader = Lo.loadOffice(); + XTextDocument doc = Write.createDoc(loader); + if (doc == null) { + System.out.println("Writer doc creation failed"); + Lo.closeOffice(); + return; + } + + XTextCursor cursor = Write.getCursor(doc); + Write.appendPara(cursor, "Math Questions"); + Write.stylePrevParagraph(cursor, "Heading 1"); + Write.appendPara(cursor, "Solve the formulae for x:\n"); + + Random r = new Random(); + String formula; + for(int i=0; i < 10; i++) { // generate 10 formulae + int iA = r.nextInt(8)+2; // generate some integers + int iB = r.nextInt(8)+2; + int iC = r.nextInt(9)+1; + int iD = r.nextInt(8)+2; + int iE = r.nextInt(9)+1; + int iF1 = r.nextInt(8)+2; + + int choice = r.nextInt(3); // decide between 3 formulae + if (choice == 0) + formula = "{{{sqrt{" + iA + "x}} over " + iB + "} + + {" + iC + " over " + iD + + "}={" + iE + " over " + iF1 + "}}"; + else if (choice == 1) + formula = "{{{" + iA + "x} over " + iB + "} + + {" + iC + " over " + iD + + "}={" + iE + " over " + iF1 + "}}"; + else + formula = "{" + iA + "x + " + iB + " =" + iC + "}"; + + Write.addFormula(cursor, formula); + Write.endParagraph(cursor); + } + + Write.append(cursor, "Timestamp: " + Lo.getTimeStamp() + "\n"); + Lo.saveDoc(doc, "mathQuestions.pdf"); + Lo.closeDoc(doc); + Lo.closeOffice(); +} // end of main() +``` + +Figure 5 shows a screenshot of part of mathQuestions.pdf. + ![](images/07-Non-text_Content-5.png) -Figure 5. Math Formulae in a Text Document. +Figure 5. Math Formulae in a Text Document. - - -## 4. Text Fields -A text field differs from other text content in that its data is generated dynamically by -the document, or by an external source such as a database . Document-generated text -fields include text showing the current date, the page number, the total number of -pages in the document, and cross-references to other areas in the text. I'll look at three -examples: the DateTime, PageNumber, and PageCount text fields. +## 4. Text Fields -When a text field depends on an external source, there are two fields to initialize: the -master field representing the external source, and the dependent field for the data used -in the document; only the dependent field is visible. I won't be giving any -dependent/master field examples, but there's one in the Development Guide section -on text fields, at: -https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Text_Fields (or type -loGuide "Text Fields"). +A text field differs from other text content in that its data is generated dynamically by +the document, or by an external source such as a database . Document-generated text +fields include text showing the current date, the page number, the total number of +pages in the document, and cross-references to other areas in the text. I'll look at three +examples: the DateTime, PageNumber, and PageCount text fields. -It utilizes the User master field, which allows the external source to be user-defined -data. The code appears in the TextDocuments.java example at -http://api.libreoffice.org/examples/DevelopersGuide/examples.html#Text. +When a text field depends on an external source, there are two fields to initialize: the +master field representing the external source, and the dependent field for the data used +in the document; only the dependent field is visible. I won't be giving any +dependent/master field examples, but there's one in the Development Guide section +on text fields, at: +https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Text_Fields (or type +`loGuide "Text Fields"`). -Different kinds of text field are implemented as subclasses of the TextField service. +It utilizes the User master field, which allows the external source to be user-defined +data. The code appears in the TextDocuments.java example at +http://api.libreoffice.org/examples/DevelopersGuide/examples.html#Text. -You can see the complete hierarchy in the online documentation for TextField (lodoc -TextField service). Figure 6 presents a simplified version. +Different kinds of text field are implemented as subclasses of the TextField service. +You can see the complete hierarchy in the online documentation for TextField +(`lodoc TextField service`). Figure 6 presents a simplified version. - ![](images/07-Non-text_Content-6.png) -Figure 6. Simplified Hierarchy for the TextField Service. +Figure 6. Simplified Hierarchy for the TextField Service. + + +### 4.1. The DateTime TextField + +The BuildDoc.java example ends with a few lines that appear to do the same thing +twice: - -### 4.1. The DateTime TextField +```java +// code fragment from BuildDoc.java +Write.appendPara(cursor, "\nTimestamp: " + Lo.getTimeStamp()+"\n"); -The BuildDoc.java example ends with a few lines that appear to do the same thing -twice: - -// code fragment from BuildDoc.java -Write.appendPara(cursor, "\nTimestamp: " + Lo.getTimeStamp()+"\n"); - -Write.append(cursor, "Time (according to office): "); -Write.appendDateTime(cursor); -Write.endParagraph(cursor); - -Lo.getTimeStamp() inserts a timestamp (which includes the date and time), and then -Write.appendDateTime() inserts the date and time. Although these may seem to be the -same, getTimeStamp() adds a string while appendDateTime() creates a text field. The -difference becomes apparent if you open the file some time after it was created. +Write.append(cursor, "Time (according to office): "); +Write.appendDateTime(cursor); +Write.endParagraph(cursor); +``` -Figure 7 shows two screenshots of the time-stamped parts of the document taken after -it was first generated, and nearly 50 minutes later. +Lo.getTimeStamp() inserts a timestamp (which includes the date and time), and then +Write.appendDateTime() inserts the date and time. Although these may seem to be the +same, getTimeStamp() adds a string while appendDateTime() creates a text field. The +difference becomes apparent if you open the file some time after it was created. + +Figure 7 shows two screenshots of the time-stamped parts of the document taken after +it was first generated, and nearly 50 minutes later. - ![](images/07-Non-text_Content-7.png) -Figure 7. Screenshots of the Timestamps. - - -The text field timestamp is updated each time the file is opened in edit mode (which is -the default in Writer). - -This dynamic updating occurs in all text fields. For example, if you add some pages to -a document, all the places in the document that use the PageCount text field will be -updated to show the new length. - -Write.appendDateTime() creates a DateTime service, and returns its XTextField -interface (see Figure 6). The TextField service only contains two properties, with -most being in the subclass (DateTime in this case). - - -public static int appendDateTime(XTextCursor cursor) -{ - XTextField dtField = Lo.createInstanceMSF(XTextField.class, - "com.sun.star.text.TextField.DateTime"); - Props.setProperty(dtField, "IsDate", true); - // so date is reported - append(cursor, dtField); - append(cursor, "; "); - - dtField = Lo.createInstanceMSF(XTextField.class, - "com.sun.star.text.TextField.DateTime"); - Props.setProperty(dtField, "IsDate", false); - // so time is reported - return append(cursor, dtField); -} // end of appendDateTime() - -The method adds two DateTime text fields to the document. The first has its "IsDate" -property set to true, so that the current date is inserted; the second sets "IsDate" to -false so the current time is shown. - - -### 4.2. The PageNumber and PageCount Text Fields - -I discussed most of StoryCreator.java in Chapter 6 on styles, but skipped over how -page numbers were added to the document's page footer. The footer is shown in +Figure 7. Screenshots of the Timestamps. -![](images/07-Non-text_Content-8.png) -Figure 8. +The text field timestamp is updated each time the file is opened in edit mode (which is +the default in Writer). + +This dynamic updating occurs in all text fields. For example, if you add some pages to +a document, all the places in the document that use the PageCount text field will be +updated to show the new length. + +Write.appendDateTime() creates a DateTime service, and returns its XTextField +interface (see Figure 6). The TextField service only contains two properties, with +most being in the subclass (DateTime in this case). + +```java +public static int appendDateTime(XTextCursor cursor) +{ + XTextField dtField = Lo.createInstanceMSF(XTextField.class, + "com.sun.star.text.TextField.DateTime"); + Props.setProperty(dtField, "IsDate", true); + // so date is reported + append(cursor, dtField); + append(cursor, "; "); + + dtField = Lo.createInstanceMSF(XTextField.class, + "com.sun.star.text.TextField.DateTime"); + Props.setProperty(dtField, "IsDate", false); + // so time is reported + return append(cursor, dtField); +} // end of appendDateTime() +``` + +The method adds two DateTime text fields to the document. The first has its "IsDate" +property set to true, so that the current date is inserted; the second sets "IsDate" to +false so the current time is shown. + + +### 4.2. The PageNumber and PageCount Text Fields + +I discussed most of StoryCreator.java in Chapter 6 on styles, but skipped over how +page numbers were added to the document's page footer. The footer is shown in +Figure 8. - ![](images/07-Non-text_Content-8.png) -Figure 8. Page Footer using Text Fields. - - -Write.setPageNumbers() inserts the PageNumber and PageCount text fields into the -footer's text area: - -// in the Write class -public static void setPageNumbers(XTextDocument textDoc) -{ - // get 'standard' style for page style family - XPropertySet props = Info.getStyleProps(textDoc, - "PageStyles", "Standard"); - if (props == null) { - System.out.println("Could not access standard page style"); - return; - } - - try { - props.setPropertyValue("FooterIsOn", Boolean.TRUE); - // footer must be turned on in the document - - // access the footer as XText - XText footerText = Lo.qi(XText.class, - props.getPropertyValue("FooterText")); - XTextCursor footerCursor = footerText.createTextCursor(); - - /* set footer text properties via its cursor: - font, font size, paragraph orientation */ - Props.setProperty(footerCursor, "CharFontName", - "Times New Roman"); - Props.setProperty(footerCursor, "CharHeight", 12.0f); - Props.setProperty(footerCursor, "ParaAdjust", - ParagraphAdjust.CENTER); - - // add text fields to the footer - append(footerCursor, getPageNumber()); - append(footerCursor, " of "); - append(footerCursor, getPageCount()); - } - catch (Exception ex) - { System.out.println(ex); } -} // end of setPageNumbers() - - -public static XTextField getPageNumber() -// return Arabic style number showing current page value -{ - XTextField numField = Lo.createInstanceMSF(XTextField.class, - "com.sun.star.text.TextField.PageNumber"); - Props.setProperty(numField, "NumberingType", - NumberingType.ARABIC); - Props.setProperty(numField, "SubType", PageNumberType.CURRENT); - return numField; -} - - -public static XTextField getPageCount() -// return Arabic style number showing current page count -{ - XTextField pcField = Lo.createInstanceMSF(XTextField.class, - "com.sun.star.text.TextField.PageCount"); - Props.setProperty(pcField, "NumberingType", - NumberingType.ARABIC); - return pcField; -} - -Write.setPageNumbers() starts by accessing the "Standard" property set (style) for the -page style family. Via its properties, the method turns on footer functionality and -accesses the footer text area as an XText object. - -An XTextCursor is created for the footer text area, and properties are configured: - -XText footerText = Lo.qi(XText.class, - props.getPropertyValue("FooterText")); - -XTextCursor footerCursor = footerText.createTextCursor(); - -Props.setProperty(footerCursor, "CharFontName", "Times New Roman"); - -These properties will be applied to the text and text fields added afterwards: - -append(footerCursor, getPageNumber()); -append(footerCursor, " of "); -append(footerCursor, getPageCount()); - -getPageNumber() and getPageCount() deal with the properties for the PageNumber -and PageCount fields. - -I found out about them by looking at the documentation for their services (e.g. use -lodoc TextField.PageNumber service; "TextField." is needed due to PageNumber -being a common word). - - - -## 5. Adding a Text Table to a Document - -The MakeTable.java example reads in data about James Bond movies from -"bondMovies.txt" and stores it as a text table in "table.odt". The first few rows are -shown in Figure 9. - - +Figure 8. Page Footer using Text Fields. + + +Write.setPageNumbers() inserts the PageNumber and PageCount text fields into the +footer's text area: + +```java +// in the Write class +public static void setPageNumbers(XTextDocument textDoc) +{ + // get 'standard' style for page style family + XPropertySet props = Info.getStyleProps(textDoc, + "PageStyles", "Standard"); + if (props == null) { + System.out.println("Could not access standard page style"); + return; + } + + try { + props.setPropertyValue("FooterIsOn", Boolean.TRUE); + // footer must be turned on in the document + + // access the footer as XText + XText footerText = Lo.qi(XText.class, + props.getPropertyValue("FooterText")); + XTextCursor footerCursor = footerText.createTextCursor(); + + /* set footer text properties via its cursor: + font, font size, paragraph orientation */ + Props.setProperty(footerCursor, "CharFontName", + "Times New Roman"); + Props.setProperty(footerCursor, "CharHeight", 12.0f); + Props.setProperty(footerCursor, "ParaAdjust", + ParagraphAdjust.CENTER); + + // add text fields to the footer + append(footerCursor, getPageNumber()); + append(footerCursor, " of "); + append(footerCursor, getPageCount()); + } + catch (Exception ex) + { System.out.println(ex); } +} // end of setPageNumbers() + + +public static XTextField getPageNumber() +// return Arabic style number showing current page value +{ + XTextField numField = Lo.createInstanceMSF(XTextField.class, + "com.sun.star.text.TextField.PageNumber"); + Props.setProperty(numField, "NumberingType", + NumberingType.ARABIC); + Props.setProperty(numField, "SubType", PageNumberType.CURRENT); + return numField; +} + + +public static XTextField getPageCount() +// return Arabic style number showing current page count +{ + XTextField pcField = Lo.createInstanceMSF(XTextField.class, + "com.sun.star.text.TextField.PageCount"); + Props.setProperty(pcField, "NumberingType", + NumberingType.ARABIC); + return pcField; +} +``` + +Write.setPageNumbers() starts by accessing the "Standard" property set (style) for the +page style family. Via its properties, the method turns on footer functionality and +accesses the footer text area as an XText object. + +An XTextCursor is created for the footer text area, and properties are configured: + +```java +XText footerText = Lo.qi(XText.class, + props.getPropertyValue("FooterText")); + +XTextCursor footerCursor = footerText.createTextCursor(); + +Props.setProperty(footerCursor, "CharFontName", "Times New Roman"); +``` + +These properties will be applied to the text and text fields added afterwards: + +```java +append(footerCursor, getPageNumber()); +append(footerCursor, " of "); +append(footerCursor, getPageCount()); +``` + +getPageNumber() and getPageCount() deal with the properties for the PageNumber +and PageCount fields. + +I found out about them by looking at the documentation for their services (e.g. use +`lodoc TextField.PageNumber` service; "TextField." is needed due to PageNumber +being a common word). + + +## 5. Adding a Text Table to a Document + +The MakeTable.java example reads in data about James Bond movies from +"bondMovies.txt" and stores it as a text table in "table.odt". The first few rows are +shown in Figure 9. + ![](images/07-Non-text_Content-9.png) -Figure 9. A Bond Movies Table. - - -The "bondMovies.txt" file is read by readTable() utilizing standard Java file -processing. It returns a list of String arrays: -ArrayList rowsList = readTable("bondMovies.txt"); -Each line in "bondMovies.txt" is converted into a string array by pulling out the -substrings delimited by tab characters. - -readTable() ignores blank lines in the file, and lines beginning with "//". Also, I -assume that the first row in the list contains the table's header text. - -The first few lines of "bondMovies.txt" are: - -// http://en.wikipedia.org/wiki/James_Bond#Ian_Fleming_novels - -Title Year Actor Director - -Dr. No 1962 Sean Connery Terence Young -From Russia with Love 1963 Sean Connery Terence Young -Goldfinger 1964 Sean Connery Guy Hamilton -Thunderball 1965 Sean Connery Terence Young -You Only Live Twice 1967 Sean Connery Lewis Gilbert -On Her Majesty's Secret Service 1969 George Lazenby Peter R. Hunt -Diamonds Are Forever 1971 Sean Connery Guy Hamilton -Live and Let Die 1973 Roger Moore Guy Hamilton -The Man with the Golden Gun 1974 Roger Moore Guy Hamilton - : - -The main() function for MakeTable.java is: - -public static void main(String args[]) -{ - - ArrayList rowsList = readTable("bondMovies.txt"); - if (rowsList.size() == 0) { - System.out.println("No data read from bondMovies.txt"); - return; - } - - XComponentLoader loader = Lo.loadOffice(); - XTextDocument doc = Write.createDoc(loader); - if (doc == null) { - System.out.println("Writer doc creation failed"); - Lo.closeOffice(); - return; - } - - XTextCursor cursor = Write.getCursor(doc); - Write.appendPara(cursor, "Table of Bond Movies"); - Write.stylePrevParagraph(cursor, "Heading 1"); - Write.appendPara(cursor, - "The following table comes from \"bondMovies.txt\":\n"); - - Write.addTable(cursor, rowsList); - - Write.endParagraph(cursor); - Write.append(cursor, "Timestamp: " + Lo.getTimeStamp() + "\n"); - - Lo.saveDoc(doc, "table.odt"); - Lo.closeDoc(doc); - Lo.closeOffice(); -} // end of main() - -Write.addTable() does the work of converting the list of rows into a text table. - -Figure 10 shows the hierarchy for the TextTable service: it's a subclass of -TextContent and supports the XTextTable interface. - - +Figure 9. A Bond Movies Table. + + +The "bondMovies.txt" file is read by readTable() utilizing standard Java file +processing. It returns a list of String arrays: + +```java +ArrayList rowsList = readTable("bondMovies.txt"); +``` + +Each line in "bondMovies.txt" is converted into a string array by pulling out the +substrings delimited by tab characters. + +readTable() ignores blank lines in the file, and lines beginning with "//". Also, I +assume that the first row in the list contains the table's header text. + +The first few lines of "bondMovies.txt" are: + +``` +// http://en.wikipedia.org/wiki/James_Bond#Ian_Fleming_novels + +Title Year Actor Director + +Dr. No 1962 Sean Connery Terence Young +From Russia with Love 1963 Sean Connery Terence Young +Goldfinger 1964 Sean Connery Guy Hamilton +Thunderball 1965 Sean Connery Terence Young +You Only Live Twice 1967 Sean Connery Lewis Gilbert +On Her Majesty's Secret Service 1969 George Lazenby Peter R. Hunt +Diamonds Are Forever 1971 Sean Connery Guy Hamilton +Live and Let Die 1973 Roger Moore Guy Hamilton +The Man with the Golden Gun 1974 Roger Moore Guy Hamilton + : +``` + +The main() function for MakeTable.java is: + +```java +public static void main(String args[]) +{ + + ArrayList rowsList = readTable("bondMovies.txt"); + if (rowsList.size() == 0) { + System.out.println("No data read from bondMovies.txt"); + return; + } + + XComponentLoader loader = Lo.loadOffice(); + XTextDocument doc = Write.createDoc(loader); + if (doc == null) { + System.out.println("Writer doc creation failed"); + Lo.closeOffice(); + return; + } + + XTextCursor cursor = Write.getCursor(doc); + Write.appendPara(cursor, "Table of Bond Movies"); + Write.stylePrevParagraph(cursor, "Heading 1"); + Write.appendPara(cursor, + "The following table comes from \"bondMovies.txt\":\n"); + + Write.addTable(cursor, rowsList); + + Write.endParagraph(cursor); + Write.append(cursor, "Timestamp: " + Lo.getTimeStamp() + "\n"); + + Lo.saveDoc(doc, "table.odt"); + Lo.closeDoc(doc); + Lo.closeOffice(); +} // end of main() +``` + +Write.addTable() does the work of converting the list of rows into a text table. + +Figure 10 shows the hierarchy for the TextTable service: it's a subclass of +TextContent and supports the XTextTable interface. + ![](images/07-Non-text_Content-10.png) -Figure 10. The TextTable Hierarchy. +Figure 10. The TextTable Hierarchy. - -XTextTable contains methods for accessing a table in terms of its rows, columns, and -cells. The cells are referred to using names, based on letters for columns and integers -for rows, as in Figure 11. - +XTextTable contains methods for accessing a table in terms of its rows, columns, and +cells. The cells are referred to using names, based on letters for columns and integers +for rows, as in Figure 11. + ![](images/07-Non-text_Content-11.png) -Figure 11. The Cell Names in a Table. - - -Write.addTable() uses this naming scheme in the XTextTable.getCellByName() -method to assign data to cells: - -public static void addTable(XTextCursor cursor, - ArrayList rowsList) -// part of the Write utility class -{ - try { - // create a text table - XTextTable table = Lo.createInstanceMSF(XTextTable.class, - "com.sun.star.text.TextTable"); - if (table == null) { - System.out.println("Could not create a text table"); - return; - } - - // initialize table dimensions - int numRows = rowsList.size(); - int numCols = (rowsList.get(0)).length; - if (numCols > 26) { // column labelling goes from 'A' to 'Z' - System.out.println("Too many columns: " + numCols + - "; using first 26"); - numCols = 26; - } - table.initialize(numRows, numCols); - - // add the table to the document - append(cursor, table); - endParagraph(cursor); - - // set table properties - XPropertySet tableProps = Lo.qi(XPropertySet.class, table); - tableProps.setPropertyValue("BackTransparent", false); - // not transparent - tableProps.setPropertyValue("BackColor", 0xCCCCFF); - // light blue - - // set color of first row (i.e. the header) to dark blue - XTableRows rows = table.getRows(); - Props.setProperty(rows.getByIndex(0), "BackColor", 0x666694); - // dark blue - // write table header - String[] rowData = rowsList.get(0); - for (int x=0; x < numCols; x++) - setCellHeader( mkCellName(x,1), rowData[x], table); - // e.g. "A1", "B1", "C1", etc. - - - // insert table body - for (int y=1; y < numRows; y++) { // start in 2nd row of list - rowData = rowsList.get(y); - for (int x=0; x < numCols; x++) - setCellText( mkCellName(x,y+1), rowData[x], table); - // e.g. "A2", "B5", "C3", etc. - - } - } - catch (Exception e) - { System.out.println("Table insertion failed:" + e); } -} // end of addTable() - -A TextTable service with an XTableText interface is created at the start of -addTable(). Then the required number of rows and columns is calculated so that -XTextTable.initialize() can be called to specify the table's dimensions. - -Table-wide properties are set (properties are listed in the TextTable documentation). - -Note that if "BackTransparent" isn't set to false then Office crashes when the program -tries to save the document. - -The color property of the header row is set to dark blue. This requires a call to -XTextTable.getRows() to return an XTableRows object representing all the rows. - -This object inherits XIndexAccess, so the first row is accessed with index 0. - -The filling of the table with data is performed by two loops. The first deals with -adding text to the header row, the second deals with all the other rows. - -mkCellName() converts an (x, y) integer pair into a cell name like those in Figure 11: - -public static String mkCellName(int x, int y) -{ return "" + ((char)('A' + x)) + y; } - -Write.setCellHeader() uses TextTable.getCellByName() to access a cell, which is of -type XCell. We'll study XCell in Part 4 because it's used for representing cells in a -spreadsheet. - -The Cell service supports both the XCell and XText interfaces, as in Figure 12. - - +Figure 11. The Cell Names in a Table. + + +Write.addTable() uses this naming scheme in the XTextTable.getCellByName() +method to assign data to cells: + +```java +public static void addTable(XTextCursor cursor, + ArrayList rowsList) +// part of the Write utility class +{ + try { + // create a text table + XTextTable table = Lo.createInstanceMSF(XTextTable.class, + "com.sun.star.text.TextTable"); + if (table == null) { + System.out.println("Could not create a text table"); + return; + } + + // initialize table dimensions + int numRows = rowsList.size(); + int numCols = (rowsList.get(0)).length; + if (numCols > 26) { // column labelling goes from 'A' to 'Z' + System.out.println("Too many columns: " + numCols + + "; using first 26"); + numCols = 26; + } + table.initialize(numRows, numCols); + + // add the table to the document + append(cursor, table); + endParagraph(cursor); + + // set table properties + XPropertySet tableProps = Lo.qi(XPropertySet.class, table); + tableProps.setPropertyValue("BackTransparent", false); + // not transparent + tableProps.setPropertyValue("BackColor", 0xCCCCFF); + // light blue + + // set color of first row (i.e. the header) to dark blue + XTableRows rows = table.getRows(); + Props.setProperty(rows.getByIndex(0), "BackColor", 0x666694); + // dark blue + // write table header + String[] rowData = rowsList.get(0); + for (int x=0; x < numCols; x++) + setCellHeader( mkCellName(x,1), rowData[x], table); + // e.g. "A1", "B1", "C1", etc. + + + // insert table body + for (int y=1; y < numRows; y++) { // start in 2nd row of list + rowData = rowsList.get(y); + for (int x=0; x < numCols; x++) + setCellText( mkCellName(x,y+1), rowData[x], table); + // e.g. "A2", "B5", "C3", etc. + + } + } + catch (Exception e) + { System.out.println("Table insertion failed:" + e); } +} // end of addTable() +``` + +A TextTable service with an XTableText interface is created at the start of +addTable(). Then the required number of rows and columns is calculated so that +XTextTable.initialize() can be called to specify the table's dimensions. + +Table-wide properties are set (properties are listed in the TextTable documentation). +Note that if "BackTransparent" isn't set to false then Office crashes when the program +tries to save the document. + +The color property of the header row is set to dark blue. This requires a call to +XTextTable.getRows() to return an XTableRows object representing all the rows. +This object inherits XIndexAccess, so the first row is accessed with index 0. + +The filling of the table with data is performed by two loops. The first deals with +adding text to the header row, the second deals with all the other rows. + +mkCellName() converts an (x, y) integer pair into a cell name like those in Figure 11: + +```java +public static String mkCellName(int x, int y) +{ return "" + ((char)('A' + x)) + y; } +``` + +Write.setCellHeader() uses TextTable.getCellByName() to access a cell, which is of +type XCell. We'll study XCell in Part 4 because it's used for representing cells in a +spreadsheet. + +The Cell service supports both the XCell and XText interfaces, as in Figure 12. + ![](images/07-Non-text_Content-12.png) -Figure 12. The Cell Service. - - -This means that Lo.qi() can convert an XCell instance into XText, which makes the -cell's text and properties accessible to a text cursor. Write.setCellHeader() implements -these features: - -private static void setCellHeader(String cellName, - String data, XTextTable table) -// puts text into the named cell of the table, colored white -{ - // convert XCell to XText - XText cellText = Lo.qi(XText.class, - table.getCellByName(cellName)); - - // create a cursor inside the cell - XTextCursor textCursor = cellText.createTextCursor(); - - Props.setProperty(textCursor, "CharColor", 0xFFFFFF); - // use white text - cellText.setString(data); -} // end of setCellHeader() - -The cell's "CharColor" property is changed so the inserted text in the header row is -white, as in Figure 9. - -Write.setCellText() is shorter than setCellHeader() because there's no need to change -the text's color: - -private static void setCellText(String cellName, - String data, XTextTable table) -// puts text into the named cell of the table -{ - XText cellText = Lo.qi(XText.class, - table.getCellByName(cellName)); - cellText.setString(data); -} // end of setCellText() - - -## 6. Adding a Bookmark to the Document - -Write.addBookmark() adds a named bookmark at the current cursor position: - -public static void addBookmark(XTextCursor cursor, String name) -{ - XTextContent bmkContent = Lo.createInstanceMSF( - XTextContent.class, - "com.sun.star.text.Bookmark"); - if (bmkContent == null) { - System.out.println("Could not create a bookmark"); - return; - } - - // convert bookmark content to a named collection - XNamed bmksNamed = Lo.qi(XNamed.class, bmkContent); - bmksNamed.setName(name); - - append(cursor, bmkContent); - endParagraph(cursor); -} // end of addBookmark() - -The Bookmark service doesn't have a specific interface (such as XBookmark), so -Lo.createInstanceMSF() returns an XTextContent interface. These services and -interfaces are summarized by Figure 13. - - +Figure 12. The Cell Service. + + +This means that Lo.qi() can convert an XCell instance into XText, which makes the +cell's text and properties accessible to a text cursor. Write.setCellHeader() implements +these features: + +```java +private static void setCellHeader(String cellName, + String data, XTextTable table) +// puts text into the named cell of the table, colored white +{ + // convert XCell to XText + XText cellText = Lo.qi(XText.class, + table.getCellByName(cellName)); + + // create a cursor inside the cell + XTextCursor textCursor = cellText.createTextCursor(); + + Props.setProperty(textCursor, "CharColor", 0xFFFFFF); + // use white text + cellText.setString(data); +} // end of setCellHeader() +``` + +The cell's "CharColor" property is changed so the inserted text in the header row is +white, as in Figure 9. + +Write.setCellText() is shorter than setCellHeader() because there's no need to change +the text's color: + +```java +private static void setCellText(String cellName, + String data, XTextTable table) +// puts text into the named cell of the table +{ + XText cellText = Lo.qi(XText.class, + table.getCellByName(cellName)); + cellText.setString(data); +} // end of setCellText() +``` + + +## 6. Adding a Bookmark to the Document + +Write.addBookmark() adds a named bookmark at the current cursor position: + +```java +public static void addBookmark(XTextCursor cursor, String name) +{ + XTextContent bmkContent = Lo.createInstanceMSF( + XTextContent.class, + "com.sun.star.text.Bookmark"); + if (bmkContent == null) { + System.out.println("Could not create a bookmark"); + return; + } + + // convert bookmark content to a named collection + XNamed bmksNamed = Lo.qi(XNamed.class, bmkContent); + bmksNamed.setName(name); + + append(cursor, bmkContent); + endParagraph(cursor); +} // end of addBookmark() +``` + +The Bookmark service doesn't have a specific interface (such as XBookmark), so +Lo.createInstanceMSF() returns an XTextContent interface. These services and +interfaces are summarized by Figure 13. + ![](images/07-Non-text_Content-13.png) -Figure 13. The Bookmark Service and Interfaces. +Figure 13. The Bookmark Service and Interfaces. - -Bookmark supports XNamed, which allows it to be viewed as a named collection of -bookmarks (note the plural). This is useful when searching for a bookmark or adding -one, as in the BuildDoc.java example. It calls Write.addBookmark() to add a -bookmark called "ad-Bookmark" to the document: - -// code fragment from BuildDoc.java -Write.append(cursor, "This line ends with a bookmark"); -Write.addBookmark(cursor, "ad-bookmark"); - -Bookmarks, such as "ad-bookmark", are not rendered when the document is opened, -which means that nothing appears after the "The line ends with a bookmark." string in -"build.odt". -However, bookmarks are listed in Writer's "Navigator" window (press F5), as in +Bookmark supports XNamed, which allows it to be viewed as a named collection of +bookmarks (note the plural). This is useful when searching for a bookmark or adding +one, as in the BuildDoc.java example. It calls Write.addBookmark() to add a +bookmark called "ad-Bookmark" to the document: -![](images/07-Non-text_Content-14.png) +```java +// code fragment from BuildDoc.java +Write.append(cursor, "This line ends with a bookmark"); +Write.addBookmark(cursor, "ad-bookmark"); +``` + +Bookmarks, such as "ad-bookmark", are not rendered when the document is opened, +which means that nothing appears after the "The line ends with a bookmark." string in +"build.odt". -Figure 14. +However, bookmarks are listed in Writer's "Navigator" window (press F5), as in +Figure 14. - ![](images/07-Non-text_Content-14.png) -Figure 14. The Writer Navigator Window. - - -Clicking on the bookmark causes Writer to jump to its location in the document. - - -Using Bookmarks -One programming use of bookmarks is for moving a cursor around a document. Just -as with real-world bookmarks, you can add one at some important location in a -document and jump to that position at a later time. - -Write.findBookmark() finds a bookmark by name, returning it as an XTextContent -instance: - -public static XTextContent findBookmark(XTextDocument doc, - String bmName) -{ XBookmarksSupplier supplier = - Lo.qi(XBookmarksSupplier.class, doc); - if (supplier == null) { - System.out.println("Bookmark supplier could not be created"); - return null; - } - - XNameAccess namedBookmarks = supplier.getBookmarks(); - if (namedBookmarks == null) { - System.out.println("Name access to bookmarks not possible"); - return null; - } - - if (!namedBookmarks.hasElements()) { - System.out.println("No bookmarks found"); - return null; - } - - // find the specified bookmark - Object oBookmark = null; - try { - oBookmark = namedBookmarks.getByName(bmName); - } - catch(com.sun.star.uno.Exception e) {} - - if (oBookmark == null) { - System.out.println("Bookmark \"" + bmName + "\" not found"); - return null; - } - - // there's no XBookmark, so return XTextContent - return Lo.qi(XTextContent.class, oBookmark); -} // end of findBookmark() - -findBookmark() can't return an XBookmark object since there's no such interface (see -Figure 13), but XTextContent is a good alternative. XTextContent has a getAnchor() -method which returns an XTextRange that can be used for positioning a cursor. The -following code fragment from BuildDoc.java illustrates the idea: - -// code fragment from BuildDoc.java -XTextContent bookmark = Write.findBookmark(doc, "ad-bookmark"); -XTextRange bmRange = bookmark.getAnchor(); - -XTextViewCursor viewCursor = Write.getViewCursor(doc); -viewCursor.gotoRange(bmRange, false); - -The call to gotoRange() moves the view cursor to the "ad-bookmark" position, which -causes an on-screen change. gotoRange() can be employed with any type of cursor. - - - +Figure 14. The Writer Navigator Window. + + +Clicking on the bookmark causes Writer to jump to its location in the document. + + +Using Bookmarks +One programming use of bookmarks is for moving a cursor around a document. Just +as with real-world bookmarks, you can add one at some important location in a +document and jump to that position at a later time. + +Write.findBookmark() finds a bookmark by name, returning it as an XTextContent +instance: + +```java +public static XTextContent findBookmark(XTextDocument doc, + String bmName) +{ XBookmarksSupplier supplier = + Lo.qi(XBookmarksSupplier.class, doc); + if (supplier == null) { + System.out.println("Bookmark supplier could not be created"); + return null; + } + + XNameAccess namedBookmarks = supplier.getBookmarks(); + if (namedBookmarks == null) { + System.out.println("Name access to bookmarks not possible"); + return null; + } + + if (!namedBookmarks.hasElements()) { + System.out.println("No bookmarks found"); + return null; + } + + // find the specified bookmark + Object oBookmark = null; + try { + oBookmark = namedBookmarks.getByName(bmName); + } + catch(com.sun.star.uno.Exception e) {} + + if (oBookmark == null) { + System.out.println("Bookmark \"" + bmName + "\" not found"); + return null; + } + + // there's no XBookmark, so return XTextContent + return Lo.qi(XTextContent.class, oBookmark); +} // end of findBookmark() +``` + +findBookmark() can't return an XBookmark object since there's no such interface (see +Figure 13), but XTextContent is a good alternative. XTextContent has a getAnchor() +method which returns an XTextRange that can be used for positioning a cursor. The +following code fragment from BuildDoc.java illustrates the idea: + +```java +// code fragment from BuildDoc.java +XTextContent bookmark = Write.findBookmark(doc, "ad-bookmark"); +XTextRange bmRange = bookmark.getAnchor(); + +XTextViewCursor viewCursor = Write.getViewCursor(doc); +viewCursor.gotoRange(bmRange, false); +``` + +The call to gotoRange() moves the view cursor to the "ad-bookmark" position, which +causes an on-screen change. gotoRange() can be employed with any type of cursor. \ No newline at end of file diff --git a/docs/08-Graphic_Content.md b/docs/08-Graphic_Content.md index 2f5a144..27a433f 100644 --- a/docs/08-Graphic_Content.md +++ b/docs/08-Graphic_Content.md @@ -1,781 +1,827 @@ -# Chapter 8. Graphic Content +# Chapter 8. Graphic Content !!! note "Topics" - Graphics; - Linked Images/Shapes - - Example folders: "Text - Tests" and "Utils" - -Chapter 7 looked at several forms of text document -content (e.g. text frames, math formulae, text fields and -tables, and bookmarks), as indicated by Figure 1. However -the different ways of adding graphical content (corresponding to the services -highlighted in orange) are the focus of this chapter - - + Graphics; + Linked Images/Shapes + + Example folders: "Text + Tests" and "Utils" + +Chapter 7 looked at several forms of text document +content (e.g. text frames, math formulae, text fields and +tables, and bookmarks), as indicated by Figure 1. However +the different ways of adding graphical content (corresponding to the services +highlighted in orange) are the focus of this chapter + ![](images/08-Graphic_Content-1.png) -Figure 1. The TextContent Service and Some Subclasses. - - - -## 1. Linking a Graphic Object to a Document - -Adding an image to a text document follows the same steps as other text content, as -shown in Write.addImageLink(): - -public static void addImageLink(XTextDocument doc, - XTextCursor cursor, String fnm) -{ addImageLink(doc, cursor, fnm, 0, 0); } - // 0, 0 means use image's size as width & height - - -public static void addImageLink(XTextDocument doc, - XTextCursor cursor, String fnm, - int width, int height) -{ try { - - // create TextContent for graphic - XTextContent tgo = Lo.createInstanceMSF(XTextContent.class, - "com.sun.star.text.TextGraphicObject"); - if (tgo == null) { - System.out.println("Could not create a text graphic object"); - return; - } - - // set anchor and URL properties - XPropertySet props = Lo.qi( XPropertySet.class, tgo); - props.setPropertyValue("AnchorType", - TextContentAnchorType.AS_CHARACTER); - props.setPropertyValue("GraphicURL", FileIO.fnmToURL(fnm)); - - // optionally set the width and height - if ((width > 0) && (height > 0)) { - props.setPropertyValue("Width", width); - props.setPropertyValue("Height", height); - } - - // append image to document, followed by a newline - append(cursor, tgo); - endLine(cursor); - } - catch (Exception e) { - System.out.println("Insert of \"" + fnm + "\" failed: " + e); - } -} // end of addImageLink() - -The TextGraphicObject service doesn't offer a XTextGraphicObject interface, so -Lo.createInstanceMSF() returns an XTextContext. - -The interface is also converted to XPropertySet because several properties need to be -set. The frame is anchored, and the image's filename is assigned to "GraphicURL" -(after being changed into a URL). - -The image's size on the page depends on the dimensions of its enclosing frame, which -are set in the "Width" and "Height" properties: - -props.setPropertyValue("Width", 4500); // 45 mm width -props.setPropertyValue("Height", 4000); // 40 mm height - -The values are in 1/100 mm units, so 4500 is 45 mm or 4.5 cm. - -If these properties aren't explicitly set then the frame size defaults to being the width -and height of the image. - -In more realistic code, the width and height properties would be calculated as some -scale factor of the image's size, as measured in 1/100 mm units not pixels. These -dimensions are available if the image file is loaded as an XGraphic object, as shown -in Images.getSize100mm(): - -// in the Images class -public static Size getSize100mm(String imFnm) -{ - XGraphic graphic = loadGraphicFile(imFnm); - if (graphic == null) - return null; - return (Size) Props.getProperty(graphic, "Size100thMM"); -} // end of getSize100mm() - - -public static XGraphic loadGraphicFile(String imFnm) -{ - // create graphics provider - XGraphicProvider gProvider = Lo.createInstanceMCF( - XGraphicProvider.class, - "com.sun.star.graphic.GraphicProvider"); - if (gProvider == null) { - System.out.println("Graphic Provider could not be found"); - return null; - } - - // set URL property and query it for XGraphic - PropertyValue[] fileProps = - Props.makeProps("URL", FileIO.fnmToURL(imFnm)); - try { - return gProvider.queryGraphic(fileProps); - } - catch(Exception e) - { System.out.println("Could not load XGraphic from " + imFnm); - return null; - } -} // end of loadGraphicFile() - -Displaying the image at a scaled size is possible by combining -Images.getSize100mm() and Write.addImageLink(): - -Size imSize = Images.getSize100mm(imFnm); -int w = (int)Math.round(imSize.Width*1.5); // enlarge by 1.5x -int h = (int)Math.round(imSize.Height*1.5); -Write.addImageLink(doc, cursor, imFnm, w, h); - -A possible drawback of Write.addImageLink() is that the document only contains a -link to the image. This becomes an issue if you save the document in a format other -than ".odt". In particular, when saved as a Word ".doc" file, the link is lost. - - - -## 2. Adding a Graphic to a Document as a Shape - -An alternative to inserting a graphic as a link is to treat it as a shape. Shapes will be -discussed at length in Part 3, so I won't go into much detail about them here. One -difference between a graphic link and shape is that shapes can be rotated. - -Shapes can be created using the com.sun.star.text.Shape service, -com.sun.star.drawing.Shape, or one of its subclasses, while -XDrawPageSupplier.getDrawPage() accesses the shapes in a document. - -The shape hierarchy is quite extensive (i.e. there are many kinds of shape), so only the -parts used here are shown in Figure 2: - - +Figure 1. The TextContent Service and Some Subclasses. + + +## 1. Linking a Graphic Object to a Document + +Adding an image to a text document follows the same steps as other text content, as +shown in Write.addImageLink(): + +```java +public static void addImageLink(XTextDocument doc, + XTextCursor cursor, String fnm) +{ addImageLink(doc, cursor, fnm, 0, 0); } + // 0, 0 means use image's size as width & height + + +public static void addImageLink(XTextDocument doc, + XTextCursor cursor, String fnm, + int width, int height) +{ try { + + // create TextContent for graphic + XTextContent tgo = Lo.createInstanceMSF(XTextContent.class, + "com.sun.star.text.TextGraphicObject"); + if (tgo == null) { + System.out.println("Could not create a text graphic object"); + return; + } + + // set anchor and URL properties + XPropertySet props = Lo.qi( XPropertySet.class, tgo); + props.setPropertyValue("AnchorType", + TextContentAnchorType.AS_CHARACTER); + props.setPropertyValue("GraphicURL", FileIO.fnmToURL(fnm)); + + // optionally set the width and height + if ((width > 0) && (height > 0)) { + props.setPropertyValue("Width", width); + props.setPropertyValue("Height", height); + } + + // append image to document, followed by a newline + append(cursor, tgo); + endLine(cursor); + } + catch (Exception e) { + System.out.println("Insert of \"" + fnm + "\" failed: " + e); + } +} // end of addImageLink() +``` + +The TextGraphicObject service doesn't offer a XTextGraphicObject interface, so +Lo.createInstanceMSF() returns an XTextContext. + +The interface is also converted to XPropertySet because several properties need to be +set. The frame is anchored, and the image's filename is assigned to "GraphicURL" +(after being changed into a URL). + +The image's size on the page depends on the dimensions of its enclosing frame, which +are set in the "Width" and "Height" properties: + +```java +props.setPropertyValue("Width", 4500); // 45 mm width +props.setPropertyValue("Height", 4000); // 40 mm height +``` + +The values are in 1/100 mm units, so 4500 is 45 mm or 4.5 cm. + +If these properties aren't explicitly set then the frame size defaults to being the width +and height of the image. + +In more realistic code, the width and height properties would be calculated as some +scale factor of the image's size, as measured in 1/100 mm units not pixels. These +dimensions are available if the image file is loaded as an XGraphic object, as shown +in Images.getSize100mm(): + +```java +// in the Images class +public static Size getSize100mm(String imFnm) +{ + XGraphic graphic = loadGraphicFile(imFnm); + if (graphic == null) + return null; + return (Size) Props.getProperty(graphic, "Size100thMM"); +} // end of getSize100mm() + + +public static XGraphic loadGraphicFile(String imFnm) +{ + // create graphics provider + XGraphicProvider gProvider = Lo.createInstanceMCF( + XGraphicProvider.class, + "com.sun.star.graphic.GraphicProvider"); + if (gProvider == null) { + System.out.println("Graphic Provider could not be found"); + return null; + } + + // set URL property and query it for XGraphic + PropertyValue[] fileProps = + Props.makeProps("URL", FileIO.fnmToURL(imFnm)); + try { + return gProvider.queryGraphic(fileProps); + } + catch(Exception e) + { System.out.println("Could not load XGraphic from " + imFnm); + return null; + } +} // end of loadGraphicFile() +``` + +Displaying the image at a scaled size is possible by combining +Images.getSize100mm() and Write.addImageLink(): + +```java +Size imSize = Images.getSize100mm(imFnm); +int w = (int)Math.round(imSize.Width*1.5); // enlarge by 1.5x +int h = (int)Math.round(imSize.Height*1.5); +Write.addImageLink(doc, cursor, imFnm, w, h); +``` + +A possible drawback of Write.addImageLink() is that the document only contains a +link to the image. This becomes an issue if you save the document in a format other +than ".odt". In particular, when saved as a Word ".doc" file, the link is lost. + + +## 2. Adding a Graphic to a Document as a Shape + +An alternative to inserting a graphic as a link is to treat it as a shape. Shapes will be +discussed at length in Part 3, so I won't go into much detail about them here. One +difference between a graphic link and shape is that shapes can be rotated. + +Shapes can be created using the com.sun.star.text.Shape service, +com.sun.star.drawing.Shape, or one of its subclasses, while +XDrawPageSupplier.getDrawPage() accesses the shapes in a document. + +The shape hierarchy is quite extensive (i.e. there are many kinds of shape), so only the +parts used here are shown in Figure 2: + ![](images/08-Graphic_Content-2.png) -Figure 2. Part of the Shape Hierarchy. - - -In Figure 2, "(text) Shape" refers to the com.sun.star.text.Shape service, while -"(drawing) Shape" is com.sun.star.drawing.Shape. - -My examples use GraphicObjectShape to create a shape containing an image, and -LineShape to add a line to the document. - -The XShapeDescriptor interface in com.sun.star.drawing.Shape is a useful way to -obtain the name of a shape service. - - -### 2.1. Creating an Image Shape - -The BuildDoc.java example adds an image shape to the document by calling -Write.addImageShape(): - -// code fragment in BuildDoc.java -Write.append(cursor, "Image as a shape: "); -Write.addImageShape(doc, cursor, imFnm); // image filename -Write.endParagraph(cursor); - -Write.addImageShape() comes in two versions: with and without width and height -arguments. A shape with no explicitly set width and height properties is rendered as a -miniscule image (about 1 mm wide). Call me old-fashioned, but I want to see the -graphic, so Write.addImageShape() calculates the picture's dimensions if none are -supplied by the user. - -Another difference between image shape and image link is how the content's -"GraphicURL" property is employed. The image link version contains its URL, while -the image shape's "GraphicURL" stores its bitmap as a string. - -The code for Write.addImageShape(): - -public static void addImageShape(XTextDocument doc, - XTextCursor cursor, String fnm) -{ addImageShape(doc, cursor, fnm, 0, 0); } - /* 0, 0 means that the method must calculate - the image's size */ - - -public static void addImageShape(XTextDocument doc, - XTextCursor cursor, String fnm, int width, int height) -{ - Size imSize; - if ((width > 0) && (height > 0)) - imSize = new Size(width, height); - else { - imSize = Images.getSize100mm(fnm); - if (imSize == null) - return; - } - - try { - // create TextContent for the graphic shape - XTextContent gos = Lo.createInstanceMSF( - XTextContent.class, - "com.sun.star.drawing.GraphicObjectShape"); - if (gos == null) { - System.out.println("Could not create a graphic shape"); - return; - } - - // store the image's bitmap in the "GraphicURL" property - String bitmap = Images.getBitmap(fnm); - Props.setProperty(gos, "GraphicURL", bitmap); - - // set the shape's size - XShape xDrawShape = Lo.qi(XShape.class, gos); - xDrawShape.setSize(imSize); // must be set, or image is tiny - - // insert image shape into the document, followed by newline - append(cursor, gos); - endLine(cursor); - } - catch(Exception e) { - System.out.println("Insert of \"" + fnm + "\" failed: " + e); - } -} // end of addImageShape() - -The image's size is calculated using Images.getSize100mm() if the user doesn't supply -a width and height, and is used towards the end of the method. - -An image shape is created using the GraphicObjectShape service, and its -XTextContent interface is converted to XPropertySet for assigning its properties, and -to XShape for setting its size (see Figure 2). XShape includes a setSize() method. - -Images.getBitmap() converts the graphic into a bitmap string. - - -// in the Images utility class -public static String getBitmap(String fnm) -{ - try { - // create a BitmapTable service and named container - XNameContainer bitmapContainer = - Lo.createInstanceMSF(XNameContainer.class, - "com.sun.star.drawing.BitmapTable"); - - // insert image into container - if (!FileIO.isOpenable(fnm)) - return null; - - String picURL = FileIO.fnmToURL(fnm); - if (picURL == null) - return null; - - bitmapContainer.insertByName(fnm, picURL); - // use the filename as the name of the bitmap - - // return the bitmap as a string - return (String) bitmapContainer.getByName(fnm); - } - catch(Exception e) - { System.out.println("Could not create bitmap for " + fnm); - return null; - } -} // end of getBitmap() - -getBitmap() uses a BitmapTable service to store the image's file in an -XNameContainer. When the bitmap value is retrieved from the container, it can be -cast to a string. - -A named container requires a name be assigned to the bitmap value; I use the image's -filename, but any unique string would do. - - -### 2.2. Adding Other Graphics to the Document - -The graphic text content can be any subclass of Shape. In the last section I created a -GraphicObjectShape service, and accessed its XTextContent interface: - -XTextContent gos = Lo.createInstanceMSF(XTextContent.class, - "com.sun.star.drawing.GraphicObjectShape"); - -In this section I'll use LineShape: - -XTextContent ls = Lo.createInstanceMSF(XTextContent.class, - "com.sun.star.drawing.LineShape"); - -The aim is to draw an horizontal line in the document, to act as a divider between -paragraphs. The line will be half-a-page wide and centered, like the one in Figure 3 - +Figure 2. Part of the Shape Hierarchy. + + +In Figure 2, "(text) Shape" refers to the com.sun.star.text.Shape service, while +"(drawing) Shape" is com.sun.star.drawing.Shape. + +My examples use GraphicObjectShape to create a shape containing an image, and +LineShape to add a line to the document. + +The XShapeDescriptor interface in com.sun.star.drawing.Shape is a useful way to +obtain the name of a shape service. + + +### 2.1. Creating an Image Shape + +The BuildDoc.java example adds an image shape to the document by calling +Write.addImageShape(): + +```java +// code fragment in BuildDoc.java +Write.append(cursor, "Image as a shape: "); +Write.addImageShape(doc, cursor, imFnm); // image filename +Write.endParagraph(cursor); +``` + +Write.addImageShape() comes in two versions: with and without width and height +arguments. A shape with no explicitly set width and height properties is rendered as a +miniscule image (about 1 mm wide). Call me old-fashioned, but I want to see the +graphic, so Write.addImageShape() calculates the picture's dimensions if none are +supplied by the user. + +Another difference between image shape and image link is how the content's +"GraphicURL" property is employed. The image link version contains its URL, while +the image shape's "GraphicURL" stores its bitmap as a string. + +The code for Write.addImageShape(): + +```java +public static void addImageShape(XTextDocument doc, + XTextCursor cursor, String fnm) +{ addImageShape(doc, cursor, fnm, 0, 0); } + /* 0, 0 means that the method must calculate + the image's size */ + + +public static void addImageShape(XTextDocument doc, + XTextCursor cursor, String fnm, int width, int height) +{ + Size imSize; + if ((width > 0) && (height > 0)) + imSize = new Size(width, height); + else { + imSize = Images.getSize100mm(fnm); + if (imSize == null) + return; + } + + try { + // create TextContent for the graphic shape + XTextContent gos = Lo.createInstanceMSF( + XTextContent.class, + "com.sun.star.drawing.GraphicObjectShape"); + if (gos == null) { + System.out.println("Could not create a graphic shape"); + return; + } + + // store the image's bitmap in the "GraphicURL" property + String bitmap = Images.getBitmap(fnm); + Props.setProperty(gos, "GraphicURL", bitmap); + + // set the shape's size + XShape xDrawShape = Lo.qi(XShape.class, gos); + xDrawShape.setSize(imSize); // must be set, or image is tiny + + // insert image shape into the document, followed by newline + append(cursor, gos); + endLine(cursor); + } + catch(Exception e) { + System.out.println("Insert of \"" + fnm + "\" failed: " + e); + } +} // end of addImageShape() +``` + +The image's size is calculated using Images.getSize100mm() if the user doesn't supply +a width and height, and is used towards the end of the method. + +An image shape is created using the GraphicObjectShape service, and its +XTextContent interface is converted to XPropertySet for assigning its properties, and +to XShape for setting its size (see Figure 2). XShape includes a setSize() method. + +Images.getBitmap() converts the graphic into a bitmap string. + +```java +// in the Images utility class +public static String getBitmap(String fnm) +{ + try { + // create a BitmapTable service and named container + XNameContainer bitmapContainer = + Lo.createInstanceMSF(XNameContainer.class, + "com.sun.star.drawing.BitmapTable"); + + // insert image into container + if (!FileIO.isOpenable(fnm)) + return null; + + String picURL = FileIO.fnmToURL(fnm); + if (picURL == null) + return null; + + bitmapContainer.insertByName(fnm, picURL); + // use the filename as the name of the bitmap + + // return the bitmap as a string + return (String) bitmapContainer.getByName(fnm); + } + catch(Exception e) + { System.out.println("Could not create bitmap for " + fnm); + return null; + } +} // end of getBitmap() +``` + +getBitmap() uses a BitmapTable service to store the image's file in an +XNameContainer. When the bitmap value is retrieved from the container, it can be +cast to a string. + +A named container requires a name be assigned to the bitmap value; I use the image's +filename, but any unique string would do. + + +### 2.2. Adding Other Graphics to the Document + +The graphic text content can be any subclass of Shape. In the last section I created a +GraphicObjectShape service, and accessed its XTextContent interface: + +```java +XTextContent gos = Lo.createInstanceMSF(XTextContent.class, + "com.sun.star.drawing.GraphicObjectShape"); +``` + +In this section I'll use LineShape: + +```java +XTextContent ls = Lo.createInstanceMSF(XTextContent.class, + "com.sun.star.drawing.LineShape"); +``` + +The aim is to draw an horizontal line in the document, to act as a divider between +paragraphs. The line will be half-a-page wide and centered, like the one in Figure 3 + ![](images/08-Graphic_Content-3.png) -Figure 3. A Graphical Line Divider. - - -The difficult part is calculating the width of the line, which should only extend across -half the writing width. This isn't the same as the page width because it doesn't include -the left and right margins. - -The page and margin dimensions are accessible through the "Standard" page style, as -implemented in Write.getPageTextWidth(): - -public static int getPageTextWidth(XTextDocument textDoc) -// get the page's writing width -{ - // access the "standard" style in the pages style family - XPropertySet props = - Info.getStyleProps(textDoc, "PageStyles", "Standard"); - if (props == null) { - System.out.println("Could not access the standard page style"); - return 0; - } - - // lookup the page and margin widths - try { - int width = ((Integer)props.getPropertyValue( - "Width")).intValue(); - int leftMargin = ((Integer)props.getPropertyValue( - "LeftMargin")).intValue(); - int rightMargin = ((Integer)props.getPropertyValue( - "RightMargin")).intValue(); - - return (width - (leftMargin + rightMargin)); // writing width - } - catch (Exception e) - { System.out.println("Could not access page dimensions: " + e); - return 0; - } -} // end of getPageTextWidth() - -getPageTextWidth() returns the writing width in 1/100 mm units, which is scaled, -then passed to Write.addLineDivider(): - -// code fragment in BuildDoc,java -int textWidth = Write.getPageTextWidth(doc); -Write.addLineDivider(cursor, (int)Math.round(textWidth*0.5) ); - // scale width by 0.5 - -addLineDivider() creates a LineShape service with an XTextContent interface (see -Figure 2). This is converted to XShape so its setSize() method can be passed the line -width: - -public static void addLineDivider(XTextCursor cursor, - int lineWidth) -// in the Writer class -{ try { - // create TextContent for a line - XTextContent ls = Lo.createInstanceMSF( - XTextContent.class, - "com.sun.star.drawing.LineShape"); - if (ls == null) { - System.out.println("Could not create a line shape"); - return; - } - - // set line size - XShape lineShape = Lo.qi(XShape.class, ls); - lineShape.setSize( new Size(lineWidth, 0)); - - endParagraph(cursor); - append(cursor, ls); // put line in its own paragraph - endParagraph(cursor); - - // center the line paragraph - stylePrevParagraph(cursor, "ParaAdjust", - com.sun.star.style.ParagraphAdjust.CENTER); - - endParagraph(cursor); - } - catch(Exception e) - { System.out.println("Insertion of graphic line failed"); } -} // end of addLineDivider() - -The centering of the line is achieved by placing the shape in its own paragraph, then -using Write.stylePrevParagraph() to center it. - - - -## 3. Accessing Linked Images and Shapes - -The outcome of running BuildDoc.java is a "build.odt" file containing four graphics – -two are linked images, one is an image shape, and the other a line shape. - -The ExtractGraphics.java example extracts linked graphics from a document, saving -them as PNG files. It also prints information about the shapes in the document. - -When ExtractGraphics is passed "build.odt", the following is output: - -Num. of text graphics: 2 -Saving graphic in graphics1.png -Image size in pixels: 319 x 274 -Saving graphic in graphics2.png -Image size in pixels: 319 x 274 - -Num. of draw shapes: 3 - Shape service: FrameShape; z-order: 0 - Shape service: com.sun.star.drawing.GraphicObjectShape; z-order: 1 - Shape service: com.sun.star.drawing.LineShape; z-order: 2 - -A user who looked at "build.odt" for themselves might say that it contains three -images not the two reported by ExtractGraphics. Why the discrepancy? -ExtractGraphics.java only saves linked graphics, and only two were added by -Write.addImageLink(). The other image was inserted using Write.addImageShape() -which creates an image shape. - -The number of shapes reported by ExtractGraphics.java may also confuse the user – -why are there three rather than two? The only shapes added to the document were an -image and a line. - -The names of the services gives a clue: the second and third shapes are the expected -GraphicObjectShape and LineShape, but the first is a text frame (FrameShape) added -by Write.addTextFrame(). Although this frame is an instance of the TextFrame -service, it's reported as a FrameShape. That's a bit mysterious because there's no -FrameShape service in the Office documentation. - -The main() function of ExtractGraphics.java: - -public static void main(String[] args) -{ - XComponentLoader loader = Lo.loadOffice(); - - XTextDocument textDoc = Write.openDoc(args[0], loader); - if (textDoc == null) { - System.out.println("Could not open " + args[0]); - Lo.closeOffice(); - return; - } - - // save text graphics to files - ArrayList pics = Write.getTextGraphics(textDoc); - if (pics == null) - return; - System.out.println("\nNum. of text graphics: " + pics.size()); - int i = 1; - for(XGraphic pic : pics) { - Images.saveGraphic(pic, "graphics" + i + ".png", "png"); - Size sz = (Size) Props.getProperty(pic, "SizePixel"); - System.out.println("Image size in pixels: " + - sz.Width + " x " + sz.Height); - i++; - } - System.out.println(); - - // report on shapes in the document - XDrawPage drawPage = Write.getShapes(textDoc); - ArrayList shapes = Draw.getShapes(drawPage); - if (shapes != null) - System.out.println("\nNum. of draw shapes: " + shapes.size()); - for(XShape shape : shapes) - Draw.printShapeInfo(shape); - System.out.println(); - - Lo.closeDoc(textDoc); - Lo.closeOffice(); -} // end of main() - -The first block of code saves text graphic objects as PNGs, and the second reports on -shapes. - - -### 3.1. Finding and Saving Text Graphics in a Document - -Write.getTextGraphics() returns a list of XGraphic objects: first it retrieves a -collection of the graphic links in the document, then iterates through them creating an -XGraphic object for each one: - -// in the Write class -public static ArrayList getTextGraphics( - XTextDocument textDoc) -{ // get all the graphic links - XNameAccess xNameAccess = getGraphicLinks(textDoc); - if (xNameAccess == null) - return null; - String[] names = xNameAccess.getElementNames(); - System.out.println("Number of graphics found: " + names.length); - - // build a list of XGraphic objects, one for each link - ArrayList pics = new ArrayList(); - for (int i = 0; i < names.length; i++) { - Object graphicLink = null; - try { - graphicLink = xNameAccess.getByName(names[i]); - } - catch(com.sun.star.uno.Exception e) {} - - if (graphicLink == null) - System.out.println("No graphic found for " + names[i]); - else { - XGraphic xGraphic = Images.loadGraphicLink(graphicLink); - if (xGraphic != null) - pics.add(xGraphic); - else - System.out.println(names[i] + " could not be accessed"); - } - } - return pics; -} // end of getTextGraphics() - -Graphic objects are accessed with XTextGraphicObjectsSupplier, as implemented by -Write.getGraphicLinks(): - -// in the Write class -public static XNameAccess getGraphicLinks(XComponent doc) -{ - XTextGraphicObjectsSupplier imsSupplier = - Lo.qi(XTextGraphicObjectsSupplier.class, doc); - if (imsSupplier == null) { - System.out.println("Supplier could not be created"); - return null; - } - - XNameAccess xNameAccess = imsSupplier.getGraphicObjects(); - if (xNameAccess == null) { - System.out.println("Name access to graphics not possible"); - return null; - } - - if (!xNameAccess.hasElements()) { - System.out.println("No graphics elements found"); - return null; - } - return xNameAccess; -} // end of getGraphicLinks() - -Back in Write.getTextGraphics, each graphic is loaded by calling -Images.loadGraphicLink(). It loads an image from the URL associated with a link: - -// in the Images class -public static XGraphic loadGraphicLink(Object graphicLink) -{ - // create graphic provider - XGraphicProvider gProvider = Lo.createInstanceMCF( - XGraphicProvider.class, - "com.sun.star.graphic.GraphicProvider"); - if (gProvider == null) { - System.out.println("Graphic Provider could not be found"); - return null; - } - - // set up URL property then load as an XGraphic - try { - XPropertySet xprops = Lo.qi(XPropertySet.class, graphicLink); - PropertyValue[] gProps = Props.makeProps("URL", - (String) xprops.getPropertyValue("GraphicURL") ); - return gProvider.queryGraphic(gProps); - } - catch(Exception e) - { System.out.println("Unable to retrieve graphic"); - return null; - } -} // end of loadGraphicLink() - -Note that the XGraphic is not extracted from the document but loaded from a URL. - -Back in ExtractGraphics.java, the XGraphic objects are saved as PNG files, and their -pixel sizes reported: - -private static void savePics(XTextDocument textDoc) -// method in ExtractGraphics.java -{ - ArrayList pics = Write.getTextGraphics(textDoc); - if (pics == null) - return; - System.out.println("No. of graphic retrieved: " + pics.size()); - int i = 1; - for(XGraphic pic : pics) { - Images.saveGraphic(pic, "graphics" + i + ".png", "png"); - Size sz = (Size) Props.getProperty(pic, "SizePixel"); - System.out.println("Image Pixel size: " + - sz.Width + " x " + sz.Height); - i++; - } -} // end of savePics() - -Images.saveGraphic() utilizes the graphic provider's XGraphicProvider.storeGraphic() -method: - -// in XGraphicProvider -void storeGraphic(XGraphic g, PropertyValue[] props) -{ ... } - -Its second argument is an array of PropertyValue objects, not a PropertySet. My -Props.java utility class provides several functions for creating PropertyValue -instances, which are a variant of the {name=value} pair idea, but with extra data -fields. One such function is: - -// in Props.java -PropertyValue[] makeProps(String[] nms, Object[] vals) -{ ... } - -It's passed an array of names and values, which are paired up as PropertyValue -objects, and returned in an array. - -In saveGraphic(), these methods are used like so: - -XGraphicProvider gProvider = Lo.createInstanceMCF( - XGraphicProvider.class, - "com.sun.star.graphic.GraphicProvider"); - -// set up properties for storing the graphic -PropertyValue[] props = Props.makeProps( - "URL", FileIO.fnmToURL(fnm), - "MimeType", "image/" + imFormat); - -gProvider.storeGraphic(pic, props); - -The idea is to call XGraphicProvider.saveGraphics() with the "URL" and -"MimeType" properties set – the URL is for the image file, and the mimetype is an -image type (e.g. "image/png"). - -saveGraphic() is coded as: - -public static void saveGraphic(XGraphic pic, - String fnm, String imFormat) -{ // create graphic provider - XGraphicProvider gProvider = Lo.createInstanceMCF( - XGraphicProvider.class, - "com.sun.star.graphic.GraphicProvider"); - if (gProvider == null) { - System.out.println("Graphic Provider could not be found"); - return; - } - - if (pic == null) { - System.out.println("Supplied image is null"); - return; - } - - // set up properties for storing the graphic - PropertyValue[] pngProps = Props.makeProps( - "URL", FileIO.fnmToURL(fnm), - "MimeType", "image/" + imFormat); - try { - gProvider.storeGraphic(pic, pngProps); - } - catch(com.sun.star.uno.Exception e) - { System.out.println("Unable to save graphic"); } -} // end of saveGraphic() - -Other possible image MIME types include "gif", "jpeg", "wmf", and "bmp". For -instance, this call will save the image as a GIF: -Images.saveGraphic(pic, "graphics" + i + ".gif", "gif"); -The printed output from saveGraphic() contains another surprise: - -Num. of text graphics: 2 -Saving graphic in graphics1.png -Image size in pixels: 319 x 274 -Saving graphic in graphics2.png -Image size in pixels: 319 x 274 - -The two saved graphics are the same size, but the second image is bigger inside the -document. The discrepancy is because the rendering of the image in the document is -bigger, scaled up to fit the enclosing frame; the original image is unchanged. - - -### 3.2. Finding the Shapes in a Document - -The second block of code in ExtractGraphics reports on the shapes found in the -document. The relevant code fragment is: - -// code fragment from ExtractGraphics.java -XDrawPage drawPage = Write.getShapes(textDoc); -ArrayList shapes = Draw.getShapes(drawPage); -if (shapes != null) - System.out.println("\nNum. of draw shapes: " + shapes.size()); -for(XShape shape : shapes) - Draw.printShapeInfo(shape); - -Shapes are accessed with the XDrawPageSupplier.getDrawPage() method, which -returns a single XDrawPage: - -public static XDrawPage getShapes(XTextDocument textDoc) -{ - XDrawPageSupplier drawPageSupplier = Lo.qi( - XDrawPageSupplier.class, textDoc); - if (drawPageSupplier == null) { - System.out.println("Draw page supplier could not be created"); - return null; - } - return drawPageSupplier.getDrawPage(); -} // end of getShapes() - -XDrawPage's usual role is to represent the canvas in Office's Draw, or a slide in -Impress, and so plays an important role in Part 3. Several support functions inside that -part's Draw utility class will be used here. - -XDrawPageSupplier.getDrawPage() returns a single XDrawPage for the entire text -document. That doesn't mean that the shapes all have to occur on a single text page, -but rather that all the shapes spread across multiple text pages are collected into a -single draw page. - -XDrawPage inherits from XShapes and XindexAccess, as shown in Figure 4, which -means that a page can be viewed as a indexed collection of shapes. - - +Figure 3. A Graphical Line Divider. + + +The difficult part is calculating the width of the line, which should only extend across +half the writing width. This isn't the same as the page width because it doesn't include +the left and right margins. + +The page and margin dimensions are accessible through the "Standard" page style, as +implemented in Write.getPageTextWidth(): + +```java +public static int getPageTextWidth(XTextDocument textDoc) +// get the page's writing width +{ + // access the "standard" style in the pages style family + XPropertySet props = + Info.getStyleProps(textDoc, "PageStyles", "Standard"); + if (props == null) { + System.out.println("Could not access the standard page style"); + return 0; + } + + // lookup the page and margin widths + try { + int width = ((Integer)props.getPropertyValue( + "Width")).intValue(); + int leftMargin = ((Integer)props.getPropertyValue( + "LeftMargin")).intValue(); + int rightMargin = ((Integer)props.getPropertyValue( + "RightMargin")).intValue(); + + return (width - (leftMargin + rightMargin)); // writing width + } + catch (Exception e) + { System.out.println("Could not access page dimensions: " + e); + return 0; + } +} // end of getPageTextWidth() +``` + +getPageTextWidth() returns the writing width in 1/100 mm units, which is scaled, +then passed to Write.addLineDivider(): + +```java +// code fragment in BuildDoc,java +int textWidth = Write.getPageTextWidth(doc); +Write.addLineDivider(cursor, (int)Math.round(textWidth*0.5) ); + // scale width by 0.5 +``` + +addLineDivider() creates a LineShape service with an XTextContent interface (see +Figure 2). This is converted to XShape so its setSize() method can be passed the line +width: + +```java +public static void addLineDivider(XTextCursor cursor, + int lineWidth) +// in the Writer class +{ try { + // create TextContent for a line + XTextContent ls = Lo.createInstanceMSF( + XTextContent.class, + "com.sun.star.drawing.LineShape"); + if (ls == null) { + System.out.println("Could not create a line shape"); + return; + } + + // set line size + XShape lineShape = Lo.qi(XShape.class, ls); + lineShape.setSize( new Size(lineWidth, 0)); + + endParagraph(cursor); + append(cursor, ls); // put line in its own paragraph + endParagraph(cursor); + + // center the line paragraph + stylePrevParagraph(cursor, "ParaAdjust", + com.sun.star.style.ParagraphAdjust.CENTER); + + endParagraph(cursor); + } + catch(Exception e) + { System.out.println("Insertion of graphic line failed"); } +} // end of addLineDivider() +``` + +The centering of the line is achieved by placing the shape in its own paragraph, then +using Write.stylePrevParagraph() to center it. + + +## 3. Accessing Linked Images and Shapes + +The outcome of running BuildDoc.java is a "build.odt" file containing four graphics – +two are linked images, one is an image shape, and the other a line shape. + +The ExtractGraphics.java example extracts linked graphics from a document, saving +them as PNG files. It also prints information about the shapes in the document. + +When ExtractGraphics is passed "build.odt", the following is output: + +``` +Num. of text graphics: 2 +Saving graphic in graphics1.png +Image size in pixels: 319 x 274 +Saving graphic in graphics2.png +Image size in pixels: 319 x 274 + +Num. of draw shapes: 3 + Shape service: FrameShape; z-order: 0 + Shape service: com.sun.star.drawing.GraphicObjectShape; z-order: 1 + Shape service: com.sun.star.drawing.LineShape; z-order: 2 +``` + +A user who looked at "build.odt" for themselves might say that it contains three +images not the two reported by ExtractGraphics. Why the discrepancy? +ExtractGraphics.java only saves linked graphics, and only two were added by +Write.addImageLink(). The other image was inserted using Write.addImageShape() +which creates an image shape. + +The number of shapes reported by ExtractGraphics.java may also confuse the user – +why are there three rather than two? The only shapes added to the document were an +image and a line. + +The names of the services gives a clue: the second and third shapes are the expected +GraphicObjectShape and LineShape, but the first is a text frame (FrameShape) added +by Write.addTextFrame(). Although this frame is an instance of the TextFrame +service, it's reported as a FrameShape. That's a bit mysterious because there's no +FrameShape service in the Office documentation. + +The main() function of ExtractGraphics.java: + +```java +public static void main(String[] args) +{ + XComponentLoader loader = Lo.loadOffice(); + + XTextDocument textDoc = Write.openDoc(args[0], loader); + if (textDoc == null) { + System.out.println("Could not open " + args[0]); + Lo.closeOffice(); + return; + } + + // save text graphics to files + ArrayList pics = Write.getTextGraphics(textDoc); + if (pics == null) + return; + System.out.println("\nNum. of text graphics: " + pics.size()); + int i = 1; + for(XGraphic pic : pics) { + Images.saveGraphic(pic, "graphics" + i + ".png", "png"); + Size sz = (Size) Props.getProperty(pic, "SizePixel"); + System.out.println("Image size in pixels: " + + sz.Width + " x " + sz.Height); + i++; + } + System.out.println(); + + // report on shapes in the document + XDrawPage drawPage = Write.getShapes(textDoc); + ArrayList shapes = Draw.getShapes(drawPage); + if (shapes != null) + System.out.println("\nNum. of draw shapes: " + shapes.size()); + for(XShape shape : shapes) + Draw.printShapeInfo(shape); + System.out.println(); + + Lo.closeDoc(textDoc); + Lo.closeOffice(); +} // end of main() +``` + +The first block of code saves text graphic objects as PNGs, and the second reports on +shapes. + + +### 3.1. Finding and Saving Text Graphics in a Document + +Write.getTextGraphics() returns a list of XGraphic objects: first it retrieves a +collection of the graphic links in the document, then iterates through them creating an +XGraphic object for each one: + +```java +// in the Write class +public static ArrayList getTextGraphics( + XTextDocument textDoc) +{ // get all the graphic links + XNameAccess xNameAccess = getGraphicLinks(textDoc); + if (xNameAccess == null) + return null; + String[] names = xNameAccess.getElementNames(); + System.out.println("Number of graphics found: " + names.length); + + // build a list of XGraphic objects, one for each link + ArrayList pics = new ArrayList(); + for (int i = 0; i < names.length; i++) { + Object graphicLink = null; + try { + graphicLink = xNameAccess.getByName(names[i]); + } + catch(com.sun.star.uno.Exception e) {} + + if (graphicLink == null) + System.out.println("No graphic found for " + names[i]); + else { + XGraphic xGraphic = Images.loadGraphicLink(graphicLink); + if (xGraphic != null) + pics.add(xGraphic); + else + System.out.println(names[i] + " could not be accessed"); + } + } + return pics; +} // end of getTextGraphics() +``` + +Graphic objects are accessed with XTextGraphicObjectsSupplier, as implemented by +Write.getGraphicLinks(): + +```java +// in the Write class +public static XNameAccess getGraphicLinks(XComponent doc) +{ + XTextGraphicObjectsSupplier imsSupplier = + Lo.qi(XTextGraphicObjectsSupplier.class, doc); + if (imsSupplier == null) { + System.out.println("Supplier could not be created"); + return null; + } + + XNameAccess xNameAccess = imsSupplier.getGraphicObjects(); + if (xNameAccess == null) { + System.out.println("Name access to graphics not possible"); + return null; + } + + if (!xNameAccess.hasElements()) { + System.out.println("No graphics elements found"); + return null; + } + return xNameAccess; +} // end of getGraphicLinks() +``` + +Back in Write.getTextGraphics, each graphic is loaded by calling +Images.loadGraphicLink(). It loads an image from the URL associated with a link: + +```java +// in the Images class +public static XGraphic loadGraphicLink(Object graphicLink) +{ + // create graphic provider + XGraphicProvider gProvider = Lo.createInstanceMCF( + XGraphicProvider.class, + "com.sun.star.graphic.GraphicProvider"); + if (gProvider == null) { + System.out.println("Graphic Provider could not be found"); + return null; + } + + // set up URL property then load as an XGraphic + try { + XPropertySet xprops = Lo.qi(XPropertySet.class, graphicLink); + PropertyValue[] gProps = Props.makeProps("URL", + (String) xprops.getPropertyValue("GraphicURL") ); + return gProvider.queryGraphic(gProps); + } + catch(Exception e) + { System.out.println("Unable to retrieve graphic"); + return null; + } +} // end of loadGraphicLink() +``` + +Note that the XGraphic is not extracted from the document but loaded from a URL. + +Back in ExtractGraphics.java, the XGraphic objects are saved as PNG files, and their +pixel sizes reported: + +```java +private static void savePics(XTextDocument textDoc) +// method in ExtractGraphics.java +{ + ArrayList pics = Write.getTextGraphics(textDoc); + if (pics == null) + return; + System.out.println("No. of graphic retrieved: " + pics.size()); + int i = 1; + for(XGraphic pic : pics) { + Images.saveGraphic(pic, "graphics" + i + ".png", "png"); + Size sz = (Size) Props.getProperty(pic, "SizePixel"); + System.out.println("Image Pixel size: " + + sz.Width + " x " + sz.Height); + i++; + } +} // end of savePics() +``` + +Images.saveGraphic() utilizes the graphic provider's XGraphicProvider.storeGraphic() +method: + +```java +// in XGraphicProvider +void storeGraphic(XGraphic g, PropertyValue[] props) +{ ... } +``` + +Its second argument is an array of PropertyValue objects, not a PropertySet. My +Props.java utility class provides several functions for creating PropertyValue +instances, which are a variant of the {name=value} pair idea, but with extra data +fields. One such function is: + +```java +// in Props.java +PropertyValue[] makeProps(String[] nms, Object[] vals) +{ ... } +``` + +It's passed an array of names and values, which are paired up as PropertyValue +objects, and returned in an array. + +In saveGraphic(), these methods are used like so: + +```java +XGraphicProvider gProvider = Lo.createInstanceMCF( + XGraphicProvider.class, + "com.sun.star.graphic.GraphicProvider"); + +// set up properties for storing the graphic +PropertyValue[] props = Props.makeProps( + "URL", FileIO.fnmToURL(fnm), + "MimeType", "image/" + imFormat); + +gProvider.storeGraphic(pic, props); +``` + +The idea is to call XGraphicProvider.saveGraphics() with the "URL" and +"MimeType" properties set – the URL is for the image file, and the mimetype is an +image type (e.g. "image/png"). + +saveGraphic() is coded as: + +```java +public static void saveGraphic(XGraphic pic, + String fnm, String imFormat) +{ // create graphic provider + XGraphicProvider gProvider = Lo.createInstanceMCF( + XGraphicProvider.class, + "com.sun.star.graphic.GraphicProvider"); + if (gProvider == null) { + System.out.println("Graphic Provider could not be found"); + return; + } + + if (pic == null) { + System.out.println("Supplied image is null"); + return; + } + + // set up properties for storing the graphic + PropertyValue[] pngProps = Props.makeProps( + "URL", FileIO.fnmToURL(fnm), + "MimeType", "image/" + imFormat); + try { + gProvider.storeGraphic(pic, pngProps); + } + catch(com.sun.star.uno.Exception e) + { System.out.println("Unable to save graphic"); } +} // end of saveGraphic() +``` + +Other possible image MIME types include "gif", "jpeg", "wmf", and "bmp". For +instance, this call will save the image as a GIF: +Images.saveGraphic(pic, "graphics" + i + ".gif", "gif"); +The printed output from saveGraphic() contains another surprise: + +``` +Num. of text graphics: 2 +Saving graphic in graphics1.png +Image size in pixels: 319 x 274 +Saving graphic in graphics2.png +Image size in pixels: 319 x 274 +``` + +The two saved graphics are the same size, but the second image is bigger inside the +document. The discrepancy is because the rendering of the image in the document is +bigger, scaled up to fit the enclosing frame; the original image is unchanged. + + +### 3.2. Finding the Shapes in a Document + +The second block of code in ExtractGraphics reports on the shapes found in the +document. The relevant code fragment is: + +```java +// code fragment from ExtractGraphics.java +XDrawPage drawPage = Write.getShapes(textDoc); +ArrayList shapes = Draw.getShapes(drawPage); +if (shapes != null) + System.out.println("\nNum. of draw shapes: " + shapes.size()); +for(XShape shape : shapes) + Draw.printShapeInfo(shape); +``` + +Shapes are accessed with the XDrawPageSupplier.getDrawPage() method, which +returns a single XDrawPage: + +```java +public static XDrawPage getShapes(XTextDocument textDoc) +{ + XDrawPageSupplier drawPageSupplier = Lo.qi( + XDrawPageSupplier.class, textDoc); + if (drawPageSupplier == null) { + System.out.println("Draw page supplier could not be created"); + return null; + } + return drawPageSupplier.getDrawPage(); +} // end of getShapes() +``` + +XDrawPage's usual role is to represent the canvas in Office's Draw, or a slide in +Impress, and so plays an important role in Part 3. Several support functions inside that +part's Draw utility class will be used here. + +XDrawPageSupplier.getDrawPage() returns a single XDrawPage for the entire text +document. That doesn't mean that the shapes all have to occur on a single text page, +but rather that all the shapes spread across multiple text pages are collected into a +single draw page. + +XDrawPage inherits from XShapes and XindexAccess, as shown in Figure 4, which +means that a page can be viewed as a indexed collection of shapes. + ![](images/08-Graphic_Content-4.png) -Figure 4. Partial Inheritance Hierarchy for XDrawPage. - - -Draw.getShapes() uses this idea to iterate through the draw page and store the shapes -in a list: - -// in the Draw class -public static ArrayList getShapes(XDrawPage dp) -{ - if (dp == null) { - System.out.println("Draw page is null"); - return null; - } - - if (dp.getCount() == 0) { - System.out.println("Draw page does not contain any shapes"); - return null; - } - - // create a shapes list - ArrayList xShapesList = new ArrayList(); - try { - for(int j=0; j < dp.getCount(); j++) - xShapesList.add( Lo.qi(XShape.class, dp.getByIndex(j))); - } - catch(Exception e) - { System.out.println("Extraction error in draw page"); } - - return xShapesList; -} // end of getShapes() - -XShape is part of the Shape service, which contains many shape-related properties. - -XShape inherits XShapeDescriptor, which includes a getShapeType() method for -returning the shape type as a string. Figure 5 summarizes these details. - - +Figure 4. Partial Inheritance Hierarchy for XDrawPage. + + +Draw.getShapes() uses this idea to iterate through the draw page and store the shapes +in a list: + +```java +// in the Draw class +public static ArrayList getShapes(XDrawPage dp) +{ + if (dp == null) { + System.out.println("Draw page is null"); + return null; + } + + if (dp.getCount() == 0) { + System.out.println("Draw page does not contain any shapes"); + return null; + } + + // create a shapes list + ArrayList xShapesList = new ArrayList(); + try { + for(int j=0; j < dp.getCount(); j++) + xShapesList.add( Lo.qi(XShape.class, dp.getByIndex(j))); + } + catch(Exception e) + { System.out.println("Extraction error in draw page"); } + + return xShapesList; +} // end of getShapes() +``` + +XShape is part of the Shape service, which contains many shape-related properties. + +XShape inherits XShapeDescriptor, which includes a getShapeType() method for +returning the shape type as a string. Figure 5 summarizes these details. + ![](images/08-Graphic_Content-5.png) -Figure 5. The Shape Service and XShape Interface - -Draw.printShapeInfo() accesses the Shape service associated with an XShape -reference, and prints its "XOrder" property. This number indicates the order that the -shapes were added to the document. - - -public static void printShapeInfo(XShape xShape) -{ - Integer nZOrder = (Integer) Props.getProperty(xShape, "ZOrder"); - System.out.println(" Shape service: " + xShape.getShapeType() + - "; z-order: " + nZOrder); -} - -printShapeInfo() also calls the inherited XShapeDescriptor.getShapeType() method to -report the shape's service name. - - -### 3.3. Another Way of Accessing Drawing Shapes - -The XDrawPageSupplier documentation states that this interface is deprecated, -although what's meant to replace it isn't clear. My guess is -com.sun.star.text.XTextShapesSupplier, although I wasn't able to get it to supply -anything. For example, the following always reports that the supplier is null: - -XTextShapesSupplier shpsSupplier = Lo.qi( - XTextShapesSupplier.class, textDoc); -if (shpsSupplier == null) - System.out.println("Could not obtain text shapes supplier"); -else - System.out.println("Num. of text shapes: " + - shpsSupplier.getShapes().getCount()); - +Figure 5. The Shape Service and XShape Interface + +Draw.printShapeInfo() accesses the Shape service associated with an XShape +reference, and prints its "XOrder" property. This number indicates the order that the +shapes were added to the document. + +```java +public static void printShapeInfo(XShape xShape) +{ + Integer nZOrder = (Integer) Props.getProperty(xShape, "ZOrder"); + System.out.println(" Shape service: " + xShape.getShapeType() + + "; z-order: " + nZOrder); +} +``` + +printShapeInfo() also calls the inherited XShapeDescriptor.getShapeType() method to +report the shape's service name. + + +### 3.3. Another Way of Accessing Drawing Shapes + +The XDrawPageSupplier documentation states that this interface is deprecated, +although what's meant to replace it isn't clear. My guess is +com.sun.star.text.XTextShapesSupplier, although I wasn't able to get it to supply +anything. For example, the following always reports that the supplier is null: + +```java +XTextShapesSupplier shpsSupplier = Lo.qi( + XTextShapesSupplier.class, textDoc); +if (shpsSupplier == null) + System.out.println("Could not obtain text shapes supplier"); +else + System.out.println("Num. of text shapes: " + + shpsSupplier.getShapes().getCount()); +``` \ No newline at end of file diff --git a/docs/09-Text_Search.md b/docs/09-Text_Search.md index 7623108..19ade49 100644 --- a/docs/09-Text_Search.md +++ b/docs/09-Text_Search.md @@ -1,358 +1,393 @@ -# Chapter 9. Text Search and Replace +# Chapter 9. Text Search and Replace !!! note "Topics" - Finding the First - Matching Phrase; - Replacing all the - Matching Words; - Finding all Matching - Phrases - - Example folders: "Text - Tests" and "Utils" - -The GenericTextDocument service supports the -XSearchable and XReplaceable interfaces (see Chapter 5, -Figure 2), which are the entry points for doing regular -expression based search and replace inside a document. - -XSearchable.createSearchDescriptor() builds a search -description (an ordinary string or a regular expression). The search is executed with -XSearchable.findAll() or findFirst() and findNext(). - -XReplaceable works in a similar way but with a replace descriptor which combines a -replacement string with the search string. XReplaceable.replaceAll() performs search -and replacement, but the XSearchable searching methods are available as well. This is -shown in Figure 1. - - + Finding the First + Matching Phrase; + Replacing all the + Matching Words; + Finding all Matching + Phrases + + Example folders: "Text + Tests" and "Utils" + +The GenericTextDocument service supports the +XSearchable and XReplaceable interfaces (see Chapter 5, +Figure 2), which are the entry points for doing regular +expression based search and replace inside a document. + +XSearchable.createSearchDescriptor() builds a search +description (an ordinary string or a regular expression). The search is executed with +XSearchable.findAll() or findFirst() and findNext(). + +XReplaceable works in a similar way but with a replace descriptor which combines a +replacement string with the search string. XReplaceable.replaceAll() performs search +and replacement, but the XSearchable searching methods are available as well. This is +shown in Figure 1. + ![](images/09-Text_Search-1.png) -Figure 1. The XSearchable and XReplaceable Interfaces. +Figure 1. The XSearchable and XReplaceable Interfaces. + - -The following code fragment utilizes the XSearchable and XSearchDescriptor -interfaces: - -XSearchable searchable = Lo.qi(XSearchable.class, doc); -XSearchDescriptor srchDesc = searchable.createSearchDescriptor(); -srchDesc.setSearchString("colou?r"); - // a regular expression meaning "color" or "colour" - -XReplaceable and XReplaceDescriptor objects are configured in a similar way, as -shown in the examples. +The following code fragment utilizes the XSearchable and XSearchDescriptor +interfaces: -XSearchDescriptor and XReplaceDescriptor contain get and set methods for their -strings. But a lot of the search functionality is expressed as properties in their -SearchDescriptor and ReplaceDescriptor services. Figure 2 summarizes these -arrangements. +```java +XSearchable searchable = Lo.qi(XSearchable.class, doc); +XSearchDescriptor srchDesc = searchable.createSearchDescriptor(); +srchDesc.setSearchString("colou?r"); + // a regular expression meaning "color" or "colour" +``` +XReplaceable and XReplaceDescriptor objects are configured in a similar way, as +shown in the examples. + +XSearchDescriptor and XReplaceDescriptor contain get and set methods for their +strings. But a lot of the search functionality is expressed as properties in their +SearchDescriptor and ReplaceDescriptor services. Figure 2 summarizes these +arrangements. - ![](images/09-Text_Search-2.png) -Figure 2. The SearchDescriptor and ReplaceDescriptor Services. - - -The next code fragment accesses the SearchDescriptor properties, and switches on -regular expression searching: - -XPropertySet srchProps = Lo.qi(XPropertySet.class, srchDesc); -srchProps.setPropertyValue("SearchRegularExpression", true); - -Alternatively, Props.setProperty() can be employed: -Props.setProperty(srchDesc, "SearchRegularExpression", true); -Once a search descriptor has been created (i.e. its string is set and any properties -configured), then one of the findXXX() methods in XSearchable can be called. - -For instance, XSearchable.findFirst() returns the text range of the first matching -element (or null), as in: - -XInterface srch = (XInterface) searchable.findFirst(srchDesc); -XTextRange matchTR = Lo.qi(XTextRange.class, srch); - -The example programs, TextReplace.java and ItalicsStyler.java, demonstrate search -and replacement. TextReplace.java uses XSearchable to find the first occurrence of a -regular expression and XReplaceable to replace multiple occurrences of other words. - -ItalicsStyler.java calls XSearchable's findAll() to find every occurrence of a phrase. - - - -## 1. Finding the First Matching Phrase - -TextReplace.java repeatedly calls XSearchable.findFirst() with regular expressions -taken from an array. The first matching phrase for each expression is reported. For -instance, the call: - -String words[] = {"(G|g)rit", "colou?r"}; -findWords(doc, words); - -prints the following when "story.doc" is searched: - -Searching for first occurrence of "(G|g)rit" - - found "Grit" - - on page 1 - - at char position: 929 -Searching for first occurrence of "colou?r" - - found "colour" - - on page 5 - - at char position: 10856 - -Three pieces of information are printed for each match: the text that matched, its page -location, and its character position calculated from the start of the document. The -character position could be obtained from a text cursor or a text view cursor, but I -need a page cursor to access the page number. Therefore the easiest thing to use a text -view cursor, and a linked page cursor. - -The code for findWords(): - -private static void findWords(XTextDocument doc, String[] words) -{ - // get the text view cursor and linked page cursor - XTextViewCursor tvc = Write.getViewCursor(doc); - tvc.gotoStart(false); - XPageCursor pageCursor = Lo.qi(XPageCursor.class, tvc); - - try { - XSearchable searchable = Lo.qi(XSearchable.class, doc); - XSearchDescriptor srchDesc = - searchable.createSearchDescriptor(); - - for(int i = 0; i < words.length; i++ ) { - System.out.println("Searching for first occurrence of \"" + - words[i] + "\""); - srchDesc.setSearchString(words[i]); - Props.setProperty(srchDesc, "SearchRegularExpression", true); - - XInterface srch = (XInterface) searchable.findFirst(srchDesc); - if (srch != null) { - XTextRange matchTR = Lo.qi(XTextRange.class, srch); - tvc.gotoRange(matchTR, false); - System.out.println(" - found \"" + - matchTR.getString() + "\""); - System.out.println(" - on page " + - pageCursor.getPage()); - tvc.gotoStart(true); - System.out.println(" - at char position: " + - tvc.getString().length()); - } - else - System.out.println(" - not found"); - } - } - catch(com.sun.star.uno.Exception e) { - System.out.println(e); - } -} // end of findWords() - -findWords() creates the text view cursor (tvc), moves it to the start of the document, -and links the page cursor to it: -XPageCursor pageCursor = Lo.qi(XPageCursor.class, tvc); -There is only one view cursor in an application, so when the text view cursor moves, -so does the page cursor, and vice versa. - -The XSearchable and XSearchDescriptor interfaces are instantiated, and a for-loop -searches for each word in the supplied array. If XSearchable.findFirst() returns a -matching text range, it's used by XTextCursor.gotoRange() to update the position of -the cursor. - -After the page position has been printed, the cursor is moved to the start of the -document with selection turned on: - -tvc.gotoStart(true); -System.out.println(" - at char position: " + - tvc.getString().length()); - -This means that tvc.getString() will return all the text from the start of the document -to the current matching point, and so length() will return the character position -measured from the beginning of the file. As I've mentioned previously, this approach -may fail if the size of the string being instantiated is too big. - - - -## 2. Replacing all the Matching Words - -TextReplace.java also contains a method called replaceWords(), which takes two -string arrays as arguments: - -// code fragment inside TextReplace.java -String ukWords[] = { - "colour", "neighbour", "centre", "behaviour", "metre", "through" }; - -String usWords[] = { - "color", "neighbor", "center", "behavior", "meter", "thru" }; - -replaceWords(doc, ukWords, usWords); - -replaceWords() cycles through the arrays, replacing all occurrences of the words in -the first array (e.g. in ukWords[]) with the corresponding words in the second array -(e.g. in usWords[]). For instance, every occurrence of "colour" is replaced by "color". - -The output: - -Change all occurrences of ... - - colour -> color - - no. of changes: 1 - neighbour -> neighbor - - no. of changes: 2 - centre -> center - - no. of changes: 2 - behaviour -> behavior - - no. of changes: 0 - metre -> meter - - no. of changes: 0 - through -> thru - - no. of changes: 4 - -Since replaceWords() doesn't report page and character positions, its code is -somewhat shorter than findWords(): - -private static void replaceWords(XTextDocument doc, - String[] oldWords, String[] newWords) -{ - XReplaceable replaceable = Lo.qi(XReplaceable.class, doc); - XReplaceDescriptor replaceDesc = - replaceable.createReplaceDescriptor(); - - System.out.println("Change all occurrences of ..."); - for (int i = 0; i < oldWords.length; i++) { - System.out.println(" " + oldWords[i] + " -> " + newWords[i]); - replaceDesc.setSearchString(oldWords[i]); - replaceDesc.setReplaceString(newWords[i]); - - int numChanges = replaceable.replaceAll(replaceDesc); - // replace all occurrence of word - System.out.println(" - no. of changes: " + numChanges); - } -} // end of replaceWords() - -The XReplaceable and XReplaceDescriptor interfaces are created in a similar way to -their search versions. The replace descriptor has two set methods, one for the search -string, the other for the replacement string. - - - -## 3. Finding all Matching Phrases - -ItalicsStyler.java is supplied with a filename and a string on the command line. For -instance: -> run ItalicsStyler story.doc scandal -The program opens the file and uses the "search all' method in XSearchable to find all -occurrences of the string in the document. The matching strings are italicized and -colored red, and the changed document saved as "italicized.doc". These changes are -not performed using XReplaceable methods. - -Figure 3 shows a fragment of the resulting document, with the "scandal" text in the -title and header changed. The search ignores case, so the word in the title ("Scandal") -was correctly modified. - - - +Figure 2. The SearchDescriptor and ReplaceDescriptor Services. + + +The next code fragment accesses the SearchDescriptor properties, and switches on +regular expression searching: + +```java +XPropertySet srchProps = Lo.qi(XPropertySet.class, srchDesc); +srchProps.setPropertyValue("SearchRegularExpression", true); +``` + +Alternatively, Props.setProperty() can be employed: + +```java +Props.setProperty(srchDesc, "SearchRegularExpression", true); +``` + +Once a search descriptor has been created (i.e. its string is set and any properties +configured), then one of the findXXX() methods in XSearchable can be called. + +For instance, XSearchable.findFirst() returns the text range of the first matching +element (or null), as in: + +```java +XInterface srch = (XInterface) searchable.findFirst(srchDesc); +XTextRange matchTR = Lo.qi(XTextRange.class, srch); +``` + +The example programs, TextReplace.java and ItalicsStyler.java, demonstrate search +and replacement. TextReplace.java uses XSearchable to find the first occurrence of a +regular expression and XReplaceable to replace multiple occurrences of other words. + +ItalicsStyler.java calls XSearchable's findAll() to find every occurrence of a phrase. + + +## 1. Finding the First Matching Phrase + +TextReplace.java repeatedly calls XSearchable.findFirst() with regular expressions +taken from an array. The first matching phrase for each expression is reported. For +instance, the call: + +```java +String words[] = {"(G|g)rit", "colou?r"}; +findWords(doc, words); +``` + +prints the following when "story.doc" is searched: + +``` +Searching for first occurrence of "(G|g)rit" + - found "Grit" + - on page 1 + - at char position: 929 +Searching for first occurrence of "colou?r" + - found "colour" + - on page 5 + - at char position: 10856 +``` + +Three pieces of information are printed for each match: the text that matched, its page +location, and its character position calculated from the start of the document. The +character position could be obtained from a text cursor or a text view cursor, but I +need a page cursor to access the page number. Therefore the easiest thing to use a text +view cursor, and a linked page cursor. + +The code for findWords(): + +```java +private static void findWords(XTextDocument doc, String[] words) +{ + // get the text view cursor and linked page cursor + XTextViewCursor tvc = Write.getViewCursor(doc); + tvc.gotoStart(false); + XPageCursor pageCursor = Lo.qi(XPageCursor.class, tvc); + + try { + XSearchable searchable = Lo.qi(XSearchable.class, doc); + XSearchDescriptor srchDesc = + searchable.createSearchDescriptor(); + + for(int i = 0; i < words.length; i++ ) { + System.out.println("Searching for first occurrence of \"" + + words[i] + "\""); + srchDesc.setSearchString(words[i]); + Props.setProperty(srchDesc, "SearchRegularExpression", true); + + XInterface srch = (XInterface) searchable.findFirst(srchDesc); + if (srch != null) { + XTextRange matchTR = Lo.qi(XTextRange.class, srch); + tvc.gotoRange(matchTR, false); + System.out.println(" - found \"" + + matchTR.getString() + "\""); + System.out.println(" - on page " + + pageCursor.getPage()); + tvc.gotoStart(true); + System.out.println(" - at char position: " + + tvc.getString().length()); + } + else + System.out.println(" - not found"); + } + } + catch(com.sun.star.uno.Exception e) { + System.out.println(e); + } +} // end of findWords() +``` + +findWords() creates the text view cursor (tvc), moves it to the start of the document, +and links the page cursor to it: + +```java +XPageCursor pageCursor = Lo.qi(XPageCursor.class, tvc); +``` + +There is only one view cursor in an application, so when the text view cursor moves, +so does the page cursor, and vice versa. + +The XSearchable and XSearchDescriptor interfaces are instantiated, and a for-loop +searches for each word in the supplied array. If XSearchable.findFirst() returns a +matching text range, it's used by XTextCursor.gotoRange() to update the position of +the cursor. + +After the page position has been printed, the cursor is moved to the start of the +document with selection turned on: + +```java +tvc.gotoStart(true); +System.out.println(" - at char position: " + + tvc.getString().length()); +``` + +This means that tvc.getString() will return all the text from the start of the document +to the current matching point, and so length() will return the character position +measured from the beginning of the file. As I've mentioned previously, this approach +may fail if the size of the string being instantiated is too big. + + +## 2. Replacing all the Matching Words + +TextReplace.java also contains a method called replaceWords(), which takes two +string arrays as arguments: + +```java +// code fragment inside TextReplace.java +String ukWords[] = { + "colour", "neighbour", "centre", "behaviour", "metre", "through" }; + +String usWords[] = { + "color", "neighbor", "center", "behavior", "meter", "thru" }; + +replaceWords(doc, ukWords, usWords); +``` + +replaceWords() cycles through the arrays, replacing all occurrences of the words in +the first array (e.g. in ukWords[]) with the corresponding words in the second array +(e.g. in usWords[]). For instance, every occurrence of "colour" is replaced by "color". + +The output: + +``` +Change all occurrences of ... + + colour -> color + - no. of changes: 1 + neighbour -> neighbor + - no. of changes: 2 + centre -> center + - no. of changes: 2 + behaviour -> behavior + - no. of changes: 0 + metre -> meter + - no. of changes: 0 + through -> thru + - no. of changes: 4 +``` + +Since replaceWords() doesn't report page and character positions, its code is +somewhat shorter than findWords(): + +```java +private static void replaceWords(XTextDocument doc, + String[] oldWords, String[] newWords) +{ + XReplaceable replaceable = Lo.qi(XReplaceable.class, doc); + XReplaceDescriptor replaceDesc = + replaceable.createReplaceDescriptor(); + + System.out.println("Change all occurrences of ..."); + for (int i = 0; i < oldWords.length; i++) { + System.out.println(" " + oldWords[i] + " -> " + newWords[i]); + replaceDesc.setSearchString(oldWords[i]); + replaceDesc.setReplaceString(newWords[i]); + + int numChanges = replaceable.replaceAll(replaceDesc); + // replace all occurrence of word + System.out.println(" - no. of changes: " + numChanges); + } +} // end of replaceWords() +``` + +The XReplaceable and XReplaceDescriptor interfaces are created in a similar way to +their search versions. The replace descriptor has two set methods, one for the search +string, the other for the replacement string. -![](images/09-Text_Search-3.png) -Figure 3. A Fragment of The Italicized Document. - - -The ItalicsStyler program also outputs matching details: - -Searching for all occurrence of "scandal" -No. of matches: 5 - - found "scandal" - - on page 1 - - starting at char position: 6 - - found "Scandal" - - on page 1 - - starting at char position: 2 - - found "scandal" - - on page 6 - - starting at char position: 13153 - - found "scandal" - - on page 18 - - starting at char position: 38736 - - found "scandal" - - on page 21 - - starting at char position: 46425 - -As with TextReplace.java, the printed details include the page and character positions -of the matches. - -The searching in ItalicsStyler.java is performed by italicizeAll(), which bears a close -resemblance to findWords(): - -private static void italicizeAll(XTextDocument doc, String phrase) -{ - // get the text view cursor and linked page cursor - XTextViewCursor tvc = Write.getViewCursor(doc); - tvc.gotoStart(false); - XPageCursor pageCursor = Lo.qi(XPageCursor.class, tvc); - - try { - XSearchable xSearchable = Lo.qi(XSearchable.class, doc); - XSearchDescriptor srchDesc = - xSearchable.createSearchDescriptor(); - - System.out.println("Searching for all - occurrences of \"" + phrase + "\""); - int phraseLen = phrase.length(); - srchDesc.setSearchString(phrase); - Props.setProperty(srchDesc, "SearchCaseSensitive", false); - - XIndexAccess matches = xSearchable.findAll(srchDesc); - System.out.println("No. of matches: " + matches.getCount()); - for (int i = 0; i < matches.getCount(); i++) { - XTextRange matchTR = Lo.qi(XTextRange.class, - matches.getByIndex(i)); - if (matchTR != null) { - tvc.gotoRange(matchTR, false); - System.out.println(" - found \"" + - matchTR.getString() + "\""); - System.out.println(" - on page " + - pageCursor.getPage()); - tvc.gotoStart(true); - System.out.println(" - starting at char position: " + - (tvc.getString().length() - phraseLen)); - Props.setProperties(matchTR, - new String[] {"CharColor", "CharPosture"}, - new Object[] { 0xFF0000, - com.sun.star.awt.FontSlant.ITALIC} ); - } - } - } - catch(com.sun.star.uno.Exception e) { - System.out.println(e); - } -} // end of italicizeAll() - -After the search descriptor string has been defined, the "SearchCaseSensitive" -property in SearchDescriptor is set to false: - -srchDesc.setSearchString(phrase); -Props.setProperty(srchDesc, "SearchCaseSensitive", false); - -This allows the search to match text contains both upper and lower case letters, such -as "Scandal". Many other search variants, such as restricting the search to complete -words, and the use of search similarity parameters are described in the -SearchDescriptor documentation (loDoc SearchDescriptor service). - -XSearchable.findAll() returns an XIndexAccess collection, which is examined -element-by-element inside a for-loop. The text range for each element is obtained by -applying Lo.qi(): -XTextRange matchTR = Lo.qiXTextRange.class, matches.getByIndex(i)); -The reporting of the matching page and character position use text view and page -cursors in the same way as findWords() in TextReplace.java. - -XTextRange is part of the TextRange service, which inherits ParagraphProperties and -CharacterProperties. These properties are changed to adjust the character color and -style of the selected range: - -Props.setProperties(matchTR, - new String[] {"CharColor", "CharPosture"}, - new Object[] { 0xFF0000, com.sun.star.awt.FontSlant.ITALIC} ); - -This changes the "CharColor" and "CharPosture" properties to red and italic. +## 3. Finding all Matching Phrases + +ItalicsStyler.java is supplied with a filename and a string on the command line. For +instance: + +``` +> run ItalicsStyler story.doc scandal +``` + +The program opens the file and uses the "search all' method in XSearchable to find all +occurrences of the string in the document. The matching strings are italicized and +colored red, and the changed document saved as "italicized.doc". These changes are +not performed using XReplaceable methods. + +Figure 3 shows a fragment of the resulting document, with the "scandal" text in the +title and header changed. The search ignores case, so the word in the title ("Scandal") +was correctly modified. + + +![](images/09-Text_Search-3.png) +Figure 3. A Fragment of The Italicized Document. + + +The ItalicsStyler program also outputs matching details: + +``` +Searching for all occurrence of "scandal" +No. of matches: 5 + - found "scandal" + - on page 1 + - starting at char position: 6 + - found "Scandal" + - on page 1 + - starting at char position: 2 + - found "scandal" + - on page 6 + - starting at char position: 13153 + - found "scandal" + - on page 18 + - starting at char position: 38736 + - found "scandal" + - on page 21 + - starting at char position: 46425 +``` + +As with TextReplace.java, the printed details include the page and character positions +of the matches. + +The searching in ItalicsStyler.java is performed by italicizeAll(), which bears a close +resemblance to findWords(): + +```java +private static void italicizeAll(XTextDocument doc, String phrase) +{ + // get the text view cursor and linked page cursor + XTextViewCursor tvc = Write.getViewCursor(doc); + tvc.gotoStart(false); + XPageCursor pageCursor = Lo.qi(XPageCursor.class, tvc); + + try { + XSearchable xSearchable = Lo.qi(XSearchable.class, doc); + XSearchDescriptor srchDesc = + xSearchable.createSearchDescriptor(); + + System.out.println("Searching for all + occurrences of \"" + phrase + "\""); + int phraseLen = phrase.length(); + srchDesc.setSearchString(phrase); + Props.setProperty(srchDesc, "SearchCaseSensitive", false); + + XIndexAccess matches = xSearchable.findAll(srchDesc); + System.out.println("No. of matches: " + matches.getCount()); + for (int i = 0; i < matches.getCount(); i++) { + XTextRange matchTR = Lo.qi(XTextRange.class, + matches.getByIndex(i)); + if (matchTR != null) { + tvc.gotoRange(matchTR, false); + System.out.println(" - found \"" + + matchTR.getString() + "\""); + System.out.println(" - on page " + + pageCursor.getPage()); + tvc.gotoStart(true); + System.out.println(" - starting at char position: " + + (tvc.getString().length() - phraseLen)); + Props.setProperties(matchTR, + new String[] {"CharColor", "CharPosture"}, + new Object[] { 0xFF0000, + com.sun.star.awt.FontSlant.ITALIC} ); + } + } + } + catch(com.sun.star.uno.Exception e) { + System.out.println(e); + } +} // end of italicizeAll() +``` + +After the search descriptor string has been defined, the "SearchCaseSensitive" +property in SearchDescriptor is set to false: + +```java +srchDesc.setSearchString(phrase); +Props.setProperty(srchDesc, "SearchCaseSensitive", false); +``` + +This allows the search to match text contains both upper and lower case letters, such +as "Scandal". Many other search variants, such as restricting the search to complete +words, and the use of search similarity parameters are described in the +SearchDescriptor documentation (loDoc SearchDescriptor service). + +XSearchable.findAll() returns an XIndexAccess collection, which is examined +element-by-element inside a for-loop. The text range for each element is obtained by +applying Lo.qi(): + +```java +XTextRange matchTR = Lo.qiXTextRange.class, matches.getByIndex(i)); +``` + +The reporting of the matching page and character position use text view and page +cursors in the same way as findWords() in TextReplace.java. + +XTextRange is part of the TextRange service, which inherits ParagraphProperties and +CharacterProperties. These properties are changed to adjust the character color and +style of the selected range: + +```java +Props.setProperties(matchTR, + new String[] {"CharColor", "CharPosture"}, + new Object[] { 0xFF0000, com.sun.star.awt.FontSlant.ITALIC} ); +``` + +This changes the "CharColor" and "CharPosture" properties to red and italic. \ No newline at end of file diff --git a/docs/10-Linguistics.md b/docs/10-Linguistics.md index e33a9ce..acaa537 100644 --- a/docs/10-Linguistics.md +++ b/docs/10-Linguistics.md @@ -1,1347 +1,1379 @@ -# Chapter 10. The Linguistics API +# Chapter 10. The Linguistics API !!! note "Topics" - Linguistic - Tools; Using the Spell - Checker; Using the - Thesaurus; Grammar - Checking; Guessing the - Language used in a - String; Spell Checking - and Grammar Checking - a Document - - Example folders: "Lingu - Tests" and "Utils" - - -The linguistics API has four main components – a spell -checker, a hyphenator, a thesaurus, and a grammar -checker (which Office calls a proof reader). I'll look at -how to program using the spell checker, thesaurus, and -two grammar checkers, but I'll skip the hyphenator which -is easier to use interactively through Office's GUI. - -However, if you have an urge to hyphenate, then -LingusticExamples.java in the Developer's Guide -examples contains some code; it can be downloaded from + Linguistic + Tools; Using the Spell + Checker; Using the + Thesaurus; Grammar + Checking; Guessing the + Language used in a + String; Spell Checking + and Grammar Checking + a Document + + Example folders: "Lingu + Tests" and "Utils" + + +The linguistics API has four main components – a spell +checker, a hyphenator, a thesaurus, and a grammar +checker (which Office calls a proof reader). I'll look at +how to program using the spell checker, thesaurus, and +two grammar checkers, but I'll skip the hyphenator which +is easier to use interactively through Office's GUI. + +However, if you have an urge to hyphenate, then +LingusticExamples.java in the Developer's Guide +examples contains some code; it can be downloaded from http://api.libreoffice.org/examples/DevelopersGuide/OfficeDev/Linguistic/Linguistic -Examples.java. - -I'll describe two examples, Lingo.java and LingoFile.java. The first lists information -about the linguistic services, then uses the spell checker, thesaurus, and grammar -checker 'standalone' without having to load an Office document first. LingoFile.java -automatically spell checks and grammar checks a complete file, reporting errors -without altering the document. - -One topic I'll be ignoring is how to create and edit the data files used by the linguistic -services. For that task, you should have a look at PTG (Proofing Tool GUI) developed -by Marco Pinto at http://marcoagpinto.cidadevirtual.pt/proofingtoolgui.html. It's an -open source tool for editing Office's dictionary, thesaurus, hyphenation, and -autocorrect files. - -Another area I'll be skipping is the use of events and listeners. Please refer to the -"Linguistics" sub-section of chapter 6 of the Developer's Guide for details (loGuide -Linguistics). Listener code can be found in LinguisticExamples.java mentioned -above. - -The linguistic features accessible through Office's GUI are explained in chapter 3 of -the "Writer Guide", available at http://www.libreoffice.org/get-help/documentation/, -starting from the section called "Checking spelling and grammar". - -An older information source is the "Lingucomponent Project" page at the OpenOffice -website, http://www.openoffice.org/lingucomponent/, which links to some useful -tools, such as alternative grammar checkers. An interesting set of slides by Daniel -Naber explaining the state of the project in 2005 can be found at -http://www.danielnaber.de/publications/, along with more recent material. - - - -## 1. The Linguistic Tools - -Lingo.java prints a variety of information about the linguistics services: - -public static void main(String args[]) -{ - Lo.loadOffice(); - - // print linguistics info - Write.dictsInfo(); - - XLinguProperties linguProps = Write.getLinguProperties(); - Props.showProps("Linguistic Manager", linguProps); - - Info.listExtensions(); - - // get lingo manager - XLinguServiceManager2 lingoMgr = - Lo.createInstanceMCF(XLinguServiceManager2.class, - "com.sun.star.linguistic2.LinguServiceManager"); - if (lingoMgr == null) { - System.out.println("No linguistics manager found"); - Lo.closeOffice(); - return; - } - - Write.printServicesInfo(lingoMgr); - - : // code for using the services; see later - - Lo.closeOffice(); -} // end of main() - - -### 1.1. Dictionary Information - -Write.dictsInfo() prints brief details about Office's dictionaries: - -No. of dictionaries: 5 - standard.dic (1); active; ""; positive - en-GB.dic (42); active; "GB"; positive - en-US.dic (42); active; "US"; positive - technical.dic (258); active; ""; positive - IgnoreAllList (0); active; ""; positive - -No. of conversion dictionaries: 0 - -Each line includes the name of a dictionary, its number of entries, whether it's active -(i.e. being used), its locale, and whether it's a positive, negative, or mixed dictionary. - -A positive dictionary holds correctly spelled words only, while a negative one lists -incorrectly spelt words. A mixed dictionary contains both correctly and incorrectly -spelt entries. - -If a dictionary has a locale, such as "GB" for en-GB.dic, then it's only utilized during -spell checking if its locale matches Office's. The Office locale can be set via the Tools -> Options > Language Settings > "Languages" dialog shown in Figure 1. - - - +Examples.java. + +I'll describe two examples, Lingo.java and LingoFile.java. The first lists information +about the linguistic services, then uses the spell checker, thesaurus, and grammar +checker 'standalone' without having to load an Office document first. LingoFile.java +automatically spell checks and grammar checks a complete file, reporting errors +without altering the document. + +One topic I'll be ignoring is how to create and edit the data files used by the linguistic +services. For that task, you should have a look at PTG (Proofing Tool GUI) developed +by Marco Pinto at http://marcoagpinto.cidadevirtual.pt/proofingtoolgui.html. It's an +open source tool for editing Office's dictionary, thesaurus, hyphenation, and +autocorrect files. + +Another area I'll be skipping is the use of events and listeners. Please refer to the +"Linguistics" sub-section of chapter 6 of the Developer's Guide for details +(`loGuide Linguistics`). Listener code can be found in LinguisticExamples.java mentioned +above. + +The linguistic features accessible through Office's GUI are explained in chapter 3 of +the "Writer Guide", available at http://www.libreoffice.org/get-help/documentation/, +starting from the section called "Checking spelling and grammar". + +An older information source is the "Lingucomponent Project" page at the OpenOffice +website, http://www.openoffice.org/lingucomponent/, which links to some useful +tools, such as alternative grammar checkers. An interesting set of slides by Daniel +Naber explaining the state of the project in 2005 can be found at +http://www.danielnaber.de/publications/, along with more recent material. + + +## 1. The Linguistic Tools + +Lingo.java prints a variety of information about the linguistics services: + +```java +public static void main(String args[]) +{ + Lo.loadOffice(); + + // print linguistics info + Write.dictsInfo(); + + XLinguProperties linguProps = Write.getLinguProperties(); + Props.showProps("Linguistic Manager", linguProps); + + Info.listExtensions(); + + // get lingo manager + XLinguServiceManager2 lingoMgr = + Lo.createInstanceMCF(XLinguServiceManager2.class, + "com.sun.star.linguistic2.LinguServiceManager"); + if (lingoMgr == null) { + System.out.println("No linguistics manager found"); + Lo.closeOffice(); + return; + } + + Write.printServicesInfo(lingoMgr); + + : // code for using the services; see later + + Lo.closeOffice(); +} // end of main() +``` + + +### 1.1. Dictionary Information + +Write.dictsInfo() prints brief details about Office's dictionaries: + +```java +No. of dictionaries: 5 + standard.dic (1); active; ""; positive + en-GB.dic (42); active; "GB"; positive + en-US.dic (42); active; "US"; positive + technical.dic (258); active; ""; positive + IgnoreAllList (0); active; ""; positive + +No. of conversion dictionaries: 0 +``` + +Each line includes the name of a dictionary, its number of entries, whether it's active +(i.e. being used), its locale, and whether it's a positive, negative, or mixed dictionary. + +A positive dictionary holds correctly spelled words only, while a negative one lists +incorrectly spelt words. A mixed dictionary contains both correctly and incorrectly +spelt entries. + +If a dictionary has a locale, such as "GB" for en-GB.dic, then it's only utilized during +spell checking if its locale matches Office's. The Office locale can be set via the Tools, +Options, Language Settings, "Languages" dialog shown in Figure 1. + ![](images/10-Linguistics-1.png) -Figure 1. The Languages Dialog. - - -Figure 1 shows that my version of Office is using the American English locale, and so -en-GB.dic won't be consulted when text is spell checked. - -Write.dictsInfo() is defined as: - -// in the Write class -public static void dictsInfo() -{ - XSearchableDictionaryList dictList = - Lo.createInstanceMCF(XSearchableDictionaryList.class, - "com.sun.star.linguistic2.DictionaryList"); - if (dictList == null) - System.out.println("No list of dictionaries found"); - else - printDictsInfo(dictList); - - XConversionDictionaryList cdList = - Lo.createInstanceMCF(XConversionDictionaryList.class, - "com.sun.star.linguistic2.ConversionDictionaryList"); - if (cdList == null) - System.out.println("No list of conversion dictionaries found"); - else - printConDictsInfo(cdList); -} // end of dictsInfo() - -It retrieves a conventional dictionary list first (called dictList), and iterates through its -dictionaries using printDictsInfo(). Then it obtains the conversion dictionary list -(called csList), and iterates over that with printConDictsInfo(). - -Figure 3 shows the main services and interfaces used by ordinary dictionaries. - - - +Figure 1. The Languages Dialog. + + +Figure 1 shows that my version of Office is using the American English locale, and so +en-GB.dic won't be consulted when text is spell checked. + +Write.dictsInfo() is defined as: + +```java +// in the Write class +public static void dictsInfo() +{ + XSearchableDictionaryList dictList = + Lo.createInstanceMCF(XSearchableDictionaryList.class, + "com.sun.star.linguistic2.DictionaryList"); + if (dictList == null) + System.out.println("No list of dictionaries found"); + else + printDictsInfo(dictList); + + XConversionDictionaryList cdList = + Lo.createInstanceMCF(XConversionDictionaryList.class, + "com.sun.star.linguistic2.ConversionDictionaryList"); + if (cdList == null) + System.out.println("No list of conversion dictionaries found"); + else + printConDictsInfo(cdList); +} // end of dictsInfo() +``` + +It retrieves a conventional dictionary list first (called dictList), and iterates through its +dictionaries using printDictsInfo(). Then it obtains the conversion dictionary list +(called csList), and iterates over that with printConDictsInfo(). + +Figure 3 shows the main services and interfaces used by ordinary dictionaries. + ![](images/10-Linguistics-3.png) -Figure 3. The DictionaryList and Dictionary Services. - - -Each dictionary in the list has an XDictionary interface which contains methods for -accessing and changing its entries. printDictsInfo() retrieves an XDictionary array -from the list, and prints out a summary of each dictionary: - -// in the Write class -public static void printDictsInfo( - XSearchableDictionaryList dictList) -{ if (dictList == null) { - System.out.println("Dictionary list is null"); - return; - } - System.out.println("No. of dictionaries: " + - dictList.getCount()); - XDictionary[] dicts = dictList.getDictionaries(); - for(XDictionary dict : dicts) - System.out.println(" " + dict.getName() + - " (" + dict.getCount() + - "); " + (dict.isActive() ? "active" : "na") + - "; \"" + dict.getLocale().Country + - "\"; " + - getDictType(dict.getDictionaryType())); - System.out.println(); -} // end of printDictsInfo() - - -public static String getDictType(DictionaryType dt) -{ - if (dt == DictionaryType.POSITIVE) - return "positive"; - else if (dt == DictionaryType.NEGATIVE) - return "negative"; - else if (dt == DictionaryType.MIXED) - return "mixed"; - else - return "??"; -} // end of getDictType() - - -Conversion dictionaries map words in one language/dialect to corresponding words in -another language/dialect. Figure 4 shows that conversion dictionaries are organized in -a similar way to ordinary ones. The interfaces for manipulating a conversion -dictionary are XConversionDictionary and XConversionPropertyType. - - - +Figure 3. The DictionaryList and Dictionary Services. + + +Each dictionary in the list has an XDictionary interface which contains methods for +accessing and changing its entries. printDictsInfo() retrieves an XDictionary array +from the list, and prints out a summary of each dictionary: + +```java +// in the Write class +public static void printDictsInfo( + XSearchableDictionaryList dictList) +{ if (dictList == null) { + System.out.println("Dictionary list is null"); + return; + } + System.out.println("No. of dictionaries: " + + dictList.getCount()); + XDictionary[] dicts = dictList.getDictionaries(); + for(XDictionary dict : dicts) + System.out.println(" " + dict.getName() + + " (" + dict.getCount() + + "); " + (dict.isActive() ? "active" : "na") + + "; \"" + dict.getLocale().Country + + "\"; " + + getDictType(dict.getDictionaryType())); + System.out.println(); +} // end of printDictsInfo() + + +public static String getDictType(DictionaryType dt) +{ + if (dt == DictionaryType.POSITIVE) + return "positive"; + else if (dt == DictionaryType.NEGATIVE) + return "negative"; + else if (dt == DictionaryType.MIXED) + return "mixed"; + else + return "??"; +} // end of getDictType() +``` + +Conversion dictionaries map words in one language/dialect to corresponding words in +another language/dialect. Figure 4 shows that conversion dictionaries are organized in +a similar way to ordinary ones. The interfaces for manipulating a conversion +dictionary are XConversionDictionary and XConversionPropertyType. + ![](images/10-Linguistics-4.png) -Figure 4. The ConversionDictionaryList and ConversionDictionary Services. - - -Write.dictsInfo() calls printConDictsInfo() to print the names of the conversion -dictionaries – by extracting an XNameContainer from the dictionary list, and then -pulling a list of the names from the container: - - -// in the Write clas -public static void printConDictsInfo( - XConversionDictionaryList cdList) -{ if (cdList == null) { - System.out.println("Conversion Dictionary list is null"); - return; - } - XNameContainer dcCon = cdList.getDictionaryContainer(); - String[] dcNames = dcCon.getElementNames(); - System.out.println("No. of conversion dictionaries: " + - dcNames.length); - for(String dcName : dcNames) - System.out.println(" " + dcName); - System.out.println(); -} // end of printConDictsInfo() - - -Output similar to Write.dictsInfo() can be viewed via Office's Tools > Options> -Language Settings > "Writing Aids" dialog, shown in Figure 5. - - - +Figure 4. The ConversionDictionaryList and ConversionDictionary Services. + + +Write.dictsInfo() calls printConDictsInfo() to print the names of the conversion +dictionaries – by extracting an XNameContainer from the dictionary list, and then +pulling a list of the names from the container: + +```java +// in the Write clas +public static void printConDictsInfo( + XConversionDictionaryList cdList) +{ if (cdList == null) { + System.out.println("Conversion Dictionary list is null"); + return; + } + XNameContainer dcCon = cdList.getDictionaryContainer(); + String[] dcNames = dcCon.getElementNames(); + System.out.println("No. of conversion dictionaries: " + + dcNames.length); + for(String dcName : dcNames) + System.out.println(" " + dcName); + System.out.println(); +} // end of printConDictsInfo() +``` + + +Output similar to Write.dictsInfo() can be viewed via Office's Tools, Options, +Language Settings, "Writing Aids" dialog, shown in Figure 5. + ![](images/10-Linguistics-5.png) -Figure 5. The Writing Aids Dialog. +Figure 5. The Writing Aids Dialog. + - -The dictionaries are listed in the second pane of the dialog. Also, at the bottom of the -window is a "Get more dictionaries online" hyperlink which takes the user to Office's -extension website, and displays the "Dictionary" category (see Figure 6). +The dictionaries are listed in the second pane of the dialog. Also, at the bottom of the +window is a "Get more dictionaries online" hyperlink which takes the user to Office's +extension website, and displays the "Dictionary" category (see Figure 6). - ![](images/10-Linguistics-6.png) -Figure 6. The Dictionary Extensions at the LibreOffice Website. - - -The URL of the page in Figure 6 is: -http://extensions.libreoffice.org/ - extension-center?getCategories=Dictionary -The dictionary category currently has 94 entries, but if you can't find what you're -looking for, don't forget the extensions for OpenOffice, at: -http://extensions.openoffice.org/ -If you're unclear about how to install extensions, the process is explained online at -https://wiki.documentfoundation.org/Documentation/HowTo/install_extension, or in +Figure 6. The Dictionary Extensions at the LibreOffice Website. + + +The URL of the page in Figure 6 is: +http://extensions.libreoffice.org/ + extension-center?getCategories=Dictionary +The dictionary category currently has 94 entries, but if you can't find what you're +looking for, don't forget the extensions for OpenOffice, at: +http://extensions.openoffice.org/ +If you're unclear about how to install extensions, the process is explained online at +https://wiki.documentfoundation.org/Documentation/HowTo/install_extension, or in the "Installing Extensions" guide available at http://www.libreoffice.org/get- -help/documentation/. - - -### 1.2. Linguistic Properties - -Back in the Lingo.java example, Write.getLinguProperties() returns an instance of -XLinguProperties, and its properties are printed by calling Props.showProps(): - -// in Lingo.java -XLinguProperties linguProps = Write.getLinguProperties(); -Props.showProps("Linguistic Manager", linguProps); - -The output: - -Linguistic Manager Properties - DefaultLanguage == 0 - DefaultLocale == com.sun.star.lang.Locale@1f95c5d - DefaultLocale_CJK == com.sun.star.lang.Locale@cd43c2 - DefaultLocale_CTL == com.sun.star.lang.Locale@a8f0b4 - HyphMinLeading == 2 - HyphMinTrailing == 2 - HyphMinWordLength == 5 - IsGermanPreReform == Any[Type[void], null] - IsHyphAuto == false - IsHyphSpecial == true - IsIgnoreControlCharacters == true - IsSpellAuto == false - IsSpellCapitalization == true - IsSpellHide == Any[Type[void], null] - IsSpellInAllLanguages == Any[Type[void], null] - IsSpellSpecial == true - IsSpellUpperCase == true - IsSpellWithDigits == false - IsUseDictionaryList == true - IsWrapReverse == false - -These properties are explained in the online documentation for the XLinguProperties -interface (lodoc XLinguProperties), and also in the Developer's Guide. - -The properties are spread across several dialogs in Office's GUI, starting from the -Tools > Options > "Language Settings" menu item. However, most of them are in the -"Options" pane of the "Writing Aids" Dialog in Figure 5. - - -### 1.3. Installed Extensions - -Additional dictionaries, and other language tools such as grammar checkers, are -loaded into Office as extensions, so calling Info.listExtensions() can be informative. - -The output on one of my test machine is: - -Extensions: -## 1. ID: org.openoffice.en.hunspell.dictionaries - - Version: 2011.12.05.1 - Loc: file:///C:/Program%20Files/LibreOffice%205/program/ - ../share/extensions/dict-en - -## 2. ID: French.linguistic.resources.from.Dicollecte.by.OlivierR - - Version: 5.4.1 - Loc: file:///C:/Program%20Files/LibreOffice%205/program/ - ../share/extensions/dict-fr - -## 3. ID: org.openoffice.languagetool.oxt - - Version: 3.4 - Loc: file:///C:/Program%20Files/LibreOffice%205/program/ - ../share/uno_packages/cache/uno_packages/ - lu4156ef34f.tmp_/LanguageTool-3.4.oxt - -## 4. ID: com.sun.star.comp.Calc.NLPSolver - - Version: 0.9 - Loc: file:///C:/Program%20Files/LibreOffice%205/program/ - ../share/extensions/nlpsolver - -## 5. ID: spanish.es_ANY.dicts.from.rla-es - - Version: 0.8 - Loc: file:///C:/Program%20Files/LibreOffice%205/program/ - ../share/extensions/dict-es - -## 6. ID: com.sun.wiki-publisher - - Version: 1.2.0 - Loc: file:///C:/Program%20Files/LibreOffice%205/program/ - ../share/extensions/wiki-publisher - -The "Loc" entries are the directories or OXT files containing the extensions. Most -extensions are placed in the \share\extensions\ folder on Windows. - -Office can display similar information via its Tools > "Extension Manager" dialog, as -in Figure 7. - - - +help/documentation/. + + +### 1.2. Linguistic Properties + +Back in the Lingo.java example, Write.getLinguProperties() returns an instance of +XLinguProperties, and its properties are printed by calling Props.showProps(): + +```java +// in Lingo.java +XLinguProperties linguProps = Write.getLinguProperties(); +Props.showProps("Linguistic Manager", linguProps); +``` + +The output: + +``` +Linguistic Manager Properties + DefaultLanguage == 0 + DefaultLocale == com.sun.star.lang.Locale@1f95c5d + DefaultLocale_CJK == com.sun.star.lang.Locale@cd43c2 + DefaultLocale_CTL == com.sun.star.lang.Locale@a8f0b4 + HyphMinLeading == 2 + HyphMinTrailing == 2 + HyphMinWordLength == 5 + IsGermanPreReform == Any[Type[void], null] + IsHyphAuto == false + IsHyphSpecial == true + IsIgnoreControlCharacters == true + IsSpellAuto == false + IsSpellCapitalization == true + IsSpellHide == Any[Type[void], null] + IsSpellInAllLanguages == Any[Type[void], null] + IsSpellSpecial == true + IsSpellUpperCase == true + IsSpellWithDigits == false + IsUseDictionaryList == true + IsWrapReverse == false +``` + +These properties are explained in the online documentation for the XLinguProperties +interface (lodoc XLinguProperties), and also in the Developer's Guide. + +The properties are spread across several dialogs in Office's GUI, starting from the +Tools, Options, "Language Settings" menu item. However, most of them are in the +"Options" pane of the "Writing Aids" Dialog in Figure 5. + + +### 1.3. Installed Extensions + +Additional dictionaries, and other language tools such as grammar checkers, are +loaded into Office as extensions, so calling Info.listExtensions() can be informative. + +The output on one of my test machine is: + +``` +Extensions: +1. ID: org.openoffice.en.hunspell.dictionaries + + Version: 2011.12.05.1 + Loc: file:///C:/Program%20Files/LibreOffice%205/program/ + ../share/extensions/dict-en + +2. ID: French.linguistic.resources.from.Dicollecte.by.OlivierR + + Version: 5.4.1 + Loc: file:///C:/Program%20Files/LibreOffice%205/program/ + ../share/extensions/dict-fr + +3. ID: org.openoffice.languagetool.oxt + + Version: 3.4 + Loc: file:///C:/Program%20Files/LibreOffice%205/program/ + ../share/uno_packages/cache/uno_packages/ + lu4156ef34f.tmp_/LanguageTool-3.4.oxt + +4. ID: com.sun.star.comp.Calc.NLPSolver + + Version: 0.9 + Loc: file:///C:/Program%20Files/LibreOffice%205/program/ + ../share/extensions/nlpsolver + +5. ID: spanish.es_ANY.dicts.from.rla-es + + Version: 0.8 + Loc: file:///C:/Program%20Files/LibreOffice%205/program/ + ../share/extensions/dict-es + +6. ID: com.sun.wiki-publisher + + Version: 1.2.0 + Loc: file:///C:/Program%20Files/LibreOffice%205/program/ + ../share/extensions/wiki-publisher +``` + +The "Loc" entries are the directories or OXT files containing the extensions. Most +extensions are placed in the \share\extensions\ folder on Windows. + +Office can display similar information via its Tools, "Extension Manager" dialog, as +in Figure 7. + ![](images/10-Linguistics-7.png) -Figure 7. The Extension Manager Dialog. - - -The code for Info.listExtensions(): - -// in the Info class -public static void listExtensions() -{ - XPackageInformationProvider pip = getPip(); - if (pip == null) - System.out.println("No package info provider found"); - else { - String[][] extsTable = pip.getExtensionList(); - System.out.println("\nExtensions:"); - String serviceName; - for(int i=0; i < extsTable.length; i++) { - System.out.println((i+1) + ". ID: " + extsTable[i][0]); - System.out.println(" Version: " + extsTable[i][1]); - System.out.println(" Loc: " + - pip.getPackageLocation(extsTable[i][0])); - System.out.println(); - } - } -} // end of listExtensions() - - -public static XPackageInformationProvider getPip() -{ return PackageInformationProvider.get(Lo.getContext()); } - -Extensions are accessed via the XPackageInformationProvider interface. - - -### 1.4. Examining the Lingu Services - -The LinguServiceManager provides access to three of the four main linguistic -services: the spell checker, the hyphenator, and thesaurus.The proof reader (i.e. the -grammar checker) is managed by a separate Proofreader service, which I'll explain -later. - -Figure 8 shows the interfaces accessible from the LinguServiceManager service. - - - +Figure 7. The Extension Manager Dialog. + + +The code for Info.listExtensions(): + +```java +// in the Info class +public static void listExtensions() +{ + XPackageInformationProvider pip = getPip(); + if (pip == null) + System.out.println("No package info provider found"); + else { + String[][] extsTable = pip.getExtensionList(); + System.out.println("\nExtensions:"); + String serviceName; + for(int i=0; i < extsTable.length; i++) { + System.out.println((i+1) + ". ID: " + extsTable[i][0]); + System.out.println(" Version: " + extsTable[i][1]); + System.out.println(" Loc: " + + pip.getPackageLocation(extsTable[i][0])); + System.out.println(); + } + } +} // end of listExtensions() + + +public static XPackageInformationProvider getPip() +{ return PackageInformationProvider.get(Lo.getContext()); } +``` + +Extensions are accessed via the XPackageInformationProvider interface. + + +### 1.4. Examining the Lingu Services + +The LinguServiceManager provides access to three of the four main linguistic +services: the spell checker, the hyphenator, and thesaurus.The proof reader (i.e. the +grammar checker) is managed by a separate Proofreader service, which I'll explain +later. + +Figure 8 shows the interfaces accessible from the LinguServiceManager service. + + + ![](images/10-Linguistics-8.png) -Figure 8. The LinguServiceManager Service and Interfaces. - - -In Lingo.java, the LinguServiceManager is instantiated and then -Write.printServicesInfo() reports details about its services: - -// in Lingo.java - : -// get lingo manager -XLinguServiceManager2 lingoMgr = - Lo.createInstanceMCF(XLinguServiceManager2.class, - "com.sun.star.linguistic2.LinguServiceManager"); -if (lingoMgr == null) { - System.out.println("No linguistics manager found"); - Lo.closeOffice(); - return; -} - -Write.printServicesInfo(lingoMgr); - -Typical output from Write.printServicesInfo(): - -Available Services: -SpellChecker (1): - org.openoffice.lingu.MySpellSpellChecker -Thesaurus (1): - org.openoffice.lingu.new.Thesaurus -Hyphenator (1): - org.openoffice.lingu.LibHnjHyphenator -Proofreader (2): - org.languagetool.openoffice.Main - org.libreoffice.comp.pyuno.Lightproof.en - -Configured Services: -SpellChecker (1): - org.openoffice.lingu.MySpellSpellChecker -Thesaurus (1): - org.openoffice.lingu.new.Thesaurus -Hyphenator (1): - org.openoffice.lingu.LibHnjHyphenator -Proofreader (1): - org.languagetool.openoffice.Main - -Locales for SpellChecker (43): - AR AU BE BO BS BZ CA CA CH CL - CO CR CU DO EC ES FR GB GH GT - HN IE IN JM LU MC MW MX NA NI - NZ PA PE PH PR PY SV TT US UY - VE ZA ZW - -Locales for Thesaurus (43): - AR AU BE BO BS BZ CA CA CH CL - CO CR CU DO EC ES FR GB GH GT - HN IE IN JM LU MC MW MX NA NI - NZ PA PE PH PR PY SV TT US UY - VE ZA ZW - -Locales for Hyphenator (43): - AR AU BE BO BS BZ CA CA CH CL - CO CR CU DO EC ES FR GB GH GT - HN IE IN JM LU MC MW MX NA NI - NZ PA PE PH PR PY SV TT US UY - VE ZA ZW - -Locales for Proofreader (94): - AF AO AR AT AU BE BE - BE BO BR BS BY BZ CA CA CD CH - CH CH CI CL CM CN CR CU DE DE - DK DO EC ES ES ES ES ES FI FR - FR GB GH GR GT HN HT IE IN IN - IN IR IS IT JM JP KH LI LT LU - LU MA MC ML MX MZ NA NI NL NZ - PA PE PH PH PL PR PT PY RE RO - RU SE SI SK SN SV TT UA US US - UY VE ZA ZW - -The print-out contains three lists: a list of available services, a list of configured -services (i.e. ones that are activated inside Office), and a list of the locales available to -each service. - -Figure 8 shows that LinguServiceManager only manages the spell checker, -hyphenator, and thesaurus, and yet Write.printServicesInfo() includes information -about the proof reader. Somewhat confusingly, although LinguServiceManager -cannot instantiate a proof reader it can print information about it. - -The output shows that two proofreader services are available -(org.languagetool.openoffice.Main and org.libreoffice.comp.pyuno.Lightproof.en), -but only one is configured (i.e. active). I'll explain this setup when I talk about the -proof reader later. - -The three lists are generated by Write.printServicesInfo() calling -Write.printAvailServiceInfo(), Write.printConfigServiceInfo(), and -Write.printLocales(): - -// in the Writer class -public static void printServicesInfo( - XLinguServiceManager2 lingoMgr) -{ - com.sun.star.lang.Locale loc = - new com.sun.star.lang.Locale("en","US",""); - // American English locale - - System.out.println("Available Services:"); - printAvailServiceInfo(lingoMgr, "SpellChecker", loc); - printAvailServiceInfo(lingoMgr, "Thesaurus", loc); - printAvailServiceInfo(lingoMgr, "Hyphenator", loc); - printAvailServiceInfo(lingoMgr, "Proofreader", loc); - System.out.println(); - - System.out.println("Configured Services:"); - printConfigServiceInfo(lingoMgr, "SpellChecker", loc); - printConfigServiceInfo(lingoMgr, "Thesaurus", loc); - printConfigServiceInfo(lingoMgr, "Hyphenator", loc); - printConfigServiceInfo(lingoMgr, "Proofreader", loc); - System.out.println(); - - printLocales("SpellChecker", lingoMgr.getAvailableLocales( - "com.sun.star.linguistic2.SpellChecker")); - printLocales("Thesaurus", lingoMgr.getAvailableLocales( - "com.sun.star.linguistic2.Thesaurus")); - printLocales("Hyphenator", lingoMgr.getAvailableLocales( - "com.sun.star.linguistic2.Hyphenator")); - printLocales("Proofreader", lingoMgr.getAvailableLocales( - "com.sun.star.linguistic2.Proofreader")); - System.out.println(); - -} // end of printServicesInfo() - -The choice of services depends on the current locale, so printServicesInfo() begins by -creating an American English locale, which matches my version of Office. - -Write.printAvailServiceInfo() utilizes XLinguServiceManager.getAvailableServices() -to retrieve a list of the available services. In a similar way, -Write.printConfigServiceInfo() calls -XLinguServiceManager.getConfiguredServices(), and printLocales gets an array of -Locale objects from XLinguServiceManager.getAvailableLocales(). - - - -## 2. Using the Spell Checker - -There's a few examples in Lingo.java of applying the spell checker to individual -words: - -// in Lingo.java -XSpellChecker speller = lingoMgr.getSpellChecker(); - -Write.spellWord("horseback", speller); -Write.spellWord("ceurse", speller); -Write.spellWord("magisian", speller); -Write.spellWord("ellucidate", speller); - -XLinguServiceManager.getSpellChecker() returns a reference to the spell checker, -and Write.spellWord() checks the supplied word. For the code above, the following is -printed: - -* "ceurse" is unknown. Try: -No. of names: 8 - "curse" "course" "secateurs" "cerise" - "surcease" "secure" "cease" "Ceausescu" - -* "magisian" is unknown. Try: -No. of names: 7 - "magician" "magnesia" "Malaysian" "mismanage" - "imagining" "mastication" "fumigation" - -* "ellucidate" is unknown. Try: -No. of names: 7 - "elucidate" "elucidation" "hallucinate" "pellucid" - "fluoridate" "elasticated" "illustrated" - -Nothing is reported for "horseback" because that's correctly spelt, and spellWord() -returns the boolean true. - -The SpellChecker service and its important interfaces are shown in Figure 9. - - - +Figure 8. The LinguServiceManager Service and Interfaces. + + +In Lingo.java, the LinguServiceManager is instantiated and then +Write.printServicesInfo() reports details about its services: + +```java +// in Lingo.java + : +// get lingo manager +XLinguServiceManager2 lingoMgr = + Lo.createInstanceMCF(XLinguServiceManager2.class, + "com.sun.star.linguistic2.LinguServiceManager"); +if (lingoMgr == null) { + System.out.println("No linguistics manager found"); + Lo.closeOffice(); + return; +} + +Write.printServicesInfo(lingoMgr); +``` + +Typical output from Write.printServicesInfo(): + +``` +Available Services: +SpellChecker (1): + org.openoffice.lingu.MySpellSpellChecker +Thesaurus (1): + org.openoffice.lingu.new.Thesaurus +Hyphenator (1): + org.openoffice.lingu.LibHnjHyphenator +Proofreader (2): + org.languagetool.openoffice.Main + org.libreoffice.comp.pyuno.Lightproof.en + +Configured Services: +SpellChecker (1): + org.openoffice.lingu.MySpellSpellChecker +Thesaurus (1): + org.openoffice.lingu.new.Thesaurus +Hyphenator (1): + org.openoffice.lingu.LibHnjHyphenator +Proofreader (1): + org.languagetool.openoffice.Main + +Locales for SpellChecker (43): + AR AU BE BO BS BZ CA CA CH CL + CO CR CU DO EC ES FR GB GH GT + HN IE IN JM LU MC MW MX NA NI + NZ PA PE PH PR PY SV TT US UY + VE ZA ZW + +Locales for Thesaurus (43): + AR AU BE BO BS BZ CA CA CH CL + CO CR CU DO EC ES FR GB GH GT + HN IE IN JM LU MC MW MX NA NI + NZ PA PE PH PR PY SV TT US UY + VE ZA ZW + +Locales for Hyphenator (43): + AR AU BE BO BS BZ CA CA CH CL + CO CR CU DO EC ES FR GB GH GT + HN IE IN JM LU MC MW MX NA NI + NZ PA PE PH PR PY SV TT US UY + VE ZA ZW + +Locales for Proofreader (94): + AF AO AR AT AU BE BE + BE BO BR BS BY BZ CA CA CD CH + CH CH CI CL CM CN CR CU DE DE + DK DO EC ES ES ES ES ES FI FR + FR GB GH GR GT HN HT IE IN IN + IN IR IS IT JM JP KH LI LT LU + LU MA MC ML MX MZ NA NI NL NZ + PA PE PH PH PL PR PT PY RE RO + RU SE SI SK SN SV TT UA US US + UY VE ZA ZW +``` + +The print-out contains three lists: a list of available services, a list of configured +services (i.e. ones that are activated inside Office), and a list of the locales available to +each service. + +Figure 8 shows that LinguServiceManager only manages the spell checker, +hyphenator, and thesaurus, and yet Write.printServicesInfo() includes information +about the proof reader. Somewhat confusingly, although LinguServiceManager +cannot instantiate a proof reader it can print information about it. + +The output shows that two proofreader services are available +(org.languagetool.openoffice.Main and org.libreoffice.comp.pyuno.Lightproof.en), +but only one is configured (i.e. active). I'll explain this setup when I talk about the +proof reader later. + +The three lists are generated by Write.printServicesInfo() calling +Write.printAvailServiceInfo(), Write.printConfigServiceInfo(), and +Write.printLocales(): + +```java +// in the Writer class +public static void printServicesInfo( + XLinguServiceManager2 lingoMgr) +{ + com.sun.star.lang.Locale loc = + new com.sun.star.lang.Locale("en","US",""); + // American English locale + + System.out.println("Available Services:"); + printAvailServiceInfo(lingoMgr, "SpellChecker", loc); + printAvailServiceInfo(lingoMgr, "Thesaurus", loc); + printAvailServiceInfo(lingoMgr, "Hyphenator", loc); + printAvailServiceInfo(lingoMgr, "Proofreader", loc); + System.out.println(); + + System.out.println("Configured Services:"); + printConfigServiceInfo(lingoMgr, "SpellChecker", loc); + printConfigServiceInfo(lingoMgr, "Thesaurus", loc); + printConfigServiceInfo(lingoMgr, "Hyphenator", loc); + printConfigServiceInfo(lingoMgr, "Proofreader", loc); + System.out.println(); + + printLocales("SpellChecker", lingoMgr.getAvailableLocales( + "com.sun.star.linguistic2.SpellChecker")); + printLocales("Thesaurus", lingoMgr.getAvailableLocales( + "com.sun.star.linguistic2.Thesaurus")); + printLocales("Hyphenator", lingoMgr.getAvailableLocales( + "com.sun.star.linguistic2.Hyphenator")); + printLocales("Proofreader", lingoMgr.getAvailableLocales( + "com.sun.star.linguistic2.Proofreader")); + System.out.println(); + +} // end of printServicesInfo() +``` + +The choice of services depends on the current locale, so printServicesInfo() begins by +creating an American English locale, which matches my version of Office. + +Write.printAvailServiceInfo() utilizes XLinguServiceManager.getAvailableServices() +to retrieve a list of the available services. In a similar way, +Write.printConfigServiceInfo() calls +XLinguServiceManager.getConfiguredServices(), and printLocales gets an array of +Locale objects from XLinguServiceManager.getAvailableLocales(). + + +## 2. Using the Spell Checker + +There's a few examples in Lingo.java of applying the spell checker to individual +words: + +```java +// in Lingo.java +XSpellChecker speller = lingoMgr.getSpellChecker(); + +Write.spellWord("horseback", speller); +Write.spellWord("ceurse", speller); +Write.spellWord("magisian", speller); +Write.spellWord("ellucidate", speller); +``` + +XLinguServiceManager.getSpellChecker() returns a reference to the spell checker, +and Write.spellWord() checks the supplied word. For the code above, the following is +printed: + +``` +* "ceurse" is unknown. Try: +No. of names: 8 + "curse" "course" "secateurs" "cerise" + "surcease" "secure" "cease" "Ceausescu" + +* "magisian" is unknown. Try: +No. of names: 7 + "magician" "magnesia" "Malaysian" "mismanage" + "imagining" "mastication" "fumigation" + +* "ellucidate" is unknown. Try: +No. of names: 7 + "elucidate" "elucidation" "hallucinate" "pellucid" + "fluoridate" "elasticated" "illustrated" +``` + +Nothing is reported for "horseback" because that's correctly spelt, and spellWord() +returns the boolean true. + +The SpellChecker service and its important interfaces are shown in Figure 9. + ![](images/10-Linguistics-9.png) -Figure 9. The SpellChecker Service and Interfaces. - - -Write.spellWord() utilizes XSpellChecker.spell() to find a spelling mistake, then -prints the alternative spellings: - -// in the Write class -public static boolean spellWord(String word, XSpellChecker speller) -{ - com.sun.star.lang.Locale loc = - new com.sun.star.lang.Locale("en", "US", ""); - // American English - PropertyValue[] props = new PropertyValue[0]; - - XSpellAlternatives alts = speller.spell(word, loc, props); - if (alts != null) { - System.out.println("* \"" + word + "\" is unknown. Try:"); - String[] altWords = alts.getAlternatives(); - Lo.printNames(altWords); - return false; - } - else - return true; -} // end of spellWord() - -XSpellChecker.spell() requires a locale and an array of properties, which I've left -empty. The properties are those associated with XLinguProperties, which were listed -above using Write.getLinguProperties(). Its output shows that "IsSpellCapitalization" -is presently true, which means that words in all-caps will be checked. The property -can be changed to false inside the PropertyValue array passed to -XSpellChecker.spell(). For example: - -PropertyValue[] props = - Props.makeProps("IsSpellCapitalization", false); -XSpellAlternatives alts = speller.spell(word, loc, props); - -Now an incorrectly spelt word in all-caps, such as "CEURSE", will be skipped over. - -This means that Write.spellWord("CEURSE", speller) should return true. - -Unfortunately, XSpellChecker.spell() seems to ignore the property array, and still -reports "CEURSE" as incorrectly spelt. - -Even a property change performed through the XLinguProperties interface, such as: - -XLinguProperties linguProps = Write.getLinguProperties(); -Props.setProperty(linguProps, "IsSpellCapitalization", false); - -fails to change XSpellChecker.spell()'s behavior. The only way to make a change to -the linguistic properties that is acted upon is through the "Options" pane in the -"Writing Aids" dialog, as in Figure 10. - - - +Figure 9. The SpellChecker Service and Interfaces. + + +Write.spellWord() utilizes XSpellChecker.spell() to find a spelling mistake, then +prints the alternative spellings: + +```java +// in the Write class +public static boolean spellWord(String word, XSpellChecker speller) +{ + com.sun.star.lang.Locale loc = + new com.sun.star.lang.Locale("en", "US", ""); + // American English + PropertyValue[] props = new PropertyValue[0]; + + XSpellAlternatives alts = speller.spell(word, loc, props); + if (alts != null) { + System.out.println("* \"" + word + "\" is unknown. Try:"); + String[] altWords = alts.getAlternatives(); + Lo.printNames(altWords); + return false; + } + else + return true; +} // end of spellWord() +``` + +XSpellChecker.spell() requires a locale and an array of properties, which I've left +empty. The properties are those associated with XLinguProperties, which were listed +above using Write.getLinguProperties(). Its output shows that "IsSpellCapitalization" +is presently true, which means that words in all-caps will be checked. The property +can be changed to false inside the PropertyValue array passed to +XSpellChecker.spell(). For example: + +```java +PropertyValue[] props = + Props.makeProps("IsSpellCapitalization", false); +XSpellAlternatives alts = speller.spell(word, loc, props); +``` + +Now an incorrectly spelt word in all-caps, such as "CEURSE", will be skipped over. + +This means that Write.spellWord("CEURSE", speller) should return true. + +Unfortunately, XSpellChecker.spell() seems to ignore the property array, and still +reports "CEURSE" as incorrectly spelt. + +Even a property change performed through the XLinguProperties interface, such as: + +```java +XLinguProperties linguProps = Write.getLinguProperties(); +Props.setProperty(linguProps, "IsSpellCapitalization", false); +``` + +fails to change XSpellChecker.spell()'s behavior. The only way to make a change to +the linguistic properties that is acted upon is through the "Options" pane in the +"Writing Aids" dialog, as in Figure 10. + ![](images/10-Linguistics-10.png) -Figure 10. Changing the Capitalization Property. +Figure 10. Changing the Capitalization Property. + - -Office's default spell checker is Hunspell (from http://hunspell.github.io/), and has -been part of OpenOffice since v.2, when it replaced MySpell, adding several features -such as support for Unicode. The "MySpell" name lives on in a few places, such as in -the spelling service (org.openoffice.lingu.MySpellSpellChecker). +Office's default spell checker is Hunspell (from http://hunspell.github.io/), and has +been part of OpenOffice since v.2, when it replaced MySpell, adding several features +such as support for Unicode. The "MySpell" name lives on in a few places, such as in +the spelling service (org.openoffice.lingu.MySpellSpellChecker). -Hunspell offers extra properties in addition to those in the "Options" pane of the -"Writing Aids" dialog. They can be accessed through the Tools > Options > Language -Settings > "English sentence checking" dialog shown in Figure 11. +Hunspell offers extra properties in addition to those in the "Options" pane of the +"Writing Aids" dialog. They can be accessed through the Tools, Options, Language +Settings, "English sentence checking" dialog shown in Figure 11. - - ![](images/10-Linguistics-11.png) -Figure 11. The English Sentence Checking Dialog. +Figure 11. The English Sentence Checking Dialog. + - -The same dialog can also be reached through the Extension Manager window shown -back in Figure 7. Click on the "English Spelling dictionaries" extension, and then -press the "Options" button which appears as in Figure 12. +The same dialog can also be reached through the Extension Manager window shown +back in Figure 7. Click on the "English Spelling dictionaries" extension, and then +press the "Options" button which appears as in Figure 12. - - ![](images/10-Linguistics-12.png) -Figure 12. The English Spelling Options Button. - - -Unfortunately, there appears to be no API for accessing these Hunspell options. The -best that can be done is to use a dispatch message to open the "English Sentence -Checking" dialog in Figure 11. This done by calling Write.openSentCheckOptions(): - -GUI.setVisible(doc, true); // Office must be visible... - -Lo.wait(2000); -Write.openSentCheckOptions(); // for the dialog to appear - -Write.openSentCheckOptions() uses an ".uno:OptionsTreeDialog" dispatch along -with an URL argument for the dialog's XML definition file: - -// in the Writer class -public static void openSentCheckOptions() -// open "Options - Language Settings - English sentence checking -{ - XPackageInformationProvider pip = Info.getPip(); - String langExt = pip.getPackageLocation( - "org.openoffice.en.hunspell.dictionaries"); - // System.out.println("Lang Ext: " + langExt); - String url = langExt + "/dialog/en.xdl"; - - PropertyValue[] props = Props.makeProps("OptionsPageURL", url); - Lo.dispatchCmd("OptionsTreeDialog", props); - Lo.wait(2000); -} // end of openSentCheckOptions() - -The XML file's location is obtained in two steps. First the ID of the Hunspell service -("org.openoffice.en.hunspell.dictionaries") is passed to -XPackageInformationProvider.getPackageLocation() to obtain the spell checker's -installation folder. Figure 13 shows the directory on my machine. - - - +Figure 12. The English Spelling Options Button. + + +Unfortunately, there appears to be no API for accessing these Hunspell options. The +best that can be done is to use a dispatch message to open the "English Sentence +Checking" dialog in Figure 11. This done by calling Write.openSentCheckOptions(): + +```java +GUI.setVisible(doc, true); // Office must be visible... +Lo.wait(2000); +Write.openSentCheckOptions(); // for the dialog to appear +``` + +Write.openSentCheckOptions() uses an ".uno:OptionsTreeDialog" dispatch along +with an URL argument for the dialog's XML definition file: + +```java +// in the Writer class +public static void openSentCheckOptions() +// open "Options - Language Settings - English sentence checking +{ + XPackageInformationProvider pip = Info.getPip(); + String langExt = pip.getPackageLocation( + "org.openoffice.en.hunspell.dictionaries"); + // System.out.println("Lang Ext: " + langExt); + String url = langExt + "/dialog/en.xdl"; + + PropertyValue[] props = Props.makeProps("OptionsPageURL", url); + Lo.dispatchCmd("OptionsTreeDialog", props); + Lo.wait(2000); +} // end of openSentCheckOptions() +``` + +The XML file's location is obtained in two steps. First the ID of the Hunspell service +("org.openoffice.en.hunspell.dictionaries") is passed to +XPackageInformationProvider.getPackageLocation() to obtain the spell checker's +installation folder. Figure 13 shows the directory on my machine. + ![](images/10-Linguistics-13.png) -Figure 13. The Hunspell Installation Folder. - - -The directory contains a dialog\ subdirectory, which holds an XXX.xdl file that -defines the dialog's XML structure and data. The "XXX" name will be Office's locale -language, which in my case is "en". - -The URL required by the "OptionsTreeDialog" dispatch is constructed by appending -"/dialog/en.xdl" to the installation folder string. - - - -## 3. Using the Thesaurus - -Lingo.java contains two examples of how to use the thesaurus: - -// in Lingo.java - : -XLinguServiceManager2 lingoMgr = - Lo.createInstanceMCF(XLinguServiceManager2.class, - "com.sun.star.linguistic2.LinguServiceManager"); - : -XThesaurus thesaurus = lingoMgr.getThesaurus(); -Write.printMeaning("magician", thesaurus); -Write.printMeaning("elucidate", thesaurus); - -The output from the first call to Write.printMeaning() is: - -"magician" found in thesaurus; number of meanings: 2 -## 1. Meaning: (noun) prestidigitator - - No. of synonyms: 6 - prestidigitator - conjurer - conjuror - illusionist - performer (generic term) - performing artist (generic term) - -## 2. Meaning: (noun) sorcerer - - No. of synonyms: 6 - sorcerer - wizard - necromancer - thaumaturge - thaumaturgist - occultist (generic term) - -XLinguServiceManager2.getThesaurus() returns an instance of XThesaurus whose -service and main interfaces are shown in Figure 14. - - - +Figure 13. The Hunspell Installation Folder. + + +The directory contains a dialog\ subdirectory, which holds an XXX.xdl file that +defines the dialog's XML structure and data. The "XXX" name will be Office's locale +language, which in my case is "en". + +The URL required by the "OptionsTreeDialog" dispatch is constructed by appending +"/dialog/en.xdl" to the installation folder string. + + +## 3. Using the Thesaurus + +Lingo.java contains two examples of how to use the thesaurus: + +```java +// in Lingo.java + : +XLinguServiceManager2 lingoMgr = + Lo.createInstanceMCF(XLinguServiceManager2.class, + "com.sun.star.linguistic2.LinguServiceManager"); + : +XThesaurus thesaurus = lingoMgr.getThesaurus(); +Write.printMeaning("magician", thesaurus); +Write.printMeaning("elucidate", thesaurus); +``` + +The output from the first call to Write.printMeaning() is: + +``` +"magician" found in thesaurus; number of meanings: 2 +1. Meaning: (noun) prestidigitator + + No. of synonyms: 6 + prestidigitator + conjurer + conjuror + illusionist + performer (generic term) + performing artist (generic term) + +2. Meaning: (noun) sorcerer + + No. of synonyms: 6 + sorcerer + wizard + necromancer + thaumaturge + thaumaturgist + occultist (generic term) +``` + +XLinguServiceManager2.getThesaurus() returns an instance of XThesaurus whose +service and main interfaces are shown in Figure 14. + ![](images/10-Linguistics-14.png) -Figure 14. The Thesaurus Service and Interfaces. - - -Write.printMeaning() calls XThesaurus.queryMeanings(), and prints the array of -results: - -public static int printMeaning(String word, XThesaurus thesaurus) -{ - com.sun.star.lang.Locale loc = - new com.sun.star.lang.Locale("en", "US", ""); - // American English - PropertyValue[] props = new PropertyValue[0]; - - XMeaning[] meanings = thesaurus.queryMeanings(word, loc, props); - if (meanings == null) { - System.out.println("\"" + word + - "\" NOT found in thesaurus\n"); - return 0; - } - else { - System.out.println("\"" + word + "\" found in thesaurus; - number of meanings: " + meanings.length); - for (int i=0; i < meanings.length; i++) { - System.out.println((i+1) + ". Meaning: " + - meanings[i].getMeaning()); - String[] synonyms = meanings[i].querySynonyms(); - System.out.println(" No. of synonyms: " + synonyms.length); - for (int k=0; k < synonyms.length; k++) - System.out.println(" " + synonyms[k]); - System.out.println(); - } - return meanings.length; - } -} // end of printMeaning() - -In a similar way to XSpellChecker.spell(), XThesaurus.queryMeanings() requires a -locale and an optional array of properties. printMeaning() utilizes American English, -and no properties. - -If you need a non-English thesaurus which isn't part of Office, then look through the +Figure 14. The Thesaurus Service and Interfaces. + + +Write.printMeaning() calls XThesaurus.queryMeanings(), and prints the array of +results: + +```java +public static int printMeaning(String word, XThesaurus thesaurus) +{ + com.sun.star.lang.Locale loc = + new com.sun.star.lang.Locale("en", "US", ""); + // American English + PropertyValue[] props = new PropertyValue[0]; + + XMeaning[] meanings = thesaurus.queryMeanings(word, loc, props); + if (meanings == null) { + System.out.println("\"" + word + + "\" NOT found in thesaurus\n"); + return 0; + } + else { + System.out.println("\"" + word + "\" found in thesaurus; + number of meanings: " + meanings.length); + for (int i=0; i < meanings.length; i++) { + System.out.println((i+1) + ". Meaning: " + + meanings[i].getMeaning()); + String[] synonyms = meanings[i].querySynonyms(); + System.out.println(" No. of synonyms: " + synonyms.length); + for (int k=0; k < synonyms.length; k++) + System.out.println(" " + synonyms[k]); + System.out.println(); + } + return meanings.length; + } +} // end of printMeaning() +``` + +In a similar way to XSpellChecker.spell(), XThesaurus.queryMeanings() requires a +locale and an optional array of properties. printMeaning() utilizes American English, +and no properties. + +If you need a non-English thesaurus which isn't part of Office, then look through the dictionary extensions at http://extensions.libreoffice.org/extension- -center?getCategories=Dictionary; many include a thesaurus with the dictionary. +center?getCategories=Dictionary; many include a thesaurus with the dictionary. -Thesaurus data is stored as ".idx" and ".dat" files in the same directory as the spell -checker (i.e. in \share\extensions\dict-en\), as can be seen in Figure 13. +Thesaurus data is stored as ".idx" and ".dat" files in the same directory as the spell +checker (i.e. in \share\extensions\dict-en\), as can be seen in Figure 13. The files are built from WordNet data (http://wordnet.princeton.edu/), but use a text- -based format explained very briefly in Daniel Naber's slides about the +based format explained very briefly in Daniel Naber's slides about the Lingucomponent Project (at http://www.danielnaber.de/publications/ooocon2005- -lingucomponent.pdf). Also, the Lingucomponent website has some C++ code for +lingucomponent.pdf). Also, the Lingucomponent website has some C++ code for reading ".idx" and '.dat" files (in http://www.openoffice.org/lingucomponent/MyThes- -1.zip). - -However, if you want to write code using a thesaurus independently of Office, then -I'd suggest programming with one of the many APIs for WordNet; there are currently -nine for Java, listed at http://wordnet.princeton.edu/wordnet/related-projects/#Java. - - - -## 4. Grammar Checking - -Office's default grammar checker (or proof reader) is Lightproof, a Python application -developed by László Németh. Lightproof.py, and its support files, are installed in the -same folder as the spell checker and thesaurus; on my machine that's -\share\extensions\dict-en\. - -Older versions of Lightproof are available from OpenOffice's extensions website at -http://extensions.services.openoffice.org/project/lightproof. One reason for -downloading the old version is that it contains documentation on adding new -grammar rules missing from the version installed in Office. - -Another way to modify Lightproof's grammar rules is with its editor, which can be -obtained from http://extensions.libreoffice.org/extension-center/lightproof-editor. - -There are a number of alternative grammar checkers for Office, including "After the -Deadline" (http://www.afterthedeadline.com/) and "LanguageTool" -(https://www.languagetool.org/), which are easily added to Office as extensions via -the "Language Tools" subsection of the website, at -http://extensions.libreoffice.org/extension-center?getCategories=Language%20Tools. - -When I first coded my examples, I used the default Lightproof for grammar checking, -but it doesn't have a very extensive set of built-in grammar rules (it seems best at -catching punctuation mistakes). I switched to LanguageTool because of its larger set -of rules, and its support for many languages. I also like that it can be used as a -standalone Java library, separate from Office, and that its site includes lots of -documentation. Perhaps its biggest drawback is that it requires Java 8 or later. - -Another issue is that LanguageTool and Lightproof cannot happily coexist inside -Office. Lightproof must be disabled and LanguageTool enabled via the Options > -Language Settings > Writing aids > "Available language modules" pane at the top of +1.zip). -![](images/10-Linguistics-15.png) +However, if you want to write code using a thesaurus independently of Office, then +I'd suggest programming with one of the many APIs for WordNet; there are currently +nine for Java, listed at http://wordnet.princeton.edu/wordnet/related-projects/#Java. + + + +## 4. Grammar Checking + +Office's default grammar checker (or proof reader) is Lightproof, a Python application +developed by László Németh. Lightproof.py, and its support files, are installed in the +same folder as the spell checker and thesaurus; on my machine that's +\share\extensions\dict-en\. -Figure 15. +Older versions of Lightproof are available from OpenOffice's extensions website at +http://extensions.services.openoffice.org/project/lightproof. One reason for +downloading the old version is that it contains documentation on adding new +grammar rules missing from the version installed in Office. + +Another way to modify Lightproof's grammar rules is with its editor, which can be +obtained from http://extensions.libreoffice.org/extension-center/lightproof-editor. + +There are a number of alternative grammar checkers for Office, including "After the +Deadline" (http://www.afterthedeadline.com/) and "LanguageTool" +(https://www.languagetool.org/), which are easily added to Office as extensions via +the "Language Tools" subsection of the website, at +http://extensions.libreoffice.org/extension-center?getCategories=Language%20Tools. + +When I first coded my examples, I used the default Lightproof for grammar checking, +but it doesn't have a very extensive set of built-in grammar rules (it seems best at +catching punctuation mistakes). I switched to LanguageTool because of its larger set +of rules, and its support for many languages. I also like that it can be used as a +standalone Java library, separate from Office, and that its site includes lots of +documentation. Perhaps its biggest drawback is that it requires Java 8 or later. + +Another issue is that LanguageTool and Lightproof cannot happily coexist inside +Office. Lightproof must be disabled and LanguageTool enabled via the Options, +Language Settings, Writing aids, "Available language modules" pane at the top of +Figure 15. - - ![](images/10-Linguistics-15.png) -Figure 15. Goodbye Lightproof, hello LanguageTool. - - -I used Write.printServicesInfo() earlier to list the available and configured services. - -The output included: - -Available Services: - : -Proofreader (2): - org.languagetool.openoffice.Main - org.libreoffice.comp.pyuno.Lightproof.en - -Configured Services: - : -Proofreader (1): - org.languagetool.openoffice.Main - -"org.languagetool.openoffice.Main" refers to the LanguageTool extension, while -"org.libreoffice.comp.pyuno.Lightproof.en" is the English version of Lightproof. - -This information can be used to set the proof reader. LanguageTool is made the -default by calling Write.setConfiguredServices() like so: - -Write.setConfiguredServices(lingoMgr, "Proofreader", - "org.languagetool.openoffice.Main"); - -Alternatively, Lightproof can be enabled with: - -Write.setConfiguredServices(lingoMgr, "Proofreader", - "org.libreoffice.comp.pyuno.Lightproof.en"); - -The code for Write.setConfiguredServices() is: - -// in the Write class -public static void setConfiguredServices( - XLinguServiceManager2 lingoMgr, - String service, String implName) -{ - com.sun.star.lang.Locale loc = - new com.sun.star.lang.Locale("en","US",""); - String[] implNames = { implName }; - lingoMgr.setConfiguredServices( - "com.sun.star.linguistic2." + service, loc, implNames); -} // end of setConfiguredServices() - -The function utilizes XLinguServiceManager.setConfiguredServices() to attach a -particular implementation service (e.g. LanguageTool) to a specified linguistic service -(e.g. the Proofreader). - - -### 4.1. Error Reporting Options - -The kinds of errors reported by the proof reader can be adjusted through Office's GUI. - -One configuration pane, used by both Lightproof and LanguageTool, is in the -"English Sentence Checking" dialog shown back in Figure 11. If you look closely, the -first group of check boxes are titled "Grammar checking". - -If you install LanguageTool, Office's Tools menu will be modified to contain a new -"LanguageTool" submenu shown in Figure 16. - - - +Figure 15. Goodbye Lightproof, hello LanguageTool. + + +I used Write.printServicesInfo() earlier to list the available and configured services. + +The output included: + +``` +Available Services: + : +Proofreader (2): + org.languagetool.openoffice.Main + org.libreoffice.comp.pyuno.Lightproof.en + +Configured Services: + : +Proofreader (1): + org.languagetool.openoffice.Main +``` + +"org.languagetool.openoffice.Main" refers to the LanguageTool extension, while +"org.libreoffice.comp.pyuno.Lightproof.en" is the English version of Lightproof. + +This information can be used to set the proof reader. LanguageTool is made the +default by calling Write.setConfiguredServices() like so: + +```java +Write.setConfiguredServices(lingoMgr, "Proofreader", + "org.languagetool.openoffice.Main"); +``` + +Alternatively, Lightproof can be enabled with: + +```java +Write.setConfiguredServices(lingoMgr, "Proofreader", + "org.libreoffice.comp.pyuno.Lightproof.en"); +``` + +The code for Write.setConfiguredServices() is: + +```java +// in the Write class +public static void setConfiguredServices( + XLinguServiceManager2 lingoMgr, + String service, String implName) +{ + com.sun.star.lang.Locale loc = + new com.sun.star.lang.Locale("en","US",""); + String[] implNames = { implName }; + lingoMgr.setConfiguredServices( + "com.sun.star.linguistic2." + service, loc, implNames); +} // end of setConfiguredServices() +``` + +The function utilizes XLinguServiceManager.setConfiguredServices() to attach a +particular implementation service (e.g. LanguageTool) to a specified linguistic service +(e.g. the Proofreader). + + +### 4.1. Error Reporting Options + +The kinds of errors reported by the proof reader can be adjusted through Office's GUI. + +One configuration pane, used by both Lightproof and LanguageTool, is in the +"English Sentence Checking" dialog shown back in Figure 11. If you look closely, the +first group of check boxes are titled "Grammar checking". + +If you install LanguageTool, Office's Tools menu will be modified to contain a new +"LanguageTool" submenu shown in Figure 16. + ![](images/10-Linguistics-16.png) -Figure 16. The LanguageTool Submenu. +Figure 16. The LanguageTool Submenu. + - -The "Options" menu item in the "LanguageTool" submenu brings up an extensive set -of options, reflecting the greater number of grammar rules in the checker (see Figure -17). +The "Options" menu item in the "LanguageTool" submenu brings up an extensive set +of options, reflecting the greater number of grammar rules in the checker (see Figure +17). - - ![](images/10-Linguistics-17.png) -Figure 17. The LanguageTool Options Dialog. - - -Unfortunately, there seems to be no way to modify these options through Office's -Proofreader API. - - -### 4.2. Using the Proof Reader - -In Lingo.java the proof reader is loaded and called like so: - -// in Lingo.java - : -XProofreader proofreader = Write.loadProofreader(); -System.out.println("Proofing..."); -int numErrs = - Write.proofSentence("i dont have one one dogs.", proofreader); -System.out.println("No. of proofing errors: " + numErrs + "\n"); - -The output is: - -Proofing... - -G* This sentence does not start with an uppercase letter in: "i" - Suggested change: "I" - -G* Spelling mistake in: "dont" - Suggested change: "don't" - -G* Word repetition in: "one one" - Suggested change: "one" - -No. of proofing errors: 3 - -The proof reader isn't accessed through the linguistics manager; instead a Proofreader -service is created, and its interfaces employed. A simplified view of the services and -interfaces are shown in Figure 18. - - - +Figure 17. The LanguageTool Options Dialog. + + +Unfortunately, there seems to be no way to modify these options through Office's +Proofreader API. + + +### 4.2. Using the Proof Reader + +In Lingo.java the proof reader is loaded and called like so: + +```java +// in Lingo.java + : +XProofreader proofreader = Write.loadProofreader(); +System.out.println("Proofing..."); +int numErrs = + Write.proofSentence("i dont have one one dogs.", proofreader); +System.out.println("No. of proofing errors: " + numErrs + "\n"); +``` + +The output is: + +Proofing... + +``` +G* This sentence does not start with an uppercase letter in: "i" + Suggested change: "I" + +G* Spelling mistake in: "dont" + Suggested change: "don't" + +G* Word repetition in: "one one" + Suggested change: "one" + +No. of proofing errors: 3 +``` + +The proof reader isn't accessed through the linguistics manager; instead a Proofreader +service is created, and its interfaces employed. A simplified view of the services and +interfaces are shown in Figure 18. + ![](images/10-Linguistics-18.png) -Figure 18. The Proofreader Service and Interfaces. - - -Write.loadProofreader() creates the service: - -public static XProofreader loadProofreader() -{ return Lo.createInstanceMCF(XProofreader.class, - "com.sun.star.linguistic2.Proofreader"); } - -Write.proofSentence() passes a sentence to XProofreader.doProofreading(), and prints -the errors inside the returned ProofreadingResult instance: - -public static int proofSentence(String sent, - XProofreader proofreader) -{ com.sun.star.lang.Locale loc = - new com.sun.star.lang.Locale("en", "US", ""); - // American English - PropertyValue[] props = new PropertyValue[0]; - int numErrs = 0; - ProofreadingResult prRes = - proofreader.doProofreading("1", sent, loc, - 0, sent.length(), props); - if (prRes != null) { - SingleProofreadingError[] errs = prRes.aErrors; - if (errs.length > 0) - for(SingleProofreadingError err : errs) { - printProofError(sent, err); - numErrs++; - } - } - return numErrs; -} // end of proofSentence() - - -public static void printProofError(String str, - SingleProofreadingError err) -{ String errText = str.substring(err.nErrorStart, - err.nErrorStart + err.nErrorLength); - System.out.println("G* " + err.aShortComment + - " in: \"" + errText + "\""); - if (err.aSuggestions.length > 0) - System.out.println(" Suggested change: \"" + - err.aSuggestions[0] + "\""); - System.out.println(); -} // end of printProofError() - -XProofreader.doProofreading() requires a locale and properties array in the same way -as the earlier spell checking and thesaurus methods. It also needs two indices for the -start and end of the sentence being checked, and a document ID which is used in the -error results. - -The results are returned as an array of SingleProofreadingError objects, one for each -detected error. It's worth having a look at the documentation for the -SingleProofreadingError class (use "lodoc SingleProofreadingError"), since the -object contains more information than I've used in Write.printProofError(); for -example, the ID of the grammar rule that was 'broken', a full comment string, and -multiple suggestions in a String array. - -Grammar rule IDs are one area where the proof reader API could be improved. The -XProofreader interface includes methods for switching on and off rules based on their -IDs, but there's no way to find out what these IDs are except by looking at -SingleProofreadingError objects. - - - -## 5. Guessing the Language used in a String - -An oft overlooked linguistics API feature is the ability to guess the language used in a -string, which is implemented by one service, LanguageGuessing, and a single -interface, XLanguageGuessing. The documentation for XLanguageGuessing includes -a long list of supported languages, including Irish Gaelic, Scots Gaelic, and Manx -Gaelic. - -There are two examples of language guessing in Lingo.java: - -// in Lingo.java - : -Locale loc = Write.guessLocale( - "The rain in Spain stays mainly on the plain."); -Write.printLocale(loc); -if (loc != null) - System.out.println("Guessed language: " + loc.Language); - -loc = Write.guessLocale( - "A vaincre sans péril, on triomphe sans gloire."); - // To win without risk is a triumph without glory. - -if (loc != null) - System.out.println("Guessed language: " + loc.Language); - -The output is: - -Locale lang: "English"; country: ""; variant: "" -Guessed language: en -Guessed language: fr - -Write.guessLocale() creates the service, its interface, and calls -XLanguageGuessing.guessPrimaryLanguage(): - -// in the Writer class -public static com.sun.star.lang.Locale guessLocale(String testStr) -{ - XLanguageGuessing guesser = - Lo.createInstanceMCF(XLanguageGuessing.class, - "com.sun.star.linguistic2.LanguageGuessing"); - if (guesser == null) { - System.out.println("No language guesser found"); - return null; - } - else - return guesser.guessPrimaryLanguage(testStr, - 0, testStr.length()); -} // end of guessLocale() - -XLanguageGuessing actually guesses a locale rather than a language, and the locale -includes information about the language, country and a variant BCP 47 language -label. com.sun.star.lang.Locale stores that information as three strings, but XLocale -has additional methods for displaying the string codes in longer formats (e.g. "th" as -"Thailand"). Unfortunately, I was unable to get code using XLocale to compile, so fell -back to Java's Locale class which includes similar methods. - -Write.printLocale() converts an Office locale into a Java version, then prints its data -in long form: - -public static void printLocale(com.sun.star.lang.Locale loc) -{ - if (loc != null) { - java.util.Locale jloc = - new java.util.Locale(loc.Language, loc.Country, loc.Variant); - System.out.println("Locale lang: \"" + - jloc.getDisplayLanguage() + - "\"; country: \"" + jloc.getDisplayCountry() + - "\"; variant: \"" + jloc.getDisplayVariant() + "\""); - } -} // end of printLocale() - - -## 6. Spell Checking and Grammar Checking a Document - -Lingo.java only spell checks individual words, and grammar checks a single sentence. - -The LingoFile.java example shows how these features can be applied to an entire -document. - -One way to scan every sentence in a document is to combine XParagraphCursor and -XSentenceCursor, as in the TalkingBook.java example from Chapter 5, section 6. An -outer loop iterates over every paragraph using XParagraphCursor, and an inner loop -splits each paragraph into sentences with the help of XSentenceCursor. Initially, I -coded LingoFile.java in this way, but found that XSentenceCursor occasionally didn't -divide a paragraph into the correct number of sentences; sometimes two sentences -were treated as one. - -So I switched to a combined Office/Java approach – the outer loop in LingoFile.java -still utilizes XParagraphCursor to scan the paragraphs, but the sentences in a -paragraph are extracted using a sentence-based BreakIterator. - -The main() function of LingoFile.java: - -public static void main(String args[]) -{ - if (args.length < 1) { - System.out.println("Usage: run LingoFile "); - return; - } - - XComponentLoader loader = Lo.loadOffice(); - XTextDocument doc = Write.openDoc(args[0], loader); - if (doc == null) { - System.out.println("Could not open " + args[0]); - Lo.closeOffice(); - return; - } - - // load spell checker, proof reader - XSpellChecker speller = Write.loadSpellChecker(); - XProofreader proofreader = Write.loadProofreader(); - - BreakIterator bi = BreakIterator.getSentenceInstance( - java.util.Locale.US); - - // iterate through the doc by paragraphs - XParagraphCursor paraCursor = Write.getParagraphCursor(doc); - paraCursor.gotoStart(false); // go to start of text - String currPara; - do { - paraCursor.gotoEndOfParagraph(true); - // select all of paragraph - currPara = paraCursor.getString(); - if (currPara.length() > 0) - checkSentences(currPara, bi, speller, proofreader); - } while (paraCursor.gotoNextParagraph(false)); - - Lo.closeDoc(doc); - Lo.closeOffice(); -} // end of main() - -Write.loadSpellChecker() hides the creation of the linguistics manager, and its -retrieval of the spell checker: - -// in the Write class -public static XSpellChecker loadSpellChecker() -{ - XLinguServiceManager lingoMgr = - Lo.createInstanceMCF(XLinguServiceManager.class, - "com.sun.star.linguistic2.LinguServiceManager"); - if (lingoMgr == null) { - System.out.println("No linguistics manager found"); - return null; - } - else - return lingoMgr.getSpellChecker(); -} // end of loadSpellChecker() - -The BreakIterator is instantiated in main() using: -BreakIterator bi = - BreakIterator.getSentenceInstance(java.util.Locale.US); -but employed inside checkSentences(), which splits a paragraph into sentences: - -// in LingoFile.java -private static void checkSentences(String currPara, - BreakIterator bi, - XSpellChecker speller, XProofreader proofreader) -{ - System.out.println("\n>> " + currPara); - bi.setText(currPara); - int lastIdx = bi.first(); - while (lastIdx != BreakIterator.DONE) { - int firstIdx = lastIdx; - lastIdx = bi.next(); - if (lastIdx != BreakIterator.DONE) { - String sentence = currPara.substring(firstIdx, lastIdx); - Write.proofSentence(sentence, proofreader); - Write.spellSentence(sentence, speller); - } - } -} // end of checkSentences() - -The index positions of the start of the current sentence and the start of the next -sentence are stored in firstIdx and lastIdx, and used to extract a substring spanning the -current sentence. This is passed to Write.spellSentence() and Write.proofSentence() -for spell and proof checking. Write.spellSentence() splits the sentence into an array of -words, and calls Write.spellWord() on each one: - -// in the Write class -public static int spellSentence(String sent, XSpellChecker speller) -{ - String[] words = sent.split("\\W+"); // split into words - int count = 0; - boolean isCorrect; - for(String word : words) { - isCorrect = spellWord(word, speller); - count = count + (isCorrect? 0 : 1); - } - return count; -} // end of spellSentence() - -The poorly written "badGrammar.odt" is shown in Figure 19. - - +Figure 18. The Proofreader Service and Interfaces. + + +Write.loadProofreader() creates the service: + +```java +public static XProofreader loadProofreader() +{ return Lo.createInstanceMCF(XProofreader.class, + "com.sun.star.linguistic2.Proofreader"); } +``` + +Write.proofSentence() passes a sentence to XProofreader.doProofreading(), and prints +the errors inside the returned ProofreadingResult instance: + +```java +public static int proofSentence(String sent, + XProofreader proofreader) +{ com.sun.star.lang.Locale loc = + new com.sun.star.lang.Locale("en", "US", ""); + // American English + PropertyValue[] props = new PropertyValue[0]; + int numErrs = 0; + ProofreadingResult prRes = + proofreader.doProofreading("1", sent, loc, + 0, sent.length(), props); + if (prRes != null) { + SingleProofreadingError[] errs = prRes.aErrors; + if (errs.length > 0) + for(SingleProofreadingError err : errs) { + printProofError(sent, err); + numErrs++; + } + } + return numErrs; +} // end of proofSentence() + +public static void printProofError(String str, + SingleProofreadingError err) +{ String errText = str.substring(err.nErrorStart, + err.nErrorStart + err.nErrorLength); + System.out.println("G* " + err.aShortComment + + " in: \"" + errText + "\""); + if (err.aSuggestions.length > 0) + System.out.println(" Suggested change: \"" + + err.aSuggestions[0] + "\""); + System.out.println(); +} // end of printProofError() +``` + +XProofreader.doProofreading() requires a locale and properties array in the same way +as the earlier spell checking and thesaurus methods. It also needs two indices for the +start and end of the sentence being checked, and a document ID which is used in the +error results. + +The results are returned as an array of SingleProofreadingError objects, one for each +detected error. It's worth having a look at the documentation for the +SingleProofreadingError class (use "lodoc SingleProofreadingError"), since the +object contains more information than I've used in Write.printProofError(); for +example, the ID of the grammar rule that was 'broken', a full comment string, and +multiple suggestions in a String array. + +Grammar rule IDs are one area where the proof reader API could be improved. The +XProofreader interface includes methods for switching on and off rules based on their +IDs, but there's no way to find out what these IDs are except by looking at +SingleProofreadingError objects. + + + +## 5. Guessing the Language used in a String + +An oft overlooked linguistics API feature is the ability to guess the language used in a +string, which is implemented by one service, LanguageGuessing, and a single +interface, XLanguageGuessing. The documentation for XLanguageGuessing includes +a long list of supported languages, including Irish Gaelic, Scots Gaelic, and Manx +Gaelic. + +There are two examples of language guessing in Lingo.java: + +```java +// in Lingo.java + : +Locale loc = Write.guessLocale( + "The rain in Spain stays mainly on the plain."); +Write.printLocale(loc); +if (loc != null) + System.out.println("Guessed language: " + loc.Language); + +loc = Write.guessLocale( + "A vaincre sans péril, on triomphe sans gloire."); + // To win without risk is a triumph without glory. + +if (loc != null) + System.out.println("Guessed language: " + loc.Language); +``` + +The output is: + +``` +Locale lang: "English"; country: ""; variant: "" +Guessed language: en +Guessed language: fr +``` + +Write.guessLocale() creates the service, its interface, and calls +XLanguageGuessing.guessPrimaryLanguage(): + +```java +// in the Writer class +public static com.sun.star.lang.Locale guessLocale(String testStr) +{ + XLanguageGuessing guesser = + Lo.createInstanceMCF(XLanguageGuessing.class, + "com.sun.star.linguistic2.LanguageGuessing"); + if (guesser == null) { + System.out.println("No language guesser found"); + return null; + } + else + return guesser.guessPrimaryLanguage(testStr, + 0, testStr.length()); +} // end of guessLocale() +``` + +XLanguageGuessing actually guesses a locale rather than a language, and the locale +includes information about the language, country and a variant BCP 47 language +label. com.sun.star.lang.Locale stores that information as three strings, but XLocale +has additional methods for displaying the string codes in longer formats (e.g. "th" as +"Thailand"). Unfortunately, I was unable to get code using XLocale to compile, so fell +back to Java's Locale class which includes similar methods. + +Write.printLocale() converts an Office locale into a Java version, then prints its data +in long form: + +```java +public static void printLocale(com.sun.star.lang.Locale loc) +{ + if (loc != null) { + java.util.Locale jloc = + new java.util.Locale(loc.Language, loc.Country, loc.Variant); + System.out.println("Locale lang: \"" + + jloc.getDisplayLanguage() + + "\"; country: \"" + jloc.getDisplayCountry() + + "\"; variant: \"" + jloc.getDisplayVariant() + "\""); + } +} // end of printLocale() +``` + + +## 6. Spell Checking and Grammar Checking a Document + +Lingo.java only spell checks individual words, and grammar checks a single sentence. + +The LingoFile.java example shows how these features can be applied to an entire +document. + +One way to scan every sentence in a document is to combine XParagraphCursor and +XSentenceCursor, as in the TalkingBook.java example from Chapter 5, section 6. An +outer loop iterates over every paragraph using XParagraphCursor, and an inner loop +splits each paragraph into sentences with the help of XSentenceCursor. Initially, I +coded LingoFile.java in this way, but found that XSentenceCursor occasionally didn't +divide a paragraph into the correct number of sentences; sometimes two sentences +were treated as one. + +So I switched to a combined Office/Java approach – the outer loop in LingoFile.java +still utilizes XParagraphCursor to scan the paragraphs, but the sentences in a +paragraph are extracted using a sentence-based BreakIterator. + +The main() function of LingoFile.java: + +```java +public static void main(String args[]) +{ + if (args.length < 1) { + System.out.println("Usage: run LingoFile "); + return; + } + + XComponentLoader loader = Lo.loadOffice(); + XTextDocument doc = Write.openDoc(args[0], loader); + if (doc == null) { + System.out.println("Could not open " + args[0]); + Lo.closeOffice(); + return; + } + + // load spell checker, proof reader + XSpellChecker speller = Write.loadSpellChecker(); + XProofreader proofreader = Write.loadProofreader(); + + BreakIterator bi = BreakIterator.getSentenceInstance( + java.util.Locale.US); + + // iterate through the doc by paragraphs + XParagraphCursor paraCursor = Write.getParagraphCursor(doc); + paraCursor.gotoStart(false); // go to start of text + String currPara; + do { + paraCursor.gotoEndOfParagraph(true); + // select all of paragraph + currPara = paraCursor.getString(); + if (currPara.length() > 0) + checkSentences(currPara, bi, speller, proofreader); + } while (paraCursor.gotoNextParagraph(false)); + + Lo.closeDoc(doc); + Lo.closeOffice(); +} // end of main() +``` + +Write.loadSpellChecker() hides the creation of the linguistics manager, and its +retrieval of the spell checker: + +```java +// in the Write class +public static XSpellChecker loadSpellChecker() +{ + XLinguServiceManager lingoMgr = + Lo.createInstanceMCF(XLinguServiceManager.class, + "com.sun.star.linguistic2.LinguServiceManager"); + if (lingoMgr == null) { + System.out.println("No linguistics manager found"); + return null; + } + else + return lingoMgr.getSpellChecker(); +} // end of loadSpellChecker() + +The BreakIterator is instantiated in main() using: +BreakIterator bi = + BreakIterator.getSentenceInstance(java.util.Locale.US); +but employed inside checkSentences(), which splits a paragraph into sentences: + +// in LingoFile.java +private static void checkSentences(String currPara, + BreakIterator bi, + XSpellChecker speller, XProofreader proofreader) +{ + System.out.println("\n>> " + currPara); + bi.setText(currPara); + int lastIdx = bi.first(); + while (lastIdx != BreakIterator.DONE) { + int firstIdx = lastIdx; + lastIdx = bi.next(); + if (lastIdx != BreakIterator.DONE) { + String sentence = currPara.substring(firstIdx, lastIdx); + Write.proofSentence(sentence, proofreader); + Write.spellSentence(sentence, speller); + } + } +} // end of checkSentences() +``` + +The index positions of the start of the current sentence and the start of the next +sentence are stored in firstIdx and lastIdx, and used to extract a substring spanning the +current sentence. This is passed to Write.spellSentence() and Write.proofSentence() +for spell and proof checking. Write.spellSentence() splits the sentence into an array of +words, and calls Write.spellWord() on each one: + +```java +// in the Write class +public static int spellSentence(String sent, XSpellChecker speller) +{ + String[] words = sent.split("\\W+"); // split into words + int count = 0; + boolean isCorrect; + for(String word : words) { + isCorrect = spellWord(word, speller); + count = count + (isCorrect? 0 : 1); + } + return count; +} // end of spellSentence() +``` + +The poorly written "badGrammar.odt" is shown in Figure 19. + ![](images/10-Linguistics-19.png) -Figure 19. Not a Good Example of my Writing Skills. - - -The output from LingoFile.java when given "badGrammar.odt": - ->> I have a dogs. I have one dogs. - -G* Possible agreement error in: "a dogs" - Suggested change: "a dog" - - ->> I allow of of go home. i dogs. John don’t like dogs. So recieve -no cats also. - -G* Word repetition in: "of of" - Suggested change: "of" - -G* This sentence does not start with an uppercase letter in: "i" - Suggested change: "I" - -G* Grammatical problem in: "dogs" - Suggested change: "dog" - -G* 'Also' at the end of sentence in: "also" - Suggested change: "as well" - -* "recieve" is unknown. Try: -No. of names: 8 - "receive" "relieve" "retrieve" "reprieve" - "reverie" "recitative" "Recife" "reserve" - -The grammar errors (those starting with "G*") are produced by the LanguageTool -proof checker. If the default Lightproof checker is utilized instead, then less errors are -found: - ->> I have a dogs. I have one dogs. - - ->> I allow of of go home. i dogs. John don’t like dogs. So recieve -no cats also. - -G* Word duplication? in: "of of" - Suggested change: "of" - -G* Missing capitalization? in: "i" - Suggested change: "I" - -* "recieve" is unknown. Try: -No. of names: 8 - "receive" "relieve" "retrieve" "reprieve" - "reverie" "recitative" "Recife" "reserve" - -On larger documents, such as the Sherlock Holmes short story in "bigStory.doc", it's a -good idea to redirect the voluminous output to a temporary file so it can be examined -easily. - -More than half the errors for "bigStory.doc" are spurious reports of unpaired quotes. - -The output can be considerably reduced if LanguageTool's unpaired rule is disabled, -via the Options dialog in Figure 17. Figure 20 shows the dialog with the "Unpaired" -checkbox deselected in the Punctuation section. - - - +Figure 19. Not a Good Example of my Writing Skills. + + +The output from LingoFile.java when given "badGrammar.odt": + +``` +>> I have a dogs. I have one dogs. + +G* Possible agreement error in: "a dogs" + Suggested change: "a dog" + + +>> I allow of of go home. i dogs. John don’t like dogs. So recieve +no cats also. + +G* Word repetition in: "of of" + Suggested change: "of" + +G* This sentence does not start with an uppercase letter in: "i" + Suggested change: "I" + +G* Grammatical problem in: "dogs" + Suggested change: "dog" + +G* 'Also' at the end of sentence in: "also" + Suggested change: "as well" + +* "recieve" is unknown. Try: +No. of names: 8 + "receive" "relieve" "retrieve" "reprieve" + "reverie" "recitative" "Recife" "reserve" + +The grammar errors (those starting with "G*") are produced by the LanguageTool +proof checker. If the default Lightproof checker is utilized instead, then less errors are +found: + +>> I have a dogs. I have one dogs. + + +>> I allow of of go home. i dogs. John don’t like dogs. So recieve +no cats also. + +G* Word duplication? in: "of of" + Suggested change: "of" + +G* Missing capitalization? in: "i" + Suggested change: "I" + +* "recieve" is unknown. Try: +No. of names: 8 + "receive" "relieve" "retrieve" "reprieve" + "reverie" "recitative" "Recife" "reserve" +``` + +On larger documents, such as the Sherlock Holmes short story in "bigStory.doc", it's a +good idea to redirect the voluminous output to a temporary file so it can be examined +easily. + +More than half the errors for "bigStory.doc" are spurious reports of unpaired quotes. + +The output can be considerably reduced if LanguageTool's unpaired rule is disabled, +via the Options dialog in Figure 17. Figure 20 shows the dialog with the "Unpaired" +checkbox deselected in the Punctuation section. + ![](images/10-Linguistics-20.png) -Figure 20. The LanguageTool Options Dialog -with the Unpaired Rule Deselected. +Figure 20. The LanguageTool Options Dialog +with the Unpaired Rule Deselected. + + +The majority of the remaining errors are words unknown to the spell checker, such as +names and places, and British English spellings. + +Most of the grammar errors relate to how direct speech is written. The grammar +checker mistakenly reports an error if the direct speech ends with a question mark or +exclamation mark without a comma after the quoted text. + +On balance, I'd say that Sir Arthur Conan Doyle was an excellent speller and +grammarian, or his copy editor was. + - -The majority of the remaining errors are words unknown to the spell checker, such as -names and places, and British English spellings. -Most of the grammar errors relate to how direct speech is written. The grammar -checker mistakenly reports an error if the direct speech ends with a question mark or -exclamation mark without a comma after the quoted text. -On balance, I'd say that Sir Arthur Conan Doyle was an excellent speller and -grammarian, or his copy editor was. - - - - diff --git a/docs/images/bulb_on.png b/docs/images/bulb_on.png new file mode 100644 index 0000000000000000000000000000000000000000..48659d67b2986bb648a28b1f65b884ad656be330 GIT binary patch literal 4647 zcmV+?64>pDP)@~5_CmabW?9;ba!ELWdLP*XK7|GV{dJs z!?Rcb003BYMObu0Z*X~XX=iA307FACAW(H;Y-wX4L~m_jX>K4yWn^h%VRUJ4ZXjrM zbZ|N^FJp3LVRUJBWn*t`ZEtRKE^l&YFK~5YY-wX;Z*5^|ZZCRea&Ip&E-){~IK}8dyYmBa_n;2s>xQ4hfxN5Q{ zt65gpJ-Y$JG1(K>9Na__J;C*0VvHDnu>zudPz+JS4+%ujm?%GopTqEDm{FLSyMNpn zc=M*Ky5GF+u72uOuu=3yXret)m67{L3wi(Pzx*u20Hd7rAQbE90a7zmH-*2 z9;FlocXLcM?RK?Ok5Y<)*Xb;rH=TNvl6Z~-KBBX51Q_kmucD#CwF8bAgNnzCw7UvG zxeDiG%eBwO=0y=xuEH5?xi$&t7MCN%O1TQ>c+0gaU}Rj56f5N_oJybTXU5`15mTAFaup6}|8Jr$HZQ{AQs7yjx8eFTG5S*ST#dP_`cH>` z<;!2A17Op$?4$Sx`-!3&b7;kl4*kl9(}BI%2l_vB=3PLB0e|J%i9C4*@4`OIrBeYJ z2yDbInkMuOGO^6!n6vMr^9Ey_dK8RH>0H=5NdDNbg3hnpUY7Xzn8=i$NnIDfWLF)pbsYl-vWlacEpGNz-IhK^IMYN-5%u@{2xewCnm4k&)jjoFX5zj}!ZEoBwI=Z1tBz|T3G)24{ zbri6ZVB!(rr}&3*mOIn%FUNO)ey$u0i?i?_EN9zd(rIxMJ%BCP%WogWF>e-#bI>=b zpyF9UI%xLdYxsBhm$-5+DkcEC2riygBzL~Y6{|6qR}0CV;wYFAzy^Yg=S|XXtDP~} zMe{Xhp2frf;1%*4^Ta)1J|3L_9D_#EU4(`#oR04NbTOg!uXvZwVPL8Qf1FT>#<>KU zixA-c2z?m>T!XIpyntrq^7E$!4aI1KcYNdkdX& z=}~~s+59a!%f1}Ao+4U*ijoX)E4ut|tBiZQ@ZHA)(+Rd97=_iMJ!6V%tM{rS(Z zZXK*$3-7-l&=c*FXSVs{w_iP_i+l`S{cWVs=KyKT6DgxG#P`(HAZymR7m3a8xyLG=Z4}=w*rK2Yco&aC@7E7m zvBJGDg4msRTE(**T_Q;di42Byt)4xRH{NhBh@jTmicFbe70-V;DPeE=qY(f<1pXI| zsrn1>J9L&m@!W=?pdWd}y%_SfrY7X9vy8%N#qE&*L(vF=W!NJK{Ia$>XB4`@tdsM% z-s)ZiUA6V=wYBtmF5r*E+hrYx#wINyc(}fKCJCBN#!$MaD(s zS142fYw>V?$`tpN9+o}!n6;9^PAAs~xQG1qyJg!3bmPHMCZZ$fhtNbkH=qI9F8Kx?TW#8joF~^@a~`M#Xdy@rEpB(Cw_9e40Gj zePxGjbLUzs?A8LyawLXvVnD0IvYW!|P}5x7ghsNRP~d|E;R2H%3m3Yt=&AIq$u&Gn6Wj-O# z^6Q6oEnOAN8dMX3_)eZ*-hwJJYytJgFxsCs&3y$&WeXQt%lbd^D8FH7E7cXjyoYv6 zc@IlWy9WIP^mD@7n0CGCUP)Z}4$B%EXtl&H|2|;Z4WnxV;PvR1o`NPFZvZ~*!p#q& z2_y@h@$~GbG8v?<&V2<(Y3H76EZbK6|1i6wM-1fi2fqZ}w-wq>mrvoJc-drs0M0Go z-tT*T@7S^KD>iDIKHXZberKV7HDLXGfCkJLoFvn$KYJY=kj?MH_l-waPrpb0Ndr#7 z@Iap|gD|Hv&am#g^2-%%+W6Ouc6HxJ^A!yTzKaT|AWW584}2M&IQnn$dkyb~4(Kih zrV#v)tqV;TMwY>0&SS^YfH@VL-!%VE(829^wCmZ6=3-op!PhtBkdJd=5=0^Z5+j@!&yr#^s1BcJF4r<#HqT zZY*|^BAiB`!F2K6ci9%BFHX4cKI;s7;+{V#!U0X)@5%Dz#=&-}uyyuqdReZojm?vQ zBuO~N*ZGGYq5*A{N|k4^pK7-*E=PirB;goydG6lLaCF|NRCyYmR5g*dI2PWrTpNSF z=9eP1S6sm=%sFye(NLEZ1D9Z49o}14$50VkU9VzCU^CD)K-06o=AbToDV7$ee@zW1 zP zK{Ioe3UknfV$*WUN^~tS#fNjSN9GR|VQK=#C1KbG5i-~ld{2wN3W}2q_IxdOiBKWJ z8wsfRLhajV43cRVZ9N*tloF5`n1!1Q25zxD)Lw)^BJ50n zI83$_UEcP#qW`@WCEp*Eur@|j&)f#CqOu3fkzF#-V}Gil{*=9menMlM*&2GzJBLW zG^@}P=)B$tYw=08kP;33(9D`9@q7+Igfm4r>_|BO(&mSke=}k)v6Q2niQ&vn^e=h< zfCvvd5KXHHr=sUq=KA&#@tnkyV&NvttX)h6KV-(JC;0X_ZR=Fsk6f@&6 zp9ps|fC$rL63qrQH}OFaUv7xMXj1(6N4$lz2N)niZ;59e>we&J4?lLHKKI;HLZ`x_hiOI;lY8yl%Se3*~=^r5P6UwZcGLv{D=48tBDyb?XW zIr}8LmoOjtmQo}vrDKcpqzFqlZDPUX$t=X29ZLY|(SwHb&u8Vs53{hoo>xU^%4uSfD#T#f~ZT}{VmzXPPnA4`Ih=$Pf} z2;LKfdD+B?ywuXdpG3n%*JWLMG^XcDiGh`LtoR)iC>%fr4Pp_xV=sYxz`4lvQuzlBM z9ooXIZu@+DD>l6C-}Ts!P)aFMc5$p_xG2Q$UDfuTcZc|$DGA~&jtS?S5Wlw$ZNY~7 zeXtZLx4|)VJf^xnr0=Xmw;R6-d=S!iCYo{^v|HMq^||)wz6YXmI>kNhyDM$UnS$8c!2M`<*B8y4QPOiaP|~n0J)EqBSxR)m5$s1nmkj0jR@Df+j!xc1K{>u~lm*YxmgWtZi0(Jv zP2nv~BYMC`S>!A(i-*kNbaYuDn+kLb%V*mD9glA4>*MoXo6#K%FQO;e>_CX>@2HWqN06W-eoIZ6H%tM<8@>AW%+6 dAY*TCc4cyOWpXSj<0Ak7002ovPDHLkV1hA#yfy#; literal 0 HcmV?d00001 diff --git a/docs/index.md b/docs/index.md index 401ec32..e891306 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,5 +1,7 @@ # Preface +![[idea]](images/bulb_on.png) + Star Basic is the native language of Star Office which has been incorporated by Open Office and Libre Office. This means that all other supported languages must negotiate a basic bridge. This [comment](https://stackoverflow.com/a/64517979) from Dan Dascalescu offers a challenge to the community: