diff --git a/.gitignore b/.gitignore index 80a9c62..e65598c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ *.bak -"site/" +site/ diff --git a/docs/11-Draw_Impress_APIs.md b/docs/11-Draw_Impress_APIs.md index 5c32b5a..75221f1 100644 --- a/docs/11-Draw_Impress_APIs.md +++ b/docs/11-Draw_Impress_APIs.md @@ -1,379 +1,379 @@ -# Chapter 11. Draw/Impress APIs Overview +# Chapter 11. Draw/Impress APIs Overview !!! note "Topics" - Draw Pages and - Master Pages; Draw - Page Details; API - Hierarchy Code - Examples; Shapes in a - Drawing; Shapes in a - Presentation ; The Slide - Show APIs - - Example folders: "Draw - Tests" and "Utils" - - -This part discusses the APIs for both Draw and Impress -since the presentations API is an extension of Office's -drawing functionality, adding such things as slide-related -shapes (e.g. for the title, subtitle, header, and footer), more -data views (e.g. an handout mode), and slide shows. - -You'll get a good feel for the APIs' capabilities by reading -the Draw and Impress user guides, downloadable from -https://www.libreoffice.org/get-help/documentation/. - -Details about the APIs can be found in Chapter 9 of the Developer's Guide, starting at + Draw Pages and + Master Pages; Draw + Page Details; API + Hierarchy Code + Examples; Shapes in a + Drawing; Shapes in a + Presentation ; The Slide + Show APIs + + Example folders: "Draw + Tests" and "Utils" + + +This part discusses the APIs for both Draw and Impress +since the presentations API is an extension of Office's +drawing functionality, adding such things as slide-related +shapes (e.g. for the title, subtitle, header, and footer), more +data views (e.g. an handout mode), and slide shows. + +You'll get a good feel for the APIs' capabilities by reading +the Draw and Impress user guides, downloadable from +https://www.libreoffice.org/get-help/documentation/. + +Details about the APIs can be found in Chapter 9 of the Developer's Guide, starting at https://wiki.openoffice.org/wiki/Documentation/DevGuide/Drawings/Drawing_Docu -ments_and_Presentation_Documents (or type loGuide draw). The guide can also be -retrieved as a PDF file from -https://wiki.openoffice.org/w/images/d/d9/DevelopersGuide_OOo3.1.0.pdf. +ments_and_Presentation_Documents (or type loGuide draw). The guide can also be +retrieved as a PDF file from +https://wiki.openoffice.org/w/images/d/d9/DevelopersGuide_OOo3.1.0.pdf. -The guide's drawing and presentation examples are online at -http://api.libreoffice.org/examples/DevelopersGuide/examples.html#Drawing, and -there's a short Draw example in -http://api.libreoffice.org/examples/examples.html#Java_examples. +The guide's drawing and presentation examples are online at +http://api.libreoffice.org/examples/DevelopersGuide/examples.html#Drawing, and +there's a short Draw example in +http://api.libreoffice.org/examples/examples.html#Java_examples. -This chapter gives a broad overview of the drawing and presentation APIs, with some -small code snippets to illustrate the ideas. Subsequent chapters will return to these -features in much more detail. +This chapter gives a broad overview of the drawing and presentation APIs, with some +small code snippets to illustrate the ideas. Subsequent chapters will return to these +features in much more detail. + +The APIs are organized around three services which subclass OfficeDocument, as +depicted in Figure 1. -The APIs are organized around three services which subclass OfficeDocument, as -depicted in Figure 1. - ![](images/11-Draw_Impress_APIs-1.png) -Figure 1. The Drawing and Presentation Document Services. +Figure 1. The Drawing and Presentation Document Services. + - -The DrawingDocument service, and most of its related services and interfaces are in -Office's com.sun.star.drawing package (or module), which is documented at +The DrawingDocument service, and most of its related services and interfaces are in +Office's com.sun.star.drawing package (or module), which is documented at http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1drawing.ht -ml. Or you can reach it using lodoc drawing module reference. +ml. Or you can reach it using lodoc drawing module reference. -The presentation API is mostly located in Office's com.sun.star.presentation package -(or module), which is documented at +The presentation API is mostly located in Office's com.sun.star.presentation package +(or module), which is documented at http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1presentation -.html. You can also find it with lodoc presentation module reference. +.html. You can also find it with lodoc presentation module reference. + +Figure 2 shows a more detailed version of Figure 1 which includes some of the +interfaces defined by the services. -Figure 2 shows a more detailed version of Figure 1 which includes some of the -interfaces defined by the services. - ![](images/11-Draw_Impress_APIs-2.png) -Figure 2. Drawing and Presentation Document Services and Interfaces. +Figure 2. Drawing and Presentation Document Services and Interfaces. + - -The interfaces highlighted in bold in Figure 2 will be discussed in this chapter. +The interfaces highlighted in bold in Figure 2 will be discussed in this chapter. -The DrawingDocument service is pretty much empty, with the real drawing 'action' in -GenericDrawingDocument (which is in the com.sun.star.drawing package). +The DrawingDocument service is pretty much empty, with the real drawing 'action' in +GenericDrawingDocument (which is in the com.sun.star.drawing package). -PresentationDocument subclasses GenericDrawingDocument to inherit its drawing -capabilities, and adds features for slide shows (via the XPresentationSupplier and -XCustomPresntationSupplier interfaces). +PresentationDocument subclasses GenericDrawingDocument to inherit its drawing +capabilities, and adds features for slide shows (via the XPresentationSupplier and +XCustomPresntationSupplier interfaces). -The word "presentation" is a little overloaded in the API – PresentationDocument -corresponds to the slide deck, while XPresentationSupplier.getPresentation() returns -an XPresentation object which represents a slide show. +The word "presentation" is a little overloaded in the API – PresentationDocument +corresponds to the slide deck, while XPresentationSupplier.getPresentation() returns +an XPresentation object which represents a slide show. - - -## 1. Draw Pages and Master Pages -A drawing (or presentation) document consists of a series of draw pages, one for each -page (or slide) inside the document. Perhaps the most surprising aspect of this is that a -Draw document can contain multiple pages. -A document can also contain one or more master pages. A master page contains -drawing/slide elements which appear on multiple draw page. This idea is probably -most familiar from slide presentations where a master page holds the header, footer, -and graphics that appear on every slide. +## 1. Draw Pages and Master Pages -As illustrated in Figure 2, GenericDrawingDocument supports an -XDrawPagesSupplier interface whose getDrawPages() returns an XDrawPages object. +A drawing (or presentation) document consists of a series of draw pages, one for each +page (or slide) inside the document. Perhaps the most surprising aspect of this is that a +Draw document can contain multiple pages. -It also has an XMasterPagesSupplier whose getMasterPages() returns the master -pages as an object. Office views master pages as special kinds of draw pages, and so -getMasterPages() also returns an XDrawPages object. +A document can also contain one or more master pages. A master page contains +drawing/slide elements which appear on multiple draw page. This idea is probably +most familiar from slide presentations where a master page holds the header, footer, +and graphics that appear on every slide. + +As illustrated in Figure 2, GenericDrawingDocument supports an +XDrawPagesSupplier interface whose getDrawPages() returns an XDrawPages object. + +It also has an XMasterPagesSupplier whose getMasterPages() returns the master +pages as an object. Office views master pages as special kinds of draw pages, and so +getMasterPages() also returns an XDrawPages object. + +Note the "s" in "XDrawPages": an XDrawPages object is an indexed container of +XDrawPage (no "s") objects, as illustrated by Figure 3. -Note the "s" in "XDrawPages": an XDrawPages object is an indexed container of -XDrawPage (no "s") objects, as illustrated by Figure 3. - ![](images/11-Draw_Impress_APIs-3.png) -Figure 3. The XDrawPages Interface - -Since XDrawPages inherit XIndexAccess, its elements (pages) can be accessed using -index-based lookup (i.e. to return page 0, page 1, etc.). +Figure 3. The XDrawPages Interface + +Since XDrawPages inherit XIndexAccess, its elements (pages) can be accessed using +index-based lookup (i.e. to return page 0, page 1, etc.). - - -## 2. Draw Page Details -A draw page is a collection of shapes: often text shapes, such as a title box or a box -holding bulleted points. But a shape can be many more things: an ellipse, a polygon, a -bitmap, an embedded video, and so on. -This "page as shapes" notion is implemented by the API hierarchy shown in Figure 4. +## 2. Draw Page Details + +A draw page is a collection of shapes: often text shapes, such as a title box or a box +holding bulleted points. But a shape can be many more things: an ellipse, a polygon, a +bitmap, an embedded video, and so on. + +This "page as shapes" notion is implemented by the API hierarchy shown in Figure 4. + - ![](images/11-Draw_Impress_APIs-4.png) -Figure 4. The API Hierarchy for a Draw Page. +Figure 4. The API Hierarchy for a Draw Page. + - -XPresentationPage is the interface for a slide's page, but most of its functionality -comes from XDrawPage (see loDoc XPresentationPage). The XDrawPage -interface doesn't do much either, except for inheriting XShapes (note the "s"). +XPresentationPage is the interface for a slide's page, but most of its functionality +comes from XDrawPage (see loDoc XPresentationPage). The XDrawPage +interface doesn't do much either, except for inheriting XShapes (note the "s"). + +XShapes inherits XIndexAccess, which means that an XShapes object can be +manipulated as an indexed sequence of XShape objects. + +The XDrawPage and XPresentationPage interfaces are supported by services, some of +which are shown in Figure 5. These services are in some ways more important than +the interfaces, since they contain many properties related to pages and slides. -XShapes inherits XIndexAccess, which means that an XShapes object can be -manipulated as an indexed sequence of XShape objects. -The XDrawPage and XPresentationPage interfaces are supported by services, some of -which are shown in Figure 5. These services are in some ways more important than -the interfaces, since they contain many properties related to pages and slides. - - ![](images/11-Draw_Impress_APIs-5.png) -Figure 5. Some of the Draw Page Services. - - -There are two DrawPage services in the Office API, one in the drawing package, and -another in the presentation package. This is represented in Figure 5 by including the -package names in brackets after the service names. You can access the documentation -for these services by typing lodoc drawpage service drawing and lodoc -drawpage service presentation. - -No properties are defined in the drawing DrawPage, instead everything is inherited -from GenericDrawPage. - -I've put "(??)" next to the XDrawPage and XPresentationPage interfaces in Figure 5 -because they're not listed in the GenericDrawPage and presentation DrawPage -services in the documentation, but must be there because of the way that the code -works. Also, the documentation for GenericDrawPage lists XShapes as an interface, -rather than XDrawPage. - - - -## 3. API Hierarchy Code Examples - -Some code snippets will help clarify the hierarchies shown in Figures 2-5. The -following lines load a Draw (or Impress) document called "foo" as an XComponent -object. - - -XComponentLoader loader = Lo.loadOffice(); -XComponent doc = Lo.openDoc("foo", loader); - -A common next step is to access the draw pages in the document using the -XDrawPagesSupplier interface shown in Figure 2: - -XDrawPagesSupplier supplier = Lo.qi(XDrawPagesSupplier.class, doc); -XDrawPages pages = supplier.getDrawPages(); - -This code works whether the document is a sequence of draw pages (i.e. a Draw -document) or slides (i.e. an Impress document). - -Using the ideas shown in Figure 3, a particular draw page is accessed based on its -index position. The first draw page in the document is retrieved with: - -XDrawPage page = Lo.qi(XDrawPage.class, pages.getByIndex(0)); - -Pages are numbered from 0, and a newly created document always contains one page. - -The XDrawPage interface is supported by the GenericDrawPage service (see Figure -5), which holds the page's properties. The following snippet returns the width of the -page and its page number: - -int width = (Integer)Props.getProperty(page, "Width"); -int pageNumber = (Short)Props.getProperty(page, "Number"); - -The "Width" and "Number" properties are documented in the GenericDrawPage -service page at +Figure 5. Some of the Draw Page Services. + + +There are two DrawPage services in the Office API, one in the drawing package, and +another in the presentation package. This is represented in Figure 5 by including the +package names in brackets after the service names. You can access the documentation +for these services by typing lodoc drawpage service drawing and lodoc +drawpage service presentation. + +No properties are defined in the drawing DrawPage, instead everything is inherited +from GenericDrawPage. + +I've put "(??)" next to the XDrawPage and XPresentationPage interfaces in Figure 5 +because they're not listed in the GenericDrawPage and presentation DrawPage +services in the documentation, but must be there because of the way that the code +works. Also, the documentation for GenericDrawPage lists XShapes as an interface, +rather than XDrawPage. + + + +## 3. API Hierarchy Code Examples + +Some code snippets will help clarify the hierarchies shown in Figures 2-5. The +following lines load a Draw (or Impress) document called "foo" as an XComponent +object. + + +XComponentLoader loader = Lo.loadOffice(); +XComponent doc = Lo.openDoc("foo", loader); + +A common next step is to access the draw pages in the document using the +XDrawPagesSupplier interface shown in Figure 2: + +XDrawPagesSupplier supplier = Lo.qi(XDrawPagesSupplier.class, doc); +XDrawPages pages = supplier.getDrawPages(); + +This code works whether the document is a sequence of draw pages (i.e. a Draw +document) or slides (i.e. an Impress document). + +Using the ideas shown in Figure 3, a particular draw page is accessed based on its +index position. The first draw page in the document is retrieved with: + +XDrawPage page = Lo.qi(XDrawPage.class, pages.getByIndex(0)); + +Pages are numbered from 0, and a newly created document always contains one page. + +The XDrawPage interface is supported by the GenericDrawPage service (see Figure +5), which holds the page's properties. The following snippet returns the width of the +page and its page number: + +int width = (Integer)Props.getProperty(page, "Width"); +int pageNumber = (Short)Props.getProperty(page, "Number"); + +The "Width" and "Number" properties are documented in the GenericDrawPage +service page at http://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1drawing_1_1Ge -nericDrawPage.html (or use lodoc GenericDrawPage service). +nericDrawPage.html (or use lodoc GenericDrawPage service). + +Once a single page has been retrieved, it's possible to access its shapes (as shown in +Figure 4). The following code converts the XDrawPage object to XShapes, and +accesses the first XShape in its indexed container: + +XShapes shapes = Lo.qi(XShapes.class, page); +XShape shape = Lo.qi(XShape.class, shapes.getByIndex(0)) + -Once a single page has been retrieved, it's possible to access its shapes (as shown in -Figure 4). The following code converts the XDrawPage object to XShapes, and -accesses the first XShape in its indexed container: - -XShapes shapes = Lo.qi(XShapes.class, page); -XShape shape = Lo.qi(XShape.class, shapes.getByIndex(0)) - - -## 4. Shapes in a Drawing +## 4. Shapes in a Drawing -Shapes fall into two groups – drawing shapes that subclass the Shape service in -com.sun.star.drawing, and presentation-related shapes which subclass the Shape -service in com.sun.star.presentation. The first type are described here, and the -presentation shapes in section 5. +Shapes fall into two groups – drawing shapes that subclass the Shape service in +com.sun.star.drawing, and presentation-related shapes which subclass the Shape +service in com.sun.star.presentation. The first type are described here, and the +presentation shapes in section 5. + +Figure 6 shows the com.sun.star.drawing Shape service and some of its subclasses. -Figure 6 shows the com.sun.star.drawing Shape service and some of its subclasses. - ![](images/11-Draw_Impress_APIs-6.png) -Figure 6. Some of the Drawing Shapes. +Figure 6. Some of the Drawing Shapes. + - -I'll be explaining many of these shapes in Chapters 13 and 15, but you can probably -guess what most of them do – EllipseShape is for drawing ellipses and circles, -LineShape is for lines and arrows, RectangleShape is for rectangles. +I'll be explaining many of these shapes in Chapters 13 and 15, but you can probably +guess what most of them do – EllipseShape is for drawing ellipses and circles, +LineShape is for lines and arrows, RectangleShape is for rectangles. -The two "??"s in Figure 6 indicate that those services aren't shown in the online -documentation, but appear in examples. +The two "??"s in Figure 6 indicate that those services aren't shown in the online +documentation, but appear in examples. -The hardest aspect of this hierarchy is searching it for information on a shape's -properties. Many general properties are located in the Shape service, so are -documented on the Shape page (use lodoc shape service drawing to reach it). +The hardest aspect of this hierarchy is searching it for information on a shape's +properties. Many general properties are located in the Shape service, so are +documented on the Shape page (use lodoc shape service drawing to reach it). -More specialized properties are located in the specific shape's service. For instance, -RectangleShape has a "CornerRadius" property which allows a rectangle's corners to -be rounded to make it more button-like. +More specialized properties are located in the specific shape's service. For instance, +RectangleShape has a "CornerRadius" property which allows a rectangle's corners to +be rounded to make it more button-like. -Unfortunately, most shapes inherit a lot more properties than just those in Shape. +Unfortunately, most shapes inherit a lot more properties than just those in Shape. + +Figure 7 shows a typical example – RectangleShape inherits properties from at least +eight services (I've not shown the complete hierarchy)! -Figure 7 shows a typical example – RectangleShape inherits properties from at least -eight services (I've not shown the complete hierarchy)! - ![](images/11-Draw_Impress_APIs-7.png) -Figure 7. RectangleShape's Properties. - - -Aside from RectangleShape inheriting properties from Shape, it also obtains its fill, -shadow, line, and rotation attributes from the FillProperties, ShadowProperties, -LineProperties, and RotationDescriptor services. For instance, to make the rectangle -red, the "FillColor" property, defined in the FillProperties service, must be set. The -code for doing this is not complex: - -Props.setProperty(shape, "FillColor", 0xFF0000); - // hexadecimal for red - -The complication comes in knowing that a property called "FillColor" exists. Visit the -shape's service documentation (e.g. the RectangleShape page at +Figure 7. RectangleShape's Properties. + + +Aside from RectangleShape inheriting properties from Shape, it also obtains its fill, +shadow, line, and rotation attributes from the FillProperties, ShadowProperties, +LineProperties, and RotationDescriptor services. For instance, to make the rectangle +red, the "FillColor" property, defined in the FillProperties service, must be set. The +code for doing this is not complex: + +Props.setProperty(shape, "FillColor", 0xFF0000); + // hexadecimal for red + +The complication comes in knowing that a property called "FillColor" exists. Visit the +shape's service documentation (e.g. the RectangleShape page at http://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1drawing_1_1Re -ctangleShape.html, or use lodoc RectangleShape service), and look inside each -inherited Property service until you find the relevant property. +ctangleShape.html, or use lodoc RectangleShape service), and look inside each +inherited Property service until you find the relevant property. + +If the shape contains some text (e.g. the rectangle has a label inside it), and you want +to change one of the text's properties, then you'll need to look in the three property +services above the Text service (see Figure 7). + +Changing text requires that the text be selected first, which takes us back XText and +Chapter 5. For example, the text height is changed to 18pt by: + +XText xText = Lo.qi(XText.class, shape); +XTextCursor cursor = xText.createTextCursor(); +cursor.gotoStart(false); +cursor.gotoEnd(true); // select all text +Props.setProperty(cursor, "CharHeight", 18); + +First the shape is converted into an XText reference so that text selection can be done +using a cursor. + +The "CharHeight" property is inherited from the CharacterProperties service. -If the shape contains some text (e.g. the rectangle has a label inside it), and you want -to change one of the text's properties, then you'll need to look in the three property -services above the Text service (see Figure 7). +Figure 7 doesn't show all the text property services. For instance, there are also +services called CharacterPropertiesComplex and ParagraphPropertiesComplex. -Changing text requires that the text be selected first, which takes us back XText and -Chapter 5. For example, the text height is changed to 18pt by: - -XText xText = Lo.qi(XText.class, shape); -XTextCursor cursor = xText.createTextCursor(); -cursor.gotoStart(false); -cursor.gotoEnd(true); // select all text -Props.setProperty(cursor, "CharHeight", 18); - -First the shape is converted into an XText reference so that text selection can be done -using a cursor. -The "CharHeight" property is inherited from the CharacterProperties service. -Figure 7 doesn't show all the text property services. For instance, there are also -services called CharacterPropertiesComplex and ParagraphPropertiesComplex. +## 5. Shapes in a Presentation - - -## 5. Shapes in a Presentation +If the document is a slide deck, then presentation-related shapes will be subclasses of +the com.sun.star.presentation.Shape service (see the documentation with lodoc +shape service presentation). Some of those shapes are shown in Figure 8. -If the document is a slide deck, then presentation-related shapes will be subclasses of -the com.sun.star.presentation.Shape service (see the documentation with lodoc -shape service presentation). Some of those shapes are shown in Figure 8. - ![](images/11-Draw_Impress_APIs-8.png) -Figure 8. Some of the Presentation Shapes. +Figure 8. Some of the Presentation Shapes. - -The com.sun.star.presentation.Shape service doesn't subclass the -com.sun.star.drawing.Shape service. Instead, every presentation shape inherits the -presentation Shape service and a drawing shape (usually TextShape). This means that -all the presentation shapes can be treated as drawing shapes when being manipulated -in code. -Most of the presentation shapes are special kinds of text shapes. For instance, -TitleTextShape and OutlinerShape are text shapes which usually appear automatically -when you create a new slide inside Impress – the slide's title is typed into the -TitleTextShape, and bulleted points added to OutlinerShape. This is shown in Figure -9. +The com.sun.star.presentation.Shape service doesn't subclass the +com.sun.star.drawing.Shape service. Instead, every presentation shape inherits the +presentation Shape service and a drawing shape (usually TextShape). This means that +all the presentation shapes can be treated as drawing shapes when being manipulated +in code. + +Most of the presentation shapes are special kinds of text shapes. For instance, +TitleTextShape and OutlinerShape are text shapes which usually appear automatically +when you create a new slide inside Impress – the slide's title is typed into the +TitleTextShape, and bulleted points added to OutlinerShape. This is shown in Figure +9. + - ![](images/11-Draw_Impress_APIs-9.png) -Figure 9. Two Presentation Shapes in a Slide. +Figure 9. Two Presentation Shapes in a Slide. - -Using OutlinerShape as an example, its 'simplified' inheritance hierarchy looks like + +Using OutlinerShape as an example, its 'simplified' inheritance hierarchy looks like ![](images/11-Draw_Impress_APIs-10.png) -Figure 10. +Figure 10. + - ![](images/11-Draw_Impress_APIs-10.png) -Figure 10. The OutlinerShape Hierarchy. +Figure 10. The OutlinerShape Hierarchy. + + +OutlinerShape has at least nine property services that it inherits. + + - -OutlinerShape has at least nine property services that it inherits. +## 6. The Slide Show APIs - - -## 6. The Slide Show APIs +One difference between slides and drawings is that the presentations API supports +slide shows. This extra functionality can be seen in Figure 2 since the +PresentationDocument service offers an XPresntationSupplier interface which has a +getPresentation() method for returning an XPresentation object. Don't be confused by +the name – an XPresentation object represents a slide show. -One difference between slides and drawings is that the presentations API supports -slide shows. This extra functionality can be seen in Figure 2 since the -PresentationDocument service offers an XPresntationSupplier interface which has a -getPresentation() method for returning an XPresentation object. Don't be confused by -the name – an XPresentation object represents a slide show. +XPresentation offers start() and end() methods for starting and ending a slide show, +and the Presentation service contains properties for affecting how the show +progresses, as illustrated by Figure 11. -XPresentation offers start() and end() methods for starting and ending a slide show, -and the Presentation service contains properties for affecting how the show -progresses, as illustrated by Figure 11. - ![](images/11-Draw_Impress_APIs-11.png) -Figure 11. The Slide Show Presentation Services. - - -Code for starting a slide show for the "foo" document: - -XComponentLoader loader = Lo.loadOffice(); -XComponent doc = Lo.openDoc("foo", loader); - -XPresentationSupplier ps = Lo.qi(XPresentationSupplier.class, doc); -XPresentation show = Lo.qi(XPresentation.class, - ps.getPresentation()); -show.start(); - -The Presentation service is a bit lacking, so an extended service, Presentation2, was -added more recently. It can access an XSlideShowController interface which gives -finer-grained control over how the show progresses; I'll give examples later. - - - +Figure 11. The Slide Show Presentation Services. + + +Code for starting a slide show for the "foo" document: + +XComponentLoader loader = Lo.loadOffice(); +XComponent doc = Lo.openDoc("foo", loader); + +XPresentationSupplier ps = Lo.qi(XPresentationSupplier.class, doc); +XPresentation show = Lo.qi(XPresentation.class, + ps.getPresentation()); +show.start(); + +The Presentation service is a bit lacking, so an extended service, Presentation2, was +added more recently. It can access an XSlideShowController interface which gives +finer-grained control over how the show progresses; I'll give examples later. + + + diff --git a/docs/45-UNO_Components.md b/docs/45-UNO_Components.md index a3c2902..e4221c2 100644 --- a/docs/45-UNO_Components.md +++ b/docs/45-UNO_Components.md @@ -1,1246 +1,1308 @@ -# Chapter 45. Coding UNO Components +# Chapter 45. Coding UNO Components !!! note "Topics" - Using the - RandomSents - Component; Writing - IDL Definitions; Using - idlc.bat; Merging Type - Data into a Registry - Database; Generating - the Java Package and - Interface; Creating a - Skeleton Component; - Compiling the - Completed - Implementation; - Packaging the - Component; Installing - the Extension; Using a - New Component in a - Program - - Example folders: "UNO - Comps Tests" and - "Utils" - - -In its simplest form, an UNO component consists of a new -service and interface that's added to Office as an -extension. Subsequently, the service and interface can be -used in the same way as other classes in the Office API. - -This chapter's UNO component example implements a -RandomSents service, which generates random sentences -and paragraphs, optionally in all-caps. After RandomSents -has been added to Office as an extension, my -PoemCreator.java employs Office and RandomSents to -write poetry into a Word document. - -UNO components extend the API, but aren't accessible to -Office users via its GUI. However, in the next two -chapters, I'll look at how to write extended components -called add-ons and Calc add-ins. An add-on is accessible -through the Office GUI, typically as a new toolbar or -menu item. Calc add-ins appear as new functions for cell -calculations. - -Other uses of components include the creation of new Chart types (Chart add-ins), -Calc data pilots, database drivers, and filters for the input/output of new kinds of -documents. I won’t be looking at those kinds of components. - -Office's SDK has command line tools for creating a 'skeleton' component containing a -service and interface with most of the necessary boilerplate API code. Other tools -mostly automate the creation of the component's extension file and its installation into -Office. - -How this set of tools works together to form a code generation  compilation  -installation toolchain is somewhat confusing. Another problem is that some of the -tools require changes to Window's PATH environment variable, and files and folders -to be in specific locations. My solution to these issues is twelve (12!) batch files, -whose names label the arrows in Figure 1. - - - + Using the + RandomSents + Component; Writing + IDL Definitions; Using + idlc.bat; Merging Type + Data into a Registry + Database; Generating + the Java Package and + Interface; Creating a + Skeleton Component; + Compiling the + Completed + Implementation; + Packaging the + Component; Installing + the Extension; Using a + New Component in a + Program + + Example folders: "UNO + Comps Tests" and + "Utils" + + +In its simplest form, an UNO component consists of a new +service and interface that's added to Office as an +extension. Subsequently, the service and interface can be +used in the same way as other classes in the Office API. + +This chapter's UNO component example implements a +RandomSents service, which generates random sentences +and paragraphs, optionally in all-caps. After RandomSents +has been added to Office as an extension, my +PoemCreator.java employs Office and RandomSents to +write poetry into a Word document. + +UNO components extend the API, but aren't accessible to +Office users via its GUI. However, in the next two +chapters, I'll look at how to write extended components +called add-ons and Calc add-ins. An add-on is accessible +through the Office GUI, typically as a new toolbar or +menu item. Calc add-ins appear as new functions for cell +calculations. + +Other uses of components include the creation of new Chart types (Chart add-ins), +Calc data pilots, database drivers, and filters for the input/output of new kinds of +documents. I won’t be looking at those kinds of components. + +Office's SDK has command line tools for creating a 'skeleton' component containing a +service and interface with most of the necessary boilerplate API code. Other tools +mostly automate the creation of the component's extension file and its installation into +Office. + +How this set of tools works together to form a code generation  compilation  +installation toolchain is somewhat confusing. Another problem is that some of the +tools require changes to Window's PATH environment variable, and files and folders +to be in specific locations. My solution to these issues is twelve (12!) batch files, +whose names label the arrows in Figure 1. + ![](images/45-UNO_Components-1.png) -Figure 1. Toolchain for Creating a Component. - - -Figure 1 shows a two-part toolchain, separated by the need for the programmer to -finish off the skeleton code for the component's service and interface. - -The chain begins with the creation of an IDL file which holds a description of the -component's types, data attributes, and method signatures. The IDL document is -converted into registry database information (an URD file), then merged with other -URD files and converted to RDB format (a binary tree data structure). The javamaker -tool converts the RDB data into a Java package (i.e. a nested set of folders) containing -a ".class" file for the interface. The class is converted into Java code by the skelComp -tool, which also adds boilerplate methods and data needed for the installation and -calling of the component's service and interface. The domain-specific methods are left -empty, which is where the programmer takes over. - -The Office SDK also includes a cppmaker tool, a version of the skeleton generator for -C++ programmers. It's also possible to code components in Python and Office Basic. - -Five tools are utilized in the first part of the toolchain: idlc.bat, regmerge.bat, -regview.bat, javamaker.bat, and skelComp.bat. The genCode.bat script shown in -Figure 1 calls these tools in order. - -After the programmer has filled in the component's domain-specific methods, the -code is compiled with compileOrg.bat, and converted to a JAR file with toJar.bat. An -Office extension is stored as an OXT file, which is created by makeOXT.bat, and then -installed by pkg.bat. extManager.bat lists Office's installed extensions. - -The rest of this chapter visits each of these batch scripts as I develop the -RandomSents component. - - -Information on Tools, Components, and Extensions -Most of my batch files call Office SDK tools. For instance, idlc.bat, regmerge.bat, -regview.bat, javamaker.bat, and skelComp.bat utilize Office's idlc.exe, regmerge.exe, -regview.exe, javamaker.exe, and uno-skeletonmaker.exe. The best source of -information on these SDK tools is at http://api.libreoffice.org/docs/tools.html, which -gives a short but useful description of each one. - -The creation and installation of components is explained at length early in the -Developer's Guide, in chapters 3 ("Writing UNO Components") and 4 ("Extensions"). - -The guide is available from -https://wiki.openoffice.org/w/images/d/d9/DevelopersGuide_OOo3.1.0.pdf, or the -chapters are online starting at +Figure 1. Toolchain for Creating a Component. + + +Figure 1 shows a two-part toolchain, separated by the need for the programmer to +finish off the skeleton code for the component's service and interface. + +The chain begins with the creation of an IDL file which holds a description of the +component's types, data attributes, and method signatures. The IDL document is +converted into registry database information (an URD file), then merged with other +URD files and converted to RDB format (a binary tree data structure). The javamaker +tool converts the RDB data into a Java package (i.e. a nested set of folders) containing +a ".class" file for the interface. The class is converted into Java code by the skelComp +tool, which also adds boilerplate methods and data needed for the installation and +calling of the component's service and interface. The domain-specific methods are left +empty, which is where the programmer takes over. + +The Office SDK also includes a cppmaker tool, a version of the skeleton generator for +C++ programmers. It's also possible to code components in Python and Office Basic. + +Five tools are utilized in the first part of the toolchain: idlc.bat, regmerge.bat, +regview.bat, javamaker.bat, and skelComp.bat. The genCode.bat script shown in +Figure 1 calls these tools in order. + +After the programmer has filled in the component's domain-specific methods, the +code is compiled with compileOrg.bat, and converted to a JAR file with toJar.bat. An +Office extension is stored as an OXT file, which is created by makeOXT.bat, and then +installed by pkg.bat. extManager.bat lists Office's installed extensions. + +The rest of this chapter visits each of these batch scripts as I develop the +RandomSents component. + + +#### Information on Tools, Components, and Extensions + +Most of my batch files call Office SDK tools. For instance, idlc.bat, regmerge.bat, +regview.bat, javamaker.bat, and skelComp.bat utilize Office's idlc.exe, regmerge.exe, +regview.exe, javamaker.exe, and uno-skeletonmaker.exe. The best source of +information on these SDK tools is at http://api.libreoffice.org/docs/tools.html, which +gives a short but useful description of each one. + +The creation and installation of components is explained at length early in the +Developer's Guide, in chapters 3 ("Writing UNO Components") and 4 ("Extensions"). +The guide is available from +https://wiki.openoffice.org/w/images/d/d9/DevelopersGuide_OOo3.1.0.pdf, or the +chapters are online starting at https://wiki.openoffice.org/wiki/Documentation/DevGuide/WritingUNO/Writing_UN -O_Components and -https://wiki.openoffice.org/wiki/Documentation/DevGuide/Extensions/Extensions (or -use loGuide "Writing UNO Components" and loGuide Extensions). - -The drawback with the guide (both PDF and online) is that the tools available in the -current version of Office have changed slightly from when the guide was written in -2009. - -Two other documents worth studying are "How to Install Extensions" (available at -https://www.libreoffice.org/get-help/documentation/), and "Publishing Extensions for -LibreOffice" -(https://wiki.documentfoundation.org/images/1/14/Publishing_extensions.pdf) which -explains how to add an extension to LibreOffice's 'shop window' at -http://extensions.libreoffice.org/. - -The LibreOffice site concerned with how to code extensions is -https://wiki.documentfoundation.org/Development/Extension_Development, which -points to a large OpenOffice site (https://wiki.openoffice.org/wiki/Extensions), called -the OOo Extension Project. It includes a useful list of pointers to tutorials and articles -about building extensions, at -https://wiki.openoffice.org/wiki/Extensions_development. - -These websites can be a little overwhelming since an extension can be so many -different things: UNO components, add-ons, Calc add-ins, file filters, etc., and be -implemented in so many different languages (C++, Java, Python). Since this chapter -is only about components, here's a list of component examples I've found useful: - The examples from chapter 3 of the Developer's Guide +O_Components and +https://wiki.openoffice.org/wiki/Documentation/DevGuide/Extensions/Extensions (or +use `loGuide "Writing UNO Components"` and `loGuide Extensions`). + +The drawback with the guide (both PDF and online) is that the tools available in the +current version of Office have changed slightly from when the guide was written in +2009. + +Two other documents worth studying are "How to Install Extensions" (available at +https://www.libreoffice.org/get-help/documentation/), and "Publishing Extensions for +LibreOffice" +(https://wiki.documentfoundation.org/images/1/14/Publishing_extensions.pdf) which +explains how to add an extension to LibreOffice's 'shop window' at +http://extensions.libreoffice.org/. + +The LibreOffice site concerned with how to code extensions is +https://wiki.documentfoundation.org/Development/Extension_Development, which +points to a large OpenOffice site (https://wiki.openoffice.org/wiki/Extensions), called +the OOo Extension Project. It includes a useful list of pointers to tutorials and articles +about building extensions, at +https://wiki.openoffice.org/wiki/Extensions_development. + +These websites can be a little overwhelming since an extension can be so many +different things: UNO components, add-ons, Calc add-ins, file filters, etc., and be +implemented in so many different languages (C++, Java, Python). Since this chapter +is only about components, here's a list of component examples I've found useful: + +* The examples from chapter 3 of the Developer's Guide (http://api.libreoffice.org/examples/DevelopersGuide/examples.html#Component -s), including an image reducer component called Thumbs; - The "MinimalComponent" Java example in the "UNO Components Examples" -section of http://api.libreoffice.org/examples/examples.html#Java_examples; - The Python love letter writer (https://github.com/kunaldeo/Py-LibreOffice-Love- -Letter-Writer). - -For those of you less fond of command line tools, there's an Eclipse plugin called -LOEclipse for creating Office extensions -(https://marketplace.eclipse.org/content/loeclipse), and an add-on example at -https://github.com/smehrbrodt/libreoffice-starter-extension - - -## 1. Using the RandomSents Component - -The RandomSents UNO component consists of a single service and interface. The -service is employed only to access the XRandomSents interface, which has two -methods and an attribute, as depicted in Figure 2. - - +s), including an image reducer component called Thumbs; +* The "MinimalComponent" Java example in the "UNO Components Examples" +section of http://api.libreoffice.org/examples/examples.html#Java_examples; +* The Python love letter writer (https://github.com/kunaldeo/Py-LibreOffice-Love- +Letter-Writer). + +For those of you less fond of command line tools, there's an Eclipse plugin called +LOEclipse for creating Office extensions +(https://marketplace.eclipse.org/content/loeclipse), and an add-on example at +https://github.com/smehrbrodt/libreoffice-starter-extension + + +## 1. Using the RandomSents Component + +The RandomSents UNO component consists of a single service and interface. The +service is employed only to access the XRandomSents interface, which has two +methods and an attribute, as depicted in Figure 2. + ![](images/45-UNO_Components-2.png) -Figure 2. The RandomSents Component's Service and Interface. - - -getParagraph() returns a single string consisting of a specified number of sentences, -while getSentences() returns the sentences in an array. The isAllCaps boolean -indicates whether the text should be in all-caps. - -PoemCreator.java uses Office and the RandomSents component (after it's been added -to Office) to write a poem into a Word file: - -// in PoemCreator.java -import com.sun.star.uno.*; - // other imports... - - -import org.openoffice.randomsents.XRandomSents; - - -public class PoemCreator -{ - 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; - } - Info.listExtensions(); - - GUI.setVisible(doc, true); - - Write.setHeader(doc, "Muse of the Office"); - Write.setA4PageFormat(doc); - Write.setPageNumbers(doc); - - XRandomSents rs = Lo.createInstanceMCF(XRandomSents.class, - "org.openoffice.randomsents.RandomSents"); - String[] sents = rs.getSentences(5); - - XTextCursor cursor = Write.getCursor(doc); - for(String sent : sents) - Write.appendPara(cursor, sent+"\n"); - - rs.setisAllCaps(true); - Write.appendPara(cursor, rs.getParagraph(2)+"\n"); - - Write.appendPara(cursor, Lo.getTimeStamp()); - - Lo.waitEnter(); - Lo.saveDoc(doc, "poem.doc"); - - Lo.closeDoc(doc); - Lo.closeOffice(); - } // end of main() - -} // end of PoemCreator class - -The program begins and ends in a familiar way: a Writer document is created and the -generated text is saved to "poem.doc". - -The RandomSents service and its interface are created using Lo.createInstanceMCF(): - -XRandomSents rs = Lo.createInstanceMCF(XRandomSents.class, - "org.openoffice.randomsents.RandomSents"); - -This requires that the RandomSents component's interface be imported: -import org.openoffice.randomsents.XRandomSents; -PoemCreator writes five paragraphs into the document, each one a sentence from the -array returned by XRandomSents.getSentences(). After switching to all-caps (using -XRandomSents.setisAllCaps()), the last paragraph is made up of two sentences by -calling XRandomSents.getParagraph(). Typical output looks like Figure 3. - - - +Figure 2. The RandomSents Component's Service and Interface. + + +getParagraph() returns a single string consisting of a specified number of sentences, +while getSentences() returns the sentences in an array. The isAllCaps boolean +indicates whether the text should be in all-caps. + +PoemCreator.java uses Office and the RandomSents component (after it's been added +to Office) to write a poem into a Word file: + +```java +// in PoemCreator.java +import com.sun.star.uno.*; + // other imports... + + +import org.openoffice.randomsents.XRandomSents; + + +public class PoemCreator +{ + 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; + } + Info.listExtensions(); + + GUI.setVisible(doc, true); + + Write.setHeader(doc, "Muse of the Office"); + Write.setA4PageFormat(doc); + Write.setPageNumbers(doc); + + XRandomSents rs = Lo.createInstanceMCF(XRandomSents.class, + "org.openoffice.randomsents.RandomSents"); + String[] sents = rs.getSentences(5); + + XTextCursor cursor = Write.getCursor(doc); + for(String sent : sents) + Write.appendPara(cursor, sent+"\n"); + + rs.setisAllCaps(true); + Write.appendPara(cursor, rs.getParagraph(2)+"\n"); + + Write.appendPara(cursor, Lo.getTimeStamp()); + + Lo.waitEnter(); + Lo.saveDoc(doc, "poem.doc"); + + Lo.closeDoc(doc); + Lo.closeOffice(); + } // end of main() + +} // end of PoemCreator class +``` + +The program begins and ends in a familiar way: a Writer document is created and the +generated text is saved to "poem.doc". + +The RandomSents service and its interface are created using Lo.createInstanceMCF(): + +```java +XRandomSents rs = Lo.createInstanceMCF(XRandomSents.class, + "org.openoffice.randomsents.RandomSents"); +``` + +This requires that the RandomSents component's interface be imported: + +```java +import org.openoffice.randomsents.XRandomSents; +``` + +PoemCreator writes five paragraphs into the document, each one a sentence from the +array returned by XRandomSents.getSentences(). After switching to all-caps (using +XRandomSents.setisAllCaps()), the last paragraph is made up of two sentences by +calling XRandomSents.getParagraph(). Typical output looks like Figure 3. + ![](images/45-UNO_Components-3.png) -Figure 3. A Generated Poem in poem.doc. - - -Note that the XRandomSents isAllCaps boolean is not accessed directly, but via -get/set methods, setisAllCaps() and getisAllCaps(). - - - -## 2. Writing IDL Definitions - -The UNO IDL (Interface Definition Language) is used to specify the types, attributes, -and methods in the service and interface of the RandomSents component. The -RandomSents.idl file contains two definitions: - -#ifndef _org_openoffice_randomsents_RandomSents_ -#define _org_openoffice_randomsents_RandomSents_ - -#include - - -module org { module openoffice { module randomsents -{ - interface XRandomSents { - [attribute] boolean isAllCaps; - - string getParagraph([in] long numSents); - - sequence getSentences([in] long numSents); - }; -}; }; }; - - -module org { module openoffice { module randomsents -{ - service RandomSents : XRandomSents; -}; }; }; - - -#endif - -Both definitions start with the org.openoffice.randomsents module path. The first is -for the XRandomSents interface, the second for the RandomSents service. - -The IDL lets a module path be almost anything, but my batch scripts assume that it -begins with "org.openoffice", and the module name (i.e. "randomsents") is a -lowercase version of the service name ("RandomSents"). - -The tools assume that "org.openoffice" corresponds to an existing "org/" folder in the -current directory, containing an "openoffice/" folder. However, the directory for the -module (i.e. "randomsents/") will be created. - -The IDL borrows many syntactic features from the COBRA IDL (which gives a fair -indication of its age). For example: - data fields are distinguished with the phrase "[attribute]"; - an array is represented as a "sequence"; - there is no int type, "long" is the IDL equivalent; - method arguments can be both input ("[in]") and output ("[out]") or both -("[inout]"). The guide warns Java programmers to avoid "[out]" and "[inout]", -which I've done. - -Much of chapter 3 in the Developer's Guide is concerned with explaining IDL -features. An important subsection is "Using UNOIDL to Specify New Components", -which explains how to define a service and interface; it's online at -https://wiki.openoffice.org/wiki/Documentation/DevGuide/WritingUNO/Using_UNO -IDL_to_Specify_New_Components, or use loGuide "Using UNOIDL". - -Details on how IDL types are mapped to Java can be found in chapter 2 of the guide, -starting at the "Type Mapping" subsection; online at -https://wiki.openoffice.org/wiki/Documentation/DevGuide/ProUNO/Java/Type_Mapp -ings, or use loGuide "Type Mappings". - - - -## 3. Using idlc.bat - -My idlc.bat batch file utilizes idlc.exe, one of Office's SDK tools in -\sdk\bin\. Its main purpose is to generate type data, storing it in an URD -file. A little more information can be found at -http://api.libreoffice.org/docs/tools.html#idlc. - -Unfortunately, idlc.exe can only process an IDL file if it's located in -\sdk\bin\, probably because its "-I" option can't find necessary support files -when searching in other folders. Also, the path to \program\ must be -added to Window's PATH environment variable so idlc.exe can employ DLLs stored -there. - -The URD file is written to \sdk\bin\, which is inconvenient, so idlc.bat -moves the IDL and URD files back to the original directory. - -A typical call to idlc.bat uses the component's service name to identify the IDL file: -> idlc.bat RandomSents -Output looks something like: - -Found "C:\Program Files\LibreOffice 5" -Copying RandomSents.idl to "C:\Program Files\LibreOffice 5"\sdk\bin - 1 file(s) copied. - -Compiling RandomSents with idlc... - -idlc: compiling 1 source files ... - -Compiling: RandomSents.idl -idlc: returned successful idlc Version 1.1 -Copying RandomSents.urd to "C:\Users\Dell\Desktop\LibreOffice -Tests\Component Tests\" - 1 file(s) copied. - - -The end result is the creation of a RandomSents.urd file. - -idlc.bat may fail because of its need to copy files into a folder beneath C:\Program -Files\, which requires administrative privileges. The easiest workaround is to -download the elevate.exe utility from http://code.kliu.org/misc/elevate/, which starts -an administrative console so the necessary privileges are granted. The call to idlc.bat -becomes: -> elevate.exe -k -w idlc.bat RandomSents - - -## 4. Merging Type Data into a Registry Database - -regmerge.exe has two main roles: the merging of type data from multiple URD files, -and the conversion of the URD format into RDB, a binary tree data structure using -keys; RDB stands for "Registry Database". - -The regmerge call must include an "UCR" argument which is used to label the type -descriptions in RDB (UCR stands for "Uno Core Reflection"). - -The resulting RDB file can be printed using regview.exe. - -My regmerge.bat and regview.bat scripts call the corresponding UNO tools in -\program, and use the component's service name to identify the URD and -RDB files. For example: - -> regmerge.bat RandomSents -merging registry "RandomSents.urd" under key "UCR" in registry -"RandomSents.rdb". - - - -> regview.bat RandomSents -Registry "file:///C:/RandomSents.urd": - -/ - / org - / openoffice - / randomsents - / RandomSents - Value: Type = RegValueType::BINARY - Size = 158 - Data = version: 1 - documentation: "" - file name: "" - type class: service - type name: - "org/openoffice/randomsents/RandomSents" - - // many more lines ... - - -The outcome of the regview.bat call is a RandomSents.rdb file, which is displayed by -regview.bat. - -The RDB format was changed in LibreOffice 4.1, but most tools that use RDB can -understand both the old and new formats. Unfortunately, regmerge.exe only generates -old-style RDB, and regview cannot print the new format. That's means regview is -useless for examining important registry databases in \program, such as -types.rdb and services.rdb which use the new format. - -The API includes a com.sun.star.registry module with an XSimpleRegistry interface -for examining registry databases (i.e. RDB files). Unfortunately, it only understands -the old RDB format, as illustrated by the inability of my test program, -ViewRegistry.java, to display types.rdb or services.rdb (but RandomSents.rdb is -readable). - - - -## 5. Generating the Java Package and Interface - -javamaker.exe (and cppumaker.exe) map IDL types to Java (and C++) using data -from RDB files. RandomSents.rdb cannot be mapped on its own because it refers to -types, such as XInterface, which it doesn't define. javamaker also needs Office's -types.rdb, located in \program. - -javamaker generates two things – a Java package representing the IDL module -structure, and a Java ".class" file corresponding to the IDL interface. - -For example, the module structure in RandomSents.idl is: -module org { module openoffice { module randomsents -javamaker converts this into a Java package made up of three nested folders, as shown -in Figure 4. - - - +Figure 3. A Generated Poem in poem.doc. + + +Note that the XRandomSents isAllCaps boolean is not accessed directly, but via +get/set methods, setisAllCaps() and getisAllCaps(). + + +## 2. Writing IDL Definitions + +The UNO IDL (Interface Definition Language) is used to specify the types, attributes, +and methods in the service and interface of the RandomSents component. The +RandomSents.idl file contains two definitions: + +``` +#ifndef _org_openoffice_randomsents_RandomSents_ +#define _org_openoffice_randomsents_RandomSents_ + +#include + + +module org { module openoffice { module randomsents +{ + interface XRandomSents { + [attribute] boolean isAllCaps; + + string getParagraph([in] long numSents); + + sequence getSentences([in] long numSents); + }; +}; }; }; + + +module org { module openoffice { module randomsents +{ + service RandomSents : XRandomSents; +}; }; }; + + +#endif +``` + +Both definitions start with the org.openoffice.randomsents module path. The first is +for the XRandomSents interface, the second for the RandomSents service. + +The IDL lets a module path be almost anything, but my batch scripts assume that it +begins with "org.openoffice", and the module name (i.e. "randomsents") is a +lowercase version of the service name ("RandomSents"). + +The tools assume that "org.openoffice" corresponds to an existing "org/" folder in the +current directory, containing an "openoffice/" folder. However, the directory for the +module (i.e. "randomsents/") will be created. + +The IDL borrows many syntactic features from the COBRA IDL (which gives a fair +indication of its age). For example: + +* data fields are distinguished with the phrase "[attribute]"; +* an array is represented as a "sequence"; +* there is no int type, "long" is the IDL equivalent; +* method arguments can be both input ("[in]") and output ("[out]") or both +("[inout]"). The guide warns Java programmers to avoid "[out]" and "[inout]", +which I've done. + +Much of chapter 3 in the Developer's Guide is concerned with explaining IDL +features. An important subsection is "Using UNOIDL to Specify New Components", +which explains how to define a service and interface; it's online at +https://wiki.openoffice.org/wiki/Documentation/DevGuide/WritingUNO/Using_UNOIDL_to_Specify_New_Components, +or use `loGuide "Using UNOIDL"`. + +Details on how IDL types are mapped to Java can be found in chapter 2 of the guide, +starting at the "Type Mapping" subsection; online at +https://wiki.openoffice.org/wiki/Documentation/DevGuide/ProUNO/Java/Type_Mappings, +or use `loGuide "Type Mappings"`. + + +## 3. Using idlc.bat + +My idlc.bat batch file utilizes idlc.exe, one of Office's SDK tools in +\sdk\bin\. Its main purpose is to generate type data, storing it in an URD +file. A little more information can be found at +http://api.libreoffice.org/docs/tools.html#idlc. + +Unfortunately, idlc.exe can only process an IDL file if it's located in +\sdk\bin\, probably because its "-I" option can't find necessary support files +when searching in other folders. Also, the path to \program\ must be +added to Window's PATH environment variable so idlc.exe can employ DLLs stored +there. + +The URD file is written to \sdk\bin\, which is inconvenient, so idlc.bat +moves the IDL and URD files back to the original directory. + +A typical call to idlc.bat uses the component's service name to identify the IDL file: + +``` +> idlc.bat RandomSents +``` + +Output looks something like: + +``` +Found "C:\Program Files\LibreOffice 5" +Copying RandomSents.idl to "C:\Program Files\LibreOffice 5"\sdk\bin + 1 file(s) copied. + +Compiling RandomSents with idlc... + +idlc: compiling 1 source files ... + +Compiling: RandomSents.idl +idlc: returned successful idlc Version 1.1 +Copying RandomSents.urd to "C:\Users\Dell\Desktop\LibreOffice +Tests\Component Tests\" + 1 file(s) copied. +``` + +The end result is the creation of a RandomSents.urd file. + +idlc.bat may fail because of its need to copy files into a folder beneath C:\Program +Files\, which requires administrative privileges. The easiest workaround is to +download the elevate.exe utility from http://code.kliu.org/misc/elevate/, which starts +an administrative console so the necessary privileges are granted. The call to idlc.bat +becomes: + +``` +> elevate.exe -k -w idlc.bat RandomSents +``` + + +## 4. Merging Type Data into a Registry Database + +regmerge.exe has two main roles: the merging of type data from multiple URD files, +and the conversion of the URD format into RDB, a binary tree data structure using +keys; RDB stands for "Registry Database". + +The regmerge call must include an "UCR" argument which is used to label the type +descriptions in RDB (UCR stands for "Uno Core Reflection"). + +The resulting RDB file can be printed using regview.exe. + +My regmerge.bat and regview.bat scripts call the corresponding UNO tools in +\program, and use the component's service name to identify the URD and +RDB files. For example: + +``` +> regmerge.bat RandomSents +merging registry "RandomSents.urd" under key "UCR" in registry +"RandomSents.rdb". + + +> regview.bat RandomSents +Registry "file:///C:/RandomSents.urd": + +/ + / org + / openoffice + / randomsents + / RandomSents + Value: Type = RegValueType::BINARY + Size = 158 + Data = version: 1 + documentation: "" + file name: "" + type class: service + type name: + "org/openoffice/randomsents/RandomSents" + + // many more lines ... +``` + + +The outcome of the regview.bat call is a RandomSents.rdb file, which is displayed by +regview.bat. + +The RDB format was changed in LibreOffice 4.1, but most tools that use RDB can +understand both the old and new formats. Unfortunately, regmerge.exe only generates +old-style RDB, and regview cannot print the new format. That's means regview is +useless for examining important registry databases in \program, such as +types.rdb and services.rdb which use the new format. + +The API includes a com.sun.star.registry module with an XSimpleRegistry interface +for examining registry databases (i.e. RDB files). Unfortunately, it only understands +the old RDB format, as illustrated by the inability of my test program, +ViewRegistry.java, to display types.rdb or services.rdb (but RandomSents.rdb is +readable). + + +## 5. Generating the Java Package and Interface + +javamaker.exe (and cppumaker.exe) map IDL types to Java (and C++) using data +from RDB files. RandomSents.rdb cannot be mapped on its own because it refers to +types, such as XInterface, which it doesn't define. javamaker also needs Office's +types.rdb, located in \program. + +javamaker generates two things – a Java package representing the IDL module +structure, and a Java ".class" file corresponding to the IDL interface. + +For example, the module structure in RandomSents.idl is: + +``` +module org { module openoffice { module randomsents +``` + +javamaker converts this into a Java package made up of three nested folders, as shown +in Figure 4. + ![](images/45-UNO_Components-4.png) -Figure 4. The Java Package for the randomsents Module. - - -Figure 4 shows an XRandomSents.class in the randomsents folder, which holds the -compiled Java ".class" code for the XRandomSents interface. - -The simplest way to examine this class is to run it through javap: - -> javap XRandomSents.class - -public interface org.openoffice.randomsents.XRandomSents extends -com.sun.star.uno.XInterface { - public static final - com.sun.star.lib.uno.typeinfo.TypeInfo[] UNOTYPEINFO; - public abstract boolean getisAllCaps(); - public abstract void setisAllCaps(boolean); - public abstract java.lang.String getParagraph(int); - public abstract java.lang.String[] getSentences(int); -} - -I decided to improve on this by using the CFR decompiler library -(http://www.benf.org/other/cfr/) to generate the class' full source code. javamaker.bat -calls CFR to generate XRandomSents.java, which is written into the randomsents -folder: - -package org.openoffice.randomsents; - -import com.sun.star.lib.uno.typeinfo.AttributeTypeInfo; -import com.sun.star.lib.uno.typeinfo.MethodTypeInfo; -import com.sun.star.lib.uno.typeinfo.TypeInfo; -import com.sun.star.uno.XInterface; - -public interface XRandomSents extends XInterface { - public static final TypeInfo[] UNOTYPEINFO = new TypeInfo[]{ - new AttributeTypeInfo("isAllCaps", 0, 0), - new MethodTypeInfo("getParagraph", 2, 0), - new MethodTypeInfo("getSentences", 3, 0)}; - - public boolean getisAllCaps(); - - public void setisAllCaps(boolean var1); - - public String getParagraph(int var1); - - public String[] getSentences(int var1); -} - -The XRandomSents interface has four methods that need implementing. Note that the -isAllCaps IDL attribute has become a get and a set method. - -javamaker.bat is called in a similar way to the other batch scripts, by supplying the -component's service name: -> javamaker.bat RandomSents -javamaker.bat passes javamaker.exe the package name (org.openoffice.randomsents), -RandomSents.rdb, and Office's types.rdb. The script's construction of this package -name employs tr.exe, a Windows version of UNIX's tr, which I obtained from the -Gow UNIX tools site (https://github.com/bmatzelle/gow/wiki). - - - -## 6. Creating a Skeleton Component - -The implementation of the XRandomSents interface, is greatly simplified by calling -uno-skeletonmaker.exe. It generates the boilerplate code relating to how a service and -interface are found and initialized at runtime. We'll meet uno-skeletonmaker.exe a -few more times in the next two chapters since it can also generate code for add-ons -and Calc add-ins. - -uno-skeletonmaker.exe suffers from the same constraints as idlc.exe – all its input -data must be copied into its local directory (\sdk\bin), and -"\program" must be added to Window's PATH environment variable so -necessary DLLs can be located. As with idlc.exe, the copying of files into C:\Program -Files\ requires administrative privileges, which may mean calling the batch file with -"elevate.exe". - -uno-skeletonmaker.exe requires a reference to the component's RDB file, Office's -types.rdb, and a fully qualified Java interface name (i.e. - -org.openoffice.randonsents.XRandomSents). - -The following shows skelComp.bat's output when passed the RandomSents service -name: - -> skelComp RandomSents -Copying RandomSents.rdb to "C:\Program Files\LibreOffice 5"\sdk\bin - 1 file(s) copied. - -Copying Java classes in org/ to "C:\Program Files\LibreOffice -5"\sdk\bin -2 File(s) copied - -Generating RandomSentsImpl.java - -Copying RandomSentsImpl.java - 1 file(s) copied. - -Deleting copied RDB, org package, and original RandomSentsImpl.java - -**TIME** for you to complete RandomSentsImpl.java - -The skeleton Java implementation is called RandomSentsImpl.java, and its class -structure is shown in Figure 5. - - - +Figure 4. The Java Package for the randomsents Module. + + +Figure 4 shows an XRandomSents.class in the randomsents folder, which holds the +compiled Java ".class" code for the XRandomSents interface. + +The simplest way to examine this class is to run it through javap: + +``` +> javap XRandomSents.class +``` + +``` +public interface org.openoffice.randomsents.XRandomSents extends +com.sun.star.uno.XInterface { + public static final + com.sun.star.lib.uno.typeinfo.TypeInfo[] UNOTYPEINFO; + public abstract boolean getisAllCaps(); + public abstract void setisAllCaps(boolean); + public abstract java.lang.String getParagraph(int); + public abstract java.lang.String[] getSentences(int); +} +``` + +I decided to improve on this by using the CFR decompiler library +(http://www.benf.org/other/cfr/) to generate the class' full source code. javamaker.bat +calls CFR to generate XRandomSents.java, which is written into the randomsents +folder: + +```java +package org.openoffice.randomsents; + +import com.sun.star.lib.uno.typeinfo.AttributeTypeInfo; +import com.sun.star.lib.uno.typeinfo.MethodTypeInfo; +import com.sun.star.lib.uno.typeinfo.TypeInfo; +import com.sun.star.uno.XInterface; + +public interface XRandomSents extends XInterface { + public static final TypeInfo[] UNOTYPEINFO = new TypeInfo[]{ + new AttributeTypeInfo("isAllCaps", 0, 0), + new MethodTypeInfo("getParagraph", 2, 0), + new MethodTypeInfo("getSentences", 3, 0)}; + + public boolean getisAllCaps(); + + public void setisAllCaps(boolean var1); + + public String getParagraph(int var1); + + public String[] getSentences(int var1); +} +``` + +The XRandomSents interface has four methods that need implementing. Note that the +isAllCaps IDL attribute has become a get and a set method. + +javamaker.bat is called in a similar way to the other batch scripts, by supplying the +component's service name: + +``` +> javamaker.bat RandomSents +``` + +javamaker.bat passes javamaker.exe the package name (org.openoffice.randomsents), +RandomSents.rdb, and Office's types.rdb. The script's construction of this package +name employs tr.exe, a Windows version of UNIX's tr, which I obtained from the +Gow UNIX tools site (https://github.com/bmatzelle/gow/wiki). + + +## 6. Creating a Skeleton Component + +The implementation of the XRandomSents interface, is greatly simplified by calling +uno-skeletonmaker.exe. It generates the boilerplate code relating to how a service and +interface are found and initialized at runtime. We'll meet uno-skeletonmaker.exe a +few more times in the next two chapters since it can also generate code for add-ons +and Calc add-ins. + +uno-skeletonmaker.exe suffers from the same constraints as idlc.exe – all its input +data must be copied into its local directory (\sdk\bin), and +"\program" must be added to Window's PATH environment variable so +necessary DLLs can be located. As with idlc.exe, the copying of files into C:\Program +Files\ requires administrative privileges, which may mean calling the batch file with +"elevate.exe". + +uno-skeletonmaker.exe requires a reference to the component's RDB file, Office's +types.rdb, and a fully qualified Java interface name (i.e. + +org.openoffice.randonsents.XRandomSents). + +The following shows skelComp.bat's output when passed the RandomSents service +name: + +``` +> skelComp RandomSents +Copying RandomSents.rdb to "C:\Program Files\LibreOffice 5"\sdk\bin + 1 file(s) copied. + +Copying Java classes in org/ to "C:\Program Files\LibreOffice +5"\sdk\bin +2 File(s) copied + +Generating RandomSentsImpl.java + +Copying RandomSentsImpl.java + 1 file(s) copied. + +Deleting copied RDB, org package, and original RandomSentsImpl.java + +**TIME** for you to complete RandomSentsImpl.java +``` + +The skeleton Java implementation is called RandomSentsImpl.java, and its class +structure is shown in Figure 5. + ![](images/45-UNO_Components-5.png) -Figure 5. Class Diagram for RandomSentsImpl.java - -RandomSentsImpl extends WeakBase, the base class for UNO components. It's part -of the com.sun.star.lib.uno package which is documented separately from Office -modules such as Writer, Calc, and Impress. Both documentation trees can be accessed -from the LibreOffice API documentation page at http://api.libreoffice.org/. +Figure 5. Class Diagram for RandomSentsImpl.java + +RandomSentsImpl extends WeakBase, the base class for UNO components. It's part +of the com.sun.star.lib.uno package which is documented separately from Office +modules such as Writer, Calc, and Impress. Both documentation trees can be accessed +from the LibreOffice API documentation page at http://api.libreoffice.org/. -The XServiceInfo interface contains three methods for retrieving the component's -implementation name (i.e. "RandomSentsImpl") and supported services (i.e. +The XServiceInfo interface contains three methods for retrieving the component's +implementation name (i.e. "RandomSentsImpl") and supported services (i.e. "org.openoffice.randomsents.RandomSents"). These methods were generated by uno- -skeletonmaker, and added to RandomSentsImpl .java. - -The __getComponentFactory() method in RandomSentsImpl is used by Office's -service manager to create a RandomSents service and interface. - -__writeRegisryServiceInfo() is used by the service manager to register the component -in Office at runtime. - -The remaining four methods in RandomSentsImpl are stubs for the functions defined -in XRandomSents; our job is to implement them: - -// part of RandomSentsImpl.java... - - -public boolean getisAllCaps() -{ - return false; -} - -public void setisAllCaps(boolean the_value) -{ -} - -public java/lang/String getParagraph(int numSents) -{ - return new java/lang/String(); -} - -public java/lang/String[] getSentences(int numSents) -{ - return new java/lang/String[0]; -} - -Any Java classes employed in the stubs are fully qualified, and written using "/"s -rather than "."s. This can be seen in RandomSentsImpl 's getParagraph() and -getSentences(). - - - -## 7. Compiling the Completed Implementation - -The completed RandomSentsImpl class is based on a Processing example at -http://funprogramming.org/57-A-random-sentence-generator-writes-nonsense.html. I -used that program's grammar and arrays of articles, adjectives, nouns, prepositions, -and verbs. - -The arrays are defined at the start of the completed RandomSentsImpl.java: - -// globals in RandomSentsImpl.java -private static final int MAX_SENTENCES = 100; - -private static String[] articles = { "the", "my", "your", ... }; -private static String[] adjs = { "happy", "rotating", "red", ... }; -private static String[] nouns = { "forest", "tree", "flower", ... }; -private static String[] preps = { "under", "in front of", ... }; -private static String[] verbs = { "sings", "dances", ... }; - -There's a private variable to hold the current all-caps setting: -private boolean isAllCaps = false; -This variable makes the all-caps get and set methods trivial: - -public boolean getisAllCaps() -{ return isAllCaps; } - -public void setisAllCaps(boolean b) -{ isAllCaps = b; } - -getParagraph() calls getSentences() and then converts its sentences array into a single -string: - -public String getParagraph(int numSents) -{ - String[] sents = getSentences(numSents); - StringBuilder sb = new StringBuilder(); - for (int i=0; i < sents.length; i++) - sb.append( sents[i]); - return sb.toString(); -} // end of getParagraph() - -getSentences() implements the sentence grammar: -
-
-A sentence is generated by randomly selecting a word from each syntactic category, -represented by the word arrays at the top of the program. getSentences() uses a loop to -repeat this task until enough sentences have been created: - -public String[] getSentences(int numSents) -{ - if (numSents < 1) - numSents = 1; - else if (numSents > MAX_SENTENCES) - numSents = MAX_SENTENCES; - - String[] sents = new String[numSents]; - StringBuilder sb = new StringBuilder(); - for (int i=0; i < numSents; i++) { - sb.setLength(0); // empty builder - sb.append( capitalize(pickWord(articles)) + " "); - sb.append( pickWord(adjs) + " "); - sb.append( pickWord(nouns) + " "); - - sb.append( pickWord(verbs) + " "); - sb.append( pickWord(preps) + " "); - - sb.append( pickWord(articles) + " "); - sb.append( pickWord(adjs) + " "); - sb.append( pickWord(nouns) + ". "); - - sents[i] = isAllCaps ? sb.toString().toUpperCase() : - sb.toString(); - } - return sents; -} // end of getSentences() - - -private String pickWord(String[] words) -{ return words[ (int)(Math.random()*words.length) ]; } - - -private String capitalize(String word) -{ return word.substring(0,1).toUpperCase() + word.substring(1); } - -The compilation of RandomSentsImpl.java requires that the Office SDK and the -org.openoffice.randomsents package be added to javac's classpath. This is managed -by compileOrg.bat which takes the service name and filename as inputs: -> compileOrg.bat RandomSents RandomSentsImpl.java - - - -## 8. Packaging the Component - -The Java code for the component is zipped twice, once to form a JAR, and then to -create an Office OXT (extension) file. - -toJar.bat starts by compiling the Java code (using compileOrg.bat), then constructs the -JAR file from a manifest and the org.openoffice package. - -The RandomSents manifest is a one-liner, stored in ManifestRandomSents.txt: -RegistrationClassName: RandomSentsImpl -toJar.bat is called using the component's service name: - -> toJar.bat RandomSents -Compiling RandomSentsImpl.java with LibreOffice SDK, JNA, Utils, and -RandomSents service... - - -Generating RandomSentsImpl.jar -added manifest -adding: RandomSentsImpl.class(in = 7092) (out= 3944)(deflated 44%) -adding: RandomSentsImpl.java(in = 6803) (out= 2484)(deflated 63%) -adding: org/(in = 0) (out= 0)(stored 0%) -adding: org/openoffice/(in = 0) (out= 0)(stored 0%) -adding: org/openoffice/randomsents/(in = 0) (out= 0)(stored 0%) -adding: org/openoffice/randomsents/XRandomSents.class(in = 745) (out= -450)(deflated 39%) -adding: org/openoffice/randomsents/XRandomSents.java(in = 901) (out= -353)(deflated 60%) - -The resulting JAR is called RandomSentsImpl.jar. - -An OXT extension file is a zipped folder which contains a manifest.xml file in a -META-INF\ folder, a description of the extension in description.xml, the component's -RDB file, and its code (a JAR file in my case). - -makeOXT.bat looks for a pre-existing folder with the same name as the component's -service. It should already contain a manifest and description, but makeOXT.bat adds -the RDB and JAR files itself. - -The required RandomSents folder is shown in Figure 6. - - - +skeletonmaker, and added to RandomSentsImpl .java. + +The __getComponentFactory() method in RandomSentsImpl is used by Office's +service manager to create a RandomSents service and interface. + +__writeRegisryServiceInfo() is used by the service manager to register the component +in Office at runtime. + +The remaining four methods in RandomSentsImpl are stubs for the functions defined +in XRandomSents; our job is to implement them: + +```java +// part of RandomSentsImpl.java... + + +public boolean getisAllCaps() +{ + return false; +} + +public void setisAllCaps(boolean the_value) +{ +} + +public java/lang/String getParagraph(int numSents) +{ + return new java/lang/String(); +} + +public java/lang/String[] getSentences(int numSents) +{ + return new java/lang/String[0]; +} +``` + +Any Java classes employed in the stubs are fully qualified, and written using "/"s +rather than "."s. This can be seen in RandomSentsImpl 's getParagraph() and +getSentences(). + + +## 7. Compiling the Completed Implementation + +The completed RandomSentsImpl class is based on a Processing example at +http://funprogramming.org/57-A-random-sentence-generator-writes-nonsense.html. I +used that program's grammar and arrays of articles, adjectives, nouns, prepositions, +and verbs. + +The arrays are defined at the start of the completed RandomSentsImpl.java: + +```java +// globals in RandomSentsImpl.java +private static final int MAX_SENTENCES = 100; + +private static String[] articles = { "the", "my", "your", ... }; +private static String[] adjs = { "happy", "rotating", "red", ... }; +private static String[] nouns = { "forest", "tree", "flower", ... }; +private static String[] preps = { "under", "in front of", ... }; +private static String[] verbs = { "sings", "dances", ... }; +``` + +There's a private variable to hold the current all-caps setting: + +```java +private boolean isAllCaps = false; +``` + +This variable makes the all-caps get and set methods trivial: + +```java +public boolean getisAllCaps() +{ return isAllCaps; } + +public void setisAllCaps(boolean b) +{ isAllCaps = b; } +``` + +getParagraph() calls getSentences() and then converts its sentences array into a single +string: + +```java +public String getParagraph(int numSents) +{ + String[] sents = getSentences(numSents); + StringBuilder sb = new StringBuilder(); + for (int i=0; i < sents.length; i++) + sb.append( sents[i]); + return sb.toString(); +} // end of getParagraph() +``` + +getSentences() implements the sentence grammar: + +```java +
+
+``` + +A sentence is generated by randomly selecting a word from each syntactic category, +represented by the word arrays at the top of the program. getSentences() uses a loop to +repeat this task until enough sentences have been created: + +```java +public String[] getSentences(int numSents) +{ + if (numSents < 1) + numSents = 1; + else if (numSents > MAX_SENTENCES) + numSents = MAX_SENTENCES; + + String[] sents = new String[numSents]; + StringBuilder sb = new StringBuilder(); + for (int i=0; i < numSents; i++) { + sb.setLength(0); // empty builder + sb.append( capitalize(pickWord(articles)) + " "); + sb.append( pickWord(adjs) + " "); + sb.append( pickWord(nouns) + " "); + + sb.append( pickWord(verbs) + " "); + sb.append( pickWord(preps) + " "); + + sb.append( pickWord(articles) + " "); + sb.append( pickWord(adjs) + " "); + sb.append( pickWord(nouns) + ". "); + + sents[i] = isAllCaps ? sb.toString().toUpperCase() : + sb.toString(); + } + return sents; +} // end of getSentences() + + +private String pickWord(String[] words) +{ return words[ (int)(Math.random()*words.length) ]; } + + +private String capitalize(String word) +{ return word.substring(0,1).toUpperCase() + word.substring(1); } +``` + +The compilation of RandomSentsImpl.java requires that the Office SDK and the +org.openoffice.randomsents package be added to javac's classpath. This is managed +by compileOrg.bat which takes the service name and filename as inputs: + +``` +> compileOrg.bat RandomSents RandomSentsImpl.java +``` + + +## 8. Packaging the Component + +The Java code for the component is zipped twice, once to form a JAR, and then to +create an Office OXT (extension) file. + +toJar.bat starts by compiling the Java code (using compileOrg.bat), then constructs the +JAR file from a manifest and the org.openoffice package. + +The RandomSents manifest is a one-liner, stored in ManifestRandomSents.txt: +RegistrationClassName: RandomSentsImpl +toJar.bat is called using the component's service name: + +``` +> toJar.bat RandomSents +Compiling RandomSentsImpl.java with LibreOffice SDK, JNA, Utils, and +RandomSents service... + + +Generating RandomSentsImpl.jar +added manifest +adding: RandomSentsImpl.class(in = 7092) (out= 3944)(deflated 44%) +adding: RandomSentsImpl.java(in = 6803) (out= 2484)(deflated 63%) +adding: org/(in = 0) (out= 0)(stored 0%) +adding: org/openoffice/(in = 0) (out= 0)(stored 0%) +adding: org/openoffice/randomsents/(in = 0) (out= 0)(stored 0%) +adding: org/openoffice/randomsents/XRandomSents.class(in = 745) (out= +450)(deflated 39%) +adding: org/openoffice/randomsents/XRandomSents.java(in = 901) (out= +353)(deflated 60%) +``` + +The resulting JAR is called RandomSentsImpl.jar. + +An OXT extension file is a zipped folder which contains a manifest.xml file in a +META-INF\ folder, a description of the extension in description.xml, the component's +RDB file, and its code (a JAR file in my case). + +makeOXT.bat looks for a pre-existing folder with the same name as the component's +service. It should already contain a manifest and description, but makeOXT.bat adds +the RDB and JAR files itself. + +The required RandomSents folder is shown in Figure 6. + ![](images/45-UNO_Components-6.png) -Figure 6. The RandomSents Folder used by makeOXT.bat. - - -The manifest.xml file inside META-INF\ consists of two attributes which give the -names for RandomSents' RDB and JAR files: - - - - - - - - - -Only these names need to be changed when using this manifest for a different -extension. - -description.xml can be fairly minimal or relatively fancy, as in my example: - - - - - - - - - Random Sentences - - - - Andrew Davison - - - - - - - - - - - - - - - - - - - - - - - - - - - - -There are ten fields inside the tag: - identifier: the fully qualified module name (this field is mandatory); - version number (also mandatory); - display-name: a short piece of text which will act as a title; - publisher: this field can include a URL which can be clicked on inside the -Extension manager (see Figure 8 below); - registration: license text which is displayed when the component is first installed, -and the user must accept or reject. Figure 6 shows the "license.txt" file (an MIT -license) in the OXT folder. If software licenses are a mystery to you (as they were -to me), a good site for helping you choose one is http://choosealicense.com/; - extension-description: a one or two line description of the component, which may +Figure 6. The RandomSents Folder used by makeOXT.bat. + + +The manifest.xml file inside META-INF\ consists of two attributes which give the +names for RandomSents' RDB and JAR files: + +``` + + + + + + + +``` + +Only these names need to be changed when using this manifest for a different +extension. + +description.xml can be fairly minimal or relatively fancy, as in my example: + +``` + + + + + + + + Random Sentences + + + + Andrew Davison + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +There are ten fields inside the tag: + +* identifier: the fully qualified module name (this field is mandatory); +* version number (also mandatory); +* display-name: a short piece of text which will act as a title; +* publisher: this field can include a URL which can be clicked on inside the +Extension manager (see Figure 8 below); +* registration: license text which is displayed when the component is first installed, +and the user must accept or reject. Figure 6 shows the "license.txt" file (an MIT +license) in the OXT folder. If software licenses are a mystery to you (as they were +to me), a good site for helping you choose one is http://choosealicense.com/; +* extension-description: a one or two line description of the component, which may be stored in a separate file, as I've done. Figure 6 shows the "package- -description.txt" file in the OXT folder; - icon: the filename of a small image (42 x 42 pixels, PNG or JPG) which appears -next to the component information in the Extensions manager (see Figure 8 -below). Figure 6 shows the "stack.png" file in the OXT folder; - update-information: a URL link to an update page; - dependencies: the minimal version of Office that supports the component. I've -referred to OpenOffice in this tag, which is also understood to mean LibreOffice. - -OpenOffice 3.4 corresponds to versions of LibreOffice up to 4.1.3, and -OpenOffice 4.1 matches LibreOffice 4.2.4. Since LibreOffice 3.5, you can use the -tag name "LibreOffice-minimal-version"; - platform: a list of OSes that support the component; usually assigned "all" or left -out (which means the same thing). A more specific example is - -. - - -As far as I know there's no label for Windows 64 bit. - -The best place for more information on these fields is chapter 4 of the Developer's -Guide at -https://wiki.openoffice.org/wiki/Documentation/DevGuide/Extensions/Extensions, or -use loGuide Extensions. Chapter 4 talks about several configuration topics I won't -be covering, including update support, the options dialog, and help content. - -The makeOXT.bat script is called using the component's service name: - -> makeOxt.bat RandomSents -Copying RandomSents.rdb to RandomSents\ - 1 file(s) copied. - -Copying RandomSentsImpl.jar to RandomSents\ - 1 file(s) copied. - - -Zipping RandomSents\ as RandomSents.oxt - adding: description.xml (152 bytes security) (deflated 58%) - adding: license.txt (152 bytes security) (deflated 41%) - adding: META-INF/ (152 bytes security) (stored 0%) - adding: META-INF/manifest.xml (152 bytes security) (deflated 53%) - adding: package-description.txt (152 bytes security) (deflated 4%) - adding: RandomSents.rdb (152 bytes security) (deflated 90%) - adding: RandomSentsImpl.jar (152 bytes security) (deflated 9%) - adding: stack.png (152 bytes security) (stored 0%) - -The previously created RDB and JAR files are copied into the RandomSents folder in -Figure 6, and the folder is zipped up as RandomSents.oxt. The zipping is carried out -by a Windows version of the UNIX zip tool downloaded from the Gow site -(https://github.com/bmatzelle/gow/wiki). - - - -## 9. Installing the Extension - -The Extension Manager can be started independently of Office by calling the unopkg -tool in \program with a "gui" argument; my extManager.bat script does -this for you, resulting in a window something like Figure 7. - - - +description.txt" file in the OXT folder; +* icon: the filename of a small image (42 x 42 pixels, PNG or JPG) which appears +next to the component information in the Extensions manager (see Figure 8 +below). Figure 6 shows the "stack.png" file in the OXT folder; +* update-information: a URL link to an update page; +* dependencies: the minimal version of Office that supports the component. I've +referred to OpenOffice in this tag, which is also understood to mean LibreOffice. +OpenOffice 3.4 corresponds to versions of LibreOffice up to 4.1.3, and +OpenOffice 4.1 matches LibreOffice 4.2.4. Since LibreOffice 3.5, you can use the +tag name "LibreOffice-minimal-version"; +* platform: a list of OSes that support the component; usually assigned "all" or left +out (which means the same thing). A more specific example is + +``` +. +``` + +As far as I know there's no label for Windows 64 bit. + +The best place for more information on these fields is chapter 4 of the Developer's +Guide at +https://wiki.openoffice.org/wiki/Documentation/DevGuide/Extensions/Extensions, or +use `loGuide Extensions`. Chapter 4 talks about several configuration topics I won't +be covering, including update support, the options dialog, and help content. + +The makeOXT.bat script is called using the component's service name: + +``` +> makeOxt.bat RandomSents +Copying RandomSents.rdb to RandomSents\ + 1 file(s) copied. + +Copying RandomSentsImpl.jar to RandomSents\ + 1 file(s) copied. + + +Zipping RandomSents\ as RandomSents.oxt + adding: description.xml (152 bytes security) (deflated 58%) + adding: license.txt (152 bytes security) (deflated 41%) + adding: META-INF/ (152 bytes security) (stored 0%) + adding: META-INF/manifest.xml (152 bytes security) (deflated 53%) + adding: package-description.txt (152 bytes security) (deflated 4%) + adding: RandomSents.rdb (152 bytes security) (deflated 90%) + adding: RandomSentsImpl.jar (152 bytes security) (deflated 9%) + adding: stack.png (152 bytes security) (stored 0%) +``` + +The previously created RDB and JAR files are copied into the RandomSents folder in +Figure 6, and the folder is zipped up as RandomSents.oxt. The zipping is carried out +by a Windows version of the UNIX zip tool downloaded from the Gow site +(https://github.com/bmatzelle/gow/wiki). + + +## 9. Installing the Extension + +The Extension Manager can be started independently of Office by calling the unopkg +tool in \program with a "gui" argument; my extManager.bat script does +this for you, resulting in a window something like Figure 7. + ![](images/45-UNO_Components-7.png) -Figure 7. Office's Extension Manager. - - -My pkg.bat script utilizes unopkg.exe to remove any currently installed version of the -extension before installing the one stored in the OXT file. The manager window is -displayed at the end, to visually confirm that the extension has been loaded. A typical -call: - -> pkg.bat RandomSents - -Attempting to remove old version of RandomSents.oxt with unopkg... - - -ERROR: There is no such extension deployed: -org.openoffice.randomsents - -unopkg failed. - -Installing RandomSents.oxt with unopkg... - - -Extension Software License Agreement of Random Sentences: - -MIT License - -Copyright (c) 2016 Andrew Davison - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - -Read the complete License Agreement displayed above. Accept the -License Agreement by typing "yes" on the console then press the -Return key. Type "no" to decline and to abort the extension setup. - - -[Enter "yes" or "no"]:yes - -unopkg done. - - -The installation prints the license text, followed by a prompt to enter "yes" or "no"; I -typed "yes" so the installation could progress. - -Figure 8 shows the Extension Manager after a successful installation of RandomSents. - -I've added labels to show which description.xml fields are used in the display. - - - +Figure 7. Office's Extension Manager. + + +My pkg.bat script utilizes unopkg.exe to remove any currently installed version of the +extension before installing the one stored in the OXT file. The manager window is +displayed at the end, to visually confirm that the extension has been loaded. A typical +call: + +``` +> pkg.bat RandomSents + +Attempting to remove old version of RandomSents.oxt with unopkg... + + +ERROR: There is no such extension deployed: +org.openoffice.randomsents + +unopkg failed. + +Installing RandomSents.oxt with unopkg... + + +Extension Software License Agreement of Random Sentences: + +MIT License + +Copyright (c) 2016 Andrew Davison + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +Read the complete License Agreement displayed above. Accept the +License Agreement by typing "yes" on the console then press the +Return key. Type "no" to decline and to abort the extension setup. + + +[Enter "yes" or "no"]:yes + +unopkg done. +``` + +The installation prints the license text, followed by a prompt to enter "yes" or "no"; I +typed "yes" so the installation could progress. + +Figure 8 shows the Extension Manager after a successful installation of RandomSents. + +I've added labels to show which description.xml fields are used in the display. + ![](images/45-UNO_Components-8.jpg) -Figure 8. The Extension Manager with RandomSents Installed. - - - -10. Using a New Component in a Program -The PoemCreator.java example at the start of the chapter skipped over two important -problems: how the program is compiled against a new UNO Component (e.g. - -RandomSent), and how the component is used at runtime. - -Almost every one of my Java examples from previous chapters have been compiled -against the Office API located below the directory. My compile.bat script -contains something like: -javac -cp "%LO%\program\classes\*;." %* -The LO variable is assigned the path to Office by code earlier in the script, and then -javac looks in \program\classes for the JAR files that make up the API. - -This approach will not work when looking for the JAR that implements a new UNO -component (e.g. RandomSentsImpl.jar in RandomSent.oxt). Extensions aren't stored -with the API JARs, but usually in a folder below -C:\Users\\AppData\Roaming\LibreOffice\. I say "usually" because -the location depends on the license details in the OXT description. The relevant field -for RandomSent is: - -// part of description.xml - : - - - - - - : - -The license will be accepted by a "user", which causes the extension to be installed in -that user's AppData\Roaming\LibreOffice\ folder. Another licensing possibility is -"admin", which makes the extension available to everyone using Office on this -machine. These are called shared mode extensions, and stored in -\share\extensions\. - -In short then, how can javac.exe (and java.exe) find the JAR file for a new component -when it comes time to compile (and run) a program such as PoemCreator.java? -The Office API offers a PackageInformationProvider service which can access -information about installed extensions. My Info.listExtensions() utilizes -PackageInformationProvider to print extension details. It's called at the start of -PoemCreator.java before the poetry is generated: - -// in PoemCreator.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; - } - - Info.listExtensions(); - - GUI.setVisible(doc, true); - - Write.setHeader(doc, "Muse of the Office"); - : // make poetry -} - -The ID, version, and installation location are printed for each extension: - -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: org.openoffice.randomsents - - Version: 0.1 - Loc: file:///C:/Users/Ad/AppData/Roaming/LibreOffice/4/user/ - uno_packages/cache/uno_packages/ - lu29529rfgd.tmp_/RandomSents.oxt - -## 5. ID: com.sun.star.comp.Calc.NLPSolver - - Version: 0.9 - Loc: file:///C:/Program%20Files/LibreOffice%205/program/../ - share/extensions/nlpsolver - -## 6. ID: spanish.es_ANY.dicts.from.rla-es - - Version: 0.8 - Loc: file:///C:/Program%20Files/LibreOffice%205/program/../ - share/extensions/dict-es - -## 7. ID: com.sun.wiki-publisher - - Version: 1.2.0 - Loc: file:///C:/Program%20Files/LibreOffice%205/program/../ - share/extensions/wiki-publisher - -The list shows that Office's dictionaries and Calc NLPSolver are shared extensions, -but RandomSents (no. 4) is for user "Ad" only. - -Info.listExtensions() obtains an instance of the XPackageInformationProvider -interface, then calls XPackageInformationProvider.getExtensionList() to get IDs. An -extension's location is obtained by calling -XPackageInformationProvider.getPackageLocation() with its ID. The listExtensions() -code: - -// 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()); } - - -10.1. Finding an Extension's JAR File -FindExtJar.java is passed an extension's fully qualified module name (e.g. - -"org.openoffice.randomsents"), and uses it to find the extension's installation folder. - -The folder is searched for a JAR filename, and the first match is stored in -"lofindTemp.txt". - -The main() function of FindExtJar.java: - -public static void main(String args[]) -{ - if (args.length != 1) { - System.out.println("Usage: run FindExtJar "); - return; - } - - Lo.loadOffice(); - - FileIO.saveString("lofindTemp.txt", "\"xx\""); - - String extDir = Info.getExtensionLoc(args[0]); - if ((extDir == null) || extDir.equals("")) { - System.out.println("Could not find extension: " + args[0]); - Lo.closeOffice(); - return; - } - - // look in folder for JAR filename - try { - FilenameFilter filter = new FilenameFilter() { - public boolean accept(File dir, String name) - { return name.endsWith(".jar"); } - }; - File dir = new File(new URI(extDir)); - String[] fnms = dir.list(filter); - - if (fnms == null) - System.out.println("No jars found"); - else { - String extPath = dir.getAbsolutePath(); - String jarNm = "\"" + extPath + "/" + fnms[0] + "\""; - FileIO.saveString("lofindTemp.txt", jarNm); - } - } - catch(java.lang.Exception e) - { System.out.println(e); } - - Lo.closeOffice(); -} // end of main() - -Info.getExtensionLoc() calls XPackageInformationProvider.getPackageLocation(): - -// in the Info class -public static String getExtensionLoc(String id) -{ - XPackageInformationProvider pip = getPip(); - if (pip == null) { - System.out.println("No package info provider found"); - return null; - } - else - return pip.getPackageLocation(id); -} // end of getExtensionLoc() - -Back in the main() function, the directory is searched for a ".jar" file using a FileFilter -object. - -Several things may go wrong (e.g. the module name may be wrong, or the extension -might not contain a JAR file), so "xx" is written to "lofindTemp.txt" initially, to act as -an error string. - - -10.2. Using an Extension's JAR in Batch Scripts -With the help of FindExtJar.java, it's possible to write batch scripts that compile and -run a program using an extension's JAR. - -Both compileExt.bat and runExt.bat are similar, so I'll only outline how -compileExt.bat works. The script is called with two arguments: the extension's service -name, and the Java file that needs compiling. For example: - -> compileExt.bat RandomSents PoemCreator.java -Found "C:\Program Files\LibreOffice 5" -Executing FindExtJar org.openoffice.randomsents - with LibreOffice SDK, JNA, and Utils... - -Loading Office... - -Saved string to file: lofindTemp.txt -Closing Office -Office terminated - -Using RandomSents JAR: +Figure 8. The Extension Manager with RandomSents Installed. + + +## 10. Using a New Component in a Program + +The PoemCreator.java example at the start of the chapter skipped over two important +problems: how the program is compiled against a new UNO Component (e.g. + +RandomSent), and how the component is used at runtime. + +Almost every one of my Java examples from previous chapters have been compiled +against the Office API located below the directory. My compile.bat script +contains something like: + +```java +javac -cp "%LO%\program\classes\*;." %* +``` + +The LO variable is assigned the path to Office by code earlier in the script, and then +javac looks in \program\classes for the JAR files that make up the API. + +This approach will not work when looking for the JAR that implements a new UNO +component (e.g. RandomSentsImpl.jar in RandomSent.oxt). Extensions aren't stored +with the API JARs, but usually in a folder below +C:\Users\\AppData\Roaming\LibreOffice\. I say "usually" because +the location depends on the license details in the OXT description. The relevant field +for RandomSent is: + +``` +// part of description.xml + : + + + + + + : +``` + +The license will be accepted by a "user", which causes the extension to be installed in +that user's AppData\Roaming\LibreOffice\ folder. Another licensing possibility is +"admin", which makes the extension available to everyone using Office on this +machine. These are called shared mode extensions, and stored in +\share\extensions\. + +In short then, how can javac.exe (and java.exe) find the JAR file for a new component +when it comes time to compile (and run) a program such as PoemCreator.java? +The Office API offers a PackageInformationProvider service which can access +information about installed extensions. My Info.listExtensions() utilizes +PackageInformationProvider to print extension details. It's called at the start of +PoemCreator.java before the poetry is generated: + +```java +// in PoemCreator.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; + } + + Info.listExtensions(); + + GUI.setVisible(doc, true); + + Write.setHeader(doc, "Muse of the Office"); + : // make poetry +} +``` + +The ID, version, and installation location are printed for each extension: + +``` +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: org.openoffice.randomsents + + Version: 0.1 + Loc: file:///C:/Users/Ad/AppData/Roaming/LibreOffice/4/user/ + uno_packages/cache/uno_packages/ + lu29529rfgd.tmp_/RandomSents.oxt + +## 5. ID: com.sun.star.comp.Calc.NLPSolver + + Version: 0.9 + Loc: file:///C:/Program%20Files/LibreOffice%205/program/../ + share/extensions/nlpsolver + +## 6. ID: spanish.es_ANY.dicts.from.rla-es + + Version: 0.8 + Loc: file:///C:/Program%20Files/LibreOffice%205/program/../ + share/extensions/dict-es + +## 7. ID: com.sun.wiki-publisher + + Version: 1.2.0 + Loc: file:///C:/Program%20Files/LibreOffice%205/program/../ + share/extensions/wiki-publisher +``` + +The list shows that Office's dictionaries and Calc NLPSolver are shared extensions, +but RandomSents (no. 4) is for user "Ad" only. + +Info.listExtensions() obtains an instance of the XPackageInformationProvider +interface, then calls XPackageInformationProvider.getExtensionList() to get IDs. An +extension's location is obtained by calling +XPackageInformationProvider.getPackageLocation() with its ID. The listExtensions() +code: + +```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()); } +``` + + +### 10.1. Finding an Extension's JAR File + +FindExtJar.java is passed an extension's fully qualified module name (e.g. +"org.openoffice.randomsents"), and uses it to find the extension's installation folder. +The folder is searched for a JAR filename, and the first match is stored in +"lofindTemp.txt". + +The main() function of FindExtJar.java: + +```java +public static void main(String args[]) +{ + if (args.length != 1) { + System.out.println("Usage: run FindExtJar "); + return; + } + + Lo.loadOffice(); + + FileIO.saveString("lofindTemp.txt", "\"xx\""); + + String extDir = Info.getExtensionLoc(args[0]); + if ((extDir == null) || extDir.equals("")) { + System.out.println("Could not find extension: " + args[0]); + Lo.closeOffice(); + return; + } + + // look in folder for JAR filename + try { + FilenameFilter filter = new FilenameFilter() { + public boolean accept(File dir, String name) + { return name.endsWith(".jar"); } + }; + File dir = new File(new URI(extDir)); + String[] fnms = dir.list(filter); + + if (fnms == null) + System.out.println("No jars found"); + else { + String extPath = dir.getAbsolutePath(); + String jarNm = "\"" + extPath + "/" + fnms[0] + "\""; + FileIO.saveString("lofindTemp.txt", jarNm); + } + } + catch(java.lang.Exception e) + { System.out.println(e); } + + Lo.closeOffice(); +} // end of main() +``` + +Info.getExtensionLoc() calls XPackageInformationProvider.getPackageLocation(): + +```java +// in the Info class +public static String getExtensionLoc(String id) +{ + XPackageInformationProvider pip = getPip(); + if (pip == null) { + System.out.println("No package info provider found"); + return null; + } + else + return pip.getPackageLocation(id); +} // end of getExtensionLoc() +``` + +Back in the main() function, the directory is searched for a ".jar" file using a FileFilter +object. + +Several things may go wrong (e.g. the module name may be wrong, or the extension +might not contain a JAR file), so "xx" is written to "lofindTemp.txt" initially, to act as +an error string. + + +### 10.2. Using an Extension's JAR in Batch Scripts + +With the help of FindExtJar.java, it's possible to write batch scripts that compile and +run a program using an extension's JAR. + +Both compileExt.bat and runExt.bat are similar, so I'll only outline how +compileExt.bat works. The script is called with two arguments: the extension's service +name, and the Java file that needs compiling. For example: + +``` +> compileExt.bat RandomSents PoemCreator.java +Found "C:\Program Files\LibreOffice 5" +Executing FindExtJar org.openoffice.randomsents + with LibreOffice SDK, JNA, and Utils... + +Loading Office... + +Saved string to file: lofindTemp.txt +Closing Office +Office terminated + +Using RandomSents JAR: C:\Users\Ad\AppData\Roaming\LibreOffice\4\user\uno_packages\cache\uno -_packages\lu29529rfgd.tmp_\RandomSents.oxt/RandomSentsImpl.jar - -Compiling PoemCreator.java - with LibreOffice SDK, JNA, Utils, and RandomSents... - - -The output shows that Office is invoked, which happens when FindExtJar is called. - -The script reads in the contents of "lofindTemp.txt", and adds the JAR's path to -javac.exe's classpath. The compileExt.bat script is: - -@echo off -setlocal -IF [%1] == [] ( - echo No service name or Java file supplied - EXIT /B -) - -IF [%2] == [] ( - echo No service name or Java file supplied - EXIT /B -) - -call lofind.bat -set /p LOQ= tempComp.txt -set /p PACK_NM= tempComp.txt +set /p PACK_NM= Add-Ons menu item (see Figure 2) or as a toolbar created by View > -Toolbars > Add-on 2 (see Figure 3). This 'floating' toolbar can be attached to an -existing toolbar. - +When the Writer application is running, EzHighlight is accessible either through +Office's Tools, Add-Ons menu item (see Figure 2) or as a toolbar created by View, +Toolbars, Add-on 2 (see Figure 3). This 'floating' toolbar can be attached to an +existing toolbar. + ![](images/46-Addons-2.png) -Figure 2. The EzHighlight Add-on on the Tools Menu. +Figure 2. The EzHighlight Add-on on the Tools Menu. - - ![](images/46-Addons-3.png) -Figure 3. The EzHighlighter Add-on as a Toolbar. +Figure 3. The EzHighlighter Add-on as a Toolbar. + - -When the user clicks on "EzHighlight Text", either through the menu or toolbar, the -dialog shown in Figure 4 appears. +When the user clicks on "EzHighlight Text", either through the menu or toolbar, the +dialog shown in Figure 4 appears. - - ![](images/46-Addons-4.png) -Figure 4. The EzHighlighter Dialog. +Figure 4. The EzHighlighter Dialog. - -The user enters a word, and either types or presses the "Highlight" button. -Every matching word in the currently loaded Writer document is redrawn in red, and -the number of changes reported in the "Count:" field. +The user enters a word, and either types or presses the "Highlight" button. +Every matching word in the currently loaded Writer document is redrawn in red, and +the number of changes reported in the "Count:" field. -EzHighlight also has basic help support, accessible from Office's Help menu as the -"About EzHighlight" item (see Figure 5). +EzHighlight also has basic help support, accessible from Office's Help menu as the +"About EzHighlight" item (see Figure 5). - - ![](images/46-Addons-5.png) -Figure 5. The EzHighlight Help Menu Item. +Figure 5. The EzHighlight Help Menu Item. - -Clicking on the help item causes a simple message box to appear, as in Figure 6. - - +Clicking on the help item causes a simple message box to appear, as in Figure 6. + ![](images/46-Addons-6.png) -Figure 6. The EzHighlight Help Message Box. +Figure 6. The EzHighlight Help Message Box. + - - -## 1. What is an Add-on? +## 1. What is an Add-on? -Office supports two kinds of add-on: job add-ons and ProtocolHandler add-ons. +Office supports two kinds of add-on: job add-ons and ProtocolHandler add-ons. -A job add-on is a component triggered by events inside Office, such as when the -application is first opened or about to terminate, or when a document is loaded, -printed, or closed. +A job add-on is a component triggered by events inside Office, such as when the +application is first opened or about to terminate, or when a document is loaded, +printed, or closed. - -This chapter is about Protocol handler add-ons, which link menu and/or toolbar items -to a component using Office's dispatch framework. Every GUI element is assigned a -'command URL', a unique name made of two or three parts: -:[?+ ] -A protocol string can be almost anything, and I'll be using the extension's ID -("org.openoffice.ezhighlightAddon"). +This chapter is about Protocol handler add-ons, which link menu and/or toolbar items +to a component using Office's dispatch framework. Every GUI element is assigned a +'command URL', a unique name made of two or three parts: -There is a different command string for each GUI-triggered action, and I'll utilize two: -"EzHighlight" and "help". "EzHighlight" is associated with the EzHighlight menu and -toolbar items (Figures 2 and 3), and makes the dialog in Figure 4 appear. "help" is -assigned to the "About EzHighlight" help menu item, and causes the message box in -Figure 6 to be displayed. +``` +:[?+ ] +``` -I won't be using command string arguments. +A protocol string can be almost anything, and I'll be using the extension's ID +("org.openoffice.ezhighlightAddon"). -When a user chooses an item in the user interface, its command URL is passed along -a list of dispatch handlers until one accepts the command and processes it by creating -a dispatch object. Fortunately, most of the dispatch handling behavior in an add-on -can be generated automatically. +There is a different command string for each GUI-triggered action, and I'll utilize two: +"EzHighlight" and "help". "EzHighlight" is associated with the EzHighlight menu and +toolbar items (Figures 2 and 3), and makes the dialog in Figure 4 appear. "help" is +assigned to the "About EzHighlight" help menu item, and causes the message box in +Figure 6 to be displayed. -The main source of information for job and ProtocolHandler add-ons is chapter 3 of -the Developer's Guide (available at -https://wiki.openoffice.org/w/images/d/d9/DevelopersGuide_OOo3.1.0.pdf), or -online at +I won't be using command string arguments. + +When a user chooses an item in the user interface, its command URL is passed along +a list of dispatch handlers until one accepts the command and processes it by creating +a dispatch object. Fortunately, most of the dispatch handling behavior in an add-on +can be generated automatically. + +The main source of information for job and ProtocolHandler add-ons is chapter 3 of +the Developer's Guide (available at +https://wiki.openoffice.org/w/images/d/d9/DevelopersGuide_OOo3.1.0.pdf), or +online at https://wiki.openoffice.org/wiki/Documentation/DevGuide/WritingUNO/Integrating_ -Components_into_OpenOffice.org and +Components_into_OpenOffice.org and https://wiki.openoffice.org/wiki/Documentation/DevGuide/WritingUNO/AddOns/Ad -d-Ons (or use loGuide "Integrating Components" and loGuide add-ons). +d-Ons (or use `loGuide "Integrating Components"` and `loGuide add-ons`). + +The Developer's Guide add-on example can be found at +http://api.libreoffice.org/examples/DevelopersGuide/examples.html#Components -The Developer's Guide add-on example can be found at -http://api.libreoffice.org/examples/DevelopersGuide/examples.html#Components - - -## 2. Creating the EzHighlight Add-on -There's an overlap between the steps required to create an UNO component and an -add-on. The main similarity is the need for an OXT file containing a JAR and a -description.xml. However, there's no need for an IDL file or type database (a RDB -file), but there are extra details concerning the GUI and dispatch handling. +## 2. Creating the EzHighlight Add-on -The main steps in creating an add-on are illustrated by Figure 7. +There's an overlap between the steps required to create an UNO component and an +add-on. The main similarity is the need for an OXT file containing a JAR and a +description.xml. However, there's no need for an IDL file or type database (a RDB +file), but there are extra details concerning the GUI and dispatch handling. + +The main steps in creating an add-on are illustrated by Figure 7. - - ![](images/46-Addons-7.png) -Figure 7. Toolchain for Creating an Add-on. +Figure 7. Toolchain for Creating an Add-on. - -The UNO component toolchain in the previous chapter used my skelComp.bat script -to generate a partial Java implementation. The script utilizes Office's uno- -skeletonmaker which can also be employed to generate add-ons and Calc addins. -Unfortunately, I was unable to get its add-on features to work. +The UNO component toolchain in the previous chapter used my skelComp.bat script +to generate a partial Java implementation. The script utilizes Office's uno- +skeletonmaker which can also be employed to generate add-ons and Calc addins. +Unfortunately, I was unable to get its add-on features to work. This was not a great loss since I'd already been thinking about replacing uno- -skeletonmaker with FreeMarker, a powerful template processing library +skeletonmaker with FreeMarker, a powerful template processing library (http://freemarker.org/). FreeMarker can easily duplicate all the features of uno- -skeletonmaker, creating a Java class with the boilerplate code for creating a service -and handling command URLs. Also, by switching to FreeMarker, I was able to add -more automatic code generation, including code for a user input dialog and a -debugging window. +skeletonmaker, creating a Java class with the boilerplate code for creating a service +and handling command URLs. Also, by switching to FreeMarker, I was able to add +more automatic code generation, including code for a user input dialog and a +debugging window. + +The OXT file requires three configuration files: a description of the dialog in Figure +4, Addon.xcs which defines the add-on's GUI (e.g. the menu and toolbar items in +Figures 2 and 3), and ProtocolHandler.xcu which says which command URLs are +processed by the add-on's dispatch handler. -The OXT file requires three configuration files: a description of the dialog in Figure -4, Addon.xcs which defines the add-on's GUI (e.g. the menu and toolbar items in -Figures 2 and 3), and ProtocolHandler.xcu which says which command URLs are -processed by the add-on's dispatch handler. +The toolchain in Figure 7 will be explained in more detail in the rest of this chapter. -The toolchain in Figure 7 will be explained in more detail in the rest of this chapter. - - - -## 3. Creating a Partial Add-on Implementation +## 3. Creating a Partial Add-on Implementation -Figure 8 shows the main elements needed to use the FreeMarker template library: a -FTL template file, and a program that instantiates the template by employing a -collection of FreeMarker variables and their bindings. +Figure 8 shows the main elements needed to use the FreeMarker template library: a +FTL template file, and a program that instantiates the template by employing a +collection of FreeMarker variables and their bindings. - - ![](images/46-Addons-8.png) -Figure 8. Using FreeMarker. +Figure 8. Using FreeMarker. + + +The simplest FreeMarker program replaces all the FreeMarker variables in the +template by their associated bindings, generating a new text file (a Java program in +this case). However, FreeMarker contains additional programming features, including +for-loops and if-statements, which can generate more complex blocks of text. + +The add-on template is stored in addonImpl.ftl, and is processed by +CreateAddonImpl.java. It reads in the add-on's name, and initializes a HashMap of +three FreeMarker variables called "className", "extensionID, and "cmdNames". + +For example, CreateAddonImpl can be called like so: + +``` +> run CreateAddonImpl EzHighlight +``` + +The three variables are assigned: + +* "className" : "EzHighlightAddonImpl" +* "extensionID : "org.openoffice.ezhighlightAddon" +* "cmdNames" : [ "EzHighlight", "help" ] + +"className" is assigned the name of the generated Java class. "extensionID" contains +the protocol string used by the command URLs. "cmdNames" holds a list of +command URL names: "EzHighlight" is linked to the add-on's menu and toolbar +items, and "help" is attached to the add-on's help menu item. + +CreateAddonImpl outputs a class called EzHighlightAddonImpl, a fully functional +add-on, which displays a dialog like the one in Figure 4 and the help message in +Figure 6. However, after the user has typed in the dialog or pressed the +"Ok" button, no processing is carried out on the Office document. + +First I'll give an overview of the features in the generated EzHighlightAddonImpl +class, then explain the additional highlighting code in a later section. + +## 4. The FreeMarker-generated EzHighlightAddonImpl + +The template in addonImpl.ftl represents a single Java class, which extends +WeakBase and implements seven interfaces: + +```java +public class ${className} extends WeakBase implements + XInitialization, XServiceInfo, + XDispatchProvider, XDispatch, // for the add-on + XActionListener, XTopWindowListener, + XKeyListener // for the dialog +{ + // ... add-on code +} +``` + +WeakBase is the base class used by all components (which includes add-ons and Calc +Addins). XServiceInfo and XInitialization handle the creation and initialization of the +service at run time. + +The XDispatchProvider method, queryDispatch(), implements the dispatch handler +for the add-on – it accepts the command URLs associated with the add-on: + +```java +// in EzHighlightAddonInpl.java +public XDispatch queryDispatch(URL commandURL, + String targetFrameName, int searchFlags) +{ + if (commandURL.Protocol.compareTo( + "org.openoffice.ezhighlightAddon:") == 0) { + if (commandURL.Path.compareTo("EzHighlight") == 0) { + System.out.println("queryDispatch() for \"EzHighlight\""); + return this; + } + if (commandURL.Path.compareTo("help") == 0) { + System.out.println("queryDispatch() for \"help\""); + return this; + } + } + return null; +} // end of queryDispatch() +``` + +The handler signals its acceptance of a command URL by returning a dispatch object +which Office uses to process the command. EzHighlightAddonImpl also implements +the XDispatch interface, so returns a reference to itself. This means that Office calls +EzHighlightAddonImpl.dispatch(), passing it the command URL and other properties: + +```java +// in EzHighlightAddonInpl.java +public void dispatch(URL commandURL, PropertyValue[] props) +{ + if (commandURL.Protocol.compareTo( + "org.openoffice.ezhighlightAddon:") == 0) { + if (commandURL.Path.compareTo("EzHighlight") == 0) + processCmd("EzHighlight"); + if (commandURL.Path.compareTo("help") == 0) + processCmd("help"); + } +} // end of dispatch() +``` + +The template-generated code inside dispatch() distinguishes between the possible +command names ("EzHighlight" and "help") by calling processCmd() with different +arguments. + +If processCmd()'s input argument is "help", then GUI.showMessageBox() is called to +display the window shown in Figure 6, while "EzHighlight" triggers the dialog in +Figure 4. The processCmd() code is: + +```java +// in EzHighlightAddonInpl.java... + +// globals +private XDialog dialog = null; + +private Console console; // for debugging output +private int printCount = 1; + + +private void processCmd(String cmd) +{ + XComponent doc = Lo.addonInitialize(xcc); + // so my utils can be used safely + + System.out.println("Window title: " + GUI.getTitleBar()); + System.out.println(printCount++ + + ". dispatch() called for \"" + cmd + "\""); + + if (cmd.equals("help")) { + GUI.showMessageBox("Add-on Help", + "Type in the text, then press return or + click the Ok button."); + return; + } + + // "EzHighlight" is processed by the following code... + + console.setVisible(true); + + dialog = Dialogs.loadAddonDialog( + "org.openoffice.ezhighlightAddon", + "dialogLibrary/" + cmd + ".xdl"); + if (dialog == null) { + System.out.println("Could not load " + cmd + " dialog"); + return; + } + + XControl dialogControl = Dialogs.getDialogControl(dialog); + initDialog(dialogControl); + Dialogs.execute(dialogControl); + + console.setVisible(false); +} // end of processCmd() +``` + +Lo.addonInitialize() initializes the globals used by my Lo utility library and other +support classes: + +```java +// in the Lo class +// globals +private static XComponentContext xcc = null; +private static XDesktop xDesktop = null; +private static XMultiComponentFactory mcFactory = null; +private static XMultiServiceFactory msFactory = null; + + +public static XComponent addonInitialize( + XComponentContext addonXcc) +{ xcc = addonXcc; + if (xcc == null) { + System.out.println("Could not access component context"); + return null; + } + + mcFactory = xcc.getServiceManager(); + if (mcFactory == null) { + System.out.println("Office Service Manager is unavailable"); + return null; + } + + try { + Object oDesktop = mcFactory.createInstanceWithContext( + "com.sun.star.frame.Desktop", xcc); + xDesktop = Lo.qi(XDesktop.class, oDesktop); + } + catch (Exception e) { + System.out.println("Could not access desktop"); + return null; + } + + XComponent doc = xDesktop.getCurrentComponent(); + if (doc == null) { + System.out.println("Could not access document"); + return null; + } + + msFactory = Lo.qi(XMultiServiceFactory.class, doc); + return doc; +} // end of addonInitialize() +``` + +addonInitialize() returns an instance of XComponent, which refers to the document +currently loaded into Office. Later this will be used to highlight the document's text. + +processCmd() calls Console.setVisible() before and after the processing of the +"EzHighlight" command. The first call makes a Console debugging window visible, +and the call at the end hides it. These lines should be commented out of the completed +add-on, so the window doesn't appear. + +The dialog is handled by methods from my Dialogs support class, and by listeners set +up in initDialog(). + + +## 5. Creating the Dialog + +There are two approaches for dialog creation: the easy way uses Office's dialog editor, +and the hard way calls functions in my Dialogs support class to programmatically +create the dialog's components. I'll use the editor in this chapter, and employ Dialogs +methods in my macros in Chapter 48. + +The Developer's Guide has some information on the dialog editor in chapter 11, +"OpenOffice.org Basic". The relevant sub-sections are online at +https://wiki.openoffice.org/wiki/Documentation/DevGuide/Basic/Dialog_Editor and +https://wiki.openoffice.org/wiki/Documentation/DevGuide/Basic/Creating_Dialogs +(or use loGuide "Dialog Editor" and loGuide "Creating Dialogs"). + +The editor utilizes drag-and-drop to place GUI elements in a dialog drawing area, +with property windows for specializing each element. The editor is reached via +Office's menu item Tools > Macros > Organize Dialogs (see Figure 9). - -The simplest FreeMarker program replaces all the FreeMarker variables in the -template by their associated bindings, generating a new text file (a Java program in -this case). However, FreeMarker contains additional programming features, including -for-loops and if-statements, which can generate more complex blocks of text. -The add-on template is stored in addonImpl.ftl, and is processed by -CreateAddonImpl.java. It reads in the add-on's name, and initializes a HashMap of -three FreeMarker variables called "className", "extensionID, and "cmdNames". +![](images/46-Addons-9.png) -For example, CreateAddonImpl can be called like so: -> run CreateAddonImpl EzHighlight -The three variables are assigned: - "className" : "EzHighlightAddonImpl" - "extensionID : "org.openoffice.ezhighlightAddon" - "cmdNames" : [ "EzHighlight", "help" ] -"className" is assigned the name of the generated Java class. "extensionID" contains -the protocol string used by the command URLs. "cmdNames" holds a list of -command URL names: "EzHighlight" is linked to the add-on's menu and toolbar -items, and "help" is attached to the add-on's help menu item. +Figure 9. Creating a new Dialog. -CreateAddonImpl outputs a class called EzHighlightAddonImpl, a fully functional -add-on, which displays a dialog like the one in Figure 4 and the help message in -![](images/46-Addons-6.png) +Figure 10 shows the dialog editor window after the creation of a new dialog called +"Basic"; the GUI controls run along the bottom of the window. -Figure 6. However, after the user has typed in the dialog or pressed the -"Ok" button, no processing is carried out on the Office document. - -First I'll give an overview of the features in the generated EzHighlightAddonImpl -class, then explain the additional highlighting code in a later section. - - - -## 4. The FreeMarker-generated EzHighlightAddonImpl - -The template in addonImpl.ftl represents a single Java class, which extends -WeakBase and implements seven interfaces: - -public class ${className} extends WeakBase implements - XInitialization, XServiceInfo, - XDispatchProvider, XDispatch, // for the add-on - XActionListener, XTopWindowListener, - XKeyListener // for the dialog -{ - // ... add-on code -} - -WeakBase is the base class used by all components (which includes add-ons and Calc -Addins). XServiceInfo and XInitialization handle the creation and initialization of the -service at run time. - -The XDispatchProvider method, queryDispatch(), implements the dispatch handler -for the add-on – it accepts the command URLs associated with the add-on: - -// in EzHighlightAddonInpl.java -public XDispatch queryDispatch(URL commandURL, - String targetFrameName, int searchFlags) -{ - if (commandURL.Protocol.compareTo( - "org.openoffice.ezhighlightAddon:") == 0) { - if (commandURL.Path.compareTo("EzHighlight") == 0) { - System.out.println("queryDispatch() for \"EzHighlight\""); - return this; - } - if (commandURL.Path.compareTo("help") == 0) { - System.out.println("queryDispatch() for \"help\""); - return this; - } - } - return null; -} // end of queryDispatch() - -The handler signals its acceptance of a command URL by returning a dispatch object -which Office uses to process the command. EzHighlightAddonImpl also implements -the XDispatch interface, so returns a reference to itself. This means that Office calls -EzHighlightAddonImpl.dispatch(), passing it the command URL and other properties: - -// in EzHighlightAddonInpl.java -public void dispatch(URL commandURL, PropertyValue[] props) -{ - if (commandURL.Protocol.compareTo( - "org.openoffice.ezhighlightAddon:") == 0) { - if (commandURL.Path.compareTo("EzHighlight") == 0) - processCmd("EzHighlight"); - if (commandURL.Path.compareTo("help") == 0) - processCmd("help"); - } -} // end of dispatch() - -The template-generated code inside dispatch() distinguishes between the possible -command names ("EzHighlight" and "help") by calling processCmd() with different -arguments. - -If processCmd()'s input argument is "help", then GUI.showMessageBox() is called to -display the window shown in Figure 6, while "EzHighlight" triggers the dialog in -![](images/46-Addons-4.png) +![](images/46-Addons-10.png) -Figure 4. The processCmd() code is: - -// in EzHighlightAddonInpl.java... - -// globals -private XDialog dialog = null; - -private Console console; // for debugging output -private int printCount = 1; - - -private void processCmd(String cmd) -{ - XComponent doc = Lo.addonInitialize(xcc); - // so my utils can be used safely - - System.out.println("Window title: " + GUI.getTitleBar()); - System.out.println(printCount++ + - ". dispatch() called for \"" + cmd + "\""); - - if (cmd.equals("help")) { - GUI.showMessageBox("Add-on Help", - "Type in the text, then press return or - click the Ok button."); - return; - } - - // "EzHighlight" is processed by the following code... - - console.setVisible(true); - - dialog = Dialogs.loadAddonDialog( - "org.openoffice.ezhighlightAddon", - "dialogLibrary/" + cmd + ".xdl"); - if (dialog == null) { - System.out.println("Could not load " + cmd + " dialog"); - return; - } - - XControl dialogControl = Dialogs.getDialogControl(dialog); - initDialog(dialogControl); - Dialogs.execute(dialogControl); - - console.setVisible(false); -} // end of processCmd() - -Lo.addonInitialize() initializes the globals used by my Lo utility library and other -support classes: - -// in the Lo class -// globals -private static XComponentContext xcc = null; -private static XDesktop xDesktop = null; -private static XMultiComponentFactory mcFactory = null; -private static XMultiServiceFactory msFactory = null; - - -public static XComponent addonInitialize( - XComponentContext addonXcc) -{ xcc = addonXcc; - if (xcc == null) { - System.out.println("Could not access component context"); - return null; - } - - mcFactory = xcc.getServiceManager(); - if (mcFactory == null) { - System.out.println("Office Service Manager is unavailable"); - return null; - } - - try { - Object oDesktop = mcFactory.createInstanceWithContext( - "com.sun.star.frame.Desktop", xcc); - xDesktop = Lo.qi(XDesktop.class, oDesktop); - } - catch (Exception e) { - System.out.println("Could not access desktop"); - return null; - } - - XComponent doc = xDesktop.getCurrentComponent(); - if (doc == null) { - System.out.println("Could not access document"); - return null; - } - - msFactory = Lo.qi(XMultiServiceFactory.class, doc); - return doc; -} // end of addonInitialize() - -addonInitialize() returns an instance of XComponent, which refers to the document -currently loaded into Office. Later this will be used to highlight the document's text. - -processCmd() calls Console.setVisible() before and after the processing of the -"EzHighlight" command. The first call makes a Console debugging window visible, -and the call at the end hides it. These lines should be commented out of the completed -add-on, so the window doesn't appear. - -The dialog is handled by methods from my Dialogs support class, and by listeners set -up in initDialog(). - - - -## 5. Creating the Dialog - -There are two approaches for dialog creation: the easy way uses Office's dialog editor, -and the hard way calls functions in my Dialogs support class to programmatically -create the dialog's components. I'll use the editor in this chapter, and employ Dialogs -methods in my macros in Chapter 48. - -The Developer's Guide has some information on the dialog editor in chapter 11, -"OpenOffice.org Basic". The relevant sub-sections are online at -https://wiki.openoffice.org/wiki/Documentation/DevGuide/Basic/Dialog_Editor and -https://wiki.openoffice.org/wiki/Documentation/DevGuide/Basic/Creating_Dialogs -(or use loGuide "Dialog Editor" and loGuide "Creating Dialogs"). - -The editor utilizes drag-and-drop to place GUI elements in a dialog drawing area, -with property windows for specializing each element. The editor is reached via -Office's menu item Tools > Macros > Organize Dialogs (see Figure 9). - - - +Figure 10. The Dialog Editor Window for a new Dialog. -![](images/46-Addons-9.png) -Figure 9. Creating a new Dialog. +The simplest useful dialog is probably the version of the "Basic" dialog in Figure 11, +consisting of a label, text field, and "Ok" button. - -Figure 10 shows the dialog editor window after the creation of a new dialog called -"Basic"; the GUI controls run along the bottom of the window. - - +![](images/46-Addons-11.png) -![](images/46-Addons-10.png) +Figure 11. The "Basic" Dialog. -Figure 10. The Dialog Editor Window for a new Dialog. - -The simplest useful dialog is probably the version of the "Basic" dialog in Figure 11, -consisting of a label, text field, and "Ok" button. +A dialog can be exported as XML using the "Export Dialog" icon above the drawing +area (the icon includes a floppy disk which will leave young programmers mystified). - - +The resulting XDL file contains text something like: -![](images/46-Addons-11.png) +``` + + + + + + + + + + + + + + + + + +``` + +The code in EzHighlightAddonInpl assumes that the dialog contains a text field called +"TextField1" and a button called "CommandButton1", which is true of the XDL +shown above. Of course, it's possible to add more GUI components, as in the next +section. + + +### 5.1. The EzHighlight Dialog + +The dialog editor has an import icon to the left of the export icon, which can be used +to load an XDL file for modification. + +I copied the "Basic" XDL file, renaming it to "EzHighlight.xdl", and imported it; a +few changes and additions later, it looked as shown in Figure 12. -Figure 11. The "Basic" Dialog. - - -A dialog can be exported as XML using the "Export Dialog" icon above the drawing -area (the icon includes a floppy disk which will leave young programmers mystified). - -The resulting XDL file contains text something like: - - - - - - - - - - - - - - - - - - - -The code in EzHighlightAddonInpl assumes that the dialog contains a text field called -"TextField1" and a button called "CommandButton1", which is true of the XDL -shown above. Of course, it's possible to add more GUI components, as in the next -section. - - -### 5.1. The EzHighlight Dialog - -The dialog editor has an import icon to the left of the export icon, which can be used -to load an XDL file for modification. - -I copied the "Basic" XDL file, renaming it to "EzHighlight.xdl", and imported it; a -few changes and additions later, it looked as shown in Figure 12. - - - ![](images/46-Addons-12.png) -Figure 12. The EzHighlight.xdl Dialog. - - -The exported XML is: - - - - - - - - - - - - - - - - - - - - - - - -The dialog loading code in EzHighlightAddonInpl.java assumes that the dialog's -filename is the same as the command URL name, as can be seen in processCmd(): - -// part of processCmd() in EzHighlightAddonInpl.java... - -dialog = Dialogs.loadAddonDialog("org.openoffice.ezhighlightAddon", - "dialogLibrary/" + cmd + ".xdl"); - -processCmd() also assumes that the XDL file is in a dialogLibrary/ sub-directory. I'll -explain how this is part of the add-on's OXT file in a later section. - -Dialogs.loadAddonDialog() is defined as: - -// in the Dialogs class -public static XDialog loadAddonDialog(String extensionID, - String dialogFnm) -{ XDialogProvider dp = - Lo.createInstanceMCF(XDialogProvider.class, - "com.sun.star.awt.DialogProvider"); - if (dp == null) { - System.out.println("Could not access the Dialog Provider"); - return null; - } - try { - return dp.createDialog("vnd.sun.star.extension://" + - extensionID + "/" + dialogFnm); - } - catch (java.lang.Exception e) { - System.out.println("Could not load the dialog: \"" + - dialogFnm + "\": " + e); - return null; - } -} // end of loadAddonDialog() - -The crucial line is the call to XDialogProvider.createDialog(), which constructs the -dialog name: -vnd.sun.star.extension://org.openoffice.ezhighlightAddon/ - dialogLibrary/EzHighlight -The dialog is loaded from the ezhighlightAddon extension. - - -### 5.2. Listening to the Dialog - -Back in processCmd(), listeners are attached to the loaded dialog, and the dialog is -made visible on-screen: - -// in processCmd() in EzHighlightAddonInpl.java... - -XControl dialogControl = Dialogs.getDialogControl(dialog); -initDialog(dialogControl); -Dialogs.execute(dialogControl); - -Dialogs.getDialogControl() casts the XDialog into an XControl: - -// in the Dialogs class -public static XControl getDialogControl(XDialog dialog) -{ return Lo.qi(XControl.class, dialog); } - -The initDialog() method inside EzHighlightAddonInpl.java attaches three listeners to -the dialog: a window listener, an action listener for the "CommandButton1" button, -and a key listener for the "TextField1" text field: - -// in the EzHighlightAddonInpl class -// globals -private XTextComponent textBox; - // the text in the dialog's text field - - -private void initDialog(XControl dialogControl) -{ - // listen to the dialog window - XTopWindow topWin = Dialogs.getDialogWindow(dialogControl); - topWin.addTopWindowListener(this); - - // Dialogs.showControlInfo(dialogControl); - - // set listener for Ok button - XButton button = Lo.qi(XButton.class, - Dialogs.findControl(dialogControl, "CommandButton1")); - button.addActionListener(this); - - // set listener for text box - textBox = Lo.qi(XTextComponent.class, - Dialogs.findControl(dialogControl, "TextField1")); - XWindow xTFWindow = (XWindow) Lo.qi(XWindow.class, textBox); - xTFWindow.addKeyListener(this); - xTFWindow.setFocus(); -} // end of initDialog() - -These controls and listeners are all from Office's com.sun.star.awt module. The dialog -and listeners will be invoked by Office at run time, and so should use its API, not -Java's Swing. - -The call to Dialogs.showControlInfo() is commented out in initDialog(), but is a -useful way to double-check the dialog's internal construction, and particularly the IDs -of its components. showControlInfo() prints to standard output which will be -displayed in the Console window. Figure 13 reports the structure of the -EzHighlight.xdl dialog from Figure 12. - - - +Figure 12. The EzHighlight.xdl Dialog. + + +The exported XML is: + +``` + + + + + + + + + + + + + + + + + + + + + +``` + +The dialog loading code in EzHighlightAddonInpl.java assumes that the dialog's +filename is the same as the command URL name, as can be seen in processCmd(): + +```java +// part of processCmd() in EzHighlightAddonInpl.java... + +dialog = Dialogs.loadAddonDialog("org.openoffice.ezhighlightAddon", + "dialogLibrary/" + cmd + ".xdl"); +``` + +processCmd() also assumes that the XDL file is in a dialogLibrary/ sub-directory. I'll +explain how this is part of the add-on's OXT file in a later section. + +Dialogs.loadAddonDialog() is defined as: + +```java +// in the Dialogs class +public static XDialog loadAddonDialog(String extensionID, + String dialogFnm) +{ XDialogProvider dp = + Lo.createInstanceMCF(XDialogProvider.class, + "com.sun.star.awt.DialogProvider"); + if (dp == null) { + System.out.println("Could not access the Dialog Provider"); + return null; + } + try { + return dp.createDialog("vnd.sun.star.extension://" + + extensionID + "/" + dialogFnm); + } + catch (java.lang.Exception e) { + System.out.println("Could not load the dialog: \"" + + dialogFnm + "\": " + e); + return null; + } +} // end of loadAddonDialog() +``` + +The crucial line is the call to XDialogProvider.createDialog(), which constructs the +dialog name: + +```java +vnd.sun.star.extension://org.openoffice.ezhighlightAddon/ + dialogLibrary/EzHighlight +``` + +The dialog is loaded from the ezhighlightAddon extension. + + +### 5.2. Listening to the Dialog + +Back in processCmd(), listeners are attached to the loaded dialog, and the dialog is +made visible on-screen: + +```java +// in processCmd() in EzHighlightAddonInpl.java... + +XControl dialogControl = Dialogs.getDialogControl(dialog); +initDialog(dialogControl); +Dialogs.execute(dialogControl); +``` + +Dialogs.getDialogControl() casts the XDialog into an XControl: + +```java +// in the Dialogs class +public static XControl getDialogControl(XDialog dialog) +{ return Lo.qi(XControl.class, dialog); } +``` + +The initDialog() method inside EzHighlightAddonInpl.java attaches three listeners to +the dialog: a window listener, an action listener for the "CommandButton1" button, +and a key listener for the "TextField1" text field: + +```java +// in the EzHighlightAddonInpl class +// globals +private XTextComponent textBox; + // the text in the dialog's text field + + +private void initDialog(XControl dialogControl) +{ + // listen to the dialog window + XTopWindow topWin = Dialogs.getDialogWindow(dialogControl); + topWin.addTopWindowListener(this); + + // Dialogs.showControlInfo(dialogControl); + + // set listener for Ok button + XButton button = Lo.qi(XButton.class, + Dialogs.findControl(dialogControl, "CommandButton1")); + button.addActionListener(this); + + // set listener for text box + textBox = Lo.qi(XTextComponent.class, + Dialogs.findControl(dialogControl, "TextField1")); + XWindow xTFWindow = (XWindow) Lo.qi(XWindow.class, textBox); + xTFWindow.addKeyListener(this); + xTFWindow.setFocus(); +} // end of initDialog() +``` + +These controls and listeners are all from Office's com.sun.star.awt module. The dialog +and listeners will be invoked by Office at run time, and so should use its API, not +Java's Swing. + +The call to Dialogs.showControlInfo() is commented out in initDialog(), but is a +useful way to double-check the dialog's internal construction, and particularly the IDs +of its components. showControlInfo() prints to standard output which will be +displayed in the Console window. Figure 13 reports the structure of the +EzHighlight.xdl dialog from Figure 12. + ![](images/46-Addons-13.png) -Figure 13. The Console Window Showing Dialog Details. - - -The dialog contains five components: two labels, two text fields, and a button. - -Dialogs.findControl() uses a control's name to find it inside a dialog: - -// in Dialogs class -public static XControl findControl(XControl dialogCtrl, - String name) -{ XControlContainer ctrlCon = - Lo.qi(XControlContainer.class, dialogCtrl); - return ctrlCon.getControl(name); -} - -initDialog() converts the returned "CommandButton1" control into an XButton, and -the "TextField1" control into an XTextComponent so that listeners can be attached to -them. - -EzHighlightAddonInpl implements all of the listener interfaces used by the dialog: -XActionListener, XTopWindowListener, and XKeyListener. - -XActionListener.actionPerformed() deals with button presses, -XTopWindowListener.windowClosing() listens for the closing of the dialog, and -XKeyListener.keyPressed() captures the user typing into the text field: - -// in the EzHighlightAddonInpl class -// globals -private XDialog dialog = null; -private XTextComponent textBox; - - -public void actionPerformed(ActionEvent e) -{ - String info = textBox.getText(); - if (info.equals("")) - return; - System.out.println("Info: \"" + info +"\""); - textBox.setText(""); - // ADD code here -} // end of actionPerformed() - - -public void windowClosing(EventObject event) -{ dialog.endExecute(); } - - -public void keyPressed(KeyEvent event) -{ - if (event.KeyCode == Key.RETURN) { - String info = textBox.getText(); - if (info.equals("")) - return; - System.out.println("Info: \"" + info +"\""); - textBox.setText(""); - // ADD code here - } -} // end of keyPressed() - -The template generated code for actionPerformed() and keyPressed() only print -information to the Console window. Add-on specific functionality is added next. - - - -## 6. Completing the Implementation of EzHighlightAddonInpl - -The completion of EzHighlightAddonInpl.java takes the form of new code in -processCmd(), initDialog(), actionPerformed(), and keyPressed(), and a few new -global variables. - -processCmd() converts the XComponent document returned by Lo.addonInitialize() -into an XTextDocument, assuming that the currently loaded document is text-based. - -If it isn't then there's no point continuing: - -// in the EzHighlightAddonInpl class -// globals -private XTextDocument textDoc; - - -// in processCmd() - : -XComponent doc = Lo.addonInitialize(xcc); -// added -textDoc = Write.getTextDoc(doc); -if (textDoc == null) - return; - : - -initDialog() is extended to access the word count text field. It's only used to report the -number of changes, so doesn't need a listener: - -// in the EzHighlightAddonInpl class -// globals -private XTextComponent countTextBox; - - -// in initDialog() - : -// get a reference to the count text field; added -countTextBox = Lo.qi(XTextComponent.class, - Dialogs.findControl(dialogControl, "TextField2")); - : - -actionPerformed() and keyPressed() must trigger the highlighting code, which is -implemented in applyEzHighlighting(): - -// added to actionPerformed() - : -int count = applyEzHighlighting(info); -countTextBox.setText(""+count); - - -// added to keyPressed() - : -int count = applyEzHighlighting(info); -countTextBox.setText(""+count); - -applyEzHighlighting() is passed the text entered by the user through the dialog. It uses -the XReplaceable and XReplaceDescriptor interfaces to perform a search and replace -through the document. This technique, and very similar code, was explained in -Chapter 9. applyEzHighlighting() is: - -// in the EzHighlightAddonInpl class -// globals -private XTextDocument textDoc; - - -private int applyEzHighlighting(String searchKey) -/* Matches whole words and is case sensitive. - - Highlights in bold and red; */ -{ - System.out.println("applyEzHighlighting(): " + searchKey); - - XReplaceable repl = Lo.qi(XReplaceable.class, textDoc); - XReplaceDescriptor desc = repl.createReplaceDescriptor(); - - /* Get a XPropertyReplace object for altering the properties - of the replaced text */ - XPropertyReplace propReplace = Lo.qi(XPropertyReplace.class, desc); - - // Set the replaced text to bold and red - PropertyValue wv = new PropertyValue("CharWeight", -1, - FontWeight.BOLD, PropertyState.DIRECT_VALUE); - PropertyValue cv = new PropertyValue("CharColor", -1, - Color.RED.getRGB(), PropertyState.DIRECT_VALUE); - PropertyValue[] props = new PropertyValue[] {cv, wv}; - - try { - propReplace.setReplaceAttributes(props); - - // Only match whole words and be case sensitive - desc.setPropertyValue("SearchCaseSensitive", true); - desc.setPropertyValue("SearchWords", true); - } - catch (com.sun.star.uno.Exception ex) { - System.out.println("Error setting up search properties"); - return -1; - } - - /* Replaces all instances of searchKey with new Text properties - and gets the number of changed instances */ - desc.setSearchString(searchKey); - desc.setReplaceString(searchKey); - return repl.replaceAll(desc); -} // end of applyEzHighlighting() - - -## 7. Configuring the Add-on - -An add-on OXT file requires two configuration files not used by an UNO component: -ProtocolHandler.xcu and Addon.xcs. - - -### 7.1. ProtocolHandler.xcu - -ProtocolHandler.xcu specifies which command URLs will be handled by the -component. The contents of the file for EzHighlight are: - - - - - - - - - org.openoffice.ezhighlightAddon:* - - - - - - -The "EzHighlightAddonImpl" component will handle all command URLs that begin -with the "org.openoffice.ezhighlightAddon" extension ID. The "all" is specified using -the * wildcard. The component name is the name of the generated Java class. - - -### 7.2. Addon.xcs - -Addon.xcs defines the add-on's GUI elements, such as its menu and toolbar items (i.e. - -as seen in Figures 2, 3, and 5). Figure 14 illustrates how the file may set up to five -attributes: - +Figure 13. The Console Window Showing Dialog Details. + + +The dialog contains five components: two labels, two text fields, and a button. + +Dialogs.findControl() uses a control's name to find it inside a dialog: + +```java +// in Dialogs class +public static XControl findControl(XControl dialogCtrl, + String name) +{ XControlContainer ctrlCon = + Lo.qi(XControlContainer.class, dialogCtrl); + return ctrlCon.getControl(name); +} +``` + +initDialog() converts the returned "CommandButton1" control into an XButton, and +the "TextField1" control into an XTextComponent so that listeners can be attached to +them. + +EzHighlightAddonInpl implements all of the listener interfaces used by the dialog: +XActionListener, XTopWindowListener, and XKeyListener. +XActionListener.actionPerformed() deals with button presses, +XTopWindowListener.windowClosing() listens for the closing of the dialog, and +XKeyListener.keyPressed() captures the user typing into the text field: + +```java +// in the EzHighlightAddonInpl class +// globals +private XDialog dialog = null; +private XTextComponent textBox; + + +public void actionPerformed(ActionEvent e) +{ + String info = textBox.getText(); + if (info.equals("")) + return; + System.out.println("Info: \"" + info +"\""); + textBox.setText(""); + // ADD code here +} // end of actionPerformed() + + +public void windowClosing(EventObject event) +{ dialog.endExecute(); } + + +public void keyPressed(KeyEvent event) +{ + if (event.KeyCode == Key.RETURN) { + String info = textBox.getText(); + if (info.equals("")) + return; + System.out.println("Info: \"" + info +"\""); + textBox.setText(""); + // ADD code here + } +} // end of keyPressed() +``` + +The template generated code for actionPerformed() and keyPressed() only print +information to the Console window. Add-on specific functionality is added next. + + +## 6. Completing the Implementation of EzHighlightAddonInpl + +The completion of EzHighlightAddonInpl.java takes the form of new code in +processCmd(), initDialog(), actionPerformed(), and keyPressed(), and a few new +global variables. + +processCmd() converts the XComponent document returned by Lo.addonInitialize() +into an XTextDocument, assuming that the currently loaded document is text-based. +If it isn't then there's no point continuing: + +```java +// in the EzHighlightAddonInpl class +// globals +private XTextDocument textDoc; + + +// in processCmd() + : +XComponent doc = Lo.addonInitialize(xcc); +// added +textDoc = Write.getTextDoc(doc); +if (textDoc == null) + return; + : +``` + +initDialog() is extended to access the word count text field. It's only used to report the +number of changes, so doesn't need a listener: + +```java +// in the EzHighlightAddonInpl class +// globals +private XTextComponent countTextBox; + + +// in initDialog() + : +// get a reference to the count text field; added +countTextBox = Lo.qi(XTextComponent.class, + Dialogs.findControl(dialogControl, "TextField2")); + : +``` + +actionPerformed() and keyPressed() must trigger the highlighting code, which is +implemented in applyEzHighlighting(): + +```java +// added to actionPerformed() + : +int count = applyEzHighlighting(info); +countTextBox.setText(""+count); + + +// added to keyPressed() + : +int count = applyEzHighlighting(info); +countTextBox.setText(""+count); +``` + +applyEzHighlighting() is passed the text entered by the user through the dialog. It uses +the XReplaceable and XReplaceDescriptor interfaces to perform a search and replace +through the document. This technique, and very similar code, was explained in +Chapter 9. applyEzHighlighting() is: + +```java +// in the EzHighlightAddonInpl class +// globals +private XTextDocument textDoc; + + +private int applyEzHighlighting(String searchKey) +/* Matches whole words and is case sensitive. + + Highlights in bold and red; */ +{ + System.out.println("applyEzHighlighting(): " + searchKey); + + XReplaceable repl = Lo.qi(XReplaceable.class, textDoc); + XReplaceDescriptor desc = repl.createReplaceDescriptor(); + + /* Get a XPropertyReplace object for altering the properties + of the replaced text */ + XPropertyReplace propReplace = Lo.qi(XPropertyReplace.class, desc); + + // Set the replaced text to bold and red + PropertyValue wv = new PropertyValue("CharWeight", -1, + FontWeight.BOLD, PropertyState.DIRECT_VALUE); + PropertyValue cv = new PropertyValue("CharColor", -1, + Color.RED.getRGB(), PropertyState.DIRECT_VALUE); + PropertyValue[] props = new PropertyValue[] {cv, wv}; + + try { + propReplace.setReplaceAttributes(props); + + // Only match whole words and be case sensitive + desc.setPropertyValue("SearchCaseSensitive", true); + desc.setPropertyValue("SearchWords", true); + } + catch (com.sun.star.uno.Exception ex) { + System.out.println("Error setting up search properties"); + return -1; + } + + /* Replaces all instances of searchKey with new Text properties + and gets the number of changed instances */ + desc.setSearchString(searchKey); + desc.setReplaceString(searchKey); + return repl.replaceAll(desc); +} // end of applyEzHighlighting() +``` + + +## 7. Configuring the Add-on + +An add-on OXT file requires two configuration files not used by an UNO component: +ProtocolHandler.xcu and Addon.xcs. + + +### 7.1. ProtocolHandler.xcu + +ProtocolHandler.xcu specifies which command URLs will be handled by the +component. The contents of the file for EzHighlight are: + +``` + + + + + + + + org.openoffice.ezhighlightAddon:* + + + + + +``` + +The "EzHighlightAddonImpl" component will handle all command URLs that begin +with the "org.openoffice.ezhighlightAddon" extension ID. The "all" is specified using +the * wildcard. The component name is the name of the generated Java class. + + +### 7.2. Addon.xcs + +Addon.xcs defines the add-on's GUI elements, such as its menu and toolbar items (i.e. +as seen in Figures 2, 3, and 5). Figure 14 illustrates how the file may set up to five +attributes: + ![](images/46-Addons-14.png) -Figure 14. The Addon.xcs GUI Elements. - - -The "AddonMenu" attribute defines the look of the menu item that appears at the end -of Office's Tool > Add-ons menu, as in Figure 2. - -The "OfficeToolbar" attribute specifies the appearance of the floating toolbar reached -via View > Toolbars > Add-on , as in Figure 3. - -The "OfficeMenubar" attribute is used to add a menu item to Office's main menu bar, -which seems a rather poor GUI design choice, so I've chosen not to use it. - -The OfficeHelp" attribute creates the add-on's help menu item on Office's Help menu, -as in Figure 5. - -The "Images" attribute defines the icons that appear next to the text in the menu and -toolbar items. If you look back at Figures 2 and 3, you'll see that no icons appear, but -this isn't for want of me trying. This feature seems to be broken, but I'll still explain -how to set it up. - -Aside from "Images", the attributes have a similar structure: a menu or toolbar item -with five sub-attributes: URL, Title, ImageIdentifier, Context, and Target; I'll not be -using sub-menu attributes. - -The best explanation of Addon.xcs' structure is "How to distribute your macros with -an Addon" by Bernard Marcelly at -http://www.openoffice.org/documentation/HOW_TO/various_topics/Addons1_1en.pd -f, which dates from 2003. - - -The "AddonMenu" Attribute -The "AddonMenu" attribute used by the EzHighlight add-on is: - - - - - - org.openoffice.ezhighlightAddon:EzHighlight - - - - EzHighlight Text - - - - - - - - com.sun.star.text.TextDocument - - - - _self - - - - -The "URL" attribute holds the command URL which is dispatched by Office when -the menu item is clicked. - -The "Title" attribute contains the menu item's text, and "ImageIdentifier" can be -assigned the path to a PNG or BMP file for the text's icon. "ImageIdentifier" is left -empty here since all the images are defined in the "Images" attribute, explained -below. - -The "Context" attribute is assigned the document service for the Office application -utilizing the add-on. EzHighlight is accessible only from Writer, so "Context"'s value -is "com.sun.star.text.TextDocument". Other application/service mappings are shown -in Table 1. - - +Figure 14. The Addon.xcs GUI Elements. + + +The "AddonMenu" attribute defines the look of the menu item that appears at the end +of Office's Tool > Add-ons menu, as in Figure 2. + +The "OfficeToolbar" attribute specifies the appearance of the floating toolbar reached +via View > Toolbars > Add-on , as in Figure 3. + +The "OfficeMenubar" attribute is used to add a menu item to Office's main menu bar, +which seems a rather poor GUI design choice, so I've chosen not to use it. + +The OfficeHelp" attribute creates the add-on's help menu item on Office's Help menu, +as in Figure 5. + +The "Images" attribute defines the icons that appear next to the text in the menu and +toolbar items. If you look back at Figures 2 and 3, you'll see that no icons appear, but +this isn't for want of me trying. This feature seems to be broken, but I'll still explain +how to set it up. + +Aside from "Images", the attributes have a similar structure: a menu or toolbar item +with five sub-attributes: URL, Title, ImageIdentifier, Context, and Target; I'll not be +using sub-menu attributes. + +The best explanation of Addon.xcs' structure is "How to distribute your macros with +an Addon" by Bernard Marcelly at +http://www.openoffice.org/documentation/HOW_TO/various_topics/Addons1_1en.pdf, +which dates from 2003. + + +#### "AddonMenu" Attribute + +The "AddonMenu" attribute used by the EzHighlight add-on is: + +``` + + + + + org.openoffice.ezhighlightAddon:EzHighlight + + + + EzHighlight Text + + + + + + + + com.sun.star.text.TextDocument + + + + _self + + + +``` + +The "URL" attribute holds the command URL which is dispatched by Office when +the menu item is clicked. + +The "Title" attribute contains the menu item's text, and "ImageIdentifier" can be +assigned the path to a PNG or BMP file for the text's icon. "ImageIdentifier" is left +empty here since all the images are defined in the "Images" attribute, explained +below. + +The "Context" attribute is assigned the document service for the Office application +utilizing the add-on. EzHighlight is accessible only from Writer, so "Context"'s value +is "com.sun.star.text.TextDocument". Other application/service mappings are shown +in Table 1. + + |Office Application|Document Service | |------------------|----------------------------------------------| |Writer |com.sun.star.text.TextDocument | @@ -949,251 +959,268 @@ in Table 1. |Chart |com.sun.star.chart.ChartDocument | |Bibliography |com.sun.star.frame.Bibliography | -Table 1. Office Application/Service Mappings Used by "Context" - -If the "Context" field isn't assigned a value, then the add-on will be available in all of -Office's applications. - -The "Target" field can be assigned four values: "_top", "_parent", "_blank", and -"_self", but I've never seen any other value used but "_self". - - -The "OfficeToolbar" Attribute -Figure 2 (EzHighlight's menu item) and Figure 3 (its toolbar item) look the same, and -respond in the same way when clicked, so it's no great surprise that the -"OfficeToolbar" attribute in Addon.xcs is almost exactly the same as "AddonMenu": - - - - - - - org.openoffice.ezhighlightAddon:EzHighlight - - - - EzHighlight Text - - - - - - - - com.sun.star.text.TextDocument - - - - _self - - - - - -The difference is an extra attribute which allows a toolbar to hold several -items. In the description above, there's one item labeled as "m1". The labels can be -any unique string, but "m" followed by a number is used in most examples. - - -The "OfficeHelp" Attribute -The "OfficeHelp" attribute is similar to "OfficeToolbar" and "AddonMenu", but -dispatches a different command URL ("org.openoffice.ezhighlightAddon:help") and -its menu item uses a different title ("About EzHighlight"). - - - - - - - org.openoffice.ezhighlightAddon:help - - - - About EzHighlight - - - - - - - - com.sun.star.text.TextDocument - - - - _self - - - - -The "Images" Attribute -The "Images" attribute specifies the icons used by the GUI elements. An image can be -either big or small (26x26 or 16x16 pixels), high contrast or normal, and loaded from -a file or coded as hexadecimal text. These different combinations mean there are eight -variants to choose from, each with a different property name, which are listed in Table -2. - - +Table 1. Office Application/Service Mappings Used by "Context" + +If the "Context" field isn't assigned a value, then the add-on will be available in all of +Office's applications. + +The "Target" field can be assigned four values: "_top", "_parent", "_blank", and +"_self", but I've never seen any other value used but "_self". + + +#### "OfficeToolbar" Attribute + +Figure 2 (EzHighlight's menu item) and Figure 3 (its toolbar item) look the same, and +respond in the same way when clicked, so it's no great surprise that the +"OfficeToolbar" attribute in Addon.xcs is almost exactly the same as "AddonMenu": + +``` + + + + + + org.openoffice.ezhighlightAddon:EzHighlight + + + + EzHighlight Text + + + + + + + + com.sun.star.text.TextDocument + + + + _self + + + + +``` + +The difference is an extra attribute which allows a toolbar to hold several +items. In the description above, there's one item labeled as "m1". The labels can be +any unique string, but "m" followed by a number is used in most examples. + + +#### "OfficeHelp" Attribute + +The "OfficeHelp" attribute is similar to "OfficeToolbar" and "AddonMenu", but +dispatches a different command URL ("org.openoffice.ezhighlightAddon:help") and +its menu item uses a different title ("About EzHighlight"). + +``` + + + + + org.openoffice.ezhighlightAddon:help + + + + About EzHighlight + + + + + + + + com.sun.star.text.TextDocument + + + + _self + + + +``` + +#### "Images" Attribute + +The "Images" attribute specifies the icons used by the GUI elements. An image can be +either big or small (26x26 or 16x16 pixels), high contrast or normal, and loaded from +a file or coded as hexadecimal text. These different combinations mean there are eight +variants to choose from, each with a different property name, which are listed in Table +2. + + |Size in Pixels |Contrast|Hexadecimal text|File name | |---------------|--------|----------------|---------------| |16x16 |normal |ImageSmall |ImageSmallURL | -|16x16 |high |ImageSmallHC |ImageSmallHCURL| +|16x16 |high |ImageSmallHC |ImageSmallHCURL| |26x26 |normal |ImageBig |ImageBigURL | |26x26 |high |ImageBigHC |ImageBigHCURL | -Table 2. Image Property Names. - - -The "Images" attribute for EzHighlight is: - - - - - - org.openoffice.ezhighlightAddon:EzHighlight - - - - - %origin%/images/ezhighlight16.png - - - %origin%/images/ezhighlight26.png - - - - - - -The "UserDefinedImages" sub-attribute specifies two sizes of normal contrast image, -supplied as filenames. %origin% stands for the OXT file, so the two icons are stored -in the images/ subdirectory inside that zipped file. - -The images will be used for the menu and toolbar items associated with the -"org.openoffice.ezhighlightAddon:EzHighlight" command URL. Unfortunately, the -images don't appear, as shown in Figures 2 and 3. - -The hexadecimal text definition of a small image would be something like: - - - 89504E470D0A1A0A0000000D494844520000001000000 - 01008060000001FF3FF610000024F494 ... // more numbers - - - -One way to obtain an image's hexadecimal text is to call my ImageHex.java program -which prints it to stdout. Unfortunately, the icon still doesn't appear, even when -specified in this form. - - - -## 8. Building and Installing the OXT File - -Figure 7 shows the stages in building and installing the add-on as an extension. These -steps are carried out by my installAddon.bat script, which assumes that the various -configuration files have already been created and are in certain locations. - -The completed EzHighlightAddonImpl.java file is compiled, then added to -EzHighlight.jar with a manifest that refers to Utils.jar: - -RegistrationClassName: EzHighlightAddonImpl -Class-Path: Utils.jar - +Table 2. Image Property Names. + + +The "Images" attribute for EzHighlight is: + +``` + + + + + org.openoffice.ezhighlightAddon:EzHighlight + + + + + %origin%/images/ezhighlight16.png + + + %origin%/images/ezhighlight26.png + + + + + +``` + +The "UserDefinedImages" sub-attribute specifies two sizes of normal contrast image, +supplied as filenames. %origin% stands for the OXT file, so the two icons are stored +in the images/ subdirectory inside that zipped file. + +The images will be used for the menu and toolbar items associated with the +"org.openoffice.ezhighlightAddon:EzHighlight" command URL. Unfortunately, the +images don't appear, as shown in Figures 2 and 3. + +The hexadecimal text definition of a small image would be something like: + +``` + + 89504E470D0A1A0A0000000D494844520000001000000 + 01008060000001FF3FF610000024F494 ... // more numbers + + +``` + +One way to obtain an image's hexadecimal text is to call my ImageHex.java program +which prints it to stdout. Unfortunately, the icon still doesn't appear, even when +specified in this form. + + +## 8. Building and Installing the OXT File + +Figure 7 shows the stages in building and installing the add-on as an extension. These +steps are carried out by my installAddon.bat script, which assumes that the various +configuration files have already been created and are in certain locations. + +The completed EzHighlightAddonImpl.java file is compiled, then added to +EzHighlight.jar with a manifest that refers to Utils.jar: + +```java +RegistrationClassName: EzHighlightAddonImpl +Class-Path: Utils.jar +``` + Utils.jar contains my support classes, which may be required by the add-on at run- -time. installAddon.bat will add it to the OXT file along with EzHighlight.jar. - -installAddon.bat assumes that the add-on configuration files are in a sub-directory -with the same name as the add-on. For example, when it's processing the EzHighlight -add-on, it looks for a EzHighlight/ folder with the following structure: - -EzHighlight - | Addons.xcu - | description.xml - | license.txt - | package-description.txt - | ProtocolHandler.xcu - | Utils.jar - | - +---dialogLibrary - | EzHighlight.xdl - | - +---images - | ezhighlight.png - | ezhighlight16.png - | ezhighlight26.png - | - \---META-INF - manifest.xml - -EzHighlight.jar is moved into EzHighlight/, and the folder is zipped into -EzHighlight.oxt. The extension is installed into Office using the unopkg tool, in the -same way as the UNO component of the last chapter. - -EzHighlight/ contains three configuration files: ProtocolHandler.xcu, Addons.xcu, -and description.xml. description.xml plays the same role as the same-named file for -UNO components. Its contents use the same XML attributes: - - - - - - - - - EzHighlight Addon - - - - Andrew Davison - - - - - - - - - - - - - - - - - - - - - - - - - - - -The biggest difference is that the extension icon is located in the images/ subdirectory -inside EzHighlight/. license.txt and package-description.txt are referenced in the -description, which explains why those files are in EzHighlight/. - -The dialog description (EzHighlight.xdl) is stored in its own subdirectory, -dialogLibrary/, to match the dialog loading code in EzHighlightAddonInpl.java: - -// in processCmd() -dialog = Dialogs.loadAddonDialog("org.openoffice.ezhighlightAddon", - "dialogLibrary/" + cmd + ".xdl"); - -The images/ subdirectory contains the extension manager icon (ezhighlight.png) and -two sizes of GUI icons (ezhighlight16.png and ezhighlight26.png). The GUI icons -must be inside images/ to match the locations specified in the "Images" attribute in -Addon.xcs. - - +time. installAddon.bat will add it to the OXT file along with EzHighlight.jar. + +installAddon.bat assumes that the add-on configuration files are in a sub-directory +with the same name as the add-on. For example, when it's processing the EzHighlight +add-on, it looks for a EzHighlight/ folder with the following structure: + +``` +EzHighlight + | Addons.xcu + | description.xml + | license.txt + | package-description.txt + | ProtocolHandler.xcu + | Utils.jar + | + +---dialogLibrary + | EzHighlight.xdl + | + +---images + | ezhighlight.png + | ezhighlight16.png + | ezhighlight26.png + | + \---META-INF + manifest.xml +``` + +EzHighlight.jar is moved into EzHighlight/, and the folder is zipped into +EzHighlight.oxt. The extension is installed into Office using the unopkg tool, in the +same way as the UNO component of the last chapter. + +EzHighlight/ contains three configuration files: ProtocolHandler.xcu, Addons.xcu, +and description.xml. description.xml plays the same role as the same-named file for +UNO components. Its contents use the same XML attributes: + +``` + + + + + + + + EzHighlight Addon + + + + Andrew Davison + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +The biggest difference is that the extension icon is located in the images/ subdirectory +inside EzHighlight/. license.txt and package-description.txt are referenced in the +description, which explains why those files are in EzHighlight/. + +The dialog description (EzHighlight.xdl) is stored in its own subdirectory, +dialogLibrary/, to match the dialog loading code in EzHighlightAddonInpl.java: + +```java +// in processCmd() +dialog = Dialogs.loadAddonDialog("org.openoffice.ezhighlightAddon", + "dialogLibrary/" + cmd + ".xdl"); +``` + +The images/ subdirectory contains the extension manager icon (ezhighlight.png) and +two sizes of GUI icons (ezhighlight16.png and ezhighlight26.png). The GUI icons +must be inside images/ to match the locations specified in the "Images" attribute in +Addon.xcs. + + diff --git a/docs/47-Calc_Add-ins.md b/docs/47-Calc_Add-ins.md index e91d3a3..f0ed85c 100644 --- a/docs/47-Calc_Add-ins.md +++ b/docs/47-Calc_Add-ins.md @@ -1,567 +1,580 @@ -# Chapter 47. Calc Add-ins +# Chapter 47. Calc Add-ins !!! note "Topics" - Implementing a - Calc Add-in; The IDL - definitions of the - Functions; XCU creation - - Example folders: "Addin - Tests" and "Utils" - - -Calc Add-ins are extensions containing spreadsheet -functions; my Doubler add-in contains three: doubler() for -doubling a supplied value, doublerSum() for summing a -cell range after doubling each cell value, and -sortByFirstCol() which sorts a supplied cell range returning an array sorted into -ascending order based on the first column. - -Figure 1 shows the Doubler add-in as the first entry in the extension manger after it's -been installed in Office. - - - + Implementing a + Calc Add-in; The IDL + definitions of the + Functions; XCU creation + + Example folders: "Addin + Tests" and "Utils" + + +Calc Add-ins are extensions containing spreadsheet +functions; my Doubler add-in contains three: doubler() for +doubling a supplied value, doublerSum() for summing a +cell range after doubling each cell value, and +sortByFirstCol() which sorts a supplied cell range returning an array sorted into +ascending order based on the first column. + +Figure 1 shows the Doubler add-in as the first entry in the extension manger after it's +been installed in Office. + ![](images/47-Calc_Add-ins-1.png) -Figure 1. The Doubler Calc Add-in, and other Extensions. +Figure 1. The Doubler Calc Add-in, and other Extensions. + - -I created a small spreadsheet in calcTest.ods using doubler(), doublerSum(), and -sortByFirstCol(), which is displayed in Figure 2. +I created a small spreadsheet in calcTest.ods using doubler(), doublerSum(), and +sortByFirstCol(), which is displayed in Figure 2. - - ![](images/47-Calc_Add-ins-2.png) -Figure 2. Calc Add-in Functions Used in a Spreadsheet. +Figure 2. Calc Add-in Functions Used in a Spreadsheet. + - -doubler() is called six times to double the values in the "A" column, storing them in -the "B" column. doublerSum() is called twice to double and sum the "A" and "B" -columns, placing the results in C2 and C3. sortByFirstCol() sorts the cell range A1:B6 -into ascending order by the values in the "A" column, and stores the resulting array in -D1:E6. +doubler() is called six times to double the values in the "A" column, storing them in +the "B" column. doublerSum() is called twice to double and sum the "A" and "B" +columns, placing the results in C2 and C3. sortByFirstCol() sorts the cell range A1:B6 +into ascending order by the values in the "A" column, and stores the resulting array in +D1:E6. -sortByFirstCol() is as an example of an Array function (or formula) because it returns -an array which is written into a block of cells (i.e. D1:E6). The slightly tricky way of -doing that is to select the block before typing "=sortByFirstCol(A1:B6)" into the -formula text bar, and then press Shift+Ctrl+Enter instead of the Enter key. The cell -range output is denoted by curly braces surrounding the function, which are just -visible in the bottom screenshot in Figure 2. +sortByFirstCol() is as an example of an Array function (or formula) because it returns +an array which is written into a block of cells (i.e. D1:E6). The slightly tricky way of +doing that is to select the block before typing "=sortByFirstCol(A1:B6)" into the +formula text bar, and then press Shift+Ctrl+Enter instead of the Enter key. The cell +range output is denoted by curly braces surrounding the function, which are just +visible in the bottom screenshot in Figure 2. -Alternatively, the Function Wizard can be called, and its Array box checked as the -function's inputs are specified, as in Figure 3. +Alternatively, the Function Wizard can be called, and its Array box checked as the +function's inputs are specified, as in Figure 3. - - ![](images/47-Calc_Add-ins-3.png) -Figure 3. The Function Wizard and sortByFirstCol() - -The add-in functions from the Doubler extension, and other Office extensions, are -listed under the Add-in" category of the Function Wizard, as shown in Figure 4. +Figure 3. The Function Wizard and sortByFirstCol() + +The add-in functions from the Doubler extension, and other Office extensions, are +listed under the Add-in" category of the Function Wizard, as shown in Figure 4. - - ![](images/47-Calc_Add-ins-4.png) -Figure 4. The Function Wizard's Add-in Category. +Figure 4. The Function Wizard's Add-in Category. + - -If you're unfamiliar with the Function Wizard, then you should download the Calc -Guide from https://th.libreoffice.org/get-help/documentation/. Chapter 7 is about -formulas and functions, and Appendix B lists the functions by categories, including -the add-ins which come with Calc. The functions-by-category data is also online at -https://help.libreoffice.org/Calc/Functions_by_Category -An interesting wiki page comparing the features of the current version of LibreOffice -and MS Office can be found at +If you're unfamiliar with the Function Wizard, then you should download the Calc +Guide from https://th.libreoffice.org/get-help/documentation/. Chapter 7 is about +formulas and functions, and Appendix B lists the functions by categories, including +the add-ins which come with Calc. The functions-by-category data is also online at +https://help.libreoffice.org/Calc/Functions_by_Category +An interesting wiki page comparing the features of the current version of LibreOffice +and MS Office can be found at https://wiki.documentfoundation.org/Feature_Comparison:_LibreOffice_- -_Microsoft_Office. The spreadsheet section states that LibreOffice Calc has 500 -functions versus 468 in MS Excel, with a large overlap between them but with 25 -functions unique to Calc 5.2 and 14 unique to Excel 2016. Another interesting -comparison is offered by Zeki Bildirici as a spreadsheet at +_Microsoft_Office. The spreadsheet section states that LibreOffice Calc has 500 +functions versus 468 in MS Excel, with a large overlap between them but with 25 +functions unique to Calc 5.2 and 14 unique to Excel 2016. Another interesting +comparison is offered by Zeki Bildirici as a spreadsheet at https://wiki.documentfoundation.org/images/c/c6/Excel2013_Calc_Functions_Compa -rison.ods, which matches functions in Excel 2013 to their equivalents in Calc 3.x. - - - -## 1. Implementing a Calc Add-in - -The writing of a Calc add-in became much easier after OpenOffice 2.0.4 was released -in 2005, when it became possible to define an add-in as an IDL type, and use the -UNO component tools (e.g. idlc, regmerge, javamaker, and uno-skeletonmaker) to -generate the add-in's boiler-plate code. Prior to this, the programmer had to write a -class that implemented several interfaces including XAddin, XServiceName, and -XServiceInfo. - -I mention this 'ancient' history because the Developer's Guide (no spring chicken -itself, having been released in 2009) only explains the pre-2005 approach. There's a -lengthy example in chapter 8, which can also be found online at +rison.ods, which matches functions in Excel 2013 to their equivalents in Calc 3.x. + + +## 1. Implementing a Calc Add-in + +The writing of a Calc add-in became much easier after OpenOffice 2.0.4 was released +in 2005, when it became possible to define an add-in as an IDL type, and use the +UNO component tools (e.g. idlc, regmerge, javamaker, and uno-skeletonmaker) to +generate the add-in's boiler-plate code. Prior to this, the programmer had to write a +class that implemented several interfaces including XAddin, XServiceName, and +XServiceInfo. + +I mention this 'ancient' history because the Developer's Guide (no spring chicken +itself, having been released in 2009) only explains the pre-2005 approach. There's a +lengthy example in chapter 8, which can also be found online at https://wiki.openoffice.org/wiki/Documentation/DevGuide/Spreadsheets/Spreadsheet -_Add-Ins (or use loGuide "Spreadsheet Add-Ins"). The code (called -ExampleAddIn) can be downloaded from -http://api.libreoffice.org/examples/DevelopersGuide/examples.html#Spreadsheet -According to forum posts, the old approach still works, but I haven’t checked; I'm -only going to use the 'new' way of implementing add-ins (post-2005), using IDL types -and the UNO component tools. Figure 5 shows the required steps. +_Add-Ins (or use `loGuide "Spreadsheet Add-Ins"`). The code (called +ExampleAddIn) can be downloaded from +http://api.libreoffice.org/examples/DevelopersGuide/examples.html#Spreadsheet + +According to forum posts, the old approach still works, but I haven’t checked; I'm +only going to use the 'new' way of implementing add-ins (post-2005), using IDL types +and the UNO component tools. Figure 5 shows the required steps. - - ![](images/47-Calc_Add-ins-5.png) -Figure 5. Implementing a Calc Add-in. +Figure 5. Implementing a Calc Add-in. - -One point in favor of this approach is that it's almost identical to the stages needed to -create an UNO component, as described in Chapter 45. Figure 5 is very similar to -Figure 1 in that chapter, and the batch scripts labeling the arrows are unchanged, -except for skelAddin.bat. skelAddin.bat only differs from skelComp.bat of Chapter 45 -in a single argument passed to uno-skeletonmaker.exe, which tells it to generate an -add-in skeleton rather than a component. -The other, more significant, change in Figure 5 is the addition of an XML -configuration file called CalcAddin.xcu. It defines the GUI elements for the functions, -used by the Function Wizard in Figure 3. +One point in favor of this approach is that it's almost identical to the stages needed to +create an UNO component, as described in Chapter 45. Figure 5 is very similar to +Figure 1 in that chapter, and the batch scripts labeling the arrows are unchanged, +except for skelAddin.bat. skelAddin.bat only differs from skelComp.bat of Chapter 45 +in a single argument passed to uno-skeletonmaker.exe, which tells it to generate an +add-in skeleton rather than a component. -A great write-up of this way of coding Calc add-ins by Jan Holst Jensen can be found -at http://biochemfusion.com/doc/Calc_addin_howto.html. He explains how to create a -DoobieDoo add-in consisting of four Python functions, with the code available at -https://wiki.openoffice.org/wiki/Calc/Add-In/Python_How-To. +The other, more significant, change in Figure 5 is the addition of an XML +configuration file called CalcAddin.xcu. It defines the GUI elements for the functions, +used by the Function Wizard in Figure 3. -There's another good example in the OpenOffice NetBeans integration documentation -at https://wiki.openoffice.org/wiki/Calc/Add-In/Project_Type. If you skip past the -description of NetBeans' project wizard, the generated add-in code section is quite -informative. Unlike my example, the add-in can be localized to show documentation -in English or German. +A great write-up of this way of coding Calc add-ins by Jan Holst Jensen can be found +at http://biochemfusion.com/doc/Calc_addin_howto.html. He explains how to create a +DoobieDoo add-in consisting of four Python functions, with the code available at +https://wiki.openoffice.org/wiki/Calc/Add-In/Python_How-To. -The specification document for the NetBeans add-in wizard is at +There's another good example in the OpenOffice NetBeans integration documentation +at https://wiki.openoffice.org/wiki/Calc/Add-In/Project_Type. If you skip past the +description of NetBeans' project wizard, the generated add-in code section is quite +informative. Unlike my example, the add-in can be localized to show documentation +in English or German. + +The specification document for the NetBeans add-in wizard is at https://www.openoffice.org/specs/sdk/tools/spec_openoffice-netbeans-integration- -calc-addin-wizard.odt. In amongst details about dialog structuring, there's some good -material on the format of CalcAddin.xcu. I'll explain the XCU format in section 2.5. - - - -## 2. The IDL definitions of the Functions - -My Doubler functions (doubler(), doublerSum(), and sortByFirstCol()) are defined in -Doubler.idl: - -#ifndef _org_openoffice_doubler_XDoubler_ -#define _org_openoffice_doubler_XDoubler_ - -#include - - -module org { module openoffice { module doubler { - - interface XDoubler { - double doubler([in] double value); - - double doublerSum([in] sequence< sequence< double > > vals); - - sequence< sequence< double > > sortByFirstCol( - [in] sequence< sequence< double > > vals); - }; -}; }; }; - - -module org { module openoffice { module doubler -{ - service Doubler : XDoubler; -}; }; }; - -#endif - -The IDL defines an XDoubler interface and Doubler service, both in the -org.openoffice.doubler module; XDoubler supports three functions. - -As mentioned in Chapter 45, the main source for information about IDL types is -chapter 3 of the Developer's Guide. The subsection "Using UNOIDL to Specify New -Components" explains how to define an interface, and the same information is online -at +calc-addin-wizard.odt. In amongst details about dialog structuring, there's some good +material on the format of CalcAddin.xcu. I'll explain the XCU format in section 2.5. + + +## 2. The IDL definitions of the Functions + +My Doubler functions (doubler(), doublerSum(), and sortByFirstCol()) are defined in +Doubler.idl: + +``` +#ifndef _org_openoffice_doubler_XDoubler_ +#define _org_openoffice_doubler_XDoubler_ + +#include + + +module org { module openoffice { module doubler { + + interface XDoubler { + double doubler([in] double value); + + double doublerSum([in] sequence< sequence< double > > vals); + + sequence< sequence< double > > sortByFirstCol( + [in] sequence< sequence< double > > vals); + }; +}; }; }; + + +module org { module openoffice { module doubler +{ + service Doubler : XDoubler; +}; }; }; + +#endif +``` + +The IDL defines an XDoubler interface and Doubler service, both in the +org.openoffice.doubler module; XDoubler supports three functions. + +As mentioned in Chapter 45, the main source for information about IDL types is +chapter 3 of the Developer's Guide. The subsection "Using UNOIDL to Specify New +Components" explains how to define an interface, and the same information is online +at https://wiki.openoffice.org/wiki/Documentation/DevGuide/WritingUNO/Using_UNO -IDL_to_Specify_New_Components (or use loGuide "Using UNOIDL"). However, -add-in functions only support a subset of the IDL input and return types. +IDL_to_Specify_New_Components (or use `loGuide "Using UNOIDL"`). However, +add-in functions only support a subset of the IDL input and return types. -Input arguments must use "[in]", and are restricted to the types long, double, string, -and any. Sequences must be two-dimensional (e.g. sequence< sequence>), -because they're used to represent cell ranges in the spreadsheet. It's possible to use the -XCellRange and XPropertySet as input types, and use sequence as the type of -the last argument so it will be assigned the inputs not used by previous arguments. +Input arguments must use "[in]", and are restricted to the types long, double, string, +and any. Sequences must be two-dimensional (e.g. sequence< sequence>), +because they're used to represent cell ranges in the spreadsheet. It's possible to use the +XCellRange and XPropertySet as input types, and use sequence as the type of +the last argument so it will be assigned the inputs not used by previous arguments. -Return types can be long, double, string, and any, or a 2D sequence (e.g. sequence< -sequence>) which can be assigned to a cell range in the spreadsheet. +Return types can be long, double, string, and any, or a 2D sequence (e.g. sequence< +sequence>) which can be assigned to a cell range in the spreadsheet. -XVolatileResult is a special return type which represents a result that may change -over time; its value is automatically updated in the cell containing the function. +XVolatileResult is a special return type which represents a result that may change +over time; its value is automatically updated in the cell containing the function. -Details on how IDL types are mapped to Java can be found in chapter 2 of the guide, -starting at the "Type Mapping" subsection (online at +Details on how IDL types are mapped to Java can be found in chapter 2 of the guide, +starting at the "Type Mapping" subsection (online at https://wiki.openoffice.org/wiki/Documentation/DevGuide/ProUNO/Java/Type_Mapp -ings, or via loGuide "Type Mappings"). The Java versions of the add-in function -types are also detailed in the online documentation for the AddIn service (use lodoc -Addin to find the page). - - -### 2.1. Converting IDL into Java Code - -Figure 5 shows the steps needed to convert Doubler.idl into partial Java code for the -add-in functions. The idlc.bat, regmerge.bat, and javamaker.bat scripts are unchanged -from Chapter 45, and skelAddin.bat only differs from that chapter's skelComp.bat by -calling javamaker.exe with a "calc-add-in" argument rather than "component". The -four calls used to generate the Java code are: - -> idlc.bat Doubler -> regmerge.bat Doubler -> javamaker.bat Doubler -> skelAddin.bat Doubler - -or they can be called collectively through genCode.bat: -> genCode Doubler -Since idlc.bat and skelAddin.bat copy files into "Program Files/", they must be run -with Administrative privileges, as in Chapter 45. - -The end result is DoublerImpl.java, which contains all the necessary boiler-plate code, -and three stub functions: - -// part of DoublerImpl.java... - -public double doubler(double value) -{ - return 0; -} - -public double doublerSum(double[][] vals) -{ - return 0; -} - -public double[][] sortByFirstCol(double[][] vals) -{ - return new double[0][0]; -} - -In a spreadsheet, doublerSum() and sortByFirstCol() can be passed cell range -arguments (e.g. =doublerSum(A1:A6)), which are treated as 2D arrays. The -transformation retains the row ordering of the cells in the array, as illustrated by +ings, or via `loGuide "Type Mappings"`). The Java versions of the add-in function +types are also detailed in the online documentation for the AddIn service (use +`lodoc Addin` to find the page). -![](images/47-Calc_Add-ins-6.png) -Figure 6. +### 2.1. Converting IDL into Java Code + +Figure 5 shows the steps needed to convert Doubler.idl into partial Java code for the +add-in functions. The idlc.bat, regmerge.bat, and javamaker.bat scripts are unchanged +from Chapter 45, and skelAddin.bat only differs from that chapter's skelComp.bat by +calling javamaker.exe with a "calc-add-in" argument rather than "component". The +four calls used to generate the Java code are: + +``` +> idlc.bat Doubler +> regmerge.bat Doubler +> javamaker.bat Doubler +> skelAddin.bat Doubler +``` + +or they can be called collectively through genCode.bat: + +``` +> genCode Doubler +``` + +Since idlc.bat and skelAddin.bat copy files into "Program Files/", they must be run +with Administrative privileges, as in Chapter 45. + +The end result is DoublerImpl.java, which contains all the necessary boiler-plate code, +and three stub functions: + +```java +// part of DoublerImpl.java... + +public double doubler(double value) +{ + return 0; +} + +public double doublerSum(double[][] vals) +{ + return 0; +} + +public double[][] sortByFirstCol(double[][] vals) +{ + return new double[0][0]; +} +``` + +In a spreadsheet, doublerSum() and sortByFirstCol() can be passed cell range +arguments (e.g. =doublerSum(A1:A6)), which are treated as 2D arrays. The +transformation retains the row ordering of the cells in the array, as illustrated by +Figure 6. - - ![](images/47-Calc_Add-ins-6.png) -Figure 6. From Cell Range to Input Array, -and Output Array to Cell Range. - - -The same mapping is used in reverse if the function returns a 2D array, as in the case -of sortByFirstCol(). - - -### 2.2. Implementing the Add-in Functions - -Implementing doubler() and doublerSum() is easy, but I had some problems with -sortByFirstCol() when using Java's Arrays.sort(). The code was initially: - -// added at the start of DoublerImpl.java... - -import java.util.*; // import for Arrays and Comparator classes - -private static final String LOG_FNM = "c:\\arrayInfo.txt"; - // for debugging - -// : -// completed function stubs -public double doubler(double value) -{ return value*2; } - - -public double doublerSum(double[][] vals) -{ - double sum = 0; - for (int i = 0; i < vals.length; i++) - for (int j = 0; j < vals[i].length; j++) - sum += vals[i][j]*2; - return sum; -} // end of doublerSum() - - -public double[][] sortByFirstCol(double[][] vals) -{ - FileIO.appendTo(LOG_FNM, Lo.getTimeStamp() + ": sortByFirstCol()"); - selectionSort(vals); - - for (int i = 0; i < vals.length; i++) - FileIO.appendTo(LOG_FNM, " " + Arrays.toString(vals[i])); - - return vals; -} // end of sortByFirstCol() - - -private void selectionSort(double[][] vals) -// ascending order based on first column of vals; FAILS ?? -{ - Arrays.sort(vals, new Comparator() { - public int compare(double[] row1, double[] row2) - // compare first column of each row - { - FileIO.appendTo(LOG_FNM, "compared"); // never reached ?? - return Double.compare(row1[0], row2[0]); - } - }); -} // end of selectionSort() - -FileIO.appendTo() is my way of debugging add-in functions by appending messages -to a file. Its main drawback is the lack of a UNIX-like tail command in Windows for -monitoring the end of a file; I use tail.exe from the Gow UNIX utilities for Windows -(https://github.com/bmatzelle/gow/wiki). - -The above version of sortByFirstCol() fails without returning an array for the cell -range D1:E6, as can be seen by the block of #VALUE! text in Figure 7. - - - +Figure 6. From Cell Range to Input Array, +and Output Array to Cell Range. + + +The same mapping is used in reverse if the function returns a 2D array, as in the case +of sortByFirstCol(). + + +### 2.2. Implementing the Add-in Functions + +Implementing doubler() and doublerSum() is easy, but I had some problems with +sortByFirstCol() when using Java's Arrays.sort(). The code was initially: + +```java +// added at the start of DoublerImpl.java... + +import java.util.*; // import for Arrays and Comparator classes + +private static final String LOG_FNM = "c:\\arrayInfo.txt"; + // for debugging + +// : +// completed function stubs +public double doubler(double value) +{ return value*2; } + + +public double doublerSum(double[][] vals) +{ + double sum = 0; + for (int i = 0; i < vals.length; i++) + for (int j = 0; j < vals[i].length; j++) + sum += vals[i][j]*2; + return sum; +} // end of doublerSum() + + +public double[][] sortByFirstCol(double[][] vals) +{ + FileIO.appendTo(LOG_FNM, Lo.getTimeStamp() + ": sortByFirstCol()"); + selectionSort(vals); + + for (int i = 0; i < vals.length; i++) + FileIO.appendTo(LOG_FNM, " " + Arrays.toString(vals[i])); + + return vals; +} // end of sortByFirstCol() + + +private void selectionSort(double[][] vals) +// ascending order based on first column of vals; FAILS ?? +{ + Arrays.sort(vals, new Comparator() { + public int compare(double[] row1, double[] row2) + // compare first column of each row + { + FileIO.appendTo(LOG_FNM, "compared"); // never reached ?? + return Double.compare(row1[0], row2[0]); + } + }); +} // end of selectionSort() +``` + +FileIO.appendTo() is my way of debugging add-in functions by appending messages +to a file. Its main drawback is the lack of a UNIX-like tail command in Windows for +monitoring the end of a file; I use tail.exe from the Gow UNIX utilities for Windows +(https://github.com/bmatzelle/gow/wiki). + +The above version of sortByFirstCol() fails without returning an array for the cell +range D1:E6, as can be seen by the block of #VALUE! text in Figure 7. + ![](images/47-Calc_Add-ins-7.png) -Figure 7. sortByFirstCol() Failing in calcTest.ods. - - -The debugging text written to the log (c:/arrayInfo.txt) shows that Arrays.sort() fails, -although why is a mystery. - -I replaced selectionSort() with my own insertion sort: - -// part of DoublerImpl.java... - -private void selectionSort(double[][] vals) -// ascending order based on first column of vals; WORKS! -{ - double[] temp; - for(int i = vals.length-1; i > 0; i--) { - int first = 0; - for(int j = 1; j <= i; j ++) { - if(vals[j][0] > vals[first][0]) // compare first col values - first = j; - } - temp = vals[first]; // swap rows - vals[first] = vals[i]; - vals[i] = temp; - } -} // end of selectionSort() - -This version of sortByFirstCol() works correctly, and the spreadsheet looks like the -screenshot at the bottom of Figure 2. The debugging text confirms the sorting: - -2016-11-02 14:04:21: sortByFirstCol() - [1.0, 2.0] - [3.0, 6.0] - [5.0, 10.0] - [6.0, 12.0] - [8.0, 16.0] - [12.0, 24.0] - -The add-in functions in DoublerImpl.java make use of two of my utility functions, -namely Lo.getTimeStamp() and FileIO.appendTo(), so the add-in extension must -include utils.jar. - - -### 2.3. Functions that use Global State (a Bad Idea) - -The Java code generated by uno-skeletonmaker.exe includes a reference to Office's -component context, stored as a XComponentContext reference: - -private final XComponentContext m_xContext; // in DoublerImpl.java - -The variable is initialized in the constructor: - -public DoublerImpl( XComponentContext context ) -{ m_xContext = context; } - -This reference can be used to initialize the globals used by my Lo utility library and -other support classes, by calling Lo.addonInitialize(): - -// part of DoublerImpl()... - -doc = Lo.addonInitialize(m_xContext); - -Lo.addonInitialize() returns an XComponent instance, which refers to the spreadsheet. - -These additions to DoublerImpl.java make it possible for doubler(), doublerSum(), -and sortByFirstCol() to access and change the spreadsheet independently of their -input arguments. For example, it's possible to access document information such as -the title bar text, and the supported services: - -// in DoubleImpl.java -// global -private XComponent doc; - - -public double doubler(double value) -{ - FileIO.appendTo(LOG_FNM, "Window title: " + GUI.getTitleBar()); - FileIO.appendTo(LOG_FNM, "Services for this document:"); - for(String service : Info.getServices(doc)) - FileIO.appendTo(LOG_FNM, " " + service); - - return value*2; -} - -Calls to FileIO.appendTo() are the only way to 'print' information. The lines appended -to the log are: - -Window title: calcTest.ods - LibreOffice Calc -Services for this document: - com.sun.star.document.OfficeDocument - com.sun.star.sheet.SpreadsheetDocument - com.sun.star.sheet.SpreadsheetDocumentSettings - -However, this way of using add-in functions is poor programming style because it -becomes hard to know what a function is doing without checking its implementation. - -For that reason, I'd avoid this kind of coding unless absolutely necessary. - - -### 2.4. Problems with Office Types - -The input argument types for add-in functions are usually long, double, string, any, or -a sequence, but it's also possible to use XCellRange and XPropertySet. Return types -are typically long, double, string, any, or a sequence, but a special case is -XVolatileResult. - -I tried using XCellRange as an input type and XVolatileResult as a return type in two -functions in Doubler.idl: - -#ifndef _org_openoffice_doubler_XDoubler_ -#define _org_openoffice_doubler_XDoubler_ - -#include -#include -#include - -module org { module openoffice { module doubler { - - interface XDoubler { - double doubler([in] double value); - - double doublerSum([in] sequence< sequence< double > > vals); - - sequence< sequence< double > > sortByFirstCol( - [in] sequence< sequence< double > > vals); - - long usedCells([in] com::sun::star::table::XCellRange cr); - // javamaker cannot process this function - - com::sun::star::sheet::XVolatileResult counter( - [in] string aName, [in] double value); - // javamaker cannot process this function - }; -}; }; }; - -#endif - -Office's idlc.exe and regmerge.exe tools are happy to process these types, but -javamaker.exe always fails to recognize 'com.sun.star.table.XCellRange' and -'com.sun.star.sheet.XVolatileResult', reporting an "Unknown entity" error. I tried -calling javamaker with the inclusion of extra Office RDB files, such as services.rdb -and offapi.rdb, but with no success. - -I was unable to find an add-in example online that uses XCellRange, perhaps because -it's more natural to pass a cell range to a function as an array. - -XVolatileResult is utilized by the example in the Developer's Guide (ExampleAddIn -at http://api.libreoffice.org/examples/DevelopersGuide/examples.html#Spreadsheet), -but it employs the old add-in coding style based on implementing the XAddIn -interface. - - -### 2.5. Creating CalcAddIn.xcu - -The second half of the Calc add-in development process shown in Figure 5 starts -when the programmer has finished writing the add-in functions. It uses the same batch -scripts as in Chapter 45: compileOrg.bat, toJar.bat, makeOxt.bat, and pkg.bat. The -only difference is that the Doubler/ directory zipped up as an OXT file contains a -configuration file, CalcAddIn.xcu.. The folder's structure is: - -Doubler -| CalcAddIn.xcu -| description.xml -| double.png -| license.txt -| package-description.txt -| Utils.jar -| -\---META-INF - manifest.xml - -makeOxt.bat moves the Doubler.rdb type data and DoublerImpl.jar into Doubler/, and -zips the contents into Doubler.oxt. - -CalcAddin.xcu contains an XML description for each function in Doubler. For -example, sortByFirstCol() is represented by: - - - - sortByFirstCol - - - sorts a cell range into ascending - order using the first column values - of the range - - - Add-In - - - sortByFirstCol - - - - - cell range - - range of doubles - - - - - -There are five fields: "DisplayName", "Description", "Category", -"CompatibilityName", and "Parameters", most of which are utilized by Office's -Function Wizard, as shown in Figure 3. +Figure 7. sortByFirstCol() Failing in calcTest.ods. + + +The debugging text written to the log (c:/arrayInfo.txt) shows that Arrays.sort() fails, +although why is a mystery. + +I replaced selectionSort() with my own insertion sort: + +```java +// part of DoublerImpl.java... + +private void selectionSort(double[][] vals) +// ascending order based on first column of vals; WORKS! +{ + double[] temp; + for(int i = vals.length-1; i > 0; i--) { + int first = 0; + for(int j = 1; j <= i; j ++) { + if(vals[j][0] > vals[first][0]) // compare first col values + first = j; + } + temp = vals[first]; // swap rows + vals[first] = vals[i]; + vals[i] = temp; + } +} // end of selectionSort() +``` + +This version of sortByFirstCol() works correctly, and the spreadsheet looks like the +screenshot at the bottom of Figure 2. The debugging text confirms the sorting: + +``` +2016-11-02 14:04:21: sortByFirstCol() + [1.0, 2.0] + [3.0, 6.0] + [5.0, 10.0] + [6.0, 12.0] + [8.0, 16.0] + [12.0, 24.0] +``` + +The add-in functions in DoublerImpl.java make use of two of my utility functions, +namely Lo.getTimeStamp() and FileIO.appendTo(), so the add-in extension must +include utils.jar. + + +### 2.3. Functions that use Global State (a Bad Idea) + +The Java code generated by uno-skeletonmaker.exe includes a reference to Office's +component context, stored as a XComponentContext reference: + +```java +private final XComponentContext m_xContext; // in DoublerImpl.java +``` + +The variable is initialized in the constructor: + +```java +public DoublerImpl( XComponentContext context ) +{ m_xContext = context; } +``` + +This reference can be used to initialize the globals used by my Lo utility library and +other support classes, by calling Lo.addonInitialize(): + +```java +// part of DoublerImpl()... +doc = Lo.addonInitialize(m_xContext); +``` + +Lo.addonInitialize() returns an XComponent instance, which refers to the spreadsheet. + +These additions to DoublerImpl.java make it possible for doubler(), doublerSum(), +and sortByFirstCol() to access and change the spreadsheet independently of their +input arguments. For example, it's possible to access document information such as +the title bar text, and the supported services: + +```java +// in DoubleImpl.java +// global +private XComponent doc; + + +public double doubler(double value) +{ + FileIO.appendTo(LOG_FNM, "Window title: " + GUI.getTitleBar()); + FileIO.appendTo(LOG_FNM, "Services for this document:"); + for(String service : Info.getServices(doc)) + FileIO.appendTo(LOG_FNM, " " + service); + + return value*2; +} +``` + +Calls to FileIO.appendTo() are the only way to 'print' information. The lines appended +to the log are: + +``` +Window title: calcTest.ods - LibreOffice Calc +Services for this document: + com.sun.star.document.OfficeDocument + com.sun.star.sheet.SpreadsheetDocument + com.sun.star.sheet.SpreadsheetDocumentSettings +``` + +However, this way of using add-in functions is poor programming style because it +becomes hard to know what a function is doing without checking its implementation. + +For that reason, I'd avoid this kind of coding unless absolutely necessary. + + +### 2.4. Problems with Office Types + +The input argument types for add-in functions are usually long, double, string, any, or +a sequence, but it's also possible to use XCellRange and XPropertySet. Return types +are typically long, double, string, any, or a sequence, but a special case is +XVolatileResult. + +I tried using XCellRange as an input type and XVolatileResult as a return type in two +functions in Doubler.idl: + +``` +#ifndef _org_openoffice_doubler_XDoubler_ +#define _org_openoffice_doubler_XDoubler_ + +#include +#include +#include + +module org { module openoffice { module doubler { + + interface XDoubler { + double doubler([in] double value); + + double doublerSum([in] sequence< sequence< double > > vals); + + sequence< sequence< double > > sortByFirstCol( + [in] sequence< sequence< double > > vals); + + long usedCells([in] com::sun::star::table::XCellRange cr); + // javamaker cannot process this function + + com::sun::star::sheet::XVolatileResult counter( + [in] string aName, [in] double value); + // javamaker cannot process this function + }; +}; }; }; + +#endif +``` + +Office's idlc.exe and regmerge.exe tools are happy to process these types, but +javamaker.exe always fails to recognize 'com.sun.star.table.XCellRange' and +'com.sun.star.sheet.XVolatileResult', reporting an "Unknown entity" error. I tried +calling javamaker with the inclusion of extra Office RDB files, such as services.rdb +and offapi.rdb, but with no success. + +I was unable to find an add-in example online that uses XCellRange, perhaps because +it's more natural to pass a cell range to a function as an array. + +XVolatileResult is utilized by the example in the Developer's Guide (ExampleAddIn +at http://api.libreoffice.org/examples/DevelopersGuide/examples.html#Spreadsheet), +but it employs the old add-in coding style based on implementing the XAddIn +interface. + + +### 2.5. Creating CalcAddIn.xcu + +The second half of the Calc add-in development process shown in Figure 5 starts +when the programmer has finished writing the add-in functions. It uses the same batch +scripts as in Chapter 45: compileOrg.bat, toJar.bat, makeOxt.bat, and pkg.bat. The +only difference is that the Doubler/ directory zipped up as an OXT file contains a +configuration file, CalcAddIn.xcu.. The folder's structure is: + +```java +Doubler +| CalcAddIn.xcu +| description.xml +| double.png +| license.txt +| package-description.txt +| Utils.jar +| +\---META-INF + manifest.xml +``` + +makeOxt.bat moves the Doubler.rdb type data and DoublerImpl.jar into Doubler/, and +zips the contents into Doubler.oxt. + +CalcAddin.xcu contains an XML description for each function in Doubler. For +example, sortByFirstCol() is represented by: + +``` + + + sortByFirstCol + + + sorts a cell range into ascending + order using the first column values + of the range + + + Add-In + + + sortByFirstCol + + + + + cell range + + range of doubles + + + + +``` + +There are five fields: "DisplayName", "Description", "Category", +"CompatibilityName", and "Parameters", most of which are utilized by Office's +Function Wizard, as shown in Figure 3. The "Category" value can be any of the wizard's pop-down list categories, but "Add- -In" seems the best choice. If you want to use one of the others, a complete list is given -in the documentation for getProgrammaticCategoryName() in the XAddin class (use -lodoc XAddin to access the page). - -The simplest value to use for "CompatibilityName" is the function's name. This -becomes important when the spreadsheet is exported as an Excel XLS file, when the -function's name is replaced by this string. - -According to Jan Holst Jensen in his "Calc Add-in How-to" article -(http://biochemfusion.com/doc/Calc_addin_howto.html), it's possible to supply the -name of a .NET or COM Automation function in this field, and have the exported -spreadsheet correctly call that Excel function, but I haven't tried that myself. - -The "Parameters" field may contain several sub-nodes, one for each function -argument. Each node maps the argument's name in the IDL definition to text shown -by the Function Wizard. For example, the IDL definition for sortByFirstCol() is: - -sequence< sequence< double > > sortByFirstCol( - [in] sequence< sequence< double > > vals); - -The "vals" argument is mapped to a display name and description used by the -Function Wizard in Figure 3. - - +In" seems the best choice. If you want to use one of the others, a complete list is given +in the documentation for getProgrammaticCategoryName() in the XAddin class (use +lodoc XAddin to access the page). + +The simplest value to use for "CompatibilityName" is the function's name. This +becomes important when the spreadsheet is exported as an Excel XLS file, when the +function's name is replaced by this string. + +According to Jan Holst Jensen in his "Calc Add-in How-to" article +(http://biochemfusion.com/doc/Calc_addin_howto.html), it's possible to supply the +name of a .NET or COM Automation function in this field, and have the exported +spreadsheet correctly call that Excel function, but I haven't tried that myself. + +The "Parameters" field may contain several sub-nodes, one for each function +argument. Each node maps the argument's name in the IDL definition to text shown +by the Function Wizard. For example, the IDL definition for sortByFirstCol() is: + +``` +sequence< sequence< double > > sortByFirstCol( + [in] sequence< sequence< double > > vals); +``` + +The "vals" argument is mapped to a display name and description used by the +Function Wizard in Figure 3. \ No newline at end of file diff --git a/docs/48-Event_Macros.md b/docs/48-Event_Macros.md index 2954055..7338509 100644 --- a/docs/48-Event_Macros.md +++ b/docs/48-Event_Macros.md @@ -1,1236 +1,1260 @@ -# Chapter 48. Event Macros +# Chapter 48. Event Macros !!! note "Topics" - Macro Locations; - Naming Macro - Functions; Calling - Existing Macros; The - LibreLogo Macro; - Writing a Simple Event - Macro; Automating the - Assigning of Event - Macros - - Example folders: - "EvMacro Tests" and - "Utils" - - -Event macros are functions triggered by the occurrence of -events, either within Office or in Office documents. I'll be -using event macros with forms, by attaching them to controls -such as buttons and textfields. - -A key difference between event macros and Java listeners is -that macros are attached to Office or to documents rather -than being part of an external Java program. In practical -terms this means that the macro code becomes part of the -Office installation or is embedded inside a document's ODF file. - -I'll spend two chapters discussing event macros: this chapter focuses on how to use -existing Office macros, and how to write a simple event macro that's installed by -copying it to a specific Office directory. This approach is obviously not ideal, and so -the next chapter looks at installing macros as extensions, and by attaching them to -documents. - -The Developer's Guide discusses macros in chapter 18, "Scripting Framework", but -macro programming has changed since the guide was produced (e.g. macros can now -be written in Python), so it's better to read the more current OpenOffice wiki, starting -at + Macro Locations; + Naming Macro + Functions; Calling + Existing Macros; The + LibreLogo Macro; + Writing a Simple Event + Macro; Automating the + Assigning of Event + Macros + + Example folders: + "EvMacro Tests" and + "Utils" + + +Event macros are functions triggered by the occurrence of +events, either within Office or in Office documents. I'll be +using event macros with forms, by attaching them to controls +such as buttons and textfields. + +A key difference between event macros and Java listeners is +that macros are attached to Office or to documents rather +than being part of an external Java program. In practical +terms this means that the macro code becomes part of the +Office installation or is embedded inside a document's ODF file. + +I'll spend two chapters discussing event macros: this chapter focuses on how to use +existing Office macros, and how to write a simple event macro that's installed by +copying it to a specific Office directory. This approach is obviously not ideal, and so +the next chapter looks at installing macros as extensions, and by attaching them to +documents. + +The Developer's Guide discusses macros in chapter 18, "Scripting Framework", but +macro programming has changed since the guide was produced (e.g. macros can now +be written in Python), so it's better to read the more current OpenOffice wiki, starting +at https://wiki.openoffice.org/wiki/Documentation/DevGuide/Scripting/Scripting_Frame -work (or use loGuide "Scripting Framework"). - -Most macro programming resources employ Office Basic, and Java rarely gets -mentioned, as in chapter 13, "Getting Started with Macros", of the "Getting Started -Guide" (available at http://www.libreoffice.org/get-help/documentation/). That -chapter ends with a useful list of online resources, and printed and eBook materials. - -Probably the best source for Office Basic macro coding is Andrew Pitonyak's website -at http://www.pitonyak.org/oo.php; I recommend starting with his book, -"OpenOffice.org Macros Explained" (OOME), which is free to download. There's -also a separate book of collected macros, called AndrewMacros.pdf. - -Another excellent introduction to Office Basic Macros (written in French) is: - Programmation OpenOffice.org et LibreOffice: Macros OOoBasic et API -Bernard Marcelly and Laurent Godard -Eyrolles, 2011 +work (or use `loGuide "Scripting Framework"`). + +Most macro programming resources employ Office Basic, and Java rarely gets +mentioned, as in chapter 13, "Getting Started with Macros", of the "Getting Started +Guide" (available at http://www.libreoffice.org/get-help/documentation/). That +chapter ends with a useful list of online resources, and printed and eBook materials. + +Probably the best source for Office Basic macro coding is Andrew Pitonyak's website +at http://www.pitonyak.org/oo.php; I recommend starting with his book, +"OpenOffice.org Macros Explained" (OOME), which is free to download. There's +also a separate book of collected macros, called AndrewMacros.pdf. + +Another excellent introduction to Office Basic Macros (written in French) is: + +* Programmation OpenOffice.org et LibreOffice: Macros OOoBasic et API +Bernard Marcelly and Laurent Godard +Eyrolles, 2011 http://www.editions-eyrolles.com/Livre/9782212132472/programmation- -openoffice-org-et-libreoffice -Two textbooks that focus on Office Basic macros in a particular Office application: - Learn OpenOffice.org Spreadsheet Macro Programming: OOoBasic and Calc -Automation -Mark Alexander Bain -Packt Publishing, 2006 +openoffice-org-et-libreoffice + +Two textbooks that focus on Office Basic macros in a particular Office application: + +* Learn OpenOffice.org Spreadsheet Macro Programming: OOoBasic and Calc +Automation +Mark Alexander Bain +Packt Publishing, 2006 https://www.packtpub.com/hardware-and-creative/learn-openofficeorg- -spreadsheet-macro-programming-ooobasic-and-calc-automation - Database Programming with OpenOffice.org Base and Basic -Roberto Benitez -Lulu.com, 2011 +spreadsheet-macro-programming-ooobasic-and-calc-automation +* Database Programming with OpenOffice.org Base and Basic +Roberto Benitez +Lulu.com, 2011 http://www.lulu.com/shop/roberto-benitez/database-programming-with- -openofficeorg-base-basic/paperback/product-3568728.html - - -## 1. Fantastic Macros and Where to Find Them +openofficeorg-base-basic/paperback/product-3568728.html -There are four categories of macros: - user: user macros are available only to the user who added them to Office; - share: share macros can be called by all users of that copy of Office; - extension: extension macros are added to office as an extension, and may be -available only to the user or be shared (these are discussed in the next chapter); - document: these macros are added to a document rather than to Office, and so are -useable only in that document (see the next chapter). -These categories are reflected in how the Office GUI displays installed macros in the -Macro Selector dialog of Figure 1 (accessible via the Tools > Macros > "Run Macros" -menu item). +## 1. Fantastic Macros and Where to Find Them - +There are four categories of macros: +* user: user macros are available only to the user who added them to Office; +* share: share macros can be called by all users of that copy of Office; +* extension: extension macros are added to office as an extension, and may be +available only to the user or be shared (these are discussed in the next chapter); +* document: these macros are added to a document rather than to Office, and so are +useable only in that document (see the next chapter). -![](images/48-Event_Macros-1.png) +These categories are reflected in how the Office GUI displays installed macros in the +Macro Selector dialog of Figure 1 (accessible via the Tools > Macros > "Run Macros" +menu item). -Figure 1. The "Tools > Macros > Run Macros" Dialog. +![](images/48-Event_Macros-1.png) - -The top-level folders in Figure 1 are library containers: "My Macros" and -"LibreOffice Macros" are always present, and store user and share macros -respectively. If the currently loaded document contains document macros then there -will be a third container with the same name as the file (e.g. "build.odt" in Figure 1). +Figure 1. The "Tools > Macros > Run Macros" Dialog. -Extension macros are grouped under the name of the extension file (e.g. -"FormMacros.oxt"), and may occur in either "My Macros" or "LibreOffice Macros" -depending on if the extension contains user or share macros. +The top-level folders in Figure 1 are library containers: "My Macros" and +"LibreOffice Macros" are always present, and store user and share macros +respectively. If the currently loaded document contains document macros then there +will be a third container with the same name as the file (e.g. "build.odt" in Figure 1). +Extension macros are grouped under the name of the extension file (e.g. +"FormMacros.oxt"), and may occur in either "My Macros" or "LibreOffice Macros" +depending on if the extension contains user or share macros. -Each library container (e.g. "My Macros") can hold multiple libraries; each library -can contain multiple modules; each module can store multiple macros. For example, -Figure 1 shows that the "My Macros" container holds three libraries called -"FormMacros.oxt", "Standard", and "WikiEditor". The "FormMacros.oxt" library -(which holds extension macros) consists of a single "Utils" module with two macros -called "GetNumber.get" and "GetText.show". I'll be explaining how to create and -install this extension in the next chapter. +Each library container (e.g. "My Macros") can hold multiple libraries; each library +can contain multiple modules; each module can store multiple macros. For example, +Figure 1 shows that the "My Macros" container holds three libraries called +"FormMacros.oxt", "Standard", and "WikiEditor". The "FormMacros.oxt" library +(which holds extension macros) consists of a single "Utils" module with two macros +called "GetNumber.get" and "GetText.show". I'll be explaining how to create and +install this extension in the next chapter. -In addition, the Tools > Macros > "Organize Macros" menu gives access to macros -according to some of the programming languages supported by Office, but Java isn't -included (see Figure 2). As a consequence, I'll use the Macro Selector dialog of -Figure 1 from now on. +In addition, the Tools > Macros > "Organize Macros" menu gives access to macros +according to some of the programming languages supported by Office, but Java isn't +included (see Figure 2). As a consequence, I'll use the Macro Selector dialog of +Figure 1 from now on. - - ![](images/48-Event_Macros-2.png) -Figure 2. The Tools > Macros > "Organize Macros" Menu. +Figure 2. The Tools > Macros > "Organize Macros" Menu. - - -## 2. Where are Macros Stored? -Aside from accessing macros through the Office GUI, it's useful to know where -they're located in the directory structure. This is especially true for user and share -macros since the easiest way to install them is to copy them into Office's designated -folders. However, extension macros are installed using the extension manager, and -document macros are added to their document using unzipping and zipping. +## 2. Where are Macros Stored? -Finding Office's macro folders can be a bit tricky, since their location varies -depending on if the macros are user or share, coded in Office Basic or another -language, and on the version of Office and OS. +Aside from accessing macros through the Office GUI, it's useful to know where +they're located in the directory structure. This is especially true for user and share +macros since the easiest way to install them is to copy them into Office's designated +folders. However, extension macros are installed using the extension manager, and +document macros are added to their document using unzipping and zipping. -The folders for share macros are probably the easiest to find – they're located inside -\share\ (e.g. C:\Program Files\LibreOffice 5\share\ on my machine). Basic -macros are in share\basic\ while macros in other languages, such as Java, are in -subfolders of share\Scripts\ (e.g. see Figure 3). +Finding Office's macro folders can be a bit tricky, since their location varies +depending on if the macros are user or share, coded in Office Basic or another +language, and on the version of Office and OS. + +The folders for share macros are probably the easiest to find – they're located inside +\share\ (e.g. C:\Program Files\LibreOffice 5\share\ on my machine). Basic +macros are in share\basic\ while macros in other languages, such as Java, are in +subfolders of share\Scripts\ (e.g. see Figure 3). - - ![](images/48-Event_Macros-3.png) -Figure 3. The Non-Basic share\Scripts\ Macros Folders. - - -In other words, Java share macros will be in: -C:\Program Files\LibreOffice 5\share\Scripts\java -User macros are stored in an 'application data' subfolder for LibreOffice. On -Windows, application data starts at the location stored in the APPDATA environment -variable, which you can print out: -echo %APPDATA% -On my work test machine this prints "C:\Users\Ad\AppData\Roaming". You need to -locate the LibreOffice subdirectory in the Roaming\ folder and then its user\ -subdirectory, which will be inside LibreOffice\5\ or perhaps LibreOffice\4\. For -instance, on one of my test machines the user\ folder is: -C:\Users\Ad\AppData\Roaming\LibreOffice\4\user -If you haven't previously downloaded or created Java user macros, then you'll have to -create a Scripts\ folder inside user\, and a java\ folder inside Scripts\. In other words, -the Java user macros will be in: -C:\Users\Ad\AppData\Roaming\LibreOffice\4\user\Scripts\java - - -## 3. Naming Macro Functions - -The "Macro Selector dialog displays a list of macros in its right hand window (e.g. - -"GetNumber.get" and "GetText.show" in Figure 1), but more detailed function names -are required when calling macros from code. - -A function name is specified as a URI of the form: -vnd.sun.star.script:MACROPARAM?language=LANGPARAM - &location=LOCPARAM -LANGPARAM identifies the macro's programming language, which may be "Basic", -"BeanShell", "Java", "JavaScript", or "Python". - -LOCPARAM is the macro category, which for Java macros can be "user", "share", or -"document". Extension macros use the label "user:uno_packages/" or -"share:uno_packages/" followed by the name of the extension file (e.g. - -"user:uno_packages/FormMacros.oxt"). - -MACROPARAM takes the form: -FOLDER.[PACKAGE_NAME.]CLASS_NAME.FUNCTION_NAME -FOLDER is the subdirectory holding the compiled Java code in the user or share java\ -folder, and the package name is optional. The class and function names are displayed -by the Macro Selector dialog (see Figure 1). For example, "GetNumber.get" refers to -the static function get() in the GetNumber class. - -Information about the URI formats for other languages is given on the "Scripting -Framework URI Specification" page at +Figure 3. The Non-Basic share\Scripts\ Macros Folders. + + +In other words, Java share macros will be in: + +``` +C:\Program Files\LibreOffice 5\share\Scripts\java +``` + +User macros are stored in an 'application data' subfolder for LibreOffice. On +Windows, application data starts at the location stored in the APPDATA environment +variable, which you can print out: + +``` +echo %APPDATA% +``` + +On my work test machine this prints "C:\Users\Ad\AppData\Roaming". You need to +locate the LibreOffice subdirectory in the Roaming\ folder and then its user\ +subdirectory, which will be inside LibreOffice\5\ or perhaps LibreOffice\4\. For +instance, on one of my test machines the user\ folder is: + +``` +C:\Users\Ad\AppData\Roaming\LibreOffice\4\user +``` + +If you haven't previously downloaded or created Java user macros, then you'll have to +create a Scripts\ folder inside user\, and a java\ folder inside Scripts\. In other words, +the Java user macros will be in: + +``` +C:\Users\Ad\AppData\Roaming\LibreOffice\4\user\Scripts\java +``` + + +## 3. Naming Macro Functions + +The "Macro Selector dialog displays a list of macros in its right hand window (e.g. + +"GetNumber.get" and "GetText.show" in Figure 1), but more detailed function names +are required when calling macros from code. + +A function name is specified as a URI of the form: + +``` +vnd.sun.star.script:MACROPARAM?language=LANGPARAM + &location=LOCPARAM +``` + +LANGPARAM identifies the macro's programming language, which may be "Basic", +"BeanShell", "Java", "JavaScript", or "Python". + +LOCPARAM is the macro category, which for Java macros can be "user", "share", or +"document". Extension macros use the label "user:uno_packages/" or +"share:uno_packages/" followed by the name of the extension file (e.g. +"user:uno_packages/FormMacros.oxt"). + +MACROPARAM takes the form: + +``` +FOLDER.[PACKAGE_NAME.]CLASS_NAME.FUNCTION_NAME +``` + +FOLDER is the subdirectory holding the compiled Java code in the user or share java\ +folder, and the package name is optional. The class and function names are displayed +by the Macro Selector dialog (see Figure 1). For example, "GetNumber.get" refers to +the static function get() in the GetNumber class. + +Information about the URI formats for other languages is given on the "Scripting +Framework URI Specification" page at https://wiki.openoffice.org/wiki/Documentation/DevGuide/Scripting/Scripting_Frame -work_URI_Specification (or use loGuide "Scripting Framework URI -Specification"). - - -Listing and Finding Macro Names -Knowing a macro's full name is essential, so I've added some helper functions to the -Macros.java utilities class. The ListMacros.java and FindMacros.java examples show -how to use them. - -ListMacros.java calls Macros.getLangScripts() to print the names of the macros -written in a given language: - -// in ListMacros.java -public static void main(String[] args) -{ - String lang = "Java"; - if (args.length != 1) { - System.out.println("Usage: run ListMacros [Java | Python | - BeanShell | Basic | JavaScript]"); - System.out.println("Using \"Java\""); - } - else - lang = args[0]; - - XComponentLoader loader = Lo.loadOffice(); - - ArrayList scriptURIs = Macros.getLangScripts(lang); - System.out.println(lang + " Macros in Office: (" + - scriptURIs.size() + ")"); - for(String scriptURI : scriptURIs) - System.out.println(" " + scriptURI); - - Lo.closeOffice(); -} // end of main() - -Macros.getLangScripts() obtains a list of all the macro names by calling -Macros.getScripts(), then filters out names based on a "language=LANGPARAM&" -substring: - -// in the Macros class -public static ArrayList getLangScripts(String lang) -{ - if (!isMacroLanguage(lang)) { - System.out.println("Not a Macro language; try \"Java\""); - return null; - } - ArrayList fScripts = new ArrayList<>(); - ArrayList scriptURIs = getScripts(); - for(String scriptURI : scriptURIs) - if (scriptURI.contains("language=" + lang + "&")) - fScripts.add(scriptURI); - return fScripts; -} // end of getLangScripts() - -Macros.getScripts() utilizes services and interfaces in the com.sun.star.script.browse -module to examine a scripts tree structure consisting of CONTAINER and SCRIPT -nodes – the CONTAINER nodes are the internal branches of the tree representing -libraries and modules, while SCRIPT nodes are the leaves holding macro information. - -Macros.getScripts() accesses the root of the MACROORGANIZER tree, and collects -script names by having getLibScripts() recursively traverse the tree, adding names to -a scripts ArrayList: - -// in the Macros class -public static ArrayList getScripts() -{ - ArrayList scripts = new ArrayList<>(); - - XComponentContext xcc = Lo.getContext(); - XBrowseNodeFactory bnf = Lo.qi(XBrowseNodeFactory.class, - xcc.getValueByName( - "/singletons/com.sun.star.script. - - browse.theBrowseNodeFactory")); - - XBrowseNode rootNode = Lo.qi(XBrowseNode.class, - bnf.createView( BrowseNodeFactoryViewTypes.MACROORGANIZER) ); - // for scripts - - XBrowseNode[] typeNodes = rootNode.getChildNodes(); - for(int i=0; i < typeNodes.length; i++) { - XBrowseNode typeNode = typeNodes[i]; - XBrowseNode[] libraryNodes = typeNode.getChildNodes(); - for(int j=0; j < libraryNodes.length; j++) - getLibScripts(libraryNodes[j], 0, - typeNode.getName(), scripts); - } - System.out.println(); - return scripts; -} // end of getScripts() - - -public static void getLibScripts(XBrowseNode browseNode, int level, - String path, ArrayList scripts) -{ - XBrowseNode[] scriptNodes = browseNode.getChildNodes(); - if ((scriptNodes.length == 0) && (level > 1)) - // not a top-level library - System.out.println("No scripts in " + path); - for(int i=0; i < scriptNodes.length; i++) { - XBrowseNode scriptNode = scriptNodes[i]; - if (scriptNode.getType() == BrowseNodeTypes.SCRIPT) { - XPropertySet props = Lo.qi(XPropertySet.class, scriptNode); - if (props != null) { - try { - scripts.add((String)props.getPropertyValue("URI")); - } - catch(com.sun.star.uno.Exception e) - { System.out.println(e); } - } - else - System.out.println("No props for " + scriptNode.getName()); - } - else if (scriptNode.getType() == BrowseNodeTypes.CONTAINER) - getLibScripts(scriptNode, level+1, path + ">" + - scriptNode.getName(), scripts); - else - System.out.println("Unknown node type"); - } -} // end of getLibScripts() - -All the Java macros can be listed by calling: -run ListMacros Java -It will print something like: - -Java Macros in Office: (6) - -vnd.sun.star.script:Utils.GetNumber.get?language=Java& - location=user:uno_packages/FormMacros.oxt - -vnd.sun.star.script:Utils.GetText.show?language=Java& - location=user:uno_packages/FormMacros.oxt - -vnd.sun.star.script:HelloWorld.org.libreoffice.example.java_scripts. - - HelloWorld.printHW?language=Java&location=share - -vnd.sun.star.script:Highlight.org.libreoffice.example.java_scripts. - - HighlightText.showForm?language=Java&location=share - -vnd.sun.star.script:MemoryUsage.org.libreoffice.example.java_scripts. - - MemoryUsage.updateMemoryUsage?language=Java&location=share - -vnd.sun.star.script:ShowEvent.ShowEvent.show?language=Java& - location=share - -Six Java macros were found: two are user macros from the FormMacros.oxt -extension, and four are share macros. The first three share macros -(HelloWorld.printHW, HighlightText.showForm, and -MemoryUsage.updateMemoryUsage) are LibreOffice examples; I'll explain the first -one: - -vnd.sun.star.script:HelloWorld.org.libreoffice.example.java_scripts. - - HelloWorld.printHW?language=Java&location=share - -The macro's location parameter is "location=share", which means that it appears in the -"LibreOffice Macros" section of the Macro Selector dialog in Figure 4. - - - +work_URI_Specification +(or use `loGuide "Scripting Framework URI Specification"`). + +#### Listing and Finding Macro Names + +Knowing a macro's full name is essential, so I've added some helper functions to the +Macros.java utilities class. The ListMacros.java and FindMacros.java examples show +how to use them. + +ListMacros.java calls Macros.getLangScripts() to print the names of the macros +written in a given language: + +```java +// in ListMacros.java +public static void main(String[] args) +{ + String lang = "Java"; + if (args.length != 1) { + System.out.println("Usage: run ListMacros [Java | Python | + BeanShell | Basic | JavaScript]"); + System.out.println("Using \"Java\""); + } + else + lang = args[0]; + + XComponentLoader loader = Lo.loadOffice(); + + ArrayList scriptURIs = Macros.getLangScripts(lang); + System.out.println(lang + " Macros in Office: (" + + scriptURIs.size() + ")"); + for(String scriptURI : scriptURIs) + System.out.println(" " + scriptURI); + + Lo.closeOffice(); +} // end of main() + +Macros.getLangScripts() obtains a list of all the macro names by calling +Macros.getScripts(), then filters out names based on a "language=LANGPARAM&" +substring: + +// in the Macros class +public static ArrayList getLangScripts(String lang) +{ + if (!isMacroLanguage(lang)) { + System.out.println("Not a Macro language; try \"Java\""); + return null; + } + ArrayList fScripts = new ArrayList<>(); + ArrayList scriptURIs = getScripts(); + for(String scriptURI : scriptURIs) + if (scriptURI.contains("language=" + lang + "&")) + fScripts.add(scriptURI); + return fScripts; +} // end of getLangScripts() +``` + +Macros.getScripts() utilizes services and interfaces in the com.sun.star.script.browse +module to examine a scripts tree structure consisting of CONTAINER and SCRIPT +nodes – the CONTAINER nodes are the internal branches of the tree representing +libraries and modules, while SCRIPT nodes are the leaves holding macro information. + +Macros.getScripts() accesses the root of the MACROORGANIZER tree, and collects +script names by having getLibScripts() recursively traverse the tree, adding names to +a scripts ArrayList: + +```java +// in the Macros class +public static ArrayList getScripts() +{ + ArrayList scripts = new ArrayList<>(); + + XComponentContext xcc = Lo.getContext(); + XBrowseNodeFactory bnf = Lo.qi(XBrowseNodeFactory.class, + xcc.getValueByName( + "/singletons/com.sun.star.script. + + browse.theBrowseNodeFactory")); + + XBrowseNode rootNode = Lo.qi(XBrowseNode.class, + bnf.createView( BrowseNodeFactoryViewTypes.MACROORGANIZER) ); + // for scripts + + XBrowseNode[] typeNodes = rootNode.getChildNodes(); + for(int i=0; i < typeNodes.length; i++) { + XBrowseNode typeNode = typeNodes[i]; + XBrowseNode[] libraryNodes = typeNode.getChildNodes(); + for(int j=0; j < libraryNodes.length; j++) + getLibScripts(libraryNodes[j], 0, + typeNode.getName(), scripts); + } + System.out.println(); + return scripts; +} // end of getScripts() + + +public static void getLibScripts(XBrowseNode browseNode, int level, + String path, ArrayList scripts) +{ + XBrowseNode[] scriptNodes = browseNode.getChildNodes(); + if ((scriptNodes.length == 0) && (level > 1)) + // not a top-level library + System.out.println("No scripts in " + path); + for(int i=0; i < scriptNodes.length; i++) { + XBrowseNode scriptNode = scriptNodes[i]; + if (scriptNode.getType() == BrowseNodeTypes.SCRIPT) { + XPropertySet props = Lo.qi(XPropertySet.class, scriptNode); + if (props != null) { + try { + scripts.add((String)props.getPropertyValue("URI")); + } + catch(com.sun.star.uno.Exception e) + { System.out.println(e); } + } + else + System.out.println("No props for " + scriptNode.getName()); + } + else if (scriptNode.getType() == BrowseNodeTypes.CONTAINER) + getLibScripts(scriptNode, level+1, path + ">" + + scriptNode.getName(), scripts); + else + System.out.println("Unknown node type"); + } +} // end of getLibScripts() +``` + +All the Java macros can be listed by calling: +run ListMacros Java +It will print something like: + +``` +Java Macros in Office: (6) + +vnd.sun.star.script:Utils.GetNumber.get?language=Java& + location=user:uno_packages/FormMacros.oxt + +vnd.sun.star.script:Utils.GetText.show?language=Java& + location=user:uno_packages/FormMacros.oxt + +vnd.sun.star.script:HelloWorld.org.libreoffice.example.java_scripts. + + HelloWorld.printHW?language=Java&location=share + +vnd.sun.star.script:Highlight.org.libreoffice.example.java_scripts. + + HighlightText.showForm?language=Java&location=share + +vnd.sun.star.script:MemoryUsage.org.libreoffice.example.java_scripts. + + MemoryUsage.updateMemoryUsage?language=Java&location=share + +vnd.sun.star.script:ShowEvent.ShowEvent.show?language=Java& + location=share +``` + +Six Java macros were found: two are user macros from the FormMacros.oxt +extension, and four are share macros. The first three share macros +(HelloWorld.printHW, HighlightText.showForm, and +MemoryUsage.updateMemoryUsage) are LibreOffice examples; I'll explain the first +one: + +```java +vnd.sun.star.script:HelloWorld.org.libreoffice.example.java_scripts. + HelloWorld.printHW?language=Java&location=share +``` + +The macro's location parameter is "location=share", which means that it appears in the +"LibreOffice Macros" section of the Macro Selector dialog in Figure 4. + ![](images/48-Event_Macros-4.png) -Figure 4. The HelloWorld.printHW Macro. +Figure 4. The HelloWorld.printHW Macro. + + +The four components of +"HelloWorld.org.libreoffice.example.java_scripts.HelloWorld.printHW" are: + +* folder: HelloWorld +* package: org.libreoffice.example.java_scripts +* class name: HelloWorld +* function name: printHW - -The four components of -"HelloWorld.org.libreoffice.example.java_scripts.HelloWorld.printHW" are: - folder: HelloWorld - package: org.libreoffice.example.java_scripts - class name: HelloWorld - function name: printHW -The HelloWorld\ folder is inside C:\Program Files\LibreOffice 5\share\Scripts\java\, -as confirmed by Figure 5. +The HelloWorld\ folder is inside C:\Program Files\LibreOffice 5\share\Scripts\java\, +as confirmed by Figure 5. - - ![](images/48-Event_Macros-5.png) -Figure 5. The HelloWorld Folder in share\Scripts\java. +Figure 5. The HelloWorld Folder in share\Scripts\java. - -Figure 5 also shows Highlight\, MemoryUsage\ and ShowEvent\ folders which hold -the other three share macros listed by ListMacros.java. -The HelloWorld\ folder contains the compiled HelloWorld class in a JAR file, and a -parcel-descriptor.xml configuration file, which I'll explain later. It also has the source -code for HelloWorld, which isn’t required by Office, but included as an example for -developers. Figure 6 shows the contents of HelloWorld\. +Figure 5 also shows Highlight\, MemoryUsage\ and ShowEvent\ folders which hold +the other three share macros listed by ListMacros.java. + +The HelloWorld\ folder contains the compiled HelloWorld class in a JAR file, and a +parcel-descriptor.xml configuration file, which I'll explain later. It also has the source +code for HelloWorld, which isn’t required by Office, but included as an example for +developers. Figure 6 shows the contents of HelloWorld\. - ![](images/48-Event_Macros-6.png) -Figure 6. The Contents of the HelloWorld\ Folder. - - -The format of the HelloWorld class in the JAR file is: - -package org.libreoffice.example.java_scripts; - - -public class HelloWorld -{ - public static void printHW(XScriptContext xSc) - { /* code not shown */ } -} - -HelloWorld implements a single printHW() function. - -My FindMacros.java example calls Macros.findScripts() with a substring, and all the -macro names containing that string are printed. For example, a search for "hello" -returns five matches: - -Matching Macros in Office: (5) - -vnd.sun.star.script:Utils.HelloWorld.hello? - language=Java&location=user:uno_packages/FormMacros.oxt - -vnd.sun.star.script:HelloWorld.helloworld.bsh? - language=BeanShell&location=share - -vnd.sun.star.script:HelloWorld.org.libreoffice.example.java_scripts. - - HelloWorld.printHW?language=Java&location=share - -vnd.sun.star.script:HelloWorld.helloworld.js? - language=JavaScript&location=share - -vnd.sun.star.script:HelloWorld.py$HelloWorldPython? - language=Python&location=share - -Four of the hello-related scripts are share macros written in BeanShell, Java, -JavaScript, and Python. - - - -## 4. Calling the "hello" Macros - -The TextMacros.java example creates a text document, and calls the four "hello" -share macros listed above by FindMacros.java. They each add a short piece of text to -the document, as displayed on lines 2-5 in Figure 7. - - - +Figure 6. The Contents of the HelloWorld\ Folder. + + +The format of the HelloWorld class in the JAR file is: + +package org.libreoffice.example.java_scripts; + + +```java +public class HelloWorld +{ + public static void printHW(XScriptContext xSc) + { /* code not shown */ } +} +``` + +HelloWorld implements a single printHW() function. + +My FindMacros.java example calls Macros.findScripts() with a substring, and all the +macro names containing that string are printed. For example, a search for "hello" +returns five matches: + +``` +Matching Macros in Office: (5) + +vnd.sun.star.script:Utils.HelloWorld.hello? + language=Java&location=user:uno_packages/FormMacros.oxt + +vnd.sun.star.script:HelloWorld.helloworld.bsh? + language=BeanShell&location=share + +vnd.sun.star.script:HelloWorld.org.libreoffice.example.java_scripts. + + HelloWorld.printHW?language=Java&location=share + +vnd.sun.star.script:HelloWorld.helloworld.js? + language=JavaScript&location=share + +vnd.sun.star.script:HelloWorld.py$HelloWorldPython? + language=Python&location=share +``` + +Four of the hello-related scripts are share macros written in BeanShell, Java, +JavaScript, and Python. + + +## 4. Calling the "hello" Macros + +The TextMacros.java example creates a text document, and calls the four "hello" +share macros listed above by FindMacros.java. They each add a short piece of text to +the document, as displayed on lines 2-5 in Figure 7. + ![](images/48-Event_Macros-7.png) -Figure 7. A Document with "hello" Macros Text. - - -TextMacros.java utilizes Macros.execute() to call the macros: - -// in TextMacros.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; - } - - if (Macros.getSecurity() == Macros.LOW) - Macros.setSecurity(Macros.MEDIUM); - - XTextCursor cursor = Write.getCursor(doc); - GUI.setVisible(doc, true); - Lo.wait(1000); // make sure the document is visible - // before sending it dispatches - - Write.appendPara(cursor, "Hello LibreOffice"); - - Macros.execute("HelloWorld.helloworld.bsh", - "BeanShell", "share"); - Write.endParagraph(cursor); - - Macros.execute("HelloWorld.py$HelloWorldPython", - "Python", "share"); - Write.endParagraph(cursor); - - Macros.execute("HelloWorld.helloworld.js", - "JavaScript", "share"); - Write.endParagraph(cursor); - - Macros.execute("HelloWorld.org.libreoffice.example. - - java_scripts.HelloWorld.printHW", - "Java", "share"); - Write.endParagraph(cursor); - Write.appendPara(cursor, "Timestamp: " + Lo.getTimeStamp()); - - Lo.waitEnter(); - Lo.closeDoc(doc); - Lo.closeOffice(); -} // end of main() - -The arguments of Macros.execute() are the MACROPARAM, LANGPARAM, and -LOCPARAM parts of the macro's URI; they're separated out to make the macro's -name a little easier to read. - -Macros.execute() uses the theMasterScriptProviderFactory service to obtain a -XScriptProviderFactory It creates a XScriptProvider which can load the named macro -as an XScript object; the script is executed by XScript.invoke(): - -// in the Macros class -public static Object execute(String macroName, String language, - String location) -{ return execute(macroName, null, language, location); } - - - -public static Object execute(String macroName, Object[] params, - String language, String location) -{ - if (!isMacroLanguage(language)) { - System.out.println("\"" + language + - "\" is not a macro language name"); - return null; - } - - try { - /* deprecated approach - XScriptProviderFactory spFactory = Lo.createInstanceMCF( - XScriptProviderFactory.class, - "com.sun.star.script.provider.MasterScriptProviderFactory"); - */ - XComponentContext xcc = Lo.getContext(); - XScriptProviderFactory spFactory = - Lo.qi(XScriptProviderFactory.class, - xcc.getValueByName( - "/singletons/com.sun.star.script.provider. - - theMasterScriptProviderFactory")); - - XScriptProvider sp = spFactory.createScriptProvider(""); - XScript xScript = sp.getScript("vnd.sun.star.script:" + - macroName + "?language=" + language + - "&location=" + location); - - // minimal inout/out parameters - short[][] outParamIndex = { { 0 } }; - Object[][] outParam = { { null } }; - return xScript.invoke(params, outParamIndex, outParam); - } - catch (Exception e) { - System.out.println("Could not execute macro " + - macroName + ": " + e); - return null; - } -} // end of execute() - -Input parameters can be passed to invoke(), but the three-argument version of -Macros.execute() sets them to null. It's also possible to have the script set output -parameters, but my call to XScript.invoke() doesn't use them either. - -There's no need to pass Macros.execute() a reference to the document. An executing -script only requires Office's current context which is retrieved by Lo.getContext(). - - -### 4.1. Macro Security Levels - -Prior to the macro calls in TextMacros.java, there's a call to Macros.getSecurity(). It -reports Office's macro execution setting, which is changed to Macros.MEDIUM if the -current value is Macros.LOW: - -// part of TextMacros.java... - -if (Macros.getSecurity() == Macros.LOW) - Macros.setSecurity(Macros.MEDIUM); - -The Macros class defines four security constants: LOW, MEDIUM, HIGH, -VERY_HIGH, which correspond to the levels used in Office's "Macro Security" -dialog shown in Figure 8. It is reached via Tools > Options > Security, and the -"Macro Security" button. - - - +Figure 7. A Document with "hello" Macros Text. + + +TextMacros.java utilizes Macros.execute() to call the macros: + +```java +// in TextMacros.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; + } + + if (Macros.getSecurity() == Macros.LOW) + Macros.setSecurity(Macros.MEDIUM); + + XTextCursor cursor = Write.getCursor(doc); + GUI.setVisible(doc, true); + Lo.wait(1000); // make sure the document is visible + // before sending it dispatches + + Write.appendPara(cursor, "Hello LibreOffice"); + + Macros.execute("HelloWorld.helloworld.bsh", + "BeanShell", "share"); + Write.endParagraph(cursor); + + Macros.execute("HelloWorld.py$HelloWorldPython", + "Python", "share"); + Write.endParagraph(cursor); + + Macros.execute("HelloWorld.helloworld.js", + "JavaScript", "share"); + Write.endParagraph(cursor); + + Macros.execute("HelloWorld.org.libreoffice.example. + + java_scripts.HelloWorld.printHW", + "Java", "share"); + Write.endParagraph(cursor); + Write.appendPara(cursor, "Timestamp: " + Lo.getTimeStamp()); + + Lo.waitEnter(); + Lo.closeDoc(doc); + Lo.closeOffice(); +} // end of main() +``` + +The arguments of Macros.execute() are the MACROPARAM, LANGPARAM, and +LOCPARAM parts of the macro's URI; they're separated out to make the macro's +name a little easier to read. + +Macros.execute() uses the theMasterScriptProviderFactory service to obtain a +XScriptProviderFactory It creates a XScriptProvider which can load the named macro +as an XScript object; the script is executed by XScript.invoke(): + +```java +// in the Macros class +public static Object execute(String macroName, String language, + String location) +{ return execute(macroName, null, language, location); } + + + +public static Object execute(String macroName, Object[] params, + String language, String location) +{ + if (!isMacroLanguage(language)) { + System.out.println("\"" + language + + "\" is not a macro language name"); + return null; + } + + try { + /* deprecated approach + XScriptProviderFactory spFactory = Lo.createInstanceMCF( + XScriptProviderFactory.class, + "com.sun.star.script.provider.MasterScriptProviderFactory"); + */ + XComponentContext xcc = Lo.getContext(); + XScriptProviderFactory spFactory = + Lo.qi(XScriptProviderFactory.class, + xcc.getValueByName( + "/singletons/com.sun.star.script.provider. + + theMasterScriptProviderFactory")); + + XScriptProvider sp = spFactory.createScriptProvider(""); + XScript xScript = sp.getScript("vnd.sun.star.script:" + + macroName + "?language=" + language + + "&location=" + location); + + // minimal inout/out parameters + short[][] outParamIndex = { { 0 } }; + Object[][] outParam = { { null } }; + return xScript.invoke(params, outParamIndex, outParam); + } + catch (Exception e) { + System.out.println("Could not execute macro " + + macroName + ": " + e); + return null; + } +} // end of execute() +``` + +Input parameters can be passed to invoke(), but the three-argument version of +Macros.execute() sets them to null. It's also possible to have the script set output +parameters, but my call to XScript.invoke() doesn't use them either. + +There's no need to pass Macros.execute() a reference to the document. An executing +script only requires Office's current context which is retrieved by Lo.getContext(). + + +### 4.1. Macro Security Levels + +Prior to the macro calls in TextMacros.java, there's a call to Macros.getSecurity(). It +reports Office's macro execution setting, which is changed to Macros.MEDIUM if the +current value is Macros.LOW: + +```java +// part of TextMacros.java... +if (Macros.getSecurity() == Macros.LOW) + Macros.setSecurity(Macros.MEDIUM); +``` + +The Macros class defines four security constants: LOW, MEDIUM, HIGH, +VERY_HIGH, which correspond to the levels used in Office's "Macro Security" +dialog shown in Figure 8. It is reached via Tools, Options, Security, and the +"Macro Security" button. + ![](images/48-Event_Macros-8.png) -Figure 8. The Macro Security Dialog. - - -The Office API has a MacroExecMode class (see lodoc MacroExecMode) which -defines many more security levels, but the four levels in the GUI seem sufficient. - -Macros.getSecurity() uses Info.getConfig() to access the -"/org.openoffice.Office.Common/Security/Scripting" configuration node, and look up -its "MacroSecurityLevel" property: - -// in the Macros class -public static int getSecurity() -{ - System.out.println("Macro security level:"); - Integer val = (Integer) Info.getConfig( - "/org.openoffice.Office.Common/Security/Scripting", - "MacroSecurityLevel"); - - // various tests of val, before returning its int value - // : - return val.intValue(); -} - -Macros.setSecurity() manipulates the same configuration node but supplies a new -value for the "MacroSecurityLevel" property: - -// in the Macros class -public static boolean setSecurity(int level) -{ - if ((level == Macros.LOW) || (level == Macros.MEDIUM) || - (level == Macros.HIGH) || (level == Macros.VERY_HIGH)) { - System.out.println("Setting macro security level to " + level); - return Info.setConfig( - "/org.openoffice.Office.Common/Security/Scripting", - "MacroSecurityLevel", Integer.valueOf(level)); - } - else { - System.out.println("Use Macros class constants: - LOW, MEDIUM, HIGH, or VERY_HIGH"); - return false; - } -} // end of setSecurity() - -### 4.2. Implementing the HelloWorld Java Macro - -The four macros called by TextMacros.java add text to the document, but how -exactly? The Java macro is called using: - -// part of TextMacros.java... - -Macros.execute("HelloWorld.org.libreoffice.example. - - java_scripts.HelloWorld.printHW", - "Java", "share"); - -This invokes the printHW() static method in the HelloWorld class in the -org.libreoffice.example.java_scripts package shown back in Figure 4. The complete -code for the class (minus some comments) is: - -// the HelloWorld class -package org.libreoffice.example.java_scripts; - -import com.sun.star.script.provider.XScriptContext; -import com.sun.star.uno.*; -import com.sun.star.text.*; - - -public class HelloWorld -{ - public static void printHW(XScriptContext xSc) - { - XTextDocument xtextdocument = - (XTextDocument) UnoRuntime.queryInterface( - XTextDocument.class, xSc.getDocument()); - XText xText = xtextdocument.getText(); - XTextRange xTextRange = xText.getEnd(); - xTextRange.setString("Hello World (in Java)"); - } // end of printHW() - -} // end of HelloWorld class - -XScript.invoke() constructs a one-argument call to printHW(), passing it a -XScriptContext object. As we'll see later, a function can be called with different -arguments depending on what event triggers the macro. - -The XScriptContext interface defines four methods which allow the current context, -the desktop, and document to be accessed (see lodoc XScriptContext). printHW() -utilizes XScriptContext.getDocument() to retrieve the document, and casts it to -XTextDocument. This allows the end of the text to be accessed with XTextRange, so -a string (""Hello World (in Java)") can be appended to it. - - - -## 5. The LibreLogo Macro - -HelloWorld isn't the most exciting of macro examples. LibreLogo is a fun (and -educational) share macro for LibreOffice (see Figure 9), which has been a standard -Office add-on since version 4.0. - - +Figure 8. The Macro Security Dialog. + + +The Office API has a MacroExecMode class (see `lodoc MacroExecMode`) which +defines many more security levels, but the four levels in the GUI seem sufficient. + +Macros.getSecurity() uses Info.getConfig() to access the +"/org.openoffice.Office.Common/Security/Scripting" configuration node, and look up +its "MacroSecurityLevel" property: + +```java +// in the Macros class +public static int getSecurity() +{ + System.out.println("Macro security level:"); + Integer val = (Integer) Info.getConfig( + "/org.openoffice.Office.Common/Security/Scripting", + "MacroSecurityLevel"); + + // various tests of val, before returning its int value + // : + return val.intValue(); +} + +Macros.setSecurity() manipulates the same configuration node but supplies a new +value for the "MacroSecurityLevel" property: + +// in the Macros class +public static boolean setSecurity(int level) +{ + if ((level == Macros.LOW) || (level == Macros.MEDIUM) || + (level == Macros.HIGH) || (level == Macros.VERY_HIGH)) { + System.out.println("Setting macro security level to " + level); + return Info.setConfig( + "/org.openoffice.Office.Common/Security/Scripting", + "MacroSecurityLevel", Integer.valueOf(level)); + } + else { + System.out.println("Use Macros class constants: + LOW, MEDIUM, HIGH, or VERY_HIGH"); + return false; + } +} // end of setSecurity() +``` + +### 4.2. Implementing the HelloWorld Java Macro + +The four macros called by TextMacros.java add text to the document, but how +exactly? The Java macro is called using: + +```java +// part of TextMacros.java... +Macros.execute("HelloWorld.org.libreoffice.example. + java_scripts.HelloWorld.printHW", + "Java", "share"); +``` + +This invokes the printHW() static method in the HelloWorld class in the +org.libreoffice.example.java_scripts package shown back in Figure 4. The complete +code for the class (minus some comments) is: + +```java +// the HelloWorld class +package org.libreoffice.example.java_scripts; + +import com.sun.star.script.provider.XScriptContext; +import com.sun.star.uno.*; +import com.sun.star.text.*; + + +public class HelloWorld +{ + public static void printHW(XScriptContext xSc) + { + XTextDocument xtextdocument = + (XTextDocument) UnoRuntime.queryInterface( + XTextDocument.class, xSc.getDocument()); + XText xText = xtextdocument.getText(); + XTextRange xTextRange = xText.getEnd(); + xTextRange.setString("Hello World (in Java)"); + } // end of printHW() + +} // end of HelloWorld class +``` + +XScript.invoke() constructs a one-argument call to printHW(), passing it a +XScriptContext object. As we'll see later, a function can be called with different +arguments depending on what event triggers the macro. + +The XScriptContext interface defines four methods which allow the current context, +the desktop, and document to be accessed (see lodoc XScriptContext). printHW() +utilizes XScriptContext.getDocument() to retrieve the document, and casts it to +XTextDocument. This allows the end of the text to be accessed with XTextRange, so +a string (""Hello World (in Java)") can be appended to it. + + +## 5. The LibreLogo Macro + +HelloWorld isn't the most exciting of macro examples. LibreLogo is a fun (and +educational) share macro for LibreOffice (see Figure 9), which has been a standard +Office add-on since version 4.0. + ![](images/48-Event_Macros-9.png) -Figure 9. The LibreLogo Macro Module. +Figure 9. The LibreLogo Macro Module. + - -Normally LibreLogo is accessed through its own View > Toolbars > Logo toolbar, -which is just as well since the Macro selector doesn't list any macros in the LibreLogo -module (see the empty area on the right of Figure 9). I had to examine the module's -Python code in \share\Scripts\python\LibreLogo\LibreLogo.py to work out -how to call it as a function. +Normally LibreLogo is accessed through its own View, Toolbars, Logo toolbar, +which is just as well since the Macro selector doesn't list any macros in the LibreLogo +module (see the empty area on the right of Figure 9). I had to examine the module's +Python code in \share\Scripts\python\LibreLogo\LibreLogo.py to work out +how to call it as a function. -My UseLogo.java example creates a text document, writes the logo program text onto -the page, followed by LibreLogo's rendering of that program, as in Figure 10. +My UseLogo.java example creates a text document, writes the logo program text onto +the page, followed by LibreLogo's rendering of that program, as in Figure 10. - - ![](images/48-Event_Macros-10.png) -Figure 10. The Page Generated by UseLogo.java. - - -UseLogo.java is: - -public class UseLogo -{ - - 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); - Lo.wait(1000); // make sure doc is visible - - XTextCursor cursor = Write.getCursor(doc); - - String logoCmds = "repeat 88 [ fd 200 left 89 ] fill"; - Write.appendPara(cursor, logoCmds); - Macros.executeLogoCmds(logoCmds); - - Lo.waitEnter(); - Lo.closeDoc(doc); - Lo.closeOffice(); - } // end of main() - -} // end of UseLogo class - -Macros.executeLogoCmds() is a small wrapper around Macros.execute() which calls -the commandline() function inside LibreLogo.py: - -// in the Macros class -public static Object executeLogoCmds(String cmdsStr) -{ - Object[] params = new String[2]; - params[0] = ""; // based on looking at commandline() - params[1] = cmdsStr; // in LibreLogo.py - return execute("LibreLogo/LibreLogo.py$commandline", - params, "Python", "share"); -} - -The params[] array is treated as two arguments by XScript.invoke() and passed to -commandline(). - -The quick-start and resources pages at librelogo.org are the places to start learning -LibreLogo (http://librelogo.org/quick-start/ and http://librelogo.org/resources/). - -There's also a help page of commands at -https://help.libreoffice.org/Writer/LibreLogo_Toolbar. - -A good source of examples, slides, and code is http://www.numbertext.org/logo/. The -best introductory talk at that site is "LibreLogo – Turtle vector graphics for -everybody" from 2012 (http://www.numbertext.org/logo/librelogo.pdf), and there are -more recent presentations which update the project. One of its aims was to create a -textbook for Hungarian primary and secondary schools (the project lead, László -Németh, is Hungarian), which is free to download from +Figure 10. The Page Generated by UseLogo.java. + + +UseLogo.java is: + +```java +public class UseLogo +{ + + 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); + Lo.wait(1000); // make sure doc is visible + + XTextCursor cursor = Write.getCursor(doc); + + String logoCmds = "repeat 88 [ fd 200 left 89 ] fill"; + Write.appendPara(cursor, logoCmds); + Macros.executeLogoCmds(logoCmds); + + Lo.waitEnter(); + Lo.closeDoc(doc); + Lo.closeOffice(); + } // end of main() + +} // end of UseLogo class +``` + +Macros.executeLogoCmds() is a small wrapper around Macros.execute() which calls +the commandline() function inside LibreLogo.py: + +```java +// in the Macros class +public static Object executeLogoCmds(String cmdsStr) +{ + Object[] params = new String[2]; + params[0] = ""; // based on looking at commandline() + params[1] = cmdsStr; // in LibreLogo.py + return execute("LibreLogo/LibreLogo.py$commandline", + params, "Python", "share"); +} +``` + +The params[] array is treated as two arguments by XScript.invoke() and passed to +commandline(). + +The quick-start and resources pages at librelogo.org are the places to start learning +LibreLogo (http://librelogo.org/quick-start/ and http://librelogo.org/resources/). +There's also a help page of commands at +https://help.libreoffice.org/Writer/LibreLogo_Toolbar. + +A good source of examples, slides, and code is http://www.numbertext.org/logo/. The +best introductory talk at that site is "LibreLogo – Turtle vector graphics for +everybody" from 2012 (http://www.numbertext.org/logo/librelogo.pdf), and there are +more recent presentations which update the project. One of its aims was to create a +textbook for Hungarian primary and secondary schools (the project lead, László +Németh, is Hungarian), which is free to download from http://szabadszoftver.kormany.hu/wp- -content/uploads/librelogo_oktatasi_segedanyag_v4.pdf. - - - -## 6. Writing a Simple Event Macro - -It's time to start coding our own macros. I'll start by writing a ShowEvent class which -reports when various events occur. It does this by implementing several versions of a -static show() method: - -// in ShowEvent.java -public class ShowEvent -{ - public static void show(XScriptContext sc, ActionEvent e) - // triggered by a action event (usually a button press) - { display("action", getSource(e)); } - - - public static void show(XScriptContext sc, TextEvent e) - // called when text changes inside a text component - { display("text", getSource(e)); } - - - public static void show(XScriptContext sc, FocusEvent e) - // called when the focus changes - { display("focus"); } - - - public static void show(XScriptContext sc, Short val) - // called from a toolbar - { display("toolbar (" + val + ")"); } - - - public static void show(XScriptContext sc, KeyEvent e) - // called because of a key - { display("key " + e.KeyChar, getSource(e)); } - - - public static void show(XScriptContext sc, MouseEvent e) - // called because of the mouse - { display("mouse"); } - - - public static void show(XScriptContext sc, - com.sun.star.document.DocumentEvent e) - // triggered by a document event - { display("document", e.EventName); } - - - public static void show(XScriptContext sc, EventObject e) - { if (e != null) - display("object", getSource(e)); - else - display("object ()"); - } - - - public static void show(XScriptContext sc) - // called from a menu or the "Run Macro..." menu - { display("menu/run"); } - - // support methods; explained shortly - // : - -} // end of ShowEvent class - -A particular show() function is called depending on the event, and more versions of -show() could easily be added since Office supports a wide range of events. Probably -the best summary of them is in the online documentation for EventObject (see lodoc -EventObject); EventObject is the superclass for most event types. - -When I first wrote ShowEvent, I mistakenly assumed that the show() function with -the EventObject argument (i.e. the second to last one in the code above) would be the -default method called when no other version of show() was suitable. That isn't the -case; instead, the single argument show() (i.e. the last function, -show(XScriptContext sc)) is executed. - -The show() methods utilize display() and showDialog(): - -// part of the ShowEvent class... - -private static void display(String msg) -{ - showDialog(msg + " event"); - // JOptionPane.showMessageDialog(null, msg + " event"); -} - - -private static void display(String msg, String info) -{ - showDialog(msg + " event: " + info); - // JOptionPane.showMessageDialog(null, msg + " event: " + info); -} - - -private static void showDialog(String msg) -{ - JDialog dlg = new JDialog((java.awt.Frame)null, "Show Event"); - dlg.getContentPane().setLayout(new GridLayout(3,1)); - dlg.add(new JLabel("")); - dlg.add(new JLabel(msg, SwingConstants.CENTER)); // centered text - dlg.pack(); - - Random r = new Random(); - dlg.setLocation(100 + r.nextInt(50), 100 + r.nextInt(50)); - - dlg.setVisible(true); - dlg.setResizable(false); - dlg.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); -} // end of showDialog() - -A modeless dialog is displayed using Java's JDialog rather than -JOptionPane.showMessageDialog(), which means that the dialog won't cause Office -to suspend until the user closes the window. - -Each dialog is placed at a slightly random position, so that multiple dialogs created by -several events will all hopefully be visible. Another issue is that the dialog's parent is -null which means that the dialog may not appear in front of the Office window. - -No use is made of my utility classes, such as Macros.java or GUI.java. Although this -makes the code longer, it also makes this example self-contained, and easier to install. - -I'll use my support classes in the extension and document examples in the next -chapter. - -Events which are usually triggered by GUI components, such as ActionEvent and -TextEvent, are passed to getSource() which attempts to find the name of the -component that sent the event: - -// part of the ShowEvent class... - -private static String getSource(EventObject event) -{ - XControl control = UnoRuntime.queryInterface( - XControl.class, event.Source); - XPropertySet xProps = UnoRuntime.queryInterface( - XPropertySet.class, control.getModel()); - try { - return (String)xProps.getPropertyValue("Name"); - } - catch (com.sun.star.uno.Exception e) { - return "Exception?"; - } - catch (com.sun.star.uno.RuntimeException e) { - return "RuntimeException?"; - } -} // end of getSource() - - -### 6.1. Installing a Simple Event Macro - -A Java macro is installed as a JAR file, so ShowEvent.class is packaged as -ShowEvent.jar after compilation: - -compile ShowEvent.java -jar cvf ShowEvent.jar ShowEvent.class - -The JAR is added to a new folder called ShowEvent\ beneath -\share\Scripts\java\, thereby making it a share macro. It's also necessary to -include a parcel-descriptor.xml file: - - - - - - -Of the three 'name' attributes in the XML file, only seems to be used -by Office. The text appears in the Macro selector dialog (e.g. see -Figure 12 below) when the macro is selected, and adds the JAR -to Office's classpath. - -The final contents of the ShowEvent\ folder are a JAR and XML file, as shown in +content/uploads/librelogo_oktatasi_segedanyag_v4.pdf. -![](images/48-Event_Macros-11.png) -Figure 11. +## 6. Writing a Simple Event Macro + +It's time to start coding our own macros. I'll start by writing a ShowEvent class which +reports when various events occur. It does this by implementing several versions of a +static show() method: + +```java +// in ShowEvent.java +public class ShowEvent +{ + public static void show(XScriptContext sc, ActionEvent e) + // triggered by a action event (usually a button press) + { display("action", getSource(e)); } + + + public static void show(XScriptContext sc, TextEvent e) + // called when text changes inside a text component + { display("text", getSource(e)); } + + + public static void show(XScriptContext sc, FocusEvent e) + // called when the focus changes + { display("focus"); } + + + public static void show(XScriptContext sc, Short val) + // called from a toolbar + { display("toolbar (" + val + ")"); } + + + public static void show(XScriptContext sc, KeyEvent e) + // called because of a key + { display("key " + e.KeyChar, getSource(e)); } + + + public static void show(XScriptContext sc, MouseEvent e) + // called because of the mouse + { display("mouse"); } + + + public static void show(XScriptContext sc, + com.sun.star.document.DocumentEvent e) + // triggered by a document event + { display("document", e.EventName); } + + + public static void show(XScriptContext sc, EventObject e) + { if (e != null) + display("object", getSource(e)); + else + display("object ()"); + } + + + public static void show(XScriptContext sc) + // called from a menu or the "Run Macro..." menu + { display("menu/run"); } + + // support methods; explained shortly + // : + +} // end of ShowEvent class +``` + +A particular show() function is called depending on the event, and more versions of +show() could easily be added since Office supports a wide range of events. Probably +the best summary of them is in the online documentation for EventObject (see +`lodoc EventObject`); EventObject is the superclass for most event types. + +When I first wrote ShowEvent, I mistakenly assumed that the show() function with +the EventObject argument (i.e. the second to last one in the code above) would be the +default method called when no other version of show() was suitable. That isn't the +case; instead, the single argument show() (i.e. the last function, +`show(XScriptContext sc)`) is executed. + +The show() methods utilize display() and showDialog(): + +```java +// part of the ShowEvent class... + +private static void display(String msg) +{ + showDialog(msg + " event"); + // JOptionPane.showMessageDialog(null, msg + " event"); +} + + +private static void display(String msg, String info) +{ + showDialog(msg + " event: " + info); + // JOptionPane.showMessageDialog(null, msg + " event: " + info); +} + + +private static void showDialog(String msg) +{ + JDialog dlg = new JDialog((java.awt.Frame)null, "Show Event"); + dlg.getContentPane().setLayout(new GridLayout(3,1)); + dlg.add(new JLabel("")); + dlg.add(new JLabel(msg, SwingConstants.CENTER)); // centered text + dlg.pack(); + + Random r = new Random(); + dlg.setLocation(100 + r.nextInt(50), 100 + r.nextInt(50)); + + dlg.setVisible(true); + dlg.setResizable(false); + dlg.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); +} // end of showDialog() +``` + +A modeless dialog is displayed using Java's JDialog rather than +JOptionPane.showMessageDialog(), which means that the dialog won't cause Office +to suspend until the user closes the window. + +Each dialog is placed at a slightly random position, so that multiple dialogs created by +several events will all hopefully be visible. Another issue is that the dialog's parent is +null which means that the dialog may not appear in front of the Office window. + +No use is made of my utility classes, such as Macros.java or GUI.java. Although this +makes the code longer, it also makes this example self-contained, and easier to install. + +I'll use my support classes in the extension and document examples in the next +chapter. + +Events which are usually triggered by GUI components, such as ActionEvent and +TextEvent, are passed to getSource() which attempts to find the name of the +component that sent the event: + +```java +// part of the ShowEvent class... + +private static String getSource(EventObject event) +{ + XControl control = UnoRuntime.queryInterface( + XControl.class, event.Source); + XPropertySet xProps = UnoRuntime.queryInterface( + XPropertySet.class, control.getModel()); + try { + return (String)xProps.getPropertyValue("Name"); + } + catch (com.sun.star.uno.Exception e) { + return "Exception?"; + } + catch (com.sun.star.uno.RuntimeException e) { + return "RuntimeException?"; + } +} // end of getSource() +``` + + +### 6.1. Installing a Simple Event Macro + +A Java macro is installed as a JAR file, so ShowEvent.class is packaged as +ShowEvent.jar after compilation: + +``` +compile ShowEvent.java +jar cvf ShowEvent.jar ShowEvent.class +``` + +The JAR is added to a new folder called ShowEvent\ beneath +\share\Scripts\java\, thereby making it a share macro. It's also necessary to +include a parcel-descriptor.xml file: + +``` + + + + +``` + +Of the three 'name' attributes in the XML file, only seems to be used +by Office. The text appears in the Macro selector dialog (e.g. see +Figure 12 below) when the macro is selected, and adds the JAR +to Office's classpath. + +The final contents of the ShowEvent\ folder are a JAR and XML file, as shown in +Figure 11. - - ![](images/48-Event_Macros-11.png) -Figure 11. The ShowEvent Share Macro Folder. +Figure 11. The ShowEvent Share Macro Folder. + - -It's a good idea to check that this new macro can be seen by Office. Office should be -restarted and the "LibreOffice Macros" subsection of the Macro Selector dialog -examined. Figure 12 shows the ShowEvent.show macro in the ShowEvent module. +It's a good idea to check that this new macro can be seen by Office. Office should be +restarted and the "LibreOffice Macros" subsection of the Macro Selector dialog +examined. Figure 12 shows the ShowEvent.show macro in the ShowEvent module. - - ![](images/48-Event_Macros-12.png) -Figure 12. Checking for the Presence of ShowEvent.show. +Figure 12. Checking for the Presence of ShowEvent.show. + + - - -### 6.2. Using ShowEvent.show in a Form +### 6.2. Using ShowEvent.show in a Form -build.odt is a simple form using textfields and buttons shown in Figure 13. +build.odt is a simple form using textfields and buttons shown in Figure 13. - - ![](images/48-Event_Macros-13.png) -Figure 13. The "build.odt" Form. +Figure 13. The "build.odt" Form. - -The event handling properties of a control are accessed by changing to form design -mode (click the "OK hand" icon, second from the left in the toolbar at the bottom of -Figure 13). Subsequently, the mouse is right-clicked over a control, such as the -FIRSTNAME textfield selected in Figure 13. The resulting pop-up menu includes a -"Control" menu item which displays the control's Properties dialog (see Figure 14). - - +The event handling properties of a control are accessed by changing to form design +mode (click the "OK hand" icon, second from the left in the toolbar at the bottom of +Figure 13). Subsequently, the mouse is right-clicked over a control, such as the +FIRSTNAME textfield selected in Figure 13. The resulting pop-up menu includes a +"Control" menu item which displays the control's Properties dialog (see Figure 14). + ![](images/48-Event_Macros-14.png) -Figure 14. The Properties Dialog for the FIRSTNAME Textfield Control. +Figure 14. The Properties Dialog for the FIRSTNAME Textfield Control. + - -The Events tab (shown in Figure 14) lists all the events associated with the control, -and a macro is attached by pressing on the right-hand "…" button for an event. An -"Assign Action" dialog appears, like the one in Figure 15. +The Events tab (shown in Figure 14) lists all the events associated with the control, +and a macro is attached by pressing on the right-hand "…" button for an event. An +"Assign Action" dialog appears, like the one in Figure 15. - - ![](images/48-Event_Macros-15.png) -Figure 15. The "Assign Action" Dialog. +Figure 15. The "Assign Action" Dialog. + - -Figure 15 shows that the user has selected the "Key pressed" event. +Figure 15 shows that the user has selected the "Key pressed" event. +Clicking on the "Macro…" button brings forth the Macro Selector dialog, and a macro +can be chosen. Upon returning to the "Assign Action" dialog, the event will list the +macro in the "Assigned Action" column. Clicking "Ok" again returns to the properties +dialog which now shows the macro name next to the "Key pressed" event, as in +Figure 16. -Clicking on the "Macro…" button brings forth the Macro Selector dialog, and a macro -can be chosen. Upon returning to the "Assign Action" dialog, the event will list the -macro in the "Assigned Action" column. Clicking "Ok" again returns to the properties -dialog which now shows the macro name next to the "Key pressed" event, as in ![](images/48-Event_Macros-16.png) -Figure 16. +Figure 16. The Properties Dialog with an Assigned Macro. - - -![](images/48-Event_Macros-16.png) +The macro's arguments ("share" and "Java") are its category and implementation +language. + +The event/macro link can be tested by returning to the default mode in the form by +clicking the "Ok hand" icon again. When the user types three letters into the +FIRSTNAME textfield (e.g. "and"), three dialogs appear, as in Figure 17. -Figure 16. The Properties Dialog with an Assigned Macro. - -The macro's arguments ("share" and "Java") are its category and implementation -language. +![](images/48-Event_Macros-17.png) -The event/macro link can be tested by returning to the default mode in the form by -clicking the "Ok hand" icon again. When the user types three letters into the -FIRSTNAME textfield (e.g. "and"), three dialogs appear, as in Figure 17. +Figure 17. Reporting Key Events. - - -![](images/48-Event_Macros-17.png) +I've rearranged the three dialogs in Figure 17 so they're all visible. They're generated +by the KeyEvent version of ShowEvent.show: + +```java +// part of the ShowEvent class... +public static void show(XScriptContext sc, KeyEvent e) +// Called from a key +{ display("key " + e.KeyChar, getSource(e)); } +``` + +As each letter ("a', 'n', and 'd') is typed, this version of show() is called. + + +## 7. Automating the Assigning of Event Macros + +The preceding section illustrates that the assignment of macros to events is +straightforward, but time-consuming, especially if you have many controls in your +form. Fortunately, macros can be assigned programmatically. + +I first used the "build.odt" form of Figure 13 back in Chapter 40 on forms; it wasn't +created by hand but generated by BuildForm.java. That program is extended in this +section with calls to Forms.assignScript() which assigns a macro to an event in a +control. + +I'll employ Forms.assignScript() so that the FIRSTNAME textfield calls +ShowEvent.show whenever its text changes, focus is lost, or a key is pressed. Also, +the six buttons at the bottom of the form (see Figure 13) will execute +ShowEvent.show when they're pressed. + +The new code is highlighted in bold in createForm(): + +```java +// part of BuildForm.java... + +private void createForm(XTextDocument doc) +{ + XPropertySet props = + Forms.addLabelledControl(doc, "FIRSTNAME", "TextField", 11); + textEvents(props); // only the FIRSTNAME textfield has a listener + + Forms.addLabelledControl(doc, "LASTNAME", "TextField", 19); + + props = Forms.addLabelledControl(doc, "AGE", "NumericField", 43); + Props.setProperty(props, "DecimalAccuracy", (short) 0); + + Forms.addLabelledControl(doc, "BIRTHDATE", "FormattedField", 51); + + + // buttons, all with listeners + props = Forms.addButton(doc, "first", "<<", 2, 63, 8); + buttonEvent(props); + + props = Forms.addButton(doc, "prev", "<", 12, 63, 8); + buttonEvent(props); + + props = Forms.addButton(doc, "next", ">", 22, 63, 8); + buttonEvent(props); + + props = Forms.addButton(doc, "last", ">>", 32, 63, 8); + buttonEvent(props); + + props = Forms.addButton(doc, "new", ">*", 42, 63, 8); + buttonEvent(props); + + props = Forms.addButton(doc, "reload", "reload", 58, 63, 13); + buttonEvent(props); +} // end of createForm() +``` + +The property set for the FIRSTNAME textfield is passed to textEvents(), which calls +Forms.assignScript() three times: + +```java +// part of BuildForm.java... + +public void textEvents(XPropertySet props) +{ + // listen for text change + Forms.assignScript(props, "XTextListener", "textChanged", + "ShowEvent.ShowEvent.show", "share"); + + // listen for focus loss + Forms.assignScript(props, "XFocusListener", "focusLost", + "ShowEvent.ShowEvent.show", "share"); + + // listen for a key press + Forms.assignScript(props, "XKeyListener", "keyPressed", + "ShowEvent.ShowEvent.show", "share"); +} // end of textEvents() + +The buttonEvent() method is a (long) one-liner: + +// part of BuildForm.java... + +public void buttonEvent(XPropertySet props) +{ + Forms.assignScript(props, "XActionListener", "actionPerformed", + "ShowEvent.ShowEvent.show", "share"); +} +``` + +It's surprisingly tricky to decide which class and method names should be passed to +Forms.assignScript() since the only information about control events are the labels in +the Properties dialog (e.g. "Key pressed" in Figure 16). It can be hard to map these +labels onto suitable class and method names, especially since Office supports so many +event types. The best source for suitable listener classes is the online documentation +for XEventListener (see lodoc XEventListener); XEventListener is the superclass +of most listeners. + +After the generated form has been saved, it should be opened in Office so its controls' +properties can be inspected. For example, the FIRSTNAME textfield's event +properties are now as in Figure 18. -Figure 17. Reporting Key Events. - - -I've rearranged the three dialogs in Figure 17 so they're all visible. They're generated -by the KeyEvent version of ShowEvent.show: - -// part of the ShowEvent class... - -public static void show(XScriptContext sc, KeyEvent e) -// Called from a key -{ display("key " + e.KeyChar, getSource(e)); } - -As each letter ("a', 'n', and 'd') is typed, this version of show() is called. - - - -## 7. Automating the Assigning of Event Macros - -The preceding section illustrates that the assignment of macros to events is -straightforward, but time-consuming, especially if you have many controls in your -form. Fortunately, macros can be assigned programmatically. - -I first used the "build.odt" form of Figure 13 back in Chapter 40 on forms; it wasn't -created by hand but generated by BuildForm.java. That program is extended in this -section with calls to Forms.assignScript() which assigns a macro to an event in a -control. - -I'll employ Forms.assignScript() so that the FIRSTNAME textfield calls -ShowEvent.show whenever its text changes, focus is lost, or a key is pressed. Also, -the six buttons at the bottom of the form (see Figure 13) will execute -ShowEvent.show when they're pressed. - -The new code is highlighted in bold in createForm(): - -// part of BuildForm.java... - -private void createForm(XTextDocument doc) -{ - XPropertySet props = - Forms.addLabelledControl(doc, "FIRSTNAME", "TextField", 11); - textEvents(props); // only the FIRSTNAME textfield has a listener - - Forms.addLabelledControl(doc, "LASTNAME", "TextField", 19); - - props = Forms.addLabelledControl(doc, "AGE", "NumericField", 43); - Props.setProperty(props, "DecimalAccuracy", (short) 0); - - Forms.addLabelledControl(doc, "BIRTHDATE", "FormattedField", 51); - - - // buttons, all with listeners - props = Forms.addButton(doc, "first", "<<", 2, 63, 8); - buttonEvent(props); - - props = Forms.addButton(doc, "prev", "<", 12, 63, 8); - buttonEvent(props); - - props = Forms.addButton(doc, "next", ">", 22, 63, 8); - buttonEvent(props); - - props = Forms.addButton(doc, "last", ">>", 32, 63, 8); - buttonEvent(props); - - props = Forms.addButton(doc, "new", ">*", 42, 63, 8); - buttonEvent(props); - - props = Forms.addButton(doc, "reload", "reload", 58, 63, 13); - buttonEvent(props); -} // end of createForm() - -The property set for the FIRSTNAME textfield is passed to textEvents(), which calls -Forms.assignScript() three times: - -// part of BuildForm.java... - -public void textEvents(XPropertySet props) -{ - // listen for text change - Forms.assignScript(props, "XTextListener", "textChanged", - "ShowEvent.ShowEvent.show", "share"); - - // listen for focus loss - Forms.assignScript(props, "XFocusListener", "focusLost", - "ShowEvent.ShowEvent.show", "share"); - - // listen for a key press - Forms.assignScript(props, "XKeyListener", "keyPressed", - "ShowEvent.ShowEvent.show", "share"); -} // end of textEvents() - -The buttonEvent() method is a (long) one-liner: - -// part of BuildForm.java... - -public void buttonEvent(XPropertySet props) -{ - Forms.assignScript(props, "XActionListener", "actionPerformed", - "ShowEvent.ShowEvent.show", "share"); -} - -It's surprisingly tricky to decide which class and method names should be passed to -Forms.assignScript() since the only information about control events are the labels in -the Properties dialog (e.g. "Key pressed" in Figure 16). It can be hard to map these -labels onto suitable class and method names, especially since Office supports so many -event types. The best source for suitable listener classes is the online documentation -for XEventListener (see lodoc XEventListener); XEventListener is the superclass -of most listeners. - -After the generated form has been saved, it should be opened in Office so its controls' -properties can be inspected. For example, the FIRSTNAME textfield's event -properties are now as in Figure 18. - - - ![](images/48-Event_Macros-18.png) -Figure 18. The Properties Dialog for the FIRSTNAME textfield. - - -Figure 18 confirms that the class/method names used in textEvents() are correct. - -Forms.assignScript() utilizes the XEventAttacherManager interface which offers -multiple methods for attaching and removing scripts from events. The code for -Forms.assignScript(): - -// in the Forms class -public static void assignScript(XPropertySet controlProps, - String interfaceName, String methodName, - String scriptName, String loc) -{ - try { - XChild propsChild = Lo.qi(XChild.class, controlProps); - XIndexContainer parentForm = - Lo.qi(XIndexContainer.class, propsChild.getParent()); - int pos = -1; - for (int i = 0; i < parentForm.getCount(); i++) { - XPropertySet child = Lo.qi(XPropertySet.class, - parentForm.getByIndex(i) ); - if (UnoRuntime.areSame(child, controlProps)) { - pos = i; - break; - } - } - - if (pos == -1) - System.out.println("Could not find contol's pos in form"); - else { - XEventAttacherManager manager = - Lo.qi(XEventAttacherManager.class, parentForm); - manager.registerScriptEvent(pos, - new ScriptEventDescriptor(interfaceName, methodName, - "", "Script", - "vnd.sun.star.script:"+scriptName + - "?language=Java&location=" + loc)); - } - } - catch( com.sun.star.uno.Exception e ) - { System.out.println(e); } -} // end of assignScript() - -The first half of assignScript() calculates the control's index position inside the -parent's form. An index is used by most XEventAttacherManager methods to refer to -a control. - -The last part of assignScript() uses XEventAttacherManager.registerScriptEvent() to -attach the script to the control's event. A ScriptEventDescriptor object is constructed -from five arguments: the listener's class name, the method to be called in the listener, -any extra data for the method (which is "" here), the script type (which can be "Script" -or "Basic"), and the macro's full name. - -If you're unsure about the full name, then use my ListMacros.java or FindMacros.java -to display macro details. For example: -run FindMacros ShowEvent -produces: -Matching Macros in Office: (1) - vnd.sun.star.script:ShowEvent.ShowEvent.show? - language=Java&location=share - -"ShowEvent.ShowEvent.show" and "share" are passed to Forms.assignScript() as its -last two arguments: - -// part of BuildForm.java... - -Forms.assignScript(props, "XTextListener", "textChanged", - "ShowEvent.ShowEvent.show", "share"); - - +Figure 18. The Properties Dialog for the FIRSTNAME textfield. + + +Figure 18 confirms that the class/method names used in textEvents() are correct. + +Forms.assignScript() utilizes the XEventAttacherManager interface which offers +multiple methods for attaching and removing scripts from events. The code for +Forms.assignScript(): + +```java +// in the Forms class +public static void assignScript(XPropertySet controlProps, + String interfaceName, String methodName, + String scriptName, String loc) +{ + try { + XChild propsChild = Lo.qi(XChild.class, controlProps); + XIndexContainer parentForm = + Lo.qi(XIndexContainer.class, propsChild.getParent()); + int pos = -1; + for (int i = 0; i < parentForm.getCount(); i++) { + XPropertySet child = Lo.qi(XPropertySet.class, + parentForm.getByIndex(i) ); + if (UnoRuntime.areSame(child, controlProps)) { + pos = i; + break; + } + } + + if (pos == -1) + System.out.println("Could not find contol's pos in form"); + else { + XEventAttacherManager manager = + Lo.qi(XEventAttacherManager.class, parentForm); + manager.registerScriptEvent(pos, + new ScriptEventDescriptor(interfaceName, methodName, + "", "Script", + "vnd.sun.star.script:"+scriptName + + "?language=Java&location=" + loc)); + } + } + catch( com.sun.star.uno.Exception e ) + { System.out.println(e); } +} // end of assignScript() +``` + +The first half of assignScript() calculates the control's index position inside the +parent's form. An index is used by most XEventAttacherManager methods to refer to +a control. + +The last part of assignScript() uses XEventAttacherManager.registerScriptEvent() to +attach the script to the control's event. A ScriptEventDescriptor object is constructed +from five arguments: the listener's class name, the method to be called in the listener, +any extra data for the method (which is "" here), the script type (which can be "Script" +or "Basic"), and the macro's full name. + +If you're unsure about the full name, then use my ListMacros.java or FindMacros.java +to display macro details. For example: + +``` +run FindMacros ShowEvent +``` + +produces: + +```java +Matching Macros in Office: (1) + vnd.sun.star.script:ShowEvent.ShowEvent.show? + language=Java&location=share +``` + +"ShowEvent.ShowEvent.show" and "share" are passed to Forms.assignScript() as its +last two arguments: + +```java +// part of BuildForm.java... +Forms.assignScript(props, "XTextListener", "textChanged", + "ShowEvent.ShowEvent.show", "share");``` diff --git a/docs/49-Ext_Doc_Event_Macros.md b/docs/49-Ext_Doc_Event_Macros.md index fe7634c..690dcc9 100644 --- a/docs/49-Ext_Doc_Event_Macros.md +++ b/docs/49-Ext_Doc_Event_Macros.md @@ -1,1178 +1,1208 @@ -# Chapter 49. Extension and Document Event Macros +# Chapter 49. Extension and Document Event Macros !!! note "Topics" - Form Macros as - an Extension; Loading - an XML Dialog; - Building a Dialog at - Runtime; Storing - Macros inside the - (Form) Document; - Attaching Macros to - Other Events; Executing - Macros from the - Command Line - - Example folders: - "EvMacro Tests" and - "Utils" - - -The previous chapter introduced event macros, and described -how a simple macro could be installed by copying it to a -specific Office directory. This chapter looks at two other -ways to package macros: as extensions, and by attaching -them to documents. - - - -## 1. Form Macros as an Extension - -Adding a macro to Office by installing it as an extension means that the user doesn't -need to grapple with Office folders and copying files since Office's extension -manager does it for them. This section also uses more complex macros than the ones -in the previous chapter, namely ones that utilize their own dialogs and employ my -utility classes. - -The FormMacros.oxt extension is created by zipping up a FormMacros\ folder, which -is listed below: - -FormMacros - | description.xml - | form.png - | license.txt - | package-description.txt - | - +---dialogLibrary - | NumExtractor.xdl - | - +---META-INF - | manifest.xml - | - \---Utils - GetNumber.class - GetText.class - NumActionListener.class - parcel-descriptor.xml - Utils.jar - -The macros are in the Utils\ folder, and are utilized by the form stored in -FormMacrosTest.odt in the ways shown in Figure 1. - - + Form Macros as + an Extension; Loading + an XML Dialog; + Building a Dialog at + Runtime; Storing + Macros inside the + (Form) Document; + Attaching Macros to + Other Events; Executing + Macros from the + Command Line + + Example folders: + "EvMacro Tests" and + "Utils" + + +The previous chapter introduced event macros, and described +how a simple macro could be installed by copying it to a +specific Office directory. This chapter looks at two other +ways to package macros: as extensions, and by attaching +them to documents. + + +## 1. Form Macros as an Extension + +Adding a macro to Office by installing it as an extension means that the user doesn't +need to grapple with Office folders and copying files since Office's extension +manager does it for them. This section also uses more complex macros than the ones +in the previous chapter, namely ones that utilize their own dialogs and employ my +utility classes. + +The FormMacros.oxt extension is created by zipping up a FormMacros\ folder, which +is listed below: + +``` +FormMacros + | description.xml + | form.png + | license.txt + | package-description.txt + | + +---dialogLibrary + | NumExtractor.xdl + | + +---META-INF + | manifest.xml + | + \---Utils + GetNumber.class + GetText.class + NumActionListener.class + parcel-descriptor.xml + Utils.jar +``` + +The macros are in the Utils\ folder, and are utilized by the form stored in +FormMacrosTest.odt in the ways shown in Figure 1. + ![](images/49-Ext_Doc_Event_Macros-1.png) -Figure 1. A Form and its Macros. +Figure 1. A Form and its Macros. + - -Figure 1 illustrates the actions of two macros. GetText.show is attached to the form's -button, and display the text in the adjacent textfield when the button is pressed. The -GetNumber.get macro is attached to the second textfield, and is activated when - is pressed. A dialog, created by the macro, offers the user a choice of -replacing the text by a number or clearing the textfield. +Figure 1 illustrates the actions of two macros. GetText.show is attached to the form's +button, and display the text in the adjacent textfield when the button is pressed. The +GetNumber.get macro is attached to the second textfield, and is activated when + is pressed. A dialog, created by the macro, offers the user a choice of +replacing the text by a number or clearing the textfield. -To simplify the example a little, FormMacrosTest.odt was created by hand rather than -programmatically. Also, I'm going to attach the extension's macros to the button and -textfield using Office's GUI, as explained shortly. +To simplify the example a little, FormMacrosTest.odt was created by hand rather than +programmatically. Also, I'm going to attach the extension's macros to the button and +textfield using Office's GUI, as explained shortly. -I won’t explain all the contents of the FormMacros\ folder, because most of them -were covered in early chapters, particularly in Chapter 45. For example, I won't be -describing how I drew the "Number Extractor" dialog stored in -dialogLibrary\NumExtractor.xdl since that technique was covered in the last chapter, -in section 5. +I won’t explain all the contents of the FormMacros\ folder, because most of them +were covered in early chapters, particularly in Chapter 45. For example, I won't be +describing how I drew the "Number Extractor" dialog stored in +dialogLibrary\NumExtractor.xdl since that technique was covered in the last chapter, +in section 5. The new elements of FormMacros\ are the contents of manifest.xml and parcel- -descriptor.xml. - -manifest.xml states the location of the macros inside the extension, which in my case -are in the Utils\ subdirectory. This is encoded as: - - - - - - - - -parcel-descriptor.xml give details about the two macros, GetText.show and -GetNumber.get, which are used by the Macro Selector dialog: - - - - - - - - - - -There's an important difference between this parcel-descriptor.xml and the one given -for ShowEvent.show in the previous chapter, related to the new macros' classpaths: - -and - -The classpath line for ShowEvent.show was: - - -The difference, which took many hours of experimentation to find, is due to the -inclusion of my utilities classes as a JAR in Utils\. - -When Utils.jar is in the extension, Office is unable to recognize GetText.show and -GetNumber.get packaged as JARs (e.g. as GetText.jar and GetNumber.jar). It appears -that Office can only add a single JAR to its classpath with . - -Instead I've stored the macros as .class files in Utils\. In addition, it's necessary to -include "." in the classpath so NumActionListener.class can be found at runtime. - -The three Java files (GetText.java, GetNumber.java, and NumActionListener.java) -are compiled and manually copied into FormMacros\Utils\. Then the -installMacros.bat batch script zips up the folder as an OXT file, and calls unopkg.exe -to install it. The extension manager displays the "Form Macros" as in Figure 2. - - - +descriptor.xml. + +manifest.xml states the location of the macros inside the extension, which in my case +are in the Utils\ subdirectory. This is encoded as: + +``` + + + + + + +``` + +parcel-descriptor.xml give details about the two macros, GetText.show and +GetNumber.get, which are used by the Macro Selector dialog: + +``` + + + + + + + + +``` + +There's an important difference between this parcel-descriptor.xml and the one given +for ShowEvent.show in the previous chapter, related to the new macros' classpaths: + +```java + +``` + +and + +```java + +``` + +The classpath line for ShowEvent.show was: + +```java + +``` + +The difference, which took many hours of experimentation to find, is due to the +inclusion of my utilities classes as a JAR in Utils\. + +When Utils.jar is in the extension, Office is unable to recognize GetText.show and +GetNumber.get packaged as JARs (e.g. as GetText.jar and GetNumber.jar). It appears +that Office can only add a single JAR to its classpath with . + +Instead I've stored the macros as .class files in Utils\. In addition, it's necessary to +include "." in the classpath so NumActionListener.class can be found at runtime. + +The three Java files (GetText.java, GetNumber.java, and NumActionListener.java) +are compiled and manually copied into FormMacros\Utils\. Then the +installMacros.bat batch script zips up the folder as an OXT file, and calls unopkg.exe +to install it. The extension manager displays the "Form Macros" as in Figure 2. + ![](images/49-Ext_Doc_Event_Macros-2.png) -Figure 2. The Form Macros Extension. +Figure 2. The Form Macros Extension. + - -By default, extension macros are installed as user macros, as can be seen in the Macro -Selector dialog in Figure 3. +By default, extension macros are installed as user macros, as can be seen in the Macro +Selector dialog in Figure 3. - - ![](images/49-Ext_Doc_Event_Macros-3.png) -Figure 3. The User Extension Macros in the Macro Selector. - - -The Utils\ subdirectory of the extension has become a module called Utils. - -The full names of the extension macros can be obtained using my ListMacros.java or -FindMacros.java examples. For example: -run FindMacros Utils -produces: - -Matching Macros in Office: (2) - vnd.sun.star.script:Utils.GetNumber.get? - language=Java&location=user:uno_packages/FormMacros.oxt - - vnd.sun.star.script:Utils.GetText.show? - language=Java&location=user:uno_packages/FormMacros.oxt - -### 1.1. The GetText.show Macro - -GetText.show is triggered when a button is pressed; it displays the text currently in -the "Text Box 1" textfield inside a message box (see Figure 1). The code for the class -is: - -// in GetText.java -public class GetText -{ - private static final String LOG_FNM = "c://macrosInfo.txt"; - // log file for storing debugging output - - - public static void show(XScriptContext sc, ActionEvent e) - // Called when a button pressed - { - String controlName = Forms.getEventSourceName(e); - - FileIO.appendTo(LOG_FNM, "\"" + controlName + - "\" sent ActionEvent at " + Lo.getTimeStamp()); - - XComponent doc = Lo.scriptInitialize(sc); - if (doc == null) - return; - - // for debugging - Console console = new Console(); - console.setVisible(true); - - Forms.listForms(doc); - - XControlModel textBox = Forms.getControlModel(doc, "Text Box 1"); - // Props.showObjProps("TextBox", textBox); - - String textContents = (String)Props.getProperty(textBox, "Text"); - GUI.showXMessageBox("Textbox text", textContents); - - console.setVisible(false); - console.closeDown(); - } // end of show() for ActionEvent - -} // end of GetText class - -The class implements a single show() method suitable for responding to -ActionEvents. - -Forms.getEventSourceName() returns the name of the control that sent the event and -FileIO.appendTo() writes the details to a log file. This log is useful for debugging, -and can be removed when the macro is finished. - -Lo.scriptInitialize() uses the macro's XScriptContext object to initialize globals -maintained by my Lo class: - -// in the Lo class -// globals -private static XComponentContext xcc = null; -private static XDesktop xDesktop = null; -private static XMultiComponentFactory mcFactory = null; -private static XMultiServiceFactory msFactory = null; - - -public static XComponent scriptInitialize(XScriptContext sc) -{ - if (sc == null) { - System.out.println("Script Context is null"); - return null; - } - - xcc = sc.getComponentContext(); - if (xcc == null) { - System.out.println("Could not access component context"); - return null; - } - mcFactory = xcc.getServiceManager(); - if (mcFactory == null) { - System.out.println("Office Service Manager is unavailable"); - return null; - } - - xDesktop = sc.getDesktop(); - if (xDesktop == null) { - System.out.println("Could not access desktop"); - return null; - } - - XComponent doc = xDesktop.getCurrentComponent(); - if (doc == null) { - System.out.println("Could not access document"); - return null; - } - - msFactory = Lo.qi(XMultiServiceFactory.class, doc); - return doc; -} // end of scriptInitialize() - -The log approach is fine for simple debugging, but it's also possible to create a -Console window for displaying more complex textual output. One useful thing to -report are the form's control details, by calling Forms.listForms(). The Console -window looks as in Figure 4. - - - +Figure 3. The User Extension Macros in the Macro Selector. + + +The Utils\ subdirectory of the extension has become a module called Utils. + +The full names of the extension macros can be obtained using my ListMacros.java or +FindMacros.java examples. For example: + +``` +run FindMacros Utils +``` + +produces: + +``` +Matching Macros in Office: (2) + vnd.sun.star.script:Utils.GetNumber.get? + language=Java&location=user:uno_packages/FormMacros.oxt + + vnd.sun.star.script:Utils.GetText.show? + language=Java&location=user:uno_packages/FormMacros.oxt +``` + +### 1.1. The GetText.show Macro + +GetText.show is triggered when a button is pressed; it displays the text currently in +the "Text Box 1" textfield inside a message box (see Figure 1). The code for the class +is: + +```java +// in GetText.java +public class GetText +{ + private static final String LOG_FNM = "c://macrosInfo.txt"; + // log file for storing debugging output + + + public static void show(XScriptContext sc, ActionEvent e) + // Called when a button pressed + { + String controlName = Forms.getEventSourceName(e); + + FileIO.appendTo(LOG_FNM, "\"" + controlName + + "\" sent ActionEvent at " + Lo.getTimeStamp()); + + XComponent doc = Lo.scriptInitialize(sc); + if (doc == null) + return; + + // for debugging + Console console = new Console(); + console.setVisible(true); + + Forms.listForms(doc); + + XControlModel textBox = Forms.getControlModel(doc, "Text Box 1"); + // Props.showObjProps("TextBox", textBox); + + String textContents = (String)Props.getProperty(textBox, "Text"); + GUI.showXMessageBox("Textbox text", textContents); + + console.setVisible(false); + console.closeDown(); + } // end of show() for ActionEvent + +} // end of GetText class +``` + +The class implements a single show() method suitable for responding to +ActionEvents. + +Forms.getEventSourceName() returns the name of the control that sent the event and +FileIO.appendTo() writes the details to a log file. This log is useful for debugging, +and can be removed when the macro is finished. + +Lo.scriptInitialize() uses the macro's XScriptContext object to initialize globals +maintained by my Lo class: + +```java +// in the Lo class +// globals +private static XComponentContext xcc = null; +private static XDesktop xDesktop = null; +private static XMultiComponentFactory mcFactory = null; +private static XMultiServiceFactory msFactory = null; + + +public static XComponent scriptInitialize(XScriptContext sc) +{ + if (sc == null) { + System.out.println("Script Context is null"); + return null; + } + + xcc = sc.getComponentContext(); + if (xcc == null) { + System.out.println("Could not access component context"); + return null; + } + mcFactory = xcc.getServiceManager(); + if (mcFactory == null) { + System.out.println("Office Service Manager is unavailable"); + return null; + } + + xDesktop = sc.getDesktop(); + if (xDesktop == null) { + System.out.println("Could not access desktop"); + return null; + } + + XComponent doc = xDesktop.getCurrentComponent(); + if (doc == null) { + System.out.println("Could not access document"); + return null; + } + + msFactory = Lo.qi(XMultiServiceFactory.class, doc); + return doc; +} // end of scriptInitialize() +``` + +The log approach is fine for simple debugging, but it's also possible to create a +Console window for displaying more complex textual output. One useful thing to +report are the form's control details, by calling Forms.listForms(). The Console +window looks as in Figure 4. + ![](images/49-Ext_Doc_Event_Macros-4.png) -Figure 4. The Console Window Output for GetText.show. +Figure 4. The Console Window Output for GetText.show. - -The control names in Figure 4 include "Text Box 1", which is used by -Forms.getControlModel() to reference the textfield control: - -// part of show() in GetText.java... -XControlModel textBox = Forms.getControlModel(doc, "Text Box 1"); -String textContents = (String)Props.getProperty(textBox, "Text"); -GUI.showXMessageBox("Textbox text", textContents); - -### 1.2. The GetNumber.get Macro +The control names in Figure 4 include "Text Box 1", which is used by +Forms.getControlModel() to reference the textfield control: -The second textfield in Figure 1 is called "AgeText". Its listener processes the text -when the user types , which is implemented using a keypress listener, as -shown in Figure 5. +```java +// part of show() in GetText.java... +XControlModel textBox = Forms.getControlModel(doc, "Text Box 1"); +String textContents = (String)Props.getProperty(textBox, "Text"); +GUI.showXMessageBox("Textbox text", textContents); +``` + +### 1.2. The GetNumber.get Macro + +The second textfield in Figure 1 is called "AgeText". Its listener processes the text +when the user types , which is implemented using a keypress listener, as +shown in Figure 5. - - ![](images/49-Ext_Doc_Event_Macros-5.png) -Figure 5. The "AgeText" Properties Dialog. - - -GetNumber.get is woken up by every keypress, which should be ignored until the key -is . Then the text is read from the "AgeText" textfield, and the extracted -number displayed in the "Number Extractor" dialog (shown in Figure 1). This dialog -has its own listener (a NumActionListener object) attached to the "Ok" and "Cancel" -buttons which updates the "AgeText" textfield depending on which is pressed. - -The GetNumber class defines a single static get() method, suitable for receiving -KeyEvents: - -// in GetNumber.java -public static void get(XScriptContext sc, KeyEvent e) -{ - String controlName = Forms.getEventSourceName(e); - if (e.KeyCode == Key.RETURN) { // return typed - XComponent doc = Lo.scriptInitialize(sc); - if (doc != null) { - XControlModel cModel = - Forms.getControlModel(doc, controlName); - if (Forms.isTextField(cModel)) - loadXDLDialog(cModel); - // runtimeDialog(cModel); - } - } -} // end of get() - -When the key is pressed, Forms.getControlModel() searches the form -for the control that sent the event (i.e. the "AgeText" textfield). - -If the control is a textfield then the "Number Extractor" dialog is displayed in one of -two ways – either loadXDLDialog() loads the dialog's XML from -dialogLibrary\NumExtractor.xdl inside the extension, or runtimeDialog() creates the -dialog dynamically by calling methods in my Dialogs utility class. I'll look at each -approach in the next two sections. - - -### 1.3. Loading an XML Dialog - -Chapter 46 on Add-ons describes how to use Office's dialog editor to create a dialog -and export it as an XDL file. The same steps were used to create NumExtractor.xdl, -which is shown in Figure 6. - - +Figure 5. The "AgeText" Properties Dialog. + + +GetNumber.get is woken up by every keypress, which should be ignored until the key +is . Then the text is read from the "AgeText" textfield, and the extracted +number displayed in the "Number Extractor" dialog (shown in Figure 1). This dialog +has its own listener (a NumActionListener object) attached to the "Ok" and "Cancel" +buttons which updates the "AgeText" textfield depending on which is pressed. + +The GetNumber class defines a single static get() method, suitable for receiving +KeyEvents: + +```java +// in GetNumber.java +public static void get(XScriptContext sc, KeyEvent e) +{ + String controlName = Forms.getEventSourceName(e); + if (e.KeyCode == Key.RETURN) { // return typed + XComponent doc = Lo.scriptInitialize(sc); + if (doc != null) { + XControlModel cModel = + Forms.getControlModel(doc, controlName); + if (Forms.isTextField(cModel)) + loadXDLDialog(cModel); + // runtimeDialog(cModel); + } + } +} // end of get() +``` + +When the key is pressed, Forms.getControlModel() searches the form +for the control that sent the event (i.e. the "AgeText" textfield). + +If the control is a textfield then the "Number Extractor" dialog is displayed in one of +two ways – either loadXDLDialog() loads the dialog's XML from +dialogLibrary\NumExtractor.xdl inside the extension, or runtimeDialog() creates the +dialog dynamically by calling methods in my Dialogs utility class. I'll look at each +approach in the next two sections. + + +### 1.3. Loading an XML Dialog + +Chapter 46 on Add-ons describes how to use Office's dialog editor to create a dialog +and export it as an XDL file. The same steps were used to create NumExtractor.xdl, +which is shown in Figure 6. ![](images/49-Ext_Doc_Event_Macros-6.png) -Figure 6. The NumExtractor.xdl Dialog. - - -The XML contents of NumExtractor.xdl are: - - - - - - - - - - - - - - - - - - -The most important things to note for later are the control IDs; in particular, the -textfield and button names: "TextField1", "CommandButton1", and -"CommandButton2". - -loadXDLDialog() utilizes Dialogs.loadAddonDialog() described in Chapter 46 to -obtain a reference to the dialog. It's initialized by initDialog() and made live by -XDialog.execute(): - -// in GetNumber.java -private static void loadXDLDialog(XControlModel cModel) -{ - XDialog dialog = Dialogs.loadAddonDialog( - "org.openoffice.formmacros", - "dialogLibrary/NumExtractor.xdl"); - if (dialog == null) - return; - initDialog(dialog, cModel); - dialog.execute(); -} // end of loadXDLDialog() - -initDialog() fills the dialog's textfield with numerical data extracted from the form's -"AgeText" field, and attaches a NumActionListener to its buttons: - -// part of GetNumber.java -private static void initDialog(XDialog dialog, - XControlModel cModel) -{ - XControl dialogCtrl = Dialogs.getDialogControl(dialog); - if (dialogCtrl == null) - return; - - int val = extractDigits( - (String)Props.getProperty(cModel, "Text")); - - // store extracted number in dialog's read-only text field; - // the names of the controls are hardwired - XTextComponent numFieldTB = Lo.qi(XTextComponent.class, - Dialogs.findControl(dialogCtrl, "TextField1")); - numFieldTB.setText(""+val); - - // assign same listener to both buttons - NumActionListener naListener = - new NumActionListener(dialog, cModel, val); - - XButton okButton = Lo.qi(XButton.class, - Dialogs.findControl(dialogCtrl, "CommandButton1")); - okButton.addActionListener(naListener); - - XButton cancelButton = Lo.qi(XButton.class, - Dialogs.findControl(dialogCtrl, "CommandButton2")); - cancelButton.addActionListener(naListener); -} // end of initDialog() - -Dialogs.findControl() finds the dialog's textfield and buttons using the IDs that we -saw in NumExtractor.xdl. - -The NumActionListener class is included in the extension's Utils\ folder. It's a -standard button listener, but uses Office's XActionListener and ActionEvent not the -Java classes with similar names: - -// in NumActionListener.java -public class NumActionListener implements XActionListener -{ - private XDialog dialog; - private XControlModel cModel; - private int val; - - - public NumActionListener(XDialog dialog, - XControlModel cModel, int val) - { this.dialog = dialog; - this.cModel = cModel; - this.val = val; - } // end of NumActionListener() - - - public void actionPerformed(ActionEvent e) - { - String buttonName = Dialogs.getEventSourceName(e); - System.out.println("Event received from : " + buttonName); - - if (buttonName.equals("CommandButton1")) // "OK" button - Props.setProperty(cModel, "Text", "" + val); - // put val in text field - else if (buttonName.equals("CommandButton2")) // "Cancel" - Props.setProperty(cModel, "Text", ""); // clear text field - - dialog.endExecute(); - } // end of actionPerformed() - - - public void disposing(EventObject e) { } - -} // end of NumActionListener class - -The number extracted from the "AgeText" textfield is passed to the listener via its -constructor, along with a reference to the control. If the user presses "Ok" then the -number is written into the textfield, otherwise an empty string is used to clear its -contents. - - -### 1.4. Building a Dialog at Runtime - -Office's dialog editor is the easiest way to construct a dialog, but there may be -situations where you want to create a simple dialog at run time. The commented-out -call to runtimeDialog() in GetNumber.get shows how to do this using my Dialog class -functions: - -// part of GetNumber.java -private static void runtimeDialog(XControlModel cModel) -{ - XControl dialogCtrl = makeDialogControl(); - if (dialogCtrl == null) - return; - - XDialog dialog = Dialogs.createDialogPeer(dialogCtrl); - if (dialog == null) - return; - - initDialog(dialog, cModel); - dialog.execute(); -} // end of runtimeDialog() - -The dialog generated by makeDialogControl() (see Figure 7) is very similar to the one -defined in NumExtractor.xdl. - - - +Figure 6. The NumExtractor.xdl Dialog. + + +The XML contents of NumExtractor.xdl are: + +```java + + + + + + + + + + + + + + + + +``` + +The most important things to note for later are the control IDs; in particular, the +textfield and button names: "TextField1", "CommandButton1", and +"CommandButton2". + +loadXDLDialog() utilizes Dialogs.loadAddonDialog() described in Chapter 46 to +obtain a reference to the dialog. It's initialized by initDialog() and made live by +XDialog.execute(): + +```java +// in GetNumber.java +private static void loadXDLDialog(XControlModel cModel) +{ + XDialog dialog = Dialogs.loadAddonDialog( + "org.openoffice.formmacros", + "dialogLibrary/NumExtractor.xdl"); + if (dialog == null) + return; + initDialog(dialog, cModel); + dialog.execute(); +} // end of loadXDLDialog() +``` + +initDialog() fills the dialog's textfield with numerical data extracted from the form's +"AgeText" field, and attaches a NumActionListener to its buttons: + +```java +// part of GetNumber.java +private static void initDialog(XDialog dialog, + XControlModel cModel) +{ + XControl dialogCtrl = Dialogs.getDialogControl(dialog); + if (dialogCtrl == null) + return; + + int val = extractDigits( + (String)Props.getProperty(cModel, "Text")); + + // store extracted number in dialog's read-only text field; + // the names of the controls are hardwired + XTextComponent numFieldTB = Lo.qi(XTextComponent.class, + Dialogs.findControl(dialogCtrl, "TextField1")); + numFieldTB.setText(""+val); + + // assign same listener to both buttons + NumActionListener naListener = + new NumActionListener(dialog, cModel, val); + + XButton okButton = Lo.qi(XButton.class, + Dialogs.findControl(dialogCtrl, "CommandButton1")); + okButton.addActionListener(naListener); + + XButton cancelButton = Lo.qi(XButton.class, + Dialogs.findControl(dialogCtrl, "CommandButton2")); + cancelButton.addActionListener(naListener); +} // end of initDialog() +``` + +Dialogs.findControl() finds the dialog's textfield and buttons using the IDs that we +saw in NumExtractor.xdl. + +The NumActionListener class is included in the extension's Utils\ folder. It's a +standard button listener, but uses Office's XActionListener and ActionEvent not the +Java classes with similar names: + +```java +// in NumActionListener.java +public class NumActionListener implements XActionListener +{ + private XDialog dialog; + private XControlModel cModel; + private int val; + + + public NumActionListener(XDialog dialog, + XControlModel cModel, int val) + { this.dialog = dialog; + this.cModel = cModel; + this.val = val; + } // end of NumActionListener() + + + public void actionPerformed(ActionEvent e) + { + String buttonName = Dialogs.getEventSourceName(e); + System.out.println("Event received from : " + buttonName); + + if (buttonName.equals("CommandButton1")) // "OK" button + Props.setProperty(cModel, "Text", "" + val); + // put val in text field + else if (buttonName.equals("CommandButton2")) // "Cancel" + Props.setProperty(cModel, "Text", ""); // clear text field + + dialog.endExecute(); + } // end of actionPerformed() + + + public void disposing(EventObject e) { } + +} // end of NumActionListener class +``` + +The number extracted from the "AgeText" textfield is passed to the listener via its +constructor, along with a reference to the control. If the user presses "Ok" then the +number is written into the textfield, otherwise an empty string is used to clear its +contents. + + +### 1.4. Building a Dialog at Runtime + +Office's dialog editor is the easiest way to construct a dialog, but there may be +situations where you want to create a simple dialog at run time. The commented-out +call to runtimeDialog() in GetNumber.get shows how to do this using my Dialog class +functions: + +```java +// part of GetNumber.java +private static void runtimeDialog(XControlModel cModel) +{ + XControl dialogCtrl = makeDialogControl(); + if (dialogCtrl == null) + return; + + XDialog dialog = Dialogs.createDialogPeer(dialogCtrl); + if (dialog == null) + return; + + initDialog(dialog, cModel); + dialog.execute(); +} // end of runtimeDialog() +``` + +The dialog generated by makeDialogControl() (see Figure 7) is very similar to the one +defined in NumExtractor.xdl. + ![](images/49-Ext_Doc_Event_Macros-7.png) -Figure 7. The Rendering of the Runtime Dialog. - - -makeDialogControl() creates an empty dialog, and fills it with a label, textfield and -two buttons: - -// part of GetNumber.java -private static XControl makeDialogControl() -{ - XControl dialogCtrl = - Dialogs.createDialogControl(109, 73, 94, 44, - "Number Extractor"); - if (dialogCtrl == null) - System.out.println("dialog control is null"); - // log("Dialog name:" + Dialogs.getControlName(dialogCtrl)); - // reports "OfficeDialog1" - - XControl xc = Dialogs.insertLabel(dialogCtrl, 8, 11, 48, - "Extracted Number: "); - // log("Label name:" + Dialogs.getControlName(xc)); - // FixedText1 - - xc = Dialogs.insertTextField(dialogCtrl, 61, 9, 24, ""); - // log("Text field name:" + Dialogs.getControlName(xc)); - // TextField1 - - xc = Dialogs.insertButton(dialogCtrl, 9, 27, 33, "Ok"); - // log("Ok button name:" + Dialogs.getControlName(xc)); - // CommandButton1 - - xc = Dialogs.insertButton(dialogCtrl, 52, 27, 33, "Cancel"); - // log("Cancel button name:" + Dialogs.getControlName(xc)); - // CommandButton2 - - return dialogCtrl; -} // end of makeDialogControl() - -One tricky aspect is deciding on the control positions and widths passed to the -Dialogs.insertXXX() methods. I based them on the values in NumExtractor.xdl. - -initDialog() is again used to initialize the dialog's textfield and buttons, which -assumes they are called "TextField1", "CommandButton1", and "CommandButton2". - -I confirmed this for the runtime dialog by writing their name to the log file: - -// part of GetNumber.java -// global -private static final String LOG_FNM = "c://macrosInfo.txt"; - -private static void log(String msg) -{ FileIO.appendTo(LOG_FNM, msg); } - - -### 1.5. Dialogs and their Controls - -Dialog controls utilize the same model-view framework as form controls (which were -described back in Chapter 39). Controls store data as properties spread over a -hierarchy centered around UnoControlModel in the com.sun.star.awt module. A small -fragment of that hierarchy is shown in Figure 8. - - - +Figure 7. The Rendering of the Runtime Dialog. + + +makeDialogControl() creates an empty dialog, and fills it with a label, textfield and +two buttons: + +```java +// part of GetNumber.java +private static XControl makeDialogControl() +{ + XControl dialogCtrl = + Dialogs.createDialogControl(109, 73, 94, 44, + "Number Extractor"); + if (dialogCtrl == null) + System.out.println("dialog control is null"); + // log("Dialog name:" + Dialogs.getControlName(dialogCtrl)); + // reports "OfficeDialog1" + + XControl xc = Dialogs.insertLabel(dialogCtrl, 8, 11, 48, + "Extracted Number: "); + // log("Label name:" + Dialogs.getControlName(xc)); + // FixedText1 + + xc = Dialogs.insertTextField(dialogCtrl, 61, 9, 24, ""); + // log("Text field name:" + Dialogs.getControlName(xc)); + // TextField1 + + xc = Dialogs.insertButton(dialogCtrl, 9, 27, 33, "Ok"); + // log("Ok button name:" + Dialogs.getControlName(xc)); + // CommandButton1 + + xc = Dialogs.insertButton(dialogCtrl, 52, 27, 33, "Cancel"); + // log("Cancel button name:" + Dialogs.getControlName(xc)); + // CommandButton2 + + return dialogCtrl; +} // end of makeDialogControl() +``` + +One tricky aspect is deciding on the control positions and widths passed to the +Dialogs.insertXXX() methods. I based them on the values in NumExtractor.xdl. + +initDialog() is again used to initialize the dialog's textfield and buttons, which +assumes they are called "TextField1", "CommandButton1", and "CommandButton2". +I confirmed this for the runtime dialog by writing their name to the log file: + +```java +// part of GetNumber.java +// global +private static final String LOG_FNM = "c://macrosInfo.txt"; + +private static void log(String msg) +{ FileIO.appendTo(LOG_FNM, msg); } +``` + + +### 1.5. Dialogs and their Controls + +Dialog controls utilize the same model-view framework as form controls (which were +described back in Chapter 39). Controls store data as properties spread over a +hierarchy centered around UnoControlModel in the com.sun.star.awt module. A small +fragment of that hierarchy is shown in Figure 8. + ![](images/49-Ext_Doc_Event_Macros-8.png) -Figure 8. UnoControlModel and Some Subclasses. +Figure 8. UnoControlModel and Some Subclasses. - -A dialog is represented by UnoControlDialogModel, which stores properties such as -the title, a background image or color, and windowing flags such as whether it is -closeable. Its XNameContainer interface allows the names of its component controls -to be accessed. -Some of the important properties, such as the (x, y) position of a control, are a little -hard to find – they're not in UnoControlModel but its superclass, -UnoControlDialogElement. +A dialog is represented by UnoControlDialogModel, which stores properties such as +the title, a background image or color, and windowing flags such as whether it is +closeable. Its XNameContainer interface allows the names of its component controls +to be accessed. -The other part of the model-view framework are views which represent how a control -is drawn. Views for different controls are subclasses of the UnoControl service, as -shown in Figure 9. +Some of the important properties, such as the (x, y) position of a control, are a little +hard to find – they're not in UnoControlModel but its superclass, +UnoControlDialogElement. + +The other part of the model-view framework are views which represent how a control +is drawn. Views for different controls are subclasses of the UnoControl service, as +shown in Figure 9. - ![](images/49-Ext_Doc_Event_Macros-9.png) -Figure 9. UnoControl and Some Subclasses. +Figure 9. UnoControl and Some Subclasses. - -UnoControl's XControl contains methods for linking a model to a view. Also, -subclass interfaces, such as XButton and XTextComponent, are where listeners are -attached to controls. -It helps to remember the naming conventions illustrated by Figures 8 and 9 – a service -containing the word "Model" is almost always for storing data, while a service -without the word "Model" is almost always a view. +UnoControl's XControl contains methods for linking a model to a view. Also, +subclass interfaces, such as XButton and XTextComponent, are where listeners are +attached to controls. -Figure 9 doesn't include a dialog view; it's sufficiently different to deserve its own -diagram, Figure 10. +It helps to remember the naming conventions illustrated by Figures 8 and 9 – a service +containing the word "Model" is almost always for storing data, while a service +without the word "Model" is almost always a view. - +Figure 9 doesn't include a dialog view; it's sufficiently different to deserve its own +diagram, Figure 10. ![](images/49-Ext_Doc_Event_Macros-10.png) -Figure 10. The UnoControlDialog Service. - - -UnoControlDialog isn't a subclass of UnoControl, but its interface, -XUnoControlDialog, still inherits XControl and so can be linked to a model. It also -inherits XControlContainer which allows controls to be added to and removed from a -dialog. XDialog contains execute() which makes a dialog active on screen. - -Dialogs.createDialogControl() creates a dialog view and model, and links them. The -dialog is initialized by setting various properties in its model: - -// in the Dialogs class -public static XControl createDialogControl(int x, int y, - int width, int height, String title) -{ try { - XControl dialogCtrl = - Lo.createInstanceMCF(XControl.class, - "com.sun.star.awt.UnoControlDialog"); - XControlModel xControlModel = - Lo.createInstanceMCF(XControlModel.class, - "com.sun.star.awt.UnoControlDialogModel"); - dialogCtrl.setModel(xControlModel); // link view and model - - XPropertySet props = getControlProps(dialogCtrl.getModel()); - props.setPropertyValue("PositionX", x); - props.setPropertyValue("PositionY", y); - props.setPropertyValue("Height", height); - props.setPropertyValue("Width", width); - - props.setPropertyValue("Title", title); - props.setPropertyValue("Name", "OfficeDialog"); - - props.setPropertyValue("Step", 0); - props.setPropertyValue("Moveable", true); - props.setPropertyValue("TabIndex", new Short((short) 0)); - - return dialogCtrl; - } - catch (Exception ex) { - System.out.println("Could not create dialog control: " + ex); - return null; - } -} // end of createDialogControl() - -makeDialogControl() in the GetNumber class adds a label, textfield, and two buttons -to the dialog by calling Dialogs.insertXXX() methods. These methods are all quite -similar, so I'll only explain insertButton(). Its job is to create a button model, and -initialize its properties: - -// in the Dialogs class -public static XControl insertButton(XControl dialogCtrl, - int x, int y, int width, String label) -{ return insertButton(dialogCtrl, x, y, width, label, - PushButtonType.STANDARD_value); -} - - -public static XControl insertButton(XControl dialogCtrl, - int x, int y, int width, - String label, int pushButtonType) -{ try { - // create a button model - XMultiServiceFactory msf = - Lo.qi(XMultiServiceFactory.class, - dialogCtrl.getModel()); - Object model = msf.createInstance( - "com.sun.star.awt.UnoControlButtonModel"); - - // generate a unique name for the control - XNameContainer nameCon = getDialogNmCon(dialogCtrl); - String nm = createName(nameCon, "CommandButton"); - - // set properties in the model - XPropertySet props = getControlProps(model); - props.setPropertyValue("PositionX", x); - props.setPropertyValue("PositionY", y); - props.setPropertyValue("Height", 14); - props.setPropertyValue("Width", width); - - props.setPropertyValue("Label", label); - props.setPropertyValue("PushButtonType", - new Short((short) pushButtonType)); - props.setPropertyValue("Name", nm); - - // add the model to the dialog - nameCon.insertByName(nm, model); - - // get the dialog's container holding all the control views - XControlContainer ctrlCon = - Lo.qi(XControlContainer.class, dialogCtrl); - - // use the model's name to get its view inside the dialog - return ctrlCon.getControl(nm); - } - catch (Exception ex) { - System.out.println("Could not create button control: " + ex); - return null; - } -} // end of insertButton() - -First the model is created and added to the dialog. Its view is retrieved from the -dialog's control container, and returned as an XControl object. - -Back in GetNumber.runtimeDialog(), the dialog's window (or peer) is linked to the -Office window by Dialogs.createDialogPeer(): - -// in the Dialogs class -public static XDialog createDialogPeer(XControl dialogCtrl) -{ - XWindow xWindow = (XWindow) Lo.qi(XWindow.class, dialogCtrl); - xWindow.setVisible(false); - // set dialog window invisible until it is executed - - XToolkit xToolkit = Lo.createInstanceMCF(XToolkit.class, - "com.sun.star.awt.Toolkit"); - XWindowPeer windowParentPeer = xToolkit.getDesktopWindow(); - - dialogCtrl.createPeer(xToolkit, windowParentPeer); - - XComponent dialogComponent = Lo.qi(XComponent.class, dialogCtrl); - return getDialog(dialogCtrl); -} // end of createDialogPeer() - - -## 2. Storing Macros inside the (Form) Document - -The previous section examined how to add macros to Office as extensions. Another -popular way of utilizing macros is to embed them inside documents. - -I'll create a variation of the previous form, with the same functionality for its text -fields, but GetText.show and GetNumber.get (and its dialog and listener) will be -stored inside the document. - -Office documents, such as FormMacrosTest.odt, can be manipulated as zip files; I -chose 7-Zip (http://www.7-zip.org/) for the purpose, because it's powerful, open -source, and can be executed from the command line and from DOS batch scripts. - -I'm reusing the same form from the previous section, but stored in -FormDocMacros.odt. It's unzipped using my unzipDoc.bat script to create a folder -called FormDocMacros_odt\. The macros are added by modifying this folder: two -new subdirectories are created, and the manifest.xml file changed, as illustrated by +Figure 10. The UnoControlDialog Service. + + +UnoControlDialog isn't a subclass of UnoControl, but its interface, +XUnoControlDialog, still inherits XControl and so can be linked to a model. It also +inherits XControlContainer which allows controls to be added to and removed from a +dialog. XDialog contains execute() which makes a dialog active on screen. + +Dialogs.createDialogControl() creates a dialog view and model, and links them. The +dialog is initialized by setting various properties in its model: + +```java +// in the Dialogs class +public static XControl createDialogControl(int x, int y, + int width, int height, String title) +{ try { + XControl dialogCtrl = + Lo.createInstanceMCF(XControl.class, + "com.sun.star.awt.UnoControlDialog"); + XControlModel xControlModel = + Lo.createInstanceMCF(XControlModel.class, + "com.sun.star.awt.UnoControlDialogModel"); + dialogCtrl.setModel(xControlModel); // link view and model + + XPropertySet props = getControlProps(dialogCtrl.getModel()); + props.setPropertyValue("PositionX", x); + props.setPropertyValue("PositionY", y); + props.setPropertyValue("Height", height); + props.setPropertyValue("Width", width); + + props.setPropertyValue("Title", title); + props.setPropertyValue("Name", "OfficeDialog"); + + props.setPropertyValue("Step", 0); + props.setPropertyValue("Moveable", true); + props.setPropertyValue("TabIndex", new Short((short) 0)); + + return dialogCtrl; + } + catch (Exception ex) { + System.out.println("Could not create dialog control: " + ex); + return null; + } +} // end of createDialogControl() +``` + +makeDialogControl() in the GetNumber class adds a label, textfield, and two buttons +to the dialog by calling Dialogs.insertXXX() methods. These methods are all quite +similar, so I'll only explain insertButton(). Its job is to create a button model, and +initialize its properties: + +```java +// in the Dialogs class +public static XControl insertButton(XControl dialogCtrl, + int x, int y, int width, String label) +{ return insertButton(dialogCtrl, x, y, width, label, + PushButtonType.STANDARD_value); +} + + +public static XControl insertButton(XControl dialogCtrl, + int x, int y, int width, + String label, int pushButtonType) +{ try { + // create a button model + XMultiServiceFactory msf = + Lo.qi(XMultiServiceFactory.class, + dialogCtrl.getModel()); + Object model = msf.createInstance( + "com.sun.star.awt.UnoControlButtonModel"); + + // generate a unique name for the control + XNameContainer nameCon = getDialogNmCon(dialogCtrl); + String nm = createName(nameCon, "CommandButton"); + + // set properties in the model + XPropertySet props = getControlProps(model); + props.setPropertyValue("PositionX", x); + props.setPropertyValue("PositionY", y); + props.setPropertyValue("Height", 14); + props.setPropertyValue("Width", width); + + props.setPropertyValue("Label", label); + props.setPropertyValue("PushButtonType", + new Short((short) pushButtonType)); + props.setPropertyValue("Name", nm); + + // add the model to the dialog + nameCon.insertByName(nm, model); + + // get the dialog's container holding all the control views + XControlContainer ctrlCon = + Lo.qi(XControlContainer.class, dialogCtrl); + + // use the model's name to get its view inside the dialog + return ctrlCon.getControl(nm); + } + catch (Exception ex) { + System.out.println("Could not create button control: " + ex); + return null; + } +} // end of insertButton() +``` + +First the model is created and added to the dialog. Its view is retrieved from the +dialog's control container, and returned as an XControl object. + +Back in GetNumber.runtimeDialog(), the dialog's window (or peer) is linked to the +Office window by Dialogs.createDialogPeer(): + +```java +// in the Dialogs class +public static XDialog createDialogPeer(XControl dialogCtrl) +{ + XWindow xWindow = (XWindow) Lo.qi(XWindow.class, dialogCtrl); + xWindow.setVisible(false); + // set dialog window invisible until it is executed + + XToolkit xToolkit = Lo.createInstanceMCF(XToolkit.class, + "com.sun.star.awt.Toolkit"); + XWindowPeer windowParentPeer = xToolkit.getDesktopWindow(); + + dialogCtrl.createPeer(xToolkit, windowParentPeer); + + XComponent dialogComponent = Lo.qi(XComponent.class, dialogCtrl); + return getDialog(dialogCtrl); +} // end of createDialogPeer() +``` + + +## 2. Storing Macros inside the (Form) Document + +The previous section examined how to add macros to Office as extensions. Another +popular way of utilizing macros is to embed them inside documents. + +I'll create a variation of the previous form, with the same functionality for its text +fields, but GetText.show and GetNumber.get (and its dialog and listener) will be +stored inside the document. + +Office documents, such as FormMacrosTest.odt, can be manipulated as zip files; I +chose 7-Zip (http://www.7-zip.org/) for the purpose, because it's powerful, open +source, and can be executed from the command line and from DOS batch scripts. + +I'm reusing the same form from the previous section, but stored in +FormDocMacros.odt. It's unzipped using my unzipDoc.bat script to create a folder +called FormDocMacros_odt\. The macros are added by modifying this folder: two +new subdirectories are created, and the manifest.xml file changed, as illustrated by +Figure 11. + ![](images/49-Ext_Doc_Event_Macros-11.png) -Figure 11. +Figure 11. The Changed FormDocMacros_odt\ Folder - +The dialogLibrary\ folder contains the same "Number Extractor" dialog definition as +before. The Scripts\java\Utils\ folder contains Macros.jar, and a new version of +parcel-descriptor.xml. -![](images/49-Ext_Doc_Event_Macros-11.png) +Macros.jar is different from the earlier extension, which used three classes +(GetText.class, GetNumber.class, and NumActionListener.class) and Utils.jar. + +Unfortunately, this combination doesn't work for document macros. Instead, +Macros.jar is a renamed version of Utils.jar with GetText.class, GetNumber.class, and +NumActionListener.class added to it. + +This change to the code organization is reflected in parcel-descriptor.xml. The +classpath entries for the two macros become: + +manifest.xml specifies the structure of FormDocMacros_odt\, so lines are added +describing dialogLibrary\ and Scripts\: + +``` +// added to manifest.xml + + + + + + + + + + + +``` + +FormDocMacros_odt\ is re-zipped, becoming FormDocMacros.odt. Double-clicking +on it causes it to open, to display the same form as before, but only after the user has +clicked "Enable Macros" in the security warning (see Figure 12). -Figure 11. The Changed FormDocMacros_odt\ Folder - -The dialogLibrary\ folder contains the same "Number Extractor" dialog definition as -before. The Scripts\java\Utils\ folder contains Macros.jar, and a new version of -parcel-descriptor.xml. - -Macros.jar is different from the earlier extension, which used three classes -(GetText.class, GetNumber.class, and NumActionListener.class) and Utils.jar. - -Unfortunately, this combination doesn't work for document macros. Instead, -Macros.jar is a renamed version of Utils.jar with GetText.class, GetNumber.class, and -NumActionListener.class added to it. - -This change to the code organization is reflected in parcel-descriptor.xml. The -classpath entries for the two macros become: - -manifest.xml specifies the structure of FormDocMacros_odt\, so lines are added -describing dialogLibrary\ and Scripts\: - -// added to manifest.xml - - - - - - - - - - - - -FormDocMacros_odt\ is re-zipped, becoming FormDocMacros.odt. Double-clicking -on it causes it to open, to display the same form as before, but only after the user has -clicked "Enable Macros" in the security warning (see Figure 12). - - - ![](images/49-Ext_Doc_Event_Macros-12.png) -Figure 12. The Macro Security Warning when Opening FormDocMacros.odt. +Figure 12. The Macro Security Warning when Opening FormDocMacros.odt. + - -Although the form is the same as previously, the button and textfield must be -configured to use the document macros rather than macros stored inside Office. For -example, the button's "Execute action" event should be assigned to GetText.show in -the document, as in Figure 13. +Although the form is the same as previously, the button and textfield must be +configured to use the document macros rather than macros stored inside Office. For +example, the button's "Execute action" event should be assigned to GetText.show in +the document, as in Figure 13. - - ![](images/49-Ext_Doc_Event_Macros-13.png) -Figure 13. Selecting a Document Macro. +Figure 13. Selecting a Document Macro. - -The resulting event is displayed in Figure 14. - - +The resulting event is displayed in Figure 14. + ![](images/49-Ext_Doc_Event_Macros-14.png) -Figure 14. The "Execute action" Event. +Figure 14. The "Execute action" Event. + - - -## 3. Attaching Macros to Other Events +## 3. Attaching Macros to Other Events -This chapter and the last have concentrated on adding macros to form controls, but -other parts of Office, and other documents, can utilize event macros as well. +This chapter and the last have concentrated on adding macros to form controls, but +other parts of Office, and other documents, can utilize event macros as well. -A summary of the different ways that event macros can be employed is given on the -wiki page "Scripting LibreOffice" at https://help.libreoffice.org/Common/Scripting. It -lists uses for event macros such as: - attached to form controls (already described); - attached to menu items and toolbar icons (this is covered by Add-ons in -Chapter 46); - attached to Office and document events (described next); - attached to key combinations; - attached to an embedded object, such as a chart; - attached to a graphic; - attached to a hyperlink. +A summary of the different ways that event macros can be employed is given on the +wiki page "Scripting LibreOffice" at https://help.libreoffice.org/Common/Scripting. It +lists uses for event macros such as: -The Tools > Customize dialog window supports the connection of event macros to -menus, keyboards, toolbars, and events, as shown in Figure 15. +* attached to form controls (already described); +* attached to menu items and toolbar icons (this is covered by Add-ons in Chapter 46); +* attached to Office and document events (described next); +* attached to key combinations; +* attached to an embedded object, such as a chart; +* attached to a graphic; +* attached to a hyperlink. + +The Tools, Customize dialog window supports the connection of event macros to +menus, keyboards, toolbars, and events, as shown in Figure 15. - - ![](images/49-Ext_Doc_Event_Macros-15.png) -Figure 15. The Tools > Customize Dialog. +Figure 15. The Tools > Customize Dialog. + - -The Events tab in Figure 15 has a pop-down list at its bottom which allows macros to -be attached to Office events (as in the figure) or to events associated with the -currently open document. +The Events tab in Figure 15 has a pop-down list at its bottom which allows macros to +be attached to Office events (as in the figure) or to events associated with the +currently open document. -A macro is selected via the "Macro…" button which takes the user to the Macro -Selector dialog. In Figure 15, I've attached ShowEvent.show to the Office events -"Start Application" and "Open Document". When Office starts it will display the -dialog window on the left of Figure 16, and the dialog on the right when the document -is opened. +A macro is selected via the "Macro…" button which takes the user to the Macro +Selector dialog. In Figure 15, I've attached ShowEvent.show to the Office events +"Start Application" and "Open Document". When Office starts it will display the +dialog window on the left of Figure 16, and the dialog on the right when the document +is opened. - ![](images/49-Ext_Doc_Event_Macros-16.png) -Figure 16. The ShowEvent.show Dialogs for Office Events. - - -These dialogs are drawn by the DocumentEvent version of show() in the ShowEvent -class: - -// in the ShowEvent class -public static void show(XScriptContext sc, - com.sun.star.document.DocumentEvent e) -{ display("document", e.EventName); } - -Automatic Macro Attachment -It's possible to automate the attachment of macros to Office and document events, as -illustrated by the DocEvents.java example: - -// in DocEvents.java -public static void main(String[] args) -{ - XComponentLoader loader = Lo.loadOffice(); - - Macros.listOfficeEvents(); - - // list the "OnStartApp" and "OnLoad" Office event properties - PropertyValue[] osaProps = Macros.getEventProps("OnStartApp"); - Props.showProps("OnStartApp Event", osaProps); - - PropertyValue[] olProps = Macros.getEventProps("OnLoad"); - Props.showProps("OnLoad Event", olProps); - - // attach macros to event if it does not have macros already - if (Lo.isNullOrEmpty( (String)Props.getProp(osaProps, "Script"))) - Macros.setEventScript("OnStartApp", - "vnd.sun.star.script:ShowEvent.ShowEvent.show? - language=Java&location=share"); - - if (Lo.isNullOrEmpty( (String)Props.getProp(olProps, "Script"))) - Macros.setEventScript("OnLoad", - "vnd.sun.star.script:ShowEvent.ShowEvent.show? - language=Java&location=share"); - - - XTextDocument doc = Write.openDoc("build.odt", loader); - if (doc == null) { - System.out.println("Could not open build.odt"); - Lo.closeOffice(); - return; - } - - GUI.setVisible(doc, true); - Lo.wait(2000); - - Macros.listDocEvents(doc); - - // list the "OnPageCountChange" doc event properties - PropertyValue[] opccProps = - Macros.getDocEventProps(doc, "OnPageCountChange"); - Props.showProps("OnPageCountChange Event", opccProps); - - if (Lo.isNullOrEmpty( - (String)Props.getProp(opccProps, "Script"))) { - Macros.setDocEventScript(doc, "OnPageCountChange", - "vnd.sun.star.script:ShowEvent.ShowEvent.show? - language=Java&location=share"); - - Lo.save(doc); // must save doc after event macro change - } - - Lo.waitEnter(); - Lo.closeDoc(doc); - Lo.closeOffice(); -} // end of main() - -The program begins by listing all the names of the Office events by calling -Macros.listOfficeEvents(), and then the properties for the "OnStartApp" and -"OnLoad" events. It attaches ShowEvent.show to the two events, resulting in the -macro setup in Figure 15. - -The output from Macros.listOfficeEvents() is: - -Event Handler names -No. of names: 28 - "OnCloseApp" "OnCopyTo" "OnCopyToDone" "OnCopyToFailed" - "OnCreate" "OnFocus" "OnLoad" "OnLoadFinished" - "OnModeChanged" "OnModifyChanged" "OnNew" "OnPrepareUnload" - "OnPrepareViewClosing" "OnPrint" "OnSave" "OnSaveAs" - "OnSaveAsDone" "OnSaveAsFailed" "OnSaveDone" "OnSaveFailed" - "OnStartApp" "OnStorageChanged" "OnTitleChanged" "OnUnfocus" - "OnUnload" "OnViewClosed" "OnViewCreated" "OnVisAreaChanged" - -These names can be mapped without too much difficulty to the strings in the "Events" -column of Figure 15. - -The properties listed for the "OnStartApp" and "OnLoad" events are: - -Properties for "OnStartApp Event": - EventType: Script - Script: - -Properties for "OnLoad Event": - EventType: Script - Script: - -In other words, neither events have macros attached to them at the start of -DocEvents.java. - -Events are manipulated using the XEventSupplier interface. Its relevant services and -interfaces are shown in Figure 17. - - +Figure 16. The ShowEvent.show Dialogs for Office Events. + + +These dialogs are drawn by the DocumentEvent version of show() in the ShowEvent +class: + +```java +// in the ShowEvent class +public static void show(XScriptContext sc, + com.sun.star.document.DocumentEvent e) +{ display("document", e.EventName); } +``` + +#### Automatic Macro Attachment + +It's possible to automate the attachment of macros to Office and document events, as +illustrated by the DocEvents.java example: + +```java +// in DocEvents.java +public static void main(String[] args) +{ + XComponentLoader loader = Lo.loadOffice(); + + Macros.listOfficeEvents(); + + // list the "OnStartApp" and "OnLoad" Office event properties + PropertyValue[] osaProps = Macros.getEventProps("OnStartApp"); + Props.showProps("OnStartApp Event", osaProps); + + PropertyValue[] olProps = Macros.getEventProps("OnLoad"); + Props.showProps("OnLoad Event", olProps); + + // attach macros to event if it does not have macros already + if (Lo.isNullOrEmpty( (String)Props.getProp(osaProps, "Script"))) + Macros.setEventScript("OnStartApp", + "vnd.sun.star.script:ShowEvent.ShowEvent.show? + language=Java&location=share"); + + if (Lo.isNullOrEmpty( (String)Props.getProp(olProps, "Script"))) + Macros.setEventScript("OnLoad", + "vnd.sun.star.script:ShowEvent.ShowEvent.show? + language=Java&location=share"); + + + XTextDocument doc = Write.openDoc("build.odt", loader); + if (doc == null) { + System.out.println("Could not open build.odt"); + Lo.closeOffice(); + return; + } + + GUI.setVisible(doc, true); + Lo.wait(2000); + + Macros.listDocEvents(doc); + + // list the "OnPageCountChange" doc event properties + PropertyValue[] opccProps = + Macros.getDocEventProps(doc, "OnPageCountChange"); + Props.showProps("OnPageCountChange Event", opccProps); + + if (Lo.isNullOrEmpty( + (String)Props.getProp(opccProps, "Script"))) { + Macros.setDocEventScript(doc, "OnPageCountChange", + "vnd.sun.star.script:ShowEvent.ShowEvent.show? + language=Java&location=share"); + + Lo.save(doc); // must save doc after event macro change + } + + Lo.waitEnter(); + Lo.closeDoc(doc); + Lo.closeOffice(); +} // end of main() +``` + +The program begins by listing all the names of the Office events by calling +Macros.listOfficeEvents(), and then the properties for the "OnStartApp" and +"OnLoad" events. It attaches ShowEvent.show to the two events, resulting in the +macro setup in Figure 15. + +The output from Macros.listOfficeEvents() is: + +``` +Event Handler names +No. of names: 28 + "OnCloseApp" "OnCopyTo" "OnCopyToDone" "OnCopyToFailed" + "OnCreate" "OnFocus" "OnLoad" "OnLoadFinished" + "OnModeChanged" "OnModifyChanged" "OnNew" "OnPrepareUnload" + "OnPrepareViewClosing" "OnPrint" "OnSave" "OnSaveAs" + "OnSaveAsDone" "OnSaveAsFailed" "OnSaveDone" "OnSaveFailed" + "OnStartApp" "OnStorageChanged" "OnTitleChanged" "OnUnfocus" + "OnUnload" "OnViewClosed" "OnViewCreated" "OnVisAreaChanged" +``` + +These names can be mapped without too much difficulty to the strings in the "Events" +column of Figure 15. + +The properties listed for the "OnStartApp" and "OnLoad" events are: + +``` +Properties for "OnStartApp Event": + EventType: Script + Script: + +Properties for "OnLoad Event": + EventType: Script + Script: +``` + +In other words, neither events have macros attached to them at the start of +DocEvents.java. + +Events are manipulated using the XEventSupplier interface. Its relevant services and +interfaces are shown in Figure 17. + ![](images/49-Ext_Doc_Event_Macros-17.png) -Figure 17. The XEventSupplier Interface. - - -Document events are reached through the OfficeDocument service while Office -events are obtained via theGlobalEventBroadcaster (or the deprecated -GlobalEventBroadcaster). - -Macro.listOfficeEvents() starts with the theGlobalEventBroadcaster service, and uses -XEventSupplier.getEvents() to obtain an XNameReplace object which is a named -container whose entries can be changed: - -// in the Macros class -public static void listOfficeEvents() -{ System.out.println("\nEvent Handler names"); - XNameReplace eventHandlers = getEventHandlers(); - Lo.printNames( eventHandlers.getElementNames() ); -} - - -public static XNameReplace getEventHandlers() -{ XGlobalEventBroadcaster geb = - theGlobalEventBroadcaster.get(Lo.getContext()); - return geb.getEvents(); -} - -Macros.getEventProps() looks up a specific event handler, and casts its entry in the -XNameReplace container to a PropertyValue array: - -// in Macros class -public static PropertyValue[] getEventProps(String eventName) -{ - XNameReplace eventHandlers = getEventHandlers(); - return getEventProps( eventHandlers, eventName); -} - - -public static PropertyValue[] getEventProps( - XNameReplace eventHandlers, String eventName) -{ try { - Object oProps = eventHandlers.getByName(eventName); - if (AnyConverter.isVoid(oProps)) // or conversion may fail - return null; - else - return (PropertyValue[])oProps; - } - catch(com.sun.star.uno.Exception e) - { System.out.println("Could not find event " + eventName); - return null; - } -} // end of getEventProps() - -Macros.setEventScript() utilizes getEventProps() to get the PropertyValue[] array for -a given event, and sets the "Script" property to be the full name of the macro (e.g. - -"vnd.sun.star.script:ShowEvent.ShowEvent.show?language=Java&location=share"). - -Then the original entry in the XNameReplace object is updated with the changed -property: - -public static void setEventScript(String eventName, - String scriptName) -{ - PropertyValue[] evProps = getEventProps(eventName); - if (evProps != null) - Props.setProp(evProps, "Script", scriptName); - else - evProps = Props.makeProps("EventType", "Script", - "Script", scriptName); - - XNameReplace eventHandlers = getEventHandlers(); - try { - eventHandlers.replaceByName(eventName, evProps); - System.out.println("Set script for " + eventName + " to \"" + - scriptName + "\""); - } - catch(com.sun.star.uno.Exception e) - { System.out.println("Could not set script " + eventName); } -} // end of setEventScript() - -The Macros utilities class contains similar methods for getting and setting document -events. Macros.listDocEvents(), Macros.getDocEventProps(), and -Macros.setDocEventScript() get the XEventSupplier object from the document via its -OfficeDocument service. - -Macros.listDocEvents() prints the following document event names: - -Doc Event Handler names -No. of names: 34 - "OnCloseApp" "OnCopyTo" "OnCopyToDone" "OnCopyToFailed" - "OnCreate" "OnFieldMerge" "OnFieldMergeFinished" "OnFocus" - "OnLayoutFinished" "OnLoad" "OnLoadFinished" "OnMailMerge" - "OnMailMergeFinished" "OnModeChanged" "OnModifyChanged" "OnNew" - "OnPageCountChange" "OnPrepareUnload" - "OnPrepareViewClosing" "OnPrint" - "OnSave" "OnSaveAs" "OnSaveAsDone" "OnSaveAsFailed" - "OnSaveDone" "OnSaveFailed" "OnStartApp" "OnStorageChanged" - "OnTitleChanged" "OnUnfocus" "OnUnload" "OnViewClosed" - "OnViewCreated" "OnVisAreaChanged" - -There's a big overlap with Office events, and the difference between the same-named -events, such as "OnLoad", is that the Office version is fired when any document is -loaded, whereas the document "OnLoad" will only fire for the loading of "build.odt". - -DocEvent.java attaches ShowEvent.show to its "OnPageCountChange" event, and the -document is saved so the change is remembered. - -The change can be confirmed by opening the document and checking the Event tab of -the Tools > Customize dialog. The "OnPageCountChange" setting is at the end of the -events, as in Figure 18. - - - +Figure 17. The XEventSupplier Interface. + + +Document events are reached through the OfficeDocument service while Office +events are obtained via theGlobalEventBroadcaster (or the deprecated +GlobalEventBroadcaster). + +Macro.listOfficeEvents() starts with the theGlobalEventBroadcaster service, and uses +XEventSupplier.getEvents() to obtain an XNameReplace object which is a named +container whose entries can be changed: + +```java +// in the Macros class +public static void listOfficeEvents() +{ System.out.println("\nEvent Handler names"); + XNameReplace eventHandlers = getEventHandlers(); + Lo.printNames( eventHandlers.getElementNames() ); +} + + +public static XNameReplace getEventHandlers() +{ XGlobalEventBroadcaster geb = + theGlobalEventBroadcaster.get(Lo.getContext()); + return geb.getEvents(); +} +``` + +Macros.getEventProps() looks up a specific event handler, and casts its entry in the +XNameReplace container to a PropertyValue array: + +```java +// in Macros class +public static PropertyValue[] getEventProps(String eventName) +{ + XNameReplace eventHandlers = getEventHandlers(); + return getEventProps( eventHandlers, eventName); +} + + +public static PropertyValue[] getEventProps( + XNameReplace eventHandlers, String eventName) +{ try { + Object oProps = eventHandlers.getByName(eventName); + if (AnyConverter.isVoid(oProps)) // or conversion may fail + return null; + else + return (PropertyValue[])oProps; + } + catch(com.sun.star.uno.Exception e) + { System.out.println("Could not find event " + eventName); + return null; + } +} // end of getEventProps() +``` + +Macros.setEventScript() utilizes getEventProps() to get the PropertyValue[] array for +a given event, and sets the "Script" property to be the full name of the macro (e.g. +"vnd.sun.star.script:ShowEvent.ShowEvent.show?language=Java&location=share"). +Then the original entry in the XNameReplace object is updated with the changed +property: + +```java +public static void setEventScript(String eventName, + String scriptName) +{ + PropertyValue[] evProps = getEventProps(eventName); + if (evProps != null) + Props.setProp(evProps, "Script", scriptName); + else + evProps = Props.makeProps("EventType", "Script", + "Script", scriptName); + + XNameReplace eventHandlers = getEventHandlers(); + try { + eventHandlers.replaceByName(eventName, evProps); + System.out.println("Set script for " + eventName + " to \"" + + scriptName + "\""); + } + catch(com.sun.star.uno.Exception e) + { System.out.println("Could not set script " + eventName); } +} // end of setEventScript() +``` + +The Macros utilities class contains similar methods for getting and setting document +events. Macros.listDocEvents(), Macros.getDocEventProps(), and +Macros.setDocEventScript() get the XEventSupplier object from the document via its +OfficeDocument service. + +Macros.listDocEvents() prints the following document event names: + +``` +Doc Event Handler names +No. of names: 34 + "OnCloseApp" "OnCopyTo" "OnCopyToDone" "OnCopyToFailed" + "OnCreate" "OnFieldMerge" "OnFieldMergeFinished" "OnFocus" + "OnLayoutFinished" "OnLoad" "OnLoadFinished" "OnMailMerge" + "OnMailMergeFinished" "OnModeChanged" "OnModifyChanged" "OnNew" + "OnPageCountChange" "OnPrepareUnload" + "OnPrepareViewClosing" "OnPrint" + "OnSave" "OnSaveAs" "OnSaveAsDone" "OnSaveAsFailed" + "OnSaveDone" "OnSaveFailed" "OnStartApp" "OnStorageChanged" + "OnTitleChanged" "OnUnfocus" "OnUnload" "OnViewClosed" + "OnViewCreated" "OnVisAreaChanged" +``` + +There's a big overlap with Office events, and the difference between the same-named +events, such as "OnLoad", is that the Office version is fired when any document is +loaded, whereas the document "OnLoad" will only fire for the loading of "build.odt". + +DocEvent.java attaches ShowEvent.show to its "OnPageCountChange" event, and the +document is saved so the change is remembered. + +The change can be confirmed by opening the document and checking the Event tab of +the Tools, Customize dialog. The "OnPageCountChange" setting is at the end of the +events, as in Figure 18. + ![](images/49-Ext_Doc_Event_Macros-18.png) -Figure 18. The Event tab of the Tools > Customize Dialog. - - -Note that the "Save in:" pop-down list at the bottom of the dialog shows the document -name instead of "LibreOffice". - - - -## 4. Executing Macros from the Command Line - -One of the show() method in the ShowEvent class doesn't have an event argument: - -// part of ShowEvent.java -public static void show(XScriptContext sc) -{ display("menu/run"); } - -This method can be called in a number of different situations: - when the macro is attached to a menu item; - when the macro is executed from the "Run Macro..." menu item of the -Tools > Macros menu; - if there's no suitable event handling version of show(), then this version acts as -a default; - when the macro is executed from the command line. - -The last approach can be employed when Office is called from the command line to -open a document. The command line arguments can include the full name of a macro, -which will cause its "run" version to be called. For example: - -office.exe build.odt "vnd.sun.star.script:ShowEvent.ShowEvent.show? - language=Java&location=share" - -"build.odt" is opened, and the ShowEvent.show share macro executed. - -The execMacro.bat batch file in the examples simplifies these command line -parameters. - - +Figure 18. The Event tab of the Tools > Customize Dialog. + + +Note that the "Save in:" pop-down list at the bottom of the dialog shows the document +name instead of "LibreOffice". + + +## 4. Executing Macros from the Command Line + +One of the show() method in the ShowEvent class doesn't have an event argument: + +```java +// part of ShowEvent.java +public static void show(XScriptContext sc) +{ display("menu/run"); } +``` + +This method can be called in a number of different situations: + +* when the macro is attached to a menu item; +* when the macro is executed from the "Run Macro..." menu item of the Tools, Macros menu; +* if there's no suitable event handling version of show(), then this version acts as a default; +* when the macro is executed from the command line. + +The last approach can be employed when Office is called from the command line to +open a document. The command line arguments can include the full name of a macro, +which will cause its "run" version to be called. For example: + +``` +office.exe build.odt "vnd.sun.star.script:ShowEvent.ShowEvent.show? + language=Java&location=share" +``` + +"build.odt" is opened, and the ShowEvent.show share macro executed. + +The execMacro.bat batch file in the examples simplifies these command line +parameters. \ No newline at end of file diff --git a/docs/50-Importing_XML.md b/docs/50-Importing_XML.md index 22ccece..6a4d5f8 100644 --- a/docs/50-Importing_XML.md +++ b/docs/50-Importing_XML.md @@ -1,1848 +1,1927 @@ -# Chapter 50. Importing XML +# Chapter 50. Importing XML !!! note "Topics" - XSLT Filters; - Using Filters with Java; - Alternatives to XSLT - Filters: DOM Parsing, - Node and Attribute Data - Extraction, JAXB - Conversion - - Example folders: "Filter - Tests" and "Utils" - - -This chapter is mostly about importing XML data into -Office. I start by looking at XSLT import (and export) -filters, which give the best results but require the -programmer to know a great deal about XML and the -ODF file format. - -The second half of the chapter is about three simpler -techniques for importing XML which require less comprehensive XML skills and no -knowledge of ODF. The drawback is that the resulting document may be less -'beautiful', often requiring some manual post-processing to remove unnecessary white -space and data. - -You'd be correct in thinking that Office can already import XML, but you may be -disappointed with the results: a simple XML file, such as pay.xml shown below, is -imported unchanged, as plain text: - - - - - CD - 12.95 - 19.1234 - 2008-03-01 - - - DVD - 19.95 - 19.4321 - 2008-03-02 - - - Clothes - 99.95 - 18.5678 - 2008-03-03 - - - Book - 9.49 - 18.9876 - 2008-03-04 - - - -Office offers many ways of loading XML through the "All files (*.*)" popdown list in -the Open dialog. Figure 1 shows my selection of "Flat XML ODF Spreadsheet": - - + XSLT Filters; + Using Filters with Java; + Alternatives to XSLT + Filters: DOM Parsing, + Node and Attribute Data + Extraction, JAXB + Conversion + + Example folders: "Filter + Tests" and "Utils" + + +This chapter is mostly about importing XML data into +Office. I start by looking at XSLT import (and export) +filters, which give the best results but require the +programmer to know a great deal about XML and the +ODF file format. + +The second half of the chapter is about three simpler +techniques for importing XML which require less comprehensive XML skills and no +knowledge of ODF. The drawback is that the resulting document may be less +'beautiful', often requiring some manual post-processing to remove unnecessary white +space and data. + +You'd be correct in thinking that Office can already import XML, but you may be +disappointed with the results: a simple XML file, such as pay.xml shown below, is +imported unchanged, as plain text: + +``` + + + + CD + 12.95 + 19.1234 + 2008-03-01 + + + DVD + 19.95 + 19.4321 + 2008-03-02 + + + Clothes + 99.95 + 18.5678 + 2008-03-03 + + + Book + 9.49 + 18.9876 + 2008-03-04 + + +``` + +Office offers many ways of loading XML through the "All files (*.*)" popdown list in +the Open dialog. Figure 1 shows my selection of "Flat XML ODF Spreadsheet": + ![](images/50-Importing_XML-1.png) -Figure 1. Selecting an XML Import Format. +Figure 1. Selecting an XML Import Format. + + +Unfortunately, the result isn't a nice spreadsheet of the payments, but a Writer +document containing all the data and XML tags. - -Unfortunately, the result isn't a nice spreadsheet of the payments, but a Writer -document containing all the data and XML tags. +Flat XML is for encoding an OpenDocument format (ODF) document as a single text +file, which contrasts with how it's usually stored as a zipped folder of several files and +sub-folders. pay.xml isn't a Flat XML file, and so Office drops back to treating it as +plain text, and uses Writer to display it. -Flat XML is for encoding an OpenDocument format (ODF) document as a single text -file, which contrasts with how it's usually stored as a zipped folder of several files and -sub-folders. pay.xml isn't a Flat XML file, and so Office drops back to treating it as -plain text, and uses Writer to display it. +Incidentally, there are several Flat XML formats aimed at the different Office +applications, Writer, Calc, Draw, and Impress. -Incidentally, there are several Flat XML formats aimed at the different Office -applications, Writer, Calc, Draw, and Impress. - - -## 1. Filters to the Rescue +## 1. Filters to the Rescue -pay.xml can be opened as a spreadsheet with the help of an input filter. There are a -few ways of writing these, as explained by Fridrich Strba in his blog post "Extending -the Swiss Army knife - an overview about writing of filters for LibreOffice" -(http://fridrich.blogspot.com/2013/08/extending-swiss-army-knife-overview.html). +pay.xml can be opened as a spreadsheet with the help of an input filter. There are a +few ways of writing these, as explained by Fridrich Strba in his blog post "Extending +the Swiss Army knife - an overview about writing of filters for LibreOffice" +(http://fridrich.blogspot.com/2013/08/extending-swiss-army-knife-overview.html). -The easiest way of implementing an XML input filter is with XSLT (eXtensible -Stylesheet Language: Transformations). The filter is utilized by the XSLT processor -inside Office to load and transform the XML, as illustrated in Figure 2. +The easiest way of implementing an XML input filter is with XSLT (eXtensible +Stylesheet Language: Transformations). The filter is utilized by the XSLT processor +inside Office to load and transform the XML, as illustrated in Figure 2. - - ![](images/50-Importing_XML-2.png) -Figure 2. Using an XSLT filter. +Figure 2. Using an XSLT filter. - -XSLT is designed for transforming XML into other textual formats; in this case, it -will convert the simple XML used by pay.xml into the Flat XML used by Calc. -Office's XSLT processor used to be Saxon (http://saxon.sourceforge.net/), a Java -API, but was replaced in 2012 by the libxslt C library (http://xmlsoft.org/libxslt/). +XSLT is designed for transforming XML into other textual formats; in this case, it +will convert the simple XML used by pay.xml into the Flat XML used by Calc. -This implements XSLT 1.0 with some extensions, so it's best to avoid using features -from the newer XSLT 2.0. +Office's XSLT processor used to be Saxon (http://saxon.sourceforge.net/), a Java +API, but was replaced in 2012 by the libxslt C library (http://xmlsoft.org/libxslt/). +This implements XSLT 1.0 with some extensions, so it's best to avoid using features +from the newer XSLT 2.0. -One source for learning XLST in the context of ODF is: -OASIS OpenDocument Essentials -J. David Eisenberg, 2005 -http:// books.evc-cit.info/ -The book includes a chapter on filters, and an appendix that overviews XSLT and -XPath (XPath is used for locating parts of the input document for processing). +One source for learning XLST in the context of ODF is: -Eisenberg's website has a free draft of the text, and all the examples and support code. +OASIS OpenDocument Essentials +J. David Eisenberg, 2005 +http:// books.evc-cit.info/ +The book includes a chapter on filters, and an appendix that overviews XSLT and +XPath (XPath is used for locating parts of the input document for processing). -The site often seems to be offline, but the book can be found elsewhere, including at +Eisenberg's website has a free draft of the text, and all the examples and support code. +The site often seems to be offline, but the book can be found elsewhere, including at Lulu: http://www.lulu.com/shop/j-david-eisenberg/oasis-opendocument- -essentials/paperback/product-392512.html -If you feel the need for more information on XSLT, a good text by the developer of -Saxon is: -XSLT 2.0 and XPath 2.0 Programmer's Reference -Michael Kay -Wrox Pub., May 2008, 4th Ed. - -There's a somewhat shorter XSLT tutorial at W3Schools: -http://www.w3schools.com/xml/xsl_intro.asp -A useful forum post, "Create XSLT filters for import and export" -(https://forum.openoffice.org/en/forum/viewtopic.php?t=3490), by user hol.sten -contains XSLT import and export filters for pay.xml (in fact, it's his example). The -filters are installed via Office's Tools > "XML Filter Settings" menu item. Clicking on -the "New" button of the filter settings window brings up a dialog containing two tabs -shown in Figure 3. - - - +essentials/paperback/product-392512.html + +If you feel the need for more information on XSLT, a good text by the developer of +Saxon is: + +XSLT 2.0 and XPath 2.0 Programmer's Reference +Michael Kay +Wrox Pub., May 2008, 4th Ed. + +There's a somewhat shorter XSLT tutorial at W3Schools: +http://www.w3schools.com/xml/xsl_intro.asp + +A useful forum post, "Create XSLT filters for import and export" +(https://forum.openoffice.org/en/forum/viewtopic.php?t=3490), by user hol.sten +contains XSLT import and export filters for pay.xml (in fact, it's his example). The +filters are installed via Office's Tools > "XML Filter Settings" menu item. Clicking on +the "New" button of the filter settings window brings up a dialog containing two tabs +shown in Figure 3. + ![](images/50-Importing_XML-3.png) -Figure 3. The XML Filter Settings Dialog Tabs. +Figure 3. The XML Filter Settings Dialog Tabs. - -Figure 3 shows that I've created a "Pay" filter set containing hol.sten's import and -export filters stored in payImport.xsl and payExport.xsl. The import filter will convert -XML to Flat XML for Calc, as stated in the "Application" field of the General tab. -After clicking "Ok", the Pay filter set is added to the settings window in Figure 4. +Figure 3 shows that I've created a "Pay" filter set containing hol.sten's import and +export filters stored in payImport.xsl and payExport.xsl. The import filter will convert +XML to Flat XML for Calc, as stated in the "Application" field of the General tab. + +After clicking "Ok", the Pay filter set is added to the settings window in Figure 4. - - ![](images/50-Importing_XML-4.png) -Figure 4. The Pay Filter in the Settings Window. +Figure 4. The Pay Filter in the Settings Window. + - -When pay.xml is opened, the Pay import filter appears in the Calc section of the "All -Files (*.*) popdown list, as seen in Figure 5. +When pay.xml is opened, the Pay import filter appears in the Calc section of the "All +Files (*.*) popdown list, as seen in Figure 5. - - ![](images/50-Importing_XML-5.png) -Figure 5. The Calc Pay Import Filter. +Figure 5. The Calc Pay Import Filter. - -The "XML Payments" text in Figure 5 comes from the "Name of file type" textfield in -the General tab of the dialog in Figure 3. -The resulting Calc document is shown in Figure 6. +The "XML Payments" text in Figure 5 comes from the "Name of file type" textfield in +the General tab of the dialog in Figure 3. + +The resulting Calc document is shown in Figure 6. - - ![](images/50-Importing_XML-6.png) -Figure 6. Pay.xml Imported as a Spreadsheet. - - - -### 1.1. The Clubs Example - -There's another nice XSLT import and export filter set in chapter 9 of Eisenberg's -book. clubs.xml consists of a sequence of associations, each one made up of a -sequence of clubs. A typical club entry looks like: - - - Castro Valley Wrestling Club - Ron Maes - Castro Valley - 510-555-1491 - cvwcron@example.com - - Practices every Tuesday and Thursday at 5:00 P.M. - - at Castro Valley High School mat room. - - -A helpful way of visualizing this information is with the XML editor at -http://xmlgrid.net/, which renders it as in Figure 7. - - - +Figure 6. Pay.xml Imported as a Spreadsheet. + + +### 1.1. The Clubs Example + +There's another nice XSLT import and export filter set in chapter 9 of Eisenberg's +book. clubs.xml consists of a sequence of associations, each one made up of a +sequence of clubs. A typical club entry looks like: + +``` + + Castro Valley Wrestling Club + Ron Maes + Castro Valley + 510-555-1491 + cvwcron@example.com + + Practices every Tuesday and Thursday at 5:00 P.M. + + at Castro Valley High School mat room. + +``` + +A helpful way of visualizing this information is with the XML editor at +http://xmlgrid.net/, which renders it as in Figure 7. + ![](images/50-Importing_XML-7.png) -Figure 7. Visualization of clubs.xml. +Figure 7. Visualization of clubs.xml. + - -There are 17 associations; the first is called "BAWA" and contains 11 clubs -Eisenberg's clubsImport.xsl and clubsExport.xsl filters are imported into Office using -the "XML Filter Settings" dialog in Figure 8. +There are 17 associations; the first is called "BAWA" and contains 11 clubs +Eisenberg's clubsImport.xsl and clubsExport.xsl filters are imported into Office using +the "XML Filter Settings" dialog in Figure 8. - - ![](images/50-Importing_XML-8.png) -Figure 8. The XML Filter Settings Dialog Again. +Figure 8. The XML Filter Settings Dialog Again. - -I've called the filter set "Clubs", and it uses clubsImport.xsl to produce Flat XML for -Writer. There's also a Writer template called clubsTemplate.ott, which applies styles -to the imported data. -When clubs.xml is opened, the Clubs import filter appears in the Writer section of the -"All Files (*.*) popdown list, as in Figure 9. +I've called the filter set "Clubs", and it uses clubsImport.xsl to produce Flat XML for +Writer. There's also a Writer template called clubsTemplate.ott, which applies styles +to the imported data. + +When clubs.xml is opened, the Clubs import filter appears in the Writer section of the +"All Files (*.*) popdown list, as in Figure 9. - - ![](images/50-Importing_XML-9.png) -Figure 9. The Writer Clubs Import Filter. +Figure 9. The Writer Clubs Import Filter. + - -The start of the resulting Writer document looks like Figure 10. +The start of the resulting Writer document looks like Figure 10. - - ![](images/50-Importing_XML-10.png) -Figure 10. Clubs.xml as a Writer Document. +Figure 10. Clubs.xml as a Writer Document. + - -### 1.2. Command Line Importing (and Exporting) +### 1.2. Command Line Importing (and Exporting) -Import filter selection can be time-consuming because of the large number listed in -"Open"s popdown list. A quicker approach is to call Office from the command line, -supplying the filename and filter name as arguments. +Import filter selection can be time-consuming because of the large number listed in +"Open"s popdown list. A quicker approach is to call Office from the command line, +supplying the filename and filter name as arguments. -Most of Office's command line arguments are listed at -https://help.libreoffice.org/Common/Starting_the_Software_With_Parameters, and a +Most of Office's command line arguments are listed at +https://help.libreoffice.org/Common/Starting_the_Software_With_Parameters, and a few others when office.exe is invoked with the -h option. The filter commands are -- -infilter and --convert-to, which I've wrapped up in two batch scripts called infilter.bat -and convert.bat. - -The hardest part of using these commands is the need to supply a filter name, which -corresponds to the "Filter name" textfield string in the General tab of the XML Filter -Settings dialog. The pay.xml import filter is "Pay", as shown in the left hand window -in Figure 3; the clubs.xml filter is "Clubs", as in Figure 8. - -The infilter.bat script takes a filename and input filter name argument: -infilter pay.xml "Pay" -This causes Office to open pay.xml using the "Pay" import filter, creating the -spreadsheet shown in Figure 6. - -The convert.bat script takes an Office filename and conversion string as arguments. In -the simple case, the string is the extension of the exported file. For example, the -following exports the Writer document as XML: -convert simpleText.odt xml -If there are several filters to choose from (as there are for XML), then the default one -is used; for this example the Flat XML exporter for Writer will be employed. If a -different export filter is required then its name must be appended to the conversion -string after a ":". For instance: -convert payment.ods "xml:Pay" -This exports the payments spreadsheet using the "Pay" filter, resulting in a file called -payment.xml which has the same format as the original example at the start of the -chapter. - - -### 1.3. Finding a Filter Name - -The calls to infilter.bat and convert.bat rely on the user knowing a filter's name (e.g. - -"Pay" or "Clubs"). I knew these because I installed them, but what about the names of -other filters in Office? -A list of filters present in OpenOffice in 2007 can be found at -https://wiki.openoffice.org/wiki/Framework/Article/Filter/FilterList_OOo_3_0. - -However, a better approach is to call my FiltersInfo.java example which prints all the -filters currently installed in Office, and some extra details about the "AbiWord", -"Pay", and "Clubs" filters: - -// in FiltersInfo.java -public class FiltersInfo -{ - public static void main(String[] args) - { - XComponentLoader loader = Lo.loadOffice(); - - // print the names of all the filters in Office - String[] filterNms = Info.getFilterNames(); - System.out.println("Filter Names"); - Lo.printNames(filterNms, 3); - - // print some extra info on 3 filters - PropertyValue[] props = Info.getFilterProps("AbiWord"); - Props.showProps("AbiWord Filter", props); - - props = Info.getFilterProps("Pay"); - Props.showProps("Pay Filter", props); - - props = Info.getFilterProps("Clubs"); - Props.showProps("Clubs Filter", props); - int flags = (int) Props.getValue("Flags", props); - - System.out.println("Filter flags: " + - Integer.toHexString(flags)); - System.out.println(" Import: " + Info.isImport(flags)); - System.out.println(" Export: " + Info.isExport(flags)); - - Lo.closeOffice(); - } // end of main() - -} // end of FiltersInfo class - -The output from FiltersInfo starts with a long list of filter names: - -Filter Names -No. of names: 235 - "AbiWord" "Apple Keynote" "Apple Numbers" - "Apple Pages" "BMP - MS Windows" "BroadBand eBook" - "Calc MS Excel 2007 Binary" "Calc MS Excel 2007 VBA XML" - : - "ClarisWorks_Draw" "ClarisWorks_Impress" "Clubs" - : - "Palm_Text_Document" "PalmDoc" "Pay" - : - : - "XHTML Draw File" "XHTML Impress File" "XHTML Writer File" - "XPM" - -In the above list, I've included the lines that include the "Pay" and "Clubs" filter -names. - -The names are obtained by Info.getFilterNames(), which utilizes the FilterFactory -service: - -// in the Info class -public static String[] getFilterNames() -{ - XNameAccess na = Lo.createInstanceMCF(XNameAccess.class, - "com.sun.star.document.FilterFactory"); - if (na == null) { - System.out.println("No Filter factory found"); - return null; - } - else - return na.getElementNames(); -} // end of getFilterNames() - -Sometimes it's useful to know more about a filter than just it's name, such as whether -it's for importing, exporting, or both. Additional information is available as an array -of properties, by calling Info.getFilterProps() with the filter's name: - -// in the Info class -public static PropertyValue[] getFilterProps(String filterNm) -{ - XNameAccess na = Lo.createInstanceMCF(XNameAccess.class, - "com.sun.star.document.FilterFactory"); - if (na == null) { - System.out.println("No Filter factory found"); - return null; - } - else { - try { - return (PropertyValue[]) na.getByName(filterNm); - } - catch(Exception e) { - System.out.println("Could not find filter for " + filterNm); - return null; - } - } -} // end of getFilterProps() - -getFilterProps() is called three times in FiltersInfo.java to retrieve details about the -"AbiWord", "Pay", and "Clubs" filters. The output for "Clubs" is: - -Properties for "Clubs Filter": - UserData: [com.sun.star.documentconversion.XSLTFilter, false, -com.sun.star.comp.Writer.XMLOasisImporter, -com.sun.star.comp.Writer.XMLOasisExporter, +infilter and --convert-to, which I've wrapped up in two batch scripts called infilter.bat +and convert.bat. + +The hardest part of using these commands is the need to supply a filter name, which +corresponds to the "Filter name" textfield string in the General tab of the XML Filter +Settings dialog. The pay.xml import filter is "Pay", as shown in the left hand window +in Figure 3; the clubs.xml filter is "Clubs", as in Figure 8. + +The infilter.bat script takes a filename and input filter name argument: + +``` +infilter pay.xml "Pay" +``` + +This causes Office to open pay.xml using the "Pay" import filter, creating the +spreadsheet shown in Figure 6. + +The convert.bat script takes an Office filename and conversion string as arguments. In +the simple case, the string is the extension of the exported file. For example, the +following exports the Writer document as XML: + +``` +convert simpleText.odt xml +``` + +If there are several filters to choose from (as there are for XML), then the default one +is used; for this example the Flat XML exporter for Writer will be employed. If a +different export filter is required then its name must be appended to the conversion +string after a ":". For instance: + +``` +convert payment.ods "xml:Pay" +``` + +This exports the payments spreadsheet using the "Pay" filter, resulting in a file called +payment.xml which has the same format as the original example at the start of the +chapter. + + +### 1.3. Finding a Filter Name + +The calls to infilter.bat and convert.bat rely on the user knowing a filter's name (e.g. + +"Pay" or "Clubs"). I knew these because I installed them, but what about the names of +other filters in Office? + +A list of filters present in OpenOffice in 2007 can be found at +https://wiki.openoffice.org/wiki/Framework/Article/Filter/FilterList_OOo_3_0. +However, a better approach is to call my FiltersInfo.java example which prints all the +filters currently installed in Office, and some extra details about the "AbiWord", +"Pay", and "Clubs" filters: + +```java +// in FiltersInfo.java +public class FiltersInfo +{ + public static void main(String[] args) + { + XComponentLoader loader = Lo.loadOffice(); + + // print the names of all the filters in Office + String[] filterNms = Info.getFilterNames(); + System.out.println("Filter Names"); + Lo.printNames(filterNms, 3); + + // print some extra info on 3 filters + PropertyValue[] props = Info.getFilterProps("AbiWord"); + Props.showProps("AbiWord Filter", props); + + props = Info.getFilterProps("Pay"); + Props.showProps("Pay Filter", props); + + props = Info.getFilterProps("Clubs"); + Props.showProps("Clubs Filter", props); + int flags = (int) Props.getValue("Flags", props); + + System.out.println("Filter flags: " + + Integer.toHexString(flags)); + System.out.println(" Import: " + Info.isImport(flags)); + System.out.println(" Export: " + Info.isExport(flags)); + + Lo.closeOffice(); + } // end of main() + +} // end of FiltersInfo class +``` + +The output from FiltersInfo starts with a long list of filter names: + +``` +Filter Names +No. of names: 235 + "AbiWord" "Apple Keynote" "Apple Numbers" + "Apple Pages" "BMP - MS Windows" "BroadBand eBook" + "Calc MS Excel 2007 Binary" "Calc MS Excel 2007 VBA XML" + : + "ClarisWorks_Draw" "ClarisWorks_Impress" "Clubs" + : + "Palm_Text_Document" "PalmDoc" "Pay" + : + : + "XHTML Draw File" "XHTML Impress File" "XHTML Writer File" + "XPM" +``` + +In the above list, I've included the lines that include the "Pay" and "Clubs" filter +names. + +The names are obtained by Info.getFilterNames(), which utilizes the FilterFactory +service: + +```java +// in the Info class +public static String[] getFilterNames() +{ + XNameAccess na = Lo.createInstanceMCF(XNameAccess.class, + "com.sun.star.document.FilterFactory"); + if (na == null) { + System.out.println("No Filter factory found"); + return null; + } + else + return na.getElementNames(); +} // end of getFilterNames() +``` + +Sometimes it's useful to know more about a filter than just it's name, such as whether +it's for importing, exporting, or both. Additional information is available as an array +of properties, by calling Info.getFilterProps() with the filter's name: + +```java +// in the Info class +public static PropertyValue[] getFilterProps(String filterNm) +{ + XNameAccess na = Lo.createInstanceMCF(XNameAccess.class, + "com.sun.star.document.FilterFactory"); + if (na == null) { + System.out.println("No Filter factory found"); + return null; + } + else { + try { + return (PropertyValue[]) na.getByName(filterNm); + } + catch(Exception e) { + System.out.println("Could not find filter for " + filterNm); + return null; + } + } +} // end of getFilterProps() +``` + +getFilterProps() is called three times in FiltersInfo.java to retrieve details about the +"AbiWord", "Pay", and "Clubs" filters. The output for "Clubs" is: + +``` +Properties for "Clubs Filter": + UserData: [com.sun.star.documentconversion.XSLTFilter, false, +com.sun.star.comp.Writer.XMLOasisImporter, +com.sun.star.comp.Writer.XMLOasisExporter, file:///C:/Users/Dell/Desktop/LibreOffice%20Tests/Filter%20Tests/club -sImport.xsl, +sImport.xsl, file:///C:/Users/Dell/Desktop/LibreOffice%20Tests/Filter%20Tests/club -sExport.xsl, , ] - TemplateName: +sExport.xsl, , ] + TemplateName: file:///C:/Users/Dell/AppData/Roaming/LibreOffice/4/user/template/Clu -bs/clubsTemplate.ott - Name: Clubs - Type: Clubs - UIComponent: - FileFormatVersion: 0 - FilterService: com.sun.star.comp.Writer.XmlFilterAdaptor - DocumentService: com.sun.star.text.TextDocument - Flags: 524355 - UINames: [ en-US = XML Clubs ] - UIName: XML Clubs - Finalized: false - Mandatory: false - -The UserData and TemplateName properties indicate that "Clubs" contains an import -and export filter for Writer's Flat XML, and also a template. - -Filter properties are explained in the online documentation for the FilterFactory -service (use lodoc FilterFactory to access it), and also in the "Properties of a -Filter" subsection of the "Integrating Import and Export Filters" section of chapter 6 -of the Developer's Guide (online at: +bs/clubsTemplate.ott + Name: Clubs + Type: Clubs + UIComponent: + FileFormatVersion: 0 + FilterService: com.sun.star.comp.Writer.XmlFilterAdaptor + DocumentService: com.sun.star.text.TextDocument + Flags: 524355 + UINames: [ en-US = XML Clubs ] + UIName: XML Clubs + Finalized: false + Mandatory: false +``` + +The UserData and TemplateName properties indicate that "Clubs" contains an import +and export filter for Writer's Flat XML, and also a template. + +Filter properties are explained in the online documentation for the FilterFactory +service (use `lodoc FilterFactory` to access it), and also in the "Properties of a +Filter" subsection of the "Integrating Import and Export Filters" section of chapter 6 +of the Developer's Guide (online at: https://wiki.openoffice.org/wiki/Documentation/DevGuide/OfficeDev/Properties_of_ -a_Filter, or use loGuide "Properties of a Filter"). - -The most cryptic of the properties is the filter flags integer (524355 in the example -above). It's actually a collection of bitwise OR'ed hexadecimals, and FiltersInfo.java -shows how they can be accessed: - -// part of FiltersInfo.java... - -int flags = (int) Props.getValue("Flags", props); -System.out.println("Filter flags: " + Integer.toHexString(flags)); - // print as a hexadecimal string - -System.out.println(" Import: " + Info.isImport(flags)); -System.out.println(" Export: " + Info.isExport(flags)); - -The output is: - -Filter flags: 80043 - Import: true - Export: true - -The hexadecimals that might appear in the flag are listed in the online "Properties of a -Filter" subsection +a_Filter, or use `loGuide "Properties of a Filter"`). + +The most cryptic of the properties is the filter flags integer (524355 in the example +above). It's actually a collection of bitwise OR'ed hexadecimals, and FiltersInfo.java +shows how they can be accessed: + +```java +// part of FiltersInfo.java... + +int flags = (int) Props.getValue("Flags", props); +System.out.println("Filter flags: " + Integer.toHexString(flags)); + // print as a hexadecimal string + +System.out.println(" Import: " + Info.isImport(flags)); +System.out.println(" Export: " + Info.isExport(flags)); +``` + +The output is: + +``` +Filter flags: 80043 + Import: true + Export: true +``` + +The hexadecimals that might appear in the flag are listed in the online "Properties of a +Filter" subsection (https://wiki.openoffice.org/wiki/Documentation/DevGuide/OfficeDev/Properties_of_ -a_Filter). I've included a few isXXX() methods in the Info utilities class for testing for -their presence. The above example reports that the "Clubs" filter contains both an -import and export filter. - - - -## 2. Using Filters with Java - -Java contains an assortment of XML processing capabilities, grouped under the JAXP -(Java API for XML Processing) heading. They include DOM and SAX parsing, XML -schema validation, and XSLT transformation. The JDK's XSLT processor is Xalan -(https://xml.apache.org/xalan-j/), which supports XSLT 1.0, and so is roughly -equivalent to Office's libxslt library. This means that I can use the "Pay" and "Clubs" -filters outside of Office by passing them to Java's Xalan processor. - -There's a lot of information about JAXP online, including in Oracle's tutorial at -https://docs.oracle.com/javase/tutorial/jaxp/. Also "XSLT 2.0 and XPath 2.0 -Programmer's Reference" by Michael Kay, which I mentioned earlier, includes an -appendix on JAXP. - - -### 2.1. Importing XML with Java - -My ApplyInFilter.java example converts an XML file into an Office document in two -steps. First it uses an XSLT import filter to generate Flat XML which it saves to a -temporary file. The program then loads that file into Office with one of its Flat XML -filters. The correct one is chosen by looking at the filename extension supplied for the -final document. For example: -run ApplyInFilter pay.xml payImport.xsl payment.ods -pay.xml is transformed into Flat XML with payImport.xsl. Office isn't used since -Java's XSLT processor is sufficient. However, at the next step the temporary file is -loaded using Office's "Flat XML for Spreadsheets" filter. This filter is selected by -noting the "ods" extension of payment.ods. - -Another example: -run ApplyInFilter clubs.xml clubsImport.xsl clubs.odt -This converts clubs.xml into a Writer document stored in clubs.odt using the "Clubs" -import filter. ApplyInFilter.java doesn't support templates, so the data saved to -clubs.odt isn't nicely formatted like Figure 10. - -The ApplyInFilter program: - -public class ApplyInFilter -{ - public static void main(String[] args) - { - if (args.length != 3) { - System.out.println("Usage: java ApplyInFilter - "); - return; - } - - // convert the data to Flat XML - String xmlStr = XML.applyXSLT(args[0], args[1]); - if (xmlStr == null) { - System.out.println("Filtering failed"); - return; - } - - // save flat XML data in a temp file - String flatFnm = FileIO.createTempFile("xml"); - FileIO.saveString(flatFnm, xmlStr); - - XComponentLoader loader = Lo.loadOffice(); - - // get the type of the output file - String odfFnm = args[2]; - String docType = Lo.ext2DocType( Info.getExt(odfFnm)); - System.out.println("Doc type: " + docType); - - // open temp file using the correct Flat XML filter - XComponent doc = Lo.openFlatDoc(flatFnm, docType, loader); - if (doc == null) - System.out.println("Document creation failed"); - else { - GUI.setVisible(doc, true); - Lo.waitEnter(); - Lo.saveDoc(doc, odfFnm); - Lo.closeDoc(doc); - } - Lo.closeOffice(); - } // end of main() - -} // end of ApplyInFilter class - -Java's XSLT processor is called by XML.applyXSLT(): - -// in the XML class -public static String applyXSLT(String xmlFnm, String xslFnm) -{ - try { - TransformerFactory tf = TransformerFactory.newInstance(); - Source xslt = new StreamSource(new File(xslFnm)); - Transformer t = tf.newTransformer(xslt); - - System.out.println("Applying filter " + xslFnm + - " to " + xmlFnm); - Source text = new StreamSource(new File(xmlFnm)); - StreamResult result = new StreamResult(new StringWriter()); - - t.transform(text, result); - return result.getWriter().toString(); - } - catch(Exception e) - { System.out.println("Unable to transform " + xmlFnm + - " with " + xslFnm); - System.out.println(" " + e); - return null; - } -} // end of applyXSLT() - -The resulting Flat XML is saved to a temporary file by ApplyInFilter.java, and then -the document type of the output file is obtained: -// part of ApplyInFilter.java... - -String docType = Lo.ext2DocType( Info.getExt(odfFnm)); -The docType string is "scalc" when the ODT file is payment.ods, and "swriter" for -clubs.odt. - -Lo.openFlatDoc() uses the document type to select the correct Flat XML filter, and -passes it to Lo.openDoc() as the "FilterName" property: - -// in the Lo class -public static XComponent openFlatDoc(String fnm, String docType, - XComponentLoader loader) -{ String nm = XML.getFlatFilterName(docType); - return openDoc(fnm, loader, Props.makeProps("FilterName", nm)); -} - -XML.getFlatFilterName() maps a document type to an appropriate Flat XML filter -name: - -// in the XML class -public static String getFlatFilterName(String docType) -{ - if (docType == Lo.WRITER_STR) - return "OpenDocument Text Flat XML"; - else if (docType == Lo.CALC_STR) - return "OpenDocument Spreadsheet Flat XML"; - else if (docType == Lo.DRAW_STR) - return "OpenDocument Drawing Flat XML"; - else if (docType == Lo.IMPRESS_STR) - return "OpenDocument Presentation Flat XML"; - else { - System.out.println("No Flat XML filter for this - document type; using Flat text"); - return "OpenDocument Text Flat XML"; - } -} // end of getFlatFilterName() - - -### 2.2. Exporting XML with Java - -My ApplyOutFilter.java example saves the specified document in Flat XML form to a -temporary file. Then it applies an XSLT output filter to transform it into simple XML, -which is saved in a new file. For example: -run ApplyOutFilter payment.ods payExport.xsl payEx.xml -payment.ods contains the spreadsheet shown in Figure 6, and the transformation fills -payEx.xml with text almost identical to that on the first page of the chapter. - -Another example: -run ApplyOutFilter clubs.odt clubsExport.xsl clubsEx.xml -clubs.odt must contain data formatted by the clubs template (i.e. like the example in -Figure 10). This is necessary because clubsExport.xsl utilizes paragraph styles to -decide how to change the text. - -I had to slightly modify the style names used in Eisenberg's export filter. He utilized -names containing spaces (e.g. "Club Name", "Club Code", "Age Groups", and "Club -Info"), but Office automatically changes spaces inside names to "_20_". So I had to -change the XSLT rules to refer to these names (i.e. "Club_20_Name", -"Club_20_Code", "Age_20_Groups", and "Club_20_Info"). - -The ApplyOutFilter program: - -public class ApplyOutFilter -{ - public static void main(String[] args) - { - if (args.length != 3) { - System.out.println("Usage: java ApplyOutFilter - "); - return; - } - - XComponentLoader loader = Lo.loadOffice(); - XComponent doc = Lo.openDoc(args[0], loader); - if (doc == null) { - System.out.println("Could not open document: " + args[0]); - Lo.closeOffice(); - return; - } - - // save flat XML data - String flatFnm = FileIO.createTempFile("xml"); - Lo.saveDoc(doc, flatFnm); - Lo.closeDoc(doc); - - // use XSLT to convert Flat XML into simple XML - String filteredXML = XML.applyXSLT(flatFnm, args[1]); - if (filteredXML == null) - System.out.println("Filtering failed"); - else { - // indent, print, and save - String xmlStr = XML.indent2Str(filteredXML); - System.out.println(xmlStr); - FileIO.saveString(args[2], xmlStr); - } - - Lo.closeOffice(); - } // end of main() - - -} // end of ApplyOutFilter class - -At the end of ApplyOutFilter.java, the XML text in xmlStr is indented and printed. - -The indention is carried out by XML.indent2Str() which calls XML.applyXSLT2str() -with an indenting transformation loaded from indent.xsl: - -// in the XML class -// global -private static final String INDENT_FNM = "indent.xsl"; - // for indenting XML tags, and adding newlines between tags - - -public static String indent2Str(String xmlStr) -{ return applyXSLT2str(xmlStr, - FileIO.getUtilsFolder()+INDENT_FNM); -} - -applyXSLT2str() is a variant of XML.applyXSLT() which reads XML from a string -rather than a file. - - - -## 3. Alternatives to XSLT Filters - -The obvious drawback of the XSLT filter approach is that filter writing requires the -programmer to be knowledgable about XSLT, XPath, and the details of the Flat XML -format. - -Another way to write import and export filter is to utilize the ImportFilter and -ExportFilter services in the com.sun.star.document module, which are shown in +a_Filter). I've included a few isXXX() methods in the Info utilities class for testing for +their presence. The above example reports that the "Clubs" filter contains both an +import and export filter. + + +## 2. Using Filters with Java + +Java contains an assortment of XML processing capabilities, grouped under the JAXP +(Java API for XML Processing) heading. They include DOM and SAX parsing, XML +schema validation, and XSLT transformation. The JDK's XSLT processor is Xalan +(https://xml.apache.org/xalan-j/), which supports XSLT 1.0, and so is roughly +equivalent to Office's libxslt library. This means that I can use the "Pay" and "Clubs" +filters outside of Office by passing them to Java's Xalan processor. + +There's a lot of information about JAXP online, including in Oracle's tutorial at +https://docs.oracle.com/javase/tutorial/jaxp/. Also "XSLT 2.0 and XPath 2.0 +Programmer's Reference" by Michael Kay, which I mentioned earlier, includes an +appendix on JAXP. + + +### 2.1. Importing XML with Java + +My ApplyInFilter.java example converts an XML file into an Office document in two +steps. First it uses an XSLT import filter to generate Flat XML which it saves to a +temporary file. The program then loads that file into Office with one of its Flat XML +filters. The correct one is chosen by looking at the filename extension supplied for the +final document. For example: + +``` +run ApplyInFilter pay.xml payImport.xsl payment.ods +``` + +pay.xml is transformed into Flat XML with payImport.xsl. Office isn't used since +Java's XSLT processor is sufficient. However, at the next step the temporary file is +loaded using Office's "Flat XML for Spreadsheets" filter. This filter is selected by +noting the "ods" extension of payment.ods. + +Another example: + +``` +run ApplyInFilter clubs.xml clubsImport.xsl clubs.odt +``` + +This converts clubs.xml into a Writer document stored in clubs.odt using the "Clubs" +import filter. ApplyInFilter.java doesn't support templates, so the data saved to +clubs.odt isn't nicely formatted like Figure 10. + +The ApplyInFilter program: + +```java +public class ApplyInFilter +{ + public static void main(String[] args) + { + if (args.length != 3) { + System.out.println("Usage: java ApplyInFilter + "); + return; + } + + // convert the data to Flat XML + String xmlStr = XML.applyXSLT(args[0], args[1]); + if (xmlStr == null) { + System.out.println("Filtering failed"); + return; + } + + // save flat XML data in a temp file + String flatFnm = FileIO.createTempFile("xml"); + FileIO.saveString(flatFnm, xmlStr); + + XComponentLoader loader = Lo.loadOffice(); + + // get the type of the output file + String odfFnm = args[2]; + String docType = Lo.ext2DocType( Info.getExt(odfFnm)); + System.out.println("Doc type: " + docType); + + // open temp file using the correct Flat XML filter + XComponent doc = Lo.openFlatDoc(flatFnm, docType, loader); + if (doc == null) + System.out.println("Document creation failed"); + else { + GUI.setVisible(doc, true); + Lo.waitEnter(); + Lo.saveDoc(doc, odfFnm); + Lo.closeDoc(doc); + } + Lo.closeOffice(); + } // end of main() + +} // end of ApplyInFilter class + +Java's XSLT processor is called by XML.applyXSLT(): + +// in the XML class +public static String applyXSLT(String xmlFnm, String xslFnm) +{ + try { + TransformerFactory tf = TransformerFactory.newInstance(); + Source xslt = new StreamSource(new File(xslFnm)); + Transformer t = tf.newTransformer(xslt); + + System.out.println("Applying filter " + xslFnm + + " to " + xmlFnm); + Source text = new StreamSource(new File(xmlFnm)); + StreamResult result = new StreamResult(new StringWriter()); + + t.transform(text, result); + return result.getWriter().toString(); + } + catch(Exception e) + { System.out.println("Unable to transform " + xmlFnm + + " with " + xslFnm); + System.out.println(" " + e); + return null; + } +} // end of applyXSLT() +``` + +The resulting Flat XML is saved to a temporary file by ApplyInFilter.java, and then +the document type of the output file is obtained: + +```java +// part of ApplyInFilter.java... +String docType = Lo.ext2DocType( Info.getExt(odfFnm)); +``` + +The docType string is "scalc" when the ODT file is payment.ods, and "swriter" for +clubs.odt. + +Lo.openFlatDoc() uses the document type to select the correct Flat XML filter, and +passes it to Lo.openDoc() as the "FilterName" property: + +```java +// in the Lo class +public static XComponent openFlatDoc(String fnm, String docType, + XComponentLoader loader) +{ String nm = XML.getFlatFilterName(docType); + return openDoc(fnm, loader, Props.makeProps("FilterName", nm)); +} +``` + +XML.getFlatFilterName() maps a document type to an appropriate Flat XML filter +name: + +```java +// in the XML class +public static String getFlatFilterName(String docType) +{ + if (docType == Lo.WRITER_STR) + return "OpenDocument Text Flat XML"; + else if (docType == Lo.CALC_STR) + return "OpenDocument Spreadsheet Flat XML"; + else if (docType == Lo.DRAW_STR) + return "OpenDocument Drawing Flat XML"; + else if (docType == Lo.IMPRESS_STR) + return "OpenDocument Presentation Flat XML"; + else { + System.out.println("No Flat XML filter for this + document type; using Flat text"); + return "OpenDocument Text Flat XML"; + } +} // end of getFlatFilterName() +``` + + +### 2.2. Exporting XML with Java + +My ApplyOutFilter.java example saves the specified document in Flat XML form to a +temporary file. Then it applies an XSLT output filter to transform it into simple XML, +which is saved in a new file. For example: + +``` +run ApplyOutFilter payment.ods payExport.xsl payEx.xml +``` + +payment.ods contains the spreadsheet shown in Figure 6, and the transformation fills +payEx.xml with text almost identical to that on the first page of the chapter. + +Another example: + +``` +run ApplyOutFilter clubs.odt clubsExport.xsl clubsEx.xml +``` + +clubs.odt must contain data formatted by the clubs template (i.e. like the example in +Figure 10). This is necessary because clubsExport.xsl utilizes paragraph styles to +decide how to change the text. + +I had to slightly modify the style names used in Eisenberg's export filter. He utilized +names containing spaces (e.g. "Club Name", "Club Code", "Age Groups", and "Club +Info"), but Office automatically changes spaces inside names to "_20_". So I had to +change the XSLT rules to refer to these names (i.e. "Club_20_Name", +"Club_20_Code", "Age_20_Groups", and "Club_20_Info"). + +```java +The ApplyOutFilter program: + +public class ApplyOutFilter +{ + public static void main(String[] args) + { + if (args.length != 3) { + System.out.println("Usage: java ApplyOutFilter + "); + return; + } + + XComponentLoader loader = Lo.loadOffice(); + XComponent doc = Lo.openDoc(args[0], loader); + if (doc == null) { + System.out.println("Could not open document: " + args[0]); + Lo.closeOffice(); + return; + } + + // save flat XML data + String flatFnm = FileIO.createTempFile("xml"); + Lo.saveDoc(doc, flatFnm); + Lo.closeDoc(doc); + + // use XSLT to convert Flat XML into simple XML + String filteredXML = XML.applyXSLT(flatFnm, args[1]); + if (filteredXML == null) + System.out.println("Filtering failed"); + else { + // indent, print, and save + String xmlStr = XML.indent2Str(filteredXML); + System.out.println(xmlStr); + FileIO.saveString(args[2], xmlStr); + } + + Lo.closeOffice(); + } // end of main() + + +} // end of ApplyOutFilter class +``` + +At the end of ApplyOutFilter.java, the XML text in xmlStr is indented and printed. +The indention is carried out by XML.indent2Str() which calls XML.applyXSLT2str() +with an indenting transformation loaded from indent.xsl: + +```java +// in the XML class +// global +private static final String INDENT_FNM = "indent.xsl"; + // for indenting XML tags, and adding newlines between tags + + +public static String indent2Str(String xmlStr) +{ return applyXSLT2str(xmlStr, + FileIO.getUtilsFolder()+INDENT_FNM); +} +``` + +applyXSLT2str() is a variant of XML.applyXSLT() which reads XML from a string +rather than a file. -![](images/50-Importing_XML-11.png) -Figure 11. +## 3. Alternatives to XSLT Filters + +The obvious drawback of the XSLT filter approach is that filter writing requires the +programmer to be knowledgable about XSLT, XPath, and the details of the Flat XML +format. + +Another way to write import and export filter is to utilize the ImportFilter and +ExportFilter services in the com.sun.star.document module, which are shown in +Figure 11. - - ![](images/50-Importing_XML-11.png) -Figure 11. The ImportFilter and ExportFilter Services. +Figure 11. The ImportFilter and ExportFilter Services. + - -These services allow the implementation of non-XML based transformations by -utilizing the ImportFilter and ExportFilter services rather than their XMLImportFilter -and XMLExportFilter subclasses. The subclasses employ SAX, an event-driven way -of parsing XML. +These services allow the implementation of non-XML based transformations by +utilizing the ImportFilter and ExportFilter services rather than their XMLImportFilter +and XMLExportFilter subclasses. The subclasses employ SAX, an event-driven way +of parsing XML. -If you're interested in using ImportFilter and ExportFilter, the Developer's guide gives -some details in the "Integrating Import and Export Filters" section of chapter 6 on -"Office Development". The information is also online, starting at +If you're interested in using ImportFilter and ExportFilter, the Developer's guide gives +some details in the "Integrating Import and Export Filters" section of chapter 6 on +"Office Development". The information is also online, starting at https://wiki.openoffice.org/wiki/Documentation/DevGuide/OfficeDev/Integrating_Im -port_and_Export_Filters, or use loGuide "Import and Export Filters". +port_and_Export_Filters, or use loGuide "Import and Export Filters". -But I'm not going to use these services due to their complexity, and I'm about to stop -using XSLT as well. Instead I'm going to look at three easier ways to import XML -into Office: -## 1. Data extraction by DOM parsing; +But I'm not going to use these services due to their complexity, and I'm about to stop +using XSLT as well. Instead I'm going to look at three easier ways to import XML +into Office: -## 2. Node and attribute data extraction as labeled strings; +1. Data extraction by DOM parsing; +2. Node and attribute data extraction as labeled strings; +3. JAXB conversion of XML to Java objects. -## 3. JAXB conversion of XML to Java objects. +The drawback of these simpler approaches is that the imported data will usually need +some post-processing to make it look as good as import filter results. -The drawback of these simpler approaches is that the imported data will usually need -some post-processing to make it look as good as import filter results. +### 3.1. Data Extraction by DOM Parsing - - -### 3.1. Data Extraction by DOM Parsing +Document Object Model (DOM) parsing converts an XML document into a tree of +nodes; the three main types are: -Document Object Model (DOM) parsing converts an XML document into a tree of -nodes; the three main types are: - Elements; - Attributes; - The data/values held by the elements and attributes. +* Elements; +* Attributes; +* The data/values held by the elements and attributes. -The DOM API is quite low-level, supporting functions such as getFirstChild() and -getNextSibling() in Java's Node class. This motivated the introduction of XPath, and -other tree models such as JDOM. Fortunately, I won't be needing those more -advanced features. +The DOM API is quite low-level, supporting functions such as getFirstChild() and +getNextSibling() in Java's Node class. This motivated the introduction of XPath, and +other tree models such as JDOM. Fortunately, I won't be needing those more +advanced features. -Nodes are found by searching for their name. When a possible match is discovered, -the node's children usually need to be examined to determine the node type. For -instance, node data has the type Node.TEXT_NODE, while an attribute has the type -Node.ATTRIBUTE. +Nodes are found by searching for their name. When a possible match is discovered, +the node's children usually need to be examined to determine the node type. For +instance, node data has the type Node.TEXT_NODE, while an attribute has the type +Node.ATTRIBUTE. -There are many online tutorials on Java and DOM, such as Oracle's at -https://docs.oracle.com/javase/tutorial/jaxp/dom. Two other good ones are mkyong's -starting at http://www.mkyong.com/tutorials/java-xml-tutorials/ and "Easy DOM +There are many online tutorials on Java and DOM, such as Oracle's at +https://docs.oracle.com/javase/tutorial/jaxp/dom. Two other good ones are mkyong's +starting at http://www.mkyong.com/tutorials/java-xml-tutorials/ and "Easy DOM Parsing in Java" by Eric Bruno at http://www.drdobbs.com/jvm/easy-dom-parsing-in- -java/231002580. I've 'borrowed' some of Bruno's DOM functions for my XML.java -support class, and his company.xml example. - -A textbook on Java and XML: -Pro XML Development with Java Technology -Ajay and Deepak Vohra -Apress, 2006 -The company.xml file contains details about three companies: - - - - - ABC - - Smith - Jim - 123 Broad Street - Manchester - Cheshire - 11234 - - - - - NBC - - Jones - Lucy - 23 Bradford St - Asbury - Lincs - 33451 - - - - - BBC - - Singh - Oxley - 16d Towers - Wimbledon - London - 77392 - - - - -When writing DOM code, it helps to visualize its structure. One way is to load the file -into the editor at http://xmlgrid.net/, which displays the tree-like structure in Figure -12. - - - +java/231002580. I've 'borrowed' some of Bruno's DOM functions for my XML.java +support class, and his company.xml example. + +A textbook on Java and XML: + +Pro XML Development with Java Technology +Ajay and Deepak Vohra +Apress, 2006 + +The company.xml file contains details about three companies: + +``` + + + + ABC + + Smith + Jim + 123 Broad Street + Manchester + Cheshire + 11234 + + + + + NBC + + Jones + Lucy + 23 Bradford St + Asbury + Lincs + 33451 + + + + + BBC + + Singh + Oxley + 16d Towers + Wimbledon + London + 77392 + + + +``` + +When writing DOM code, it helps to visualize its structure. One way is to load the file +into the editor at http://xmlgrid.net/, which displays the tree-like structure in Figure +12. + ![](images/50-Importing_XML-12.png) -Figure 12. The Tree Structure of company.xml. - - -Clicking on the arrow heads expand or contract the tree view. - -My ExamineCompany.java example loads this data as a DOM tree, and extracts -various information: - -// in ExamineCompany.java -public class ExamineCompany -{ - public static void main(String[] args) throws Exception - { - Document doc = XML.loadDoc("company.xml"); - NodeList root = doc.getChildNodes(); // get the document's root - - // move down the tree to the executive in the first company node - Node comps = XML.getNode("Companies", root); - Node comp = XML.getNode("Company", comps.getChildNodes()); - Node exec = XML.getNode("Executive", comp.getChildNodes()); - - // print the executive's data - String execType = XML.getNodeAttr("type", exec); - NodeList exNodes = exec.getChildNodes(); - String lastName = XML.getNodeValue("LastName", exNodes); - String firstName = XML.getNodeValue("FirstName", exNodes); - String street = XML.getNodeValue("street", exNodes); - String city = XML.getNodeValue("city", exNodes); - String state = XML.getNodeValue("state", exNodes); - String zip = XML.getNodeValue("zip", exNodes); - - System.out.println(execType); - System.out.println(lastName + ", " + firstName); - System.out.println(street); - System.out.println(city + ", " + state + " " + zip); - - // get all the data in the tree for a given node/tag name - NodeList lnNodes = doc.getElementsByTagName("LastName"); - ArrayList lastnames = XML.getNodeValues(lnNodes); - System.out.println("All lastnames:"); - for(String lastname: lastnames) - System.out.println(" " + lastname); - } // end of main() -} // end of ExamineCompany class - -The program outputs details about the first company, and the lastnames of all the -company bosses: - -CEO -Smith, Jim -123 Broad Street -Manchester, Cheshire 11234 -All lastnames: - Smith - Jones - Singh - -XML.getNode() searches through a list of nodes, and returns the first with the -specified tag name: - -// in the XML class -public static Node getNode(String tagName, NodeList nodes) -{ - for (int i = 0; i < nodes.getLength(); i++) { - Node node = nodes.item(i); - if (node.getNodeName().equalsIgnoreCase(tagName)) - return node; - } - return null; -} // end of getNode() - -XML.getNodeValue() looks for a node in a list based on its tag name, and extracts the -data stored beneath that node. - - -// in the XML class -public static String getNodeValue(String tagName, NodeList nodes) -{ - if (nodes == null) - return ""; - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n.getNodeName().equalsIgnoreCase(tagName)) - return getNodeValue(n); - } - return ""; -} // end of getNodeValue() - -The second version of XML.getNodeValue() retrieves the text from a node's -TEXT_NODE child (if there is one): - -// in the XML class -public static String getNodeValue(Node node) -{ - if (node == null) - return ""; - NodeList childNodes = node.getChildNodes(); - for (int i = 0; i < childNodes.getLength(); i++) { - Node n = childNodes.item(i); - if (n.getNodeType() == Node.TEXT_NODE) - return n.getNodeValue().trim(); - } - return ""; -} // end of getNodeValue() - -XML.getNodeValues() constructs a list of the data stored in all the supplied nodes: - -// in the XML class -public static ArrayList getNodeValues(NodeList nodes) -{ - if (nodes == null) - return null; - ArrayList vals = new ArrayList(); - for (int i = 0; i < nodes.getLength(); i++) { - String val = getNodeValue(nodes.item(i)); - if (val != null) - vals.add(val); - } - return vals; -} // end of getNodeValues() - -XML.getNodeAttr() extracts data from a node's attribute: - -// in the XML class -public static String getNodeAttr(String attrName, Node node) -{ - if (node == null) - return ""; - NamedNodeMap attrs = node.getAttributes(); - if (attrs == null) - return ""; - for (int i = 0; i < attrs.getLength(); i++) { - Node attr = attrs.item(i); - if (attr.getNodeName().equalsIgnoreCase(attrName)) - return attr.getNodeValue().trim(); - } - return ""; -} // end of getNodeAttr() - - -Converting XML to Spreadsheet Data -It's fairly easy to map XML data into a spreadsheet format of rows and columns. - -Consider pay.xml from earlier: - - - - - CD - 12.95 - 19.1234 - 2008-03-01 - - - - DVD - 19.95 - 19.4321 - 2008-03-02 - - - - Clothes - 99.95 - 18.5678 - 2008-03-03 - - - - Book - 9.49 - 18.9876 - 2008-03-04 - - - -It can be viewed as a sequence of payment objects, each containing four fields -(purpose, amount, tax, and maturity). Each payment object can be mapped to a -spreadsheet row, and its fields to four columns in that row. This format can be -represented by a 2D array, which is easily constructed using the DOM API. - -My CreatePay.java example utilizes XML.getAllNodeValues() to build a 2D array -from the pay.xml data, and calls Calc.setArray() to add the data to a new Calc -document: - -// in CreatePay.java -public class CreatePay -{ - public static void main(String args[]) - { - Document xdoc = XML.loadDoc("pay.xml"); - NodeList pays = xdoc.getElementsByTagName("payment"); - if (pays == null) - return; - - Object[][] data = XML.getAllNodeValues(pays, - new String[]{"purpose", "amount", "tax", "maturity"}); - Lo.printTable("payments", data); - - XComponentLoader loader = Lo.loadOffice(); - XSpreadsheetDocument doc = Calc.createDoc(loader); - if (doc == null) { - System.out.println("Document creation failed"); - Lo.closeOffice(); - return; - } - GUI.setVisible(doc, true); - XSpreadsheet sheet = Calc.getSheet(doc, 0); - - Calc.setArray(sheet, "A1", data); - - // Lo.saveDoc(doc, "payCreated.ods"); - Lo.waitEnter(); - Lo.closeDoc(doc); - Lo.closeOffice(); - } // end of main() - -} // end of CreatePay class - -The list of payment nodes is obtained using: -// part of CreatePay.java... - -NodeList pays = xdoc.getElementsByTagName("payment"); -The 2D array of payment data is constructed by: - -// part of CreatePay.java... - -Object[][] data = XML.getAllNodeValues(pays, - new String[]{"purpose", "amount", "tax", "maturity"}); - -The first argument of getAllNodeValues() is the list of nodes to be scanned, and the -second parameter is an array of tag names. The named nodes are assumed to be -children of each node in the list, and their data becomes one row in the 2D array. The -method's code: - -// in the XML class -public static Object[][] getAllNodeValues(NodeList rowNodes, - String[] colIDs) -{ int numRows = rowNodes.getLength(); - int numCols = colIDs.length; - Object[][] data = new Object[numRows+1][numCols]; - - // put column names in first row of array - for (int col = 0; col < numCols; col++) - data[0][col] = Lo.capitalize( colIDs[col]); - - for (int i = 0; i < numRows; i++) { - // extract column data for ith row - NodeList colNodes = rowNodes.item(i).getChildNodes(); - for (int col = 0; col < numCols; col++) - data[i+1][col] = getNodeValue(colIDs[col], colNodes); - } - return data; -} // end of getAllNodeValues() - -XML.getAllNodeValues() also adds a header row to the array made up of the tag -names. - -The resulting table is printing by Lo.printTables(): - --- payments ---------------- - Purpose Amount Tax Maturity - CD 12.95 19.1234 2008-03-01 - DVD 19.95 19.4321 2008-03-02 - Clothes 99.95 18.5678 2008-03-03 - Book 9.49 18.9876 2008-03-04 ------------------------------ - -Calc.setArray() adds the table to the spreadsheet, which ends up like Figure 13. - - - +Figure 12. The Tree Structure of company.xml. + + +Clicking on the arrow heads expand or contract the tree view. + +My ExamineCompany.java example loads this data as a DOM tree, and extracts +various information: + +```java +// in ExamineCompany.java +public class ExamineCompany +{ + public static void main(String[] args) throws Exception + { + Document doc = XML.loadDoc("company.xml"); + NodeList root = doc.getChildNodes(); // get the document's root + + // move down the tree to the executive in the first company node + Node comps = XML.getNode("Companies", root); + Node comp = XML.getNode("Company", comps.getChildNodes()); + Node exec = XML.getNode("Executive", comp.getChildNodes()); + + // print the executive's data + String execType = XML.getNodeAttr("type", exec); + NodeList exNodes = exec.getChildNodes(); + String lastName = XML.getNodeValue("LastName", exNodes); + String firstName = XML.getNodeValue("FirstName", exNodes); + String street = XML.getNodeValue("street", exNodes); + String city = XML.getNodeValue("city", exNodes); + String state = XML.getNodeValue("state", exNodes); + String zip = XML.getNodeValue("zip", exNodes); + + System.out.println(execType); + System.out.println(lastName + ", " + firstName); + System.out.println(street); + System.out.println(city + ", " + state + " " + zip); + + // get all the data in the tree for a given node/tag name + NodeList lnNodes = doc.getElementsByTagName("LastName"); + ArrayList lastnames = XML.getNodeValues(lnNodes); + System.out.println("All lastnames:"); + for(String lastname: lastnames) + System.out.println(" " + lastname); + } // end of main() +} // end of ExamineCompany class +``` + +The program outputs details about the first company, and the lastnames of all the +company bosses: + +``` +CEO +Smith, Jim +123 Broad Street +Manchester, Cheshire 11234 +All lastnames: + Smith + Jones + Singh +``` + +XML.getNode() searches through a list of nodes, and returns the first with the +specified tag name: + +```java +// in the XML class +public static Node getNode(String tagName, NodeList nodes) +{ + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + if (node.getNodeName().equalsIgnoreCase(tagName)) + return node; + } + return null; +} // end of getNode() +``` + +XML.getNodeValue() looks for a node in a list based on its tag name, and extracts the +data stored beneath that node. + +```java +// in the XML class +public static String getNodeValue(String tagName, NodeList nodes) +{ + if (nodes == null) + return ""; + for (int i = 0; i < nodes.getLength(); i++) { + Node n = nodes.item(i); + if (n.getNodeName().equalsIgnoreCase(tagName)) + return getNodeValue(n); + } + return ""; +} // end of getNodeValue() +``` + +The second version of XML.getNodeValue() retrieves the text from a node's +TEXT_NODE child (if there is one): + +```java +// in the XML class +public static String getNodeValue(Node node) +{ + if (node == null) + return ""; + NodeList childNodes = node.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node n = childNodes.item(i); + if (n.getNodeType() == Node.TEXT_NODE) + return n.getNodeValue().trim(); + } + return ""; +} // end of getNodeValue() +``` + +XML.getNodeValues() constructs a list of the data stored in all the supplied nodes: + +```java +// in the XML class +public static ArrayList getNodeValues(NodeList nodes) +{ + if (nodes == null) + return null; + ArrayList vals = new ArrayList(); + for (int i = 0; i < nodes.getLength(); i++) { + String val = getNodeValue(nodes.item(i)); + if (val != null) + vals.add(val); + } + return vals; +} // end of getNodeValues() +``` + +XML.getNodeAttr() extracts data from a node's attribute: + +```java +// in the XML class +public static String getNodeAttr(String attrName, Node node) +{ + if (node == null) + return ""; + NamedNodeMap attrs = node.getAttributes(); + if (attrs == null) + return ""; + for (int i = 0; i < attrs.getLength(); i++) { + Node attr = attrs.item(i); + if (attr.getNodeName().equalsIgnoreCase(attrName)) + return attr.getNodeValue().trim(); + } + return ""; +} // end of getNodeAttr() +``` + +Converting XML to Spreadsheet Data +It's fairly easy to map XML data into a spreadsheet format of rows and columns. + +Consider pay.xml from earlier: + +``` + + + + CD + 12.95 + 19.1234 + 2008-03-01 + + + + DVD + 19.95 + 19.4321 + 2008-03-02 + + + + Clothes + 99.95 + 18.5678 + 2008-03-03 + + + + Book + 9.49 + 18.9876 + 2008-03-04 + + +``` + +It can be viewed as a sequence of payment objects, each containing four fields +(purpose, amount, tax, and maturity). Each payment object can be mapped to a +spreadsheet row, and its fields to four columns in that row. This format can be +represented by a 2D array, which is easily constructed using the DOM API. + +My CreatePay.java example utilizes XML.getAllNodeValues() to build a 2D array +from the pay.xml data, and calls Calc.setArray() to add the data to a new Calc +document: + +```java +// in CreatePay.java +public class CreatePay +{ + public static void main(String args[]) + { + Document xdoc = XML.loadDoc("pay.xml"); + NodeList pays = xdoc.getElementsByTagName("payment"); + if (pays == null) + return; + + Object[][] data = XML.getAllNodeValues(pays, + new String[]{"purpose", "amount", "tax", "maturity"}); + Lo.printTable("payments", data); + + XComponentLoader loader = Lo.loadOffice(); + XSpreadsheetDocument doc = Calc.createDoc(loader); + if (doc == null) { + System.out.println("Document creation failed"); + Lo.closeOffice(); + return; + } + GUI.setVisible(doc, true); + XSpreadsheet sheet = Calc.getSheet(doc, 0); + + Calc.setArray(sheet, "A1", data); + + // Lo.saveDoc(doc, "payCreated.ods"); + Lo.waitEnter(); + Lo.closeDoc(doc); + Lo.closeOffice(); + } // end of main() + +} // end of CreatePay class +``` + +The list of payment nodes is obtained using: + +```java +// part of CreatePay.java... + +NodeList pays = xdoc.getElementsByTagName("payment"); +The 2D array of payment data is constructed by: + +// part of CreatePay.java... + +Object[][] data = XML.getAllNodeValues(pays, + new String[]{"purpose", "amount", "tax", "maturity"}); +``` + +The first argument of getAllNodeValues() is the list of nodes to be scanned, and the +second parameter is an array of tag names. The named nodes are assumed to be +children of each node in the list, and their data becomes one row in the 2D array. The +method's code: + +```java +// in the XML class +public static Object[][] getAllNodeValues(NodeList rowNodes, + String[] colIDs) +{ int numRows = rowNodes.getLength(); + int numCols = colIDs.length; + Object[][] data = new Object[numRows+1][numCols]; + + // put column names in first row of array + for (int col = 0; col < numCols; col++) + data[0][col] = Lo.capitalize( colIDs[col]); + + for (int i = 0; i < numRows; i++) { + // extract column data for ith row + NodeList colNodes = rowNodes.item(i).getChildNodes(); + for (int col = 0; col < numCols; col++) + data[i+1][col] = getNodeValue(colIDs[col], colNodes); + } + return data; +} // end of getAllNodeValues() +``` + +XML.getAllNodeValues() also adds a header row to the array made up of the tag +names. + +The resulting table is printing by Lo.printTables(): + +``` +-- payments ---------------- + Purpose Amount Tax Maturity + CD 12.95 19.1234 2008-03-01 + DVD 19.95 19.4321 2008-03-02 + Clothes 99.95 18.5678 2008-03-03 + Book 9.49 18.9876 2008-03-04 +----------------------------- +``` + +Calc.setArray() adds the table to the spreadsheet, which ends up like Figure 13. + ![](images/50-Importing_XML-13.png) -Figure 13. The Spreadsheet Generated by CreatePay.java. - - -XML.getAllNodeValues() doesn't always produce such good results, as illustrated by -the CreateAssoc.java example which converts part of clubs.xml into a spreadsheet. - -As explained earlier, clubs.xml consists of a sequence of associations, with each one -made up of a sequence of clubs. The most natural spreadsheet mapping would be to -assign each association to its own sheet, and convert each club into a row on that -sheet. - -A club consists of seven elements and two attributes, as seen in the following -example: - - - Castro Valley Wrestling Club - Ron Maes - Castro Valley - 510-555-1491 - cvwcron@example.com - - Practices every Tuesday and Thursday at 5:00 P.M. - - at Castro Valley High School mat room. - - -Probably the best spreadsheet representation would be to use nine columns, so the -attributes could be listed. This approach is employed by the xmlgrid.net website as -shown in Figure 14. - - - +Figure 13. The Spreadsheet Generated by CreatePay.java. + + +XML.getAllNodeValues() doesn't always produce such good results, as illustrated by +the CreateAssoc.java example which converts part of clubs.xml into a spreadsheet. + +As explained earlier, clubs.xml consists of a sequence of associations, with each one +made up of a sequence of clubs. The most natural spreadsheet mapping would be to +assign each association to its own sheet, and convert each club into a row on that +sheet. + +A club consists of seven elements and two attributes, as seen in the following +example: + +``` + + Castro Valley Wrestling Club + Ron Maes + Castro Valley + 510-555-1491 + cvwcron@example.com + + Practices every Tuesday and Thursday at 5:00 P.M. + + at Castro Valley High School mat room. + +``` + +Probably the best spreadsheet representation would be to use nine columns, so the +attributes could be listed. This approach is employed by the xmlgrid.net website as +shown in Figure 14. + ![](images/50-Importing_XML-14.png) -Figure 14. Part of the Clubs Information. - - -Unfortunately, XML.getAllNodeValues() as currently coded doesn't extract attribute -information, so the id and charter data will be missed. - -The CreateAssoc.java example loads clubs.xml, and selects the first association. Its -list of clubs is passed to XML.getAllNodeValues() for conversion into an array. The -complete program is: - -// in CreateAssoc.java -public class CreateAssoc -{ - public static void main(String args[]) - { - Document xdoc = XML.loadDoc("clubs.xml"); - NodeList root = xdoc.getChildNodes(); - - // get the first association - Node cdb = XML.getNode("club-database", root); - Node assoc1 = XML.getNode("association", cdb.getChildNodes()); - NodeList clubs = assoc1.getChildNodes(); - - // convert clubs information into an array - Object[][] data = XML.getAllNodeValues(clubs, - new String[]{"name", "contact", "location", "phone", "email"}); - Lo.printTable("clubs", data); - - XComponentLoader loader = Lo.loadOffice(); - XSpreadsheetDocument doc = Calc.createDoc(loader); - if (doc == null) { - System.out.println("Document creation failed"); - Lo.closeOffice(); - return; - } - GUI.setVisible(doc, true); - XSpreadsheet sheet = Calc.getSheet(doc, 0); - Calc.setArray(sheet, "A1", data); - - // Lo.saveDoc(doc, "clubsCreated.ods"); - Lo.waitEnter(); - Lo.closeDoc(doc); - Lo.closeOffice(); - } // end of main() - -} // end of CreateAssoc class - -XML.getAllNodeValues() only requests data for five of the seven elements ("name", -"contact", "location", "phone", "email") to reduce the information returned. The -resulting spreadsheet is shown in Figure 15. - - - +Figure 14. Part of the Clubs Information. + + +Unfortunately, XML.getAllNodeValues() as currently coded doesn't extract attribute +information, so the id and charter data will be missed. + +The CreateAssoc.java example loads clubs.xml, and selects the first association. Its +list of clubs is passed to XML.getAllNodeValues() for conversion into an array. The +complete program is: + +```java +// in CreateAssoc.java +public class CreateAssoc +{ + public static void main(String args[]) + { + Document xdoc = XML.loadDoc("clubs.xml"); + NodeList root = xdoc.getChildNodes(); + + // get the first association + Node cdb = XML.getNode("club-database", root); + Node assoc1 = XML.getNode("association", cdb.getChildNodes()); + NodeList clubs = assoc1.getChildNodes(); + + // convert clubs information into an array + Object[][] data = XML.getAllNodeValues(clubs, + new String[]{"name", "contact", "location", "phone", "email"}); + Lo.printTable("clubs", data); + + XComponentLoader loader = Lo.loadOffice(); + XSpreadsheetDocument doc = Calc.createDoc(loader); + if (doc == null) { + System.out.println("Document creation failed"); + Lo.closeOffice(); + return; + } + GUI.setVisible(doc, true); + XSpreadsheet sheet = Calc.getSheet(doc, 0); + Calc.setArray(sheet, "A1", data); + + // Lo.saveDoc(doc, "clubsCreated.ods"); + Lo.waitEnter(); + Lo.closeDoc(doc); + Lo.closeOffice(); + } // end of main() + +} // end of CreateAssoc class +``` + +XML.getAllNodeValues() only requests data for five of the seven elements ("name", +"contact", "location", "phone", "email") to reduce the information returned. The +resulting spreadsheet is shown in Figure 15. + ![](images/50-Importing_XML-15.png) -Figure 15. Part of the Spreadsheet Generated by CreateAssoc.java. - - -An obvious issue is the empty rows between the clubs data, caused by the simple -XML.getAllNodeValues() implementation. It assumes that every node supplies one -row of data because of its data element child. Howevere, each club also contains -attributes which adds an extra empty row to the output. - - -### 3.2. Node and Attribute Data Extraction as Labeled Strings - -Not all XML data can be so easily mapped to a 2D format, especially collections such -as weather.xml: - - - - - TH - - - - - - - - - - - - - - - - - -This data was downloaded from OpenWeatherMap using the API at -http://api.openweathermap.org. Unlike pay.xml or clubs.xml there's no sequence of -objects that would naturally become rows of a spreadsheet. In addition, the data is -mostly represented by attributes rather than text nodes. - -In situations like this, a good general approach is to convert the XML to simpler text, -removing XML labels except for the element and attribute names. As a result, my -ExtractXMLInfo.java example produces the following output when applied to -weather.xml: - -current - city id= "1610780" name= "Hat Yai" - coord lat= "7.01" lon= "100.48" - country: "TH" - sun rise= "2017-01-01T23:30:31" set= "2017-01-02T11:14:18" - - temperature max= "25" min= "25" unit= "metric" value= "25" - humidity unit= "%" value= "94" - pressure unit= "hPa" value= "1011" - wind - speed name= "Gentle Breeze" value= "3.6" - gusts - direction code= "NNE" name= "North-northeast" value= "30" - - clouds name= "broken clouds" value= "75" - visibility value= "5000" - precipitation mode= "no" - weather icon= "10n" number= "501" value= "moderate rain" - lastupdate value= "2017-01-02T15:30:00" - -Line indentation is retained, and is utilized when the data is loaded into Office. - -As another example, consider pay.xml which ExtractXMLInfo.java converts into: - -payments - payment - purpose: "CD" - amount: "12.95" - tax: "19.1234" - maturity: "2008-03-01" - - payment - purpose: "DVD" - amount: "19.95" - tax: "19.4321" - maturity: "2008-03-02" - - payment - purpose: "Clothes" - amount: "99.95" - tax: "18.5678" - maturity: "2008-03-03" - - payment - purpose: "Book" - amount: "9.49" - tax: "18.9876" - maturity: "2008-03-04" - -The elements and attributes with data are highlighted by adding a ":" or "=" after -their names. Also, the data is always doubly quoted. - -ExtractXMLInfo.java travels over the DOM tree, printing what it finds to a text file: - -// in ExtractXMLInfo.java -public static void main(String[] args) throws Exception -{ - if (args.length != 1) { - System.out.println("Usage: run ExtractXMLInfo "); - return; - } - Document doc = XML.loadDoc(args[0]); - if (doc == null) - return; - - String fname = Info.getName(args[0]); - String outFnm = fname + "XML.txt"; - System.out.println("Writing XML data from " + args[0] + - " to " + outFnm); - PrintWriter pw = new PrintWriter(new FileWriter(outFnm)); - - NodeList root = doc.getChildNodes(); - // there may be multiple trees; visit each one - for (int i = 0; i < root.getLength(); i++) { - visitNode(pw, root.item(i), ""); - pw.write("\n"); - } - pw.close(); -} // end of main() - -visitNode() prints the node's tag, any attribute data, any text child node data, and -recursively visits the rest of the node's children: - -// part of ExtractXMLInfo.java -private static void visitNode(PrintWriter pw, Node node, String ind) -{ - pw.write(ind + node.getNodeName()); - visitAttrs(pw, node); - - // examine all the child nodes - NodeList nodeList = node.getChildNodes(); - for (int i = 0; i < nodeList.getLength(); i++) { - Node child = nodeList.item(i); - if (child.getNodeType() == Node.TEXT_NODE) { - String trimmedVal = child.getNodeValue().trim(); - if (trimmedVal.length() == 0) - pw.write("\n"); - else - pw.write(": \"" + trimmedVal + "\""); - // element names with values end with ':' - } - else if (child.getNodeType() == Node.ELEMENT_NODE) - visitNode(pw, child, ind+" "); - } -} // end of visitNode() - -visitAttrs() prints attribute names and data: - -// part of ExtractXMLInfo.java -private static void visitAttrs(PrintWriter pw, Node node) -{ - NamedNodeMap attrs = node.getAttributes(); - if (attrs != null) { - for (int i = 0; i < attrs.getLength(); i++) { - Node attr = attrs.item(i); - pw.write(" " + attr.getNodeName() + "= \"" + - attr.getNodeValue() + "\""); - // attribute names end with '=' - } - } -} // end of visitAttrs() - -When ExtractXMLInfo.java has finished stripping the XML, the next stage is to call -BuildXMLSheet.java to load the text into Office as a Calc sheet. This is done by -converting it into a 2D array which is added to the spreadsheet by Calc.setArray(). - -The main() function of BuildXMLSheet.java calls getData() to create the array: - -// in BuildXMLSheet.java -public static void main(String[] args) -{ - if (args.length != 1) { - System.out.println("Usage: run BuildXMLSheet "); - return; - } - - Object[][] data = getData(args[0]); - Lo.printTable(args[0] + " data", data); - - XComponentLoader loader = Lo.loadOffice(); - XSpreadsheetDocument doc = Calc.createDoc(loader); - if (doc == null) { - System.out.println("Document creation failed"); - Lo.closeOffice(); - return; - } - GUI.setVisible(doc, true); - Lo.delay(2000); - XSpreadsheet sheet = Calc.getSheet(doc, 0); - Calc.setArray(sheet, "A1", data); - - // Lo.saveDoc(doc, "createdSS.ods"); - Lo.waitEnter(); - Lo.closeDoc(doc); - Lo.closeOffice(); - } // end of main() - -getData() builds the array in two steps: initially a list of differently sized arrays is -created, one for each input line. Then the list is converted into a 2D array where every -row has the same length. - - -// part of BuildXMLSheet.java -private static Object[][] getData(String fnm) -{ - int maxCols = 0; // max number of columns across all rows - - // each input line is stored as an array inside a list - ArrayList rows = new ArrayList(); - - System.out.println("Reading data from " + fnm); - try { - BufferedReader br = new BufferedReader(new FileReader(fnm)); - String line; - while ((line = br.readLine()) != null) { - Object[] toks = splitLine(line); - // read a line as an array of tokens (strings) - if (toks.length > 0) - rows.add(toks); - if (toks.length > maxCols) - maxCols = toks.length; - } - } - catch(IOException e) { - System.out.println("Could not read " + fnm); - return null; - } - - // convert list of different length arrays into a - // fixed length 2D array - Object[][] data = new Object[rows.size()][maxCols]; - for (int r = 0; r < rows.size(); r++) { - Object[] row = rows.get(r); - for (int c = 0; c < maxCols; c++) { - if (c >= row.length) - data[r][c] = ""; // pad out array row with empty strings - else - data[r][c] = row[c]; - } - } - return data; -} // end of getData() - -splitLine() converts an input line into an array of tokens. - - -// part of BuildXMLSheet.java -private static String[] splitLine(String ln) -{ - ln += " "; // To detect last token when not quoted... - - boolean inQuote = false; - boolean isIndenting = true; - int numSpaces = 0; - - StringBuilder word = new StringBuilder(); - ArrayList toks = new ArrayList(); - // used to store tokens for the final array - - for (int i = 0; i < ln.length(); i++) { - char ch = ln.charAt(i); - - if (ch != ' ' && isIndenting) - isIndenting = false; - - if (ch == ' ' && isIndenting) { - numSpaces++; - if (numSpaces%2 == 0) - toks.add(""); // convert two space indent into "" - } - else if (ch == '\"' || ch == ' ' && !inQuote) { - // treat a double quoted string as one token - if (ch == '\"') - inQuote = !inQuote; - if (!inQuote && word.length() > 0) { - char lastCh = word.charAt(word.length()-1); - if ((lastCh == ':') || (lastCh == '=')) - // strip element and attribute assignment symbols - word.deleteCharAt(word.length()-1); - toks.add(word.toString()); - word.delete(0, word.length()); - } - } - else - word.append(ch); - } - return toks.toArray(new String[toks.size()]); -} // end of splitLine() - -Every two spaces at the start of a line is stored as an empty string in the row array. - -These strings will later occupy spreadsheet cells at the start of a row, causing the -actual data to be 'indented' to cells further to the right. - -Special care is taken to treat a doubly quoted string as a single token, and any word -ending with ':' or '=' has that character stripped away. These characters signify that the -element or attribute name has associated data. - -Figure 16 shows how the weather data is loaded as a spreadsheet by -BuildXMLSheet.java. - - - +Figure 15. Part of the Spreadsheet Generated by CreateAssoc.java. + + +An obvious issue is the empty rows between the clubs data, caused by the simple +XML.getAllNodeValues() implementation. It assumes that every node supplies one +row of data because of its data element child. Howevere, each club also contains +attributes which adds an extra empty row to the output. + + +### 3.2. Node and Attribute Data Extraction as Labeled Strings + +Not all XML data can be so easily mapped to a 2D format, especially collections such +as weather.xml: + +``` + + + + TH + + + + + + + + + + + + + + + + +``` + +This data was downloaded from OpenWeatherMap using the API at +http://api.openweathermap.org. Unlike pay.xml or clubs.xml there's no sequence of +objects that would naturally become rows of a spreadsheet. In addition, the data is +mostly represented by attributes rather than text nodes. + +In situations like this, a good general approach is to convert the XML to simpler text, +removing XML labels except for the element and attribute names. As a result, my +ExtractXMLInfo.java example produces the following output when applied to +weather.xml: + +``` +current + city id= "1610780" name= "Hat Yai" + coord lat= "7.01" lon= "100.48" + country: "TH" + sun rise= "2017-01-01T23:30:31" set= "2017-01-02T11:14:18" + + temperature max= "25" min= "25" unit= "metric" value= "25" + humidity unit= "%" value= "94" + pressure unit= "hPa" value= "1011" + wind + speed name= "Gentle Breeze" value= "3.6" + gusts + direction code= "NNE" name= "North-northeast" value= "30" + + clouds name= "broken clouds" value= "75" + visibility value= "5000" + precipitation mode= "no" + weather icon= "10n" number= "501" value= "moderate rain" + lastupdate value= "2017-01-02T15:30:00" +``` + +Line indentation is retained, and is utilized when the data is loaded into Office. + +As another example, consider pay.xml which ExtractXMLInfo.java converts into: + +``` +payments + payment + purpose: "CD" + amount: "12.95" + tax: "19.1234" + maturity: "2008-03-01" + + payment + purpose: "DVD" + amount: "19.95" + tax: "19.4321" + maturity: "2008-03-02" + + payment + purpose: "Clothes" + amount: "99.95" + tax: "18.5678" + maturity: "2008-03-03" + + payment + purpose: "Book" + amount: "9.49" + tax: "18.9876" + maturity: "2008-03-04" +``` + +The elements and attributes with data are highlighted by adding a ":" or "=" after +their names. Also, the data is always doubly quoted. + +ExtractXMLInfo.java travels over the DOM tree, printing what it finds to a text file: + +```java +// in ExtractXMLInfo.java +public static void main(String[] args) throws Exception +{ + if (args.length != 1) { + System.out.println("Usage: run ExtractXMLInfo "); + return; + } + Document doc = XML.loadDoc(args[0]); + if (doc == null) + return; + + String fname = Info.getName(args[0]); + String outFnm = fname + "XML.txt"; + System.out.println("Writing XML data from " + args[0] + + " to " + outFnm); + PrintWriter pw = new PrintWriter(new FileWriter(outFnm)); + + NodeList root = doc.getChildNodes(); + // there may be multiple trees; visit each one + for (int i = 0; i < root.getLength(); i++) { + visitNode(pw, root.item(i), ""); + pw.write("\n"); + } + pw.close(); +} // end of main() +``` + +visitNode() prints the node's tag, any attribute data, any text child node data, and +recursively visits the rest of the node's children: + +```java +// part of ExtractXMLInfo.java +private static void visitNode(PrintWriter pw, Node node, String ind) +{ + pw.write(ind + node.getNodeName()); + visitAttrs(pw, node); + + // examine all the child nodes + NodeList nodeList = node.getChildNodes(); + for (int i = 0; i < nodeList.getLength(); i++) { + Node child = nodeList.item(i); + if (child.getNodeType() == Node.TEXT_NODE) { + String trimmedVal = child.getNodeValue().trim(); + if (trimmedVal.length() == 0) + pw.write("\n"); + else + pw.write(": \"" + trimmedVal + "\""); + // element names with values end with ':' + } + else if (child.getNodeType() == Node.ELEMENT_NODE) + visitNode(pw, child, ind+" "); + } +} // end of visitNode() +``` + +visitAttrs() prints attribute names and data: + +```java +// part of ExtractXMLInfo.java +private static void visitAttrs(PrintWriter pw, Node node) +{ + NamedNodeMap attrs = node.getAttributes(); + if (attrs != null) { + for (int i = 0; i < attrs.getLength(); i++) { + Node attr = attrs.item(i); + pw.write(" " + attr.getNodeName() + "= \"" + + attr.getNodeValue() + "\""); + // attribute names end with '=' + } + } +} // end of visitAttrs() +``` + +When ExtractXMLInfo.java has finished stripping the XML, the next stage is to call +BuildXMLSheet.java to load the text into Office as a Calc sheet. This is done by +converting it into a 2D array which is added to the spreadsheet by Calc.setArray(). + +The main() function of BuildXMLSheet.java calls getData() to create the array: + +```java +// in BuildXMLSheet.java +public static void main(String[] args) +{ + if (args.length != 1) { + System.out.println("Usage: run BuildXMLSheet "); + return; + } + + Object[][] data = getData(args[0]); + Lo.printTable(args[0] + " data", data); + + XComponentLoader loader = Lo.loadOffice(); + XSpreadsheetDocument doc = Calc.createDoc(loader); + if (doc == null) { + System.out.println("Document creation failed"); + Lo.closeOffice(); + return; + } + GUI.setVisible(doc, true); + Lo.delay(2000); + XSpreadsheet sheet = Calc.getSheet(doc, 0); + Calc.setArray(sheet, "A1", data); + + // Lo.saveDoc(doc, "createdSS.ods"); + Lo.waitEnter(); + Lo.closeDoc(doc); + Lo.closeOffice(); + } // end of main() +``` + +getData() builds the array in two steps: initially a list of differently sized arrays is +created, one for each input line. Then the list is converted into a 2D array where every +row has the same length. + +```java +// part of BuildXMLSheet.java +private static Object[][] getData(String fnm) +{ + int maxCols = 0; // max number of columns across all rows + + // each input line is stored as an array inside a list + ArrayList rows = new ArrayList(); + + System.out.println("Reading data from " + fnm); + try { + BufferedReader br = new BufferedReader(new FileReader(fnm)); + String line; + while ((line = br.readLine()) != null) { + Object[] toks = splitLine(line); + // read a line as an array of tokens (strings) + if (toks.length > 0) + rows.add(toks); + if (toks.length > maxCols) + maxCols = toks.length; + } + } + catch(IOException e) { + System.out.println("Could not read " + fnm); + return null; + } + + // convert list of different length arrays into a + // fixed length 2D array + Object[][] data = new Object[rows.size()][maxCols]; + for (int r = 0; r < rows.size(); r++) { + Object[] row = rows.get(r); + for (int c = 0; c < maxCols; c++) { + if (c >= row.length) + data[r][c] = ""; // pad out array row with empty strings + else + data[r][c] = row[c]; + } + } + return data; +} // end of getData() + +splitLine() converts an input line into an array of tokens. + + +// part of BuildXMLSheet.java +private static String[] splitLine(String ln) +{ + ln += " "; // To detect last token when not quoted... + + boolean inQuote = false; + boolean isIndenting = true; + int numSpaces = 0; + + StringBuilder word = new StringBuilder(); + ArrayList toks = new ArrayList(); + // used to store tokens for the final array + + for (int i = 0; i < ln.length(); i++) { + char ch = ln.charAt(i); + + if (ch != ' ' && isIndenting) + isIndenting = false; + + if (ch == ' ' && isIndenting) { + numSpaces++; + if (numSpaces%2 == 0) + toks.add(""); // convert two space indent into "" + } + else if (ch == '\"' || ch == ' ' && !inQuote) { + // treat a double quoted string as one token + if (ch == '\"') + inQuote = !inQuote; + if (!inQuote && word.length() > 0) { + char lastCh = word.charAt(word.length()-1); + if ((lastCh == ':') || (lastCh == '=')) + // strip element and attribute assignment symbols + word.deleteCharAt(word.length()-1); + toks.add(word.toString()); + word.delete(0, word.length()); + } + } + else + word.append(ch); + } + return toks.toArray(new String[toks.size()]); +} // end of splitLine() +``` + +Every two spaces at the start of a line is stored as an empty string in the row array. +These strings will later occupy spreadsheet cells at the start of a row, causing the +actual data to be 'indented' to cells further to the right. + +Special care is taken to treat a doubly quoted string as a single token, and any word +ending with ':' or '=' has that character stripped away. These characters signify that the +element or attribute name has associated data. + +Figure 16 shows how the weather data is loaded as a spreadsheet by +BuildXMLSheet.java. + ![](images/50-Importing_XML-16.png) -Figure 16. BuildXMLSheet Applied to Weather Data. +Figure 16. BuildXMLSheet Applied to Weather Data. + - -Figure 17 shows the results for the payments data. +Figure 17 shows the results for the payments data. - ![](images/50-Importing_XML-17.png) -Figure 17. BuildXMLSheet Applied to Payments Data. +Figure 17. BuildXMLSheet Applied to Payments Data. - -### 3.3. JAXB Conversion of XML to Java Objects -Java Architecture for XML Binding (JAXB) provides methods for unmarshalling -(converting) XML documents into Java classes and objects, and for marshalling Java -objects back into XML documents. I'm interested in the unmarshalling parts so data -can be passed to Office as Java objects rather than as XML. +### 3.3. JAXB Conversion of XML to Java Objects -Most of the magic of the XML-to-Java conversion is performed by Java's xjc.exe tool -which comes as part of the JDK (you'll find it in %java_home%\bin\ on Windows). +Java Architecture for XML Binding (JAXB) provides methods for unmarshalling +(converting) XML documents into Java classes and objects, and for marshalling Java +objects back into XML documents. I'm interested in the unmarshalling parts so data +can be passed to Office as Java objects rather than as XML. -xjc processes an XML schema (an XSD file) rather than XML since the schema -contains information about the XML's underlying structure. +Most of the magic of the XML-to-Java conversion is performed by Java's xjc.exe tool +which comes as part of the JDK (you'll find it in %java_home%\bin\ on Windows). +xjc processes an XML schema (an XSD file) rather than XML since the schema +contains information about the XML's underlying structure. -The good news is that there are websites, such as freeformatter.com, which can +The good news is that there are websites, such as freeformatter.com, which can generate XSD from supplied XML (http://www.freeformatter.com/xsd- -generator.html). I employed its "Salami slice" translator so that the Java code -generated later by xjc is a bit simpler to read. freeformatter.com converted pay.xml -into the following XSD: - - - - - - - - - - - - - - - - - - - - - - - - - -The schema gives the names and types for each field in a payment object, and makes -payments a sequence of payment objects. - -A good tutorial on XSD can be found at w3schools.com: -http://www.w3schools.com/Xml/schema_intro.asp -If the resulting schema is stored in pay.xsd, then xjc generates Java classes like so: -xjc -p Pay pay.xsd -The –p option supplies a package name which causes the potentially numerous classes -to be stored in a folder of that name. Details about xjc can be found at +generator.html). I employed its "Salami slice" translator so that the Java code +generated later by xjc is a bit simpler to read. freeformatter.com converted pay.xml +into the following XSD: + +``` + + + + + + + + + + + + + + + + + + + + + + + +``` + +The schema gives the names and types for each field in a payment object, and makes +payments a sequence of payment objects. + +A good tutorial on XSD can be found at w3schools.com: +http://www.w3schools.com/Xml/schema_intro.asp + +If the resulting schema is stored in pay.xsd, then xjc generates Java classes like so: + +``` +xjc -p Pay pay.xsd +``` + +The –p option supplies a package name which causes the potentially numerous classes +to be stored in a folder of that name. Details about xjc can be found at http://docs.oracle.com/javase/6/docs/technotes/tools/share/xjc.html, or by calling xjc - -help. +help. -Three classes are written to the Pay\ folder: Payments.java, Payment.java, and -ObjectFactory.java. The first two are Java version of the XML elements, while -ObjectFactory is used to create the Java objects at runtime. Figure 18 shows class -diagrams for the code. +Three classes are written to the Pay\ folder: Payments.java, Payment.java, and +ObjectFactory.java. The first two are Java version of the XML elements, while +ObjectFactory is used to create the Java objects at runtime. Figure 18 shows class +diagrams for the code. - - ![](images/50-Importing_XML-18.png) -Figure 18. The Payments Class Diagrams. - - -As you might expect, the XML sequence of Payment objects inside Payments is -implemented as a list in Java. - -The details of the ObjectFactory aren't important, but the get/set methods in the -Payments and Payment classes will be useful. My UnmarshallPay.java example -shows how to use these classes: - -// in UnmarshallPay.java -import java.io.*; -import java.util.*; -import javax.xml.bind.*; -import Pay.*; // the package for the Pay classes - -public class UnmarshallPay -{ - public static void main(String[] args) - { - try { - // initialize the Payments objects using pay.xml - JAXBContext jaxbContext = - JAXBContext.newInstance(Payments.class); - Unmarshaller jaxbUnmarshaller = - jaxbContext.createUnmarshaller(); - Payments pays = (Payments) jaxbUnmarshaller.unmarshal( - new File("pay.xml")); - - List payList = pays.getPayment(); - - // print payment names and amounts - System.out.println("Payments"); - for(Payment p : payList) - System.out.println(" " + p.getPurpose() + - ": " + p.getAmount()); - } - catch (JAXBException e) { - e.printStackTrace(); - } - } // end of main() - -} // end of UnmarshallPay class - -main() creates an Unmarshaller object for the Payments class, which is initialized -with data from pay.xml. It then iterates through the Payment objects and prints their -purpose and amount fields. The output is: - -Payments - CD: 12.95 - DVD: 19.95 - Clothes: 99.95 - Book: 9.49 - -Now that the XML data is available through Java (using various get methods), it is -quite easy to extend UnmarshallPay.java to write it into an Office document. - - -clubs.xml as Java Code -The JAXB manipulation of clubs.xml follows the same steps as used on pay.xml: -## 1. Convert clubs.xml to XSD at freeformatter.com using the "Salami Slice" - -mapping. - -## 2. Convert company.xsd into Java classes with xjc: - -xjc -p Clubs clubs.xsd -## 3. Compile the Clubs package: - -javac Clubs/*.java -Figure 19 shows the Clubs class diagrams. - - - +Figure 18. The Payments Class Diagrams. + + +As you might expect, the XML sequence of Payment objects inside Payments is +implemented as a list in Java. + +The details of the ObjectFactory aren't important, but the get/set methods in the +Payments and Payment classes will be useful. My UnmarshallPay.java example +shows how to use these classes: + +```java +// in UnmarshallPay.java +import java.io.*; +import java.util.*; +import javax.xml.bind.*; +import Pay.*; // the package for the Pay classes + +public class UnmarshallPay +{ + public static void main(String[] args) + { + try { + // initialize the Payments objects using pay.xml + JAXBContext jaxbContext = + JAXBContext.newInstance(Payments.class); + Unmarshaller jaxbUnmarshaller = + jaxbContext.createUnmarshaller(); + Payments pays = (Payments) jaxbUnmarshaller.unmarshal( + new File("pay.xml")); + + List payList = pays.getPayment(); + + // print payment names and amounts + System.out.println("Payments"); + for(Payment p : payList) + System.out.println(" " + p.getPurpose() + + ": " + p.getAmount()); + } + catch (JAXBException e) { + e.printStackTrace(); + } + } // end of main() + +} // end of UnmarshallPay class +``` + +main() creates an Unmarshaller object for the Payments class, which is initialized +with data from pay.xml. It then iterates through the Payment objects and prints their +purpose and amount fields. The output is: + +``` +Payments + CD: 12.95 + DVD: 19.95 + Clothes: 99.95 + Book: 9.49 +``` + +Now that the XML data is available through Java (using various get methods), it is +quite easy to extend UnmarshallPay.java to write it into an Office document. + + +#### clubs.xml as Java Code + +The JAXB manipulation of clubs.xml follows the same steps as used on pay.xml: + +1. Convert clubs.xml to XSD at freeformatter.com using the "Salami Slice" mapping. +2. Convert company.xsd into Java classes with xjc: `xjc -p Clubs clubs.xsd` +3. Compile the Clubs package: `javac Clubs/*.java` + +Figure 19 shows the Clubs class diagrams. + ![](images/50-Importing_XML-19.png) -Figure 19. The Clubs Class Diagrams. - - -The top-level class is ClubDatabase which maintains a list of Association objects. - -Each Association object holds a list of Club objects. Club contains get/set methods for -accessing its fields. My UnmarshallClubs.java example shows how to use these -classes: - -// in UnmarshallClubs.java -import java.io.*; -import java.util.*; -import javax.xml.bind.*; -import Clubs.*; // the package for the Club classes - - -public class UnmarshallClubs -{ - public static void main(String[] args) - { - try { - // initialize the Clubs objects using clubs.xml - JAXBContext jaxbContext = - JAXBContext.newInstance(ClubDatabase.class); - Unmarshaller jaxbUnmarshaller = - jaxbContext.createUnmarshaller(); - ClubDatabase cd = (ClubDatabase) jaxbUnmarshaller.unmarshal( - new File("clubs.xml")); - - List assocList = cd.getAssociation(); - - // print club names for all the associations - System.out.println("Associations"); - for(Association assoc : assocList) { - System.out.println(" " + assoc.getId()); - List clubs = assoc.getClub(); - for(Club club : clubs) - System.out.println(" " + club.getName()); - } - } - catch (JAXBException e) { - e.printStackTrace(); - } - } // end of main() - -} // end of UnmarshallClubs class - -The unmarshalling is similar to before except that the ClubDatabase object is -initialized with data from clubs.xml. The code iterates through the clubs in each -association, printing their names: - -Associations - BAWA - Castro Valley Wrestling Club - Coastside Grapplers - Dixon Ram Wrestling - East Bay Freestylers - Fairfield WrestlingClub - Godfather Wrestling Club - Golden Gate Wrestling Club - Mat Club USA - Pirate Wrestling Club - SF Elite Wrestling - Titan Wrestling Club - CAGWA - Big Bear Grizzlies Wrestling Club - California Grapplers Youth Wrestling - : - : - South Coast Wrestling Club - West Hills Hawks - -Naming Conflicts -The xjc tool works so well that a deep understanding of JAXB isn't usually needed. - -The exception is when the XML contains a name conflict, which occurs if two or -more attributes have the same name. - -There's a number of "value" name conflicts in weather.xml, which I've highlighted -below: - - - - - - TH - - - - - - - - - - - - - - - - - -The "value" attribute is used nine times in different contexts. This doesn't pose a -problem for freeformatter.com, which quietly generates weather.xsd. The name -conflict is only reported when xjc tries to generate Java classes: - -> xjc -p Weather weather.xsd -parsing a schema... - -[ERROR] Property "Value" is already defined. - -Use to resolve this conflict. - - : // many more error messages … - -As the error message advises, I need to add jaxb:property annotations to the XSD file -to allow xjc to correctly translate the attributes. For example, the first offender in -weather.xsd is: - -Its jaxb:property annotation is: - - - - - - - - - - -This same change is applied to all the other conflicting attribute names. - -In addition, the new "jaxb" label must be linked to its JAXB schema at the start of the -XSD file: - - -xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" -jaxb:version="2.1" - -If you want to understand what's happening in more detail, the best place to start is the -tutorial at Oracle: https://docs.oracle.com/javase/tutorial/jaxb/intro/. - -When xjc is passed this modified weather.xsd file, the Java classes are correctly -generated. Figure 20 shows only the class names to reduce the diagram's size. - - - +Figure 19. The Clubs Class Diagrams. + + +The top-level class is ClubDatabase which maintains a list of Association objects. + +Each Association object holds a list of Club objects. Club contains get/set methods for +accessing its fields. My UnmarshallClubs.java example shows how to use these +classes: + +```java +// in UnmarshallClubs.java +import java.io.*; +import java.util.*; +import javax.xml.bind.*; +import Clubs.*; // the package for the Club classes + + +public class UnmarshallClubs +{ + public static void main(String[] args) + { + try { + // initialize the Clubs objects using clubs.xml + JAXBContext jaxbContext = + JAXBContext.newInstance(ClubDatabase.class); + Unmarshaller jaxbUnmarshaller = + jaxbContext.createUnmarshaller(); + ClubDatabase cd = (ClubDatabase) jaxbUnmarshaller.unmarshal( + new File("clubs.xml")); + + List assocList = cd.getAssociation(); + + // print club names for all the associations + System.out.println("Associations"); + for(Association assoc : assocList) { + System.out.println(" " + assoc.getId()); + List clubs = assoc.getClub(); + for(Club club : clubs) + System.out.println(" " + club.getName()); + } + } + catch (JAXBException e) { + e.printStackTrace(); + } + } // end of main() + +} // end of UnmarshallClubs class +``` + +The unmarshalling is similar to before except that the ClubDatabase object is +initialized with data from clubs.xml. The code iterates through the clubs in each +association, printing their names: + +``` +Associations + BAWA + Castro Valley Wrestling Club + Coastside Grapplers + Dixon Ram Wrestling + East Bay Freestylers + Fairfield WrestlingClub + Godfather Wrestling Club + Golden Gate Wrestling Club + Mat Club USA + Pirate Wrestling Club + SF Elite Wrestling + Titan Wrestling Club + CAGWA + Big Bear Grizzlies Wrestling Club + California Grapplers Youth Wrestling + : + : + South Coast Wrestling Club + West Hills Hawks +``` + +#### Naming Conflicts + +The xjc tool works so well that a deep understanding of JAXB isn't usually needed. +The exception is when the XML contains a name conflict, which occurs if two or +more attributes have the same name. + +There's a number of "value" name conflicts in weather.xml, which I've highlighted +below: + +``` + + + + + TH + + + + + + + + + + + + + + + + +``` + +The "value" attribute is used nine times in different contexts. This doesn't pose a +problem for freeformatter.com, which quietly generates weather.xsd. The name +conflict is only reported when xjc tries to generate Java classes: + +``` +> xjc -p Weather weather.xsd +parsing a schema... + +[ERROR] Property "Value" is already defined. + +Use to resolve this conflict. + + : // many more error messages … +``` + +As the error message advises, I need to add jaxb:property annotations to the XSD file +to allow xjc to correctly translate the attributes. For example, the first offender in +weather.xsd is: + +``` + +Its jaxb:property annotation is: + + + + + + + + + +``` + +This same change is applied to all the other conflicting attribute names. + +In addition, the new "jaxb" label must be linked to its JAXB schema at the start of the +XSD file: + +``` + +xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" +jaxb:version="2.1" +``` + +If you want to understand what's happening in more detail, the best place to start is the +tutorial at Oracle: https://docs.oracle.com/javase/tutorial/jaxb/intro/. + +When xjc is passed this modified weather.xsd file, the Java classes are correctly +generated. Figure 20 shows only the class names to reduce the diagram's size. + ![](images/50-Importing_XML-20.png) -Figure 20. The Weather Class Diagrams. - - -The top-level class is Current which stores the weather attributes as objects. - -My UnmarshallWeather.java example examines weather.xml to report if it was -raining on the report day: - -// in UnmarshallWeather.java -public class UnmarshallWeather -{ - public static void main(String[] args) - { - try { - JAXBContext jaxbContext = - JAXBContext.newInstance(Current.class); - Unmarshaller jaxbUnmarshaller = - jaxbContext.createUnmarshaller(); - Current currWeather = (Current) jaxbUnmarshaller.unmarshal( - new File("weather.xml")); - - // get precipitation as a boolean - String rainingStatus = - currWeather.getPrecipitation().getValue(); - boolean isRaining = rainingStatus.equals("yes"); - - // get date in day/month/year format - XMLGregorianCalendar gCal = - currWeather.getLastupdate().getValueAttribute(); - Calendar cal = gCal.toGregorianCalendar(); - - SimpleDateFormat formatter = - new SimpleDateFormat("dd/MM/yyyy"); - formatter.setTimeZone(cal.getTimeZone()); - String dateStr = formatter.format(cal.getTime()); - - if (isRaining) - System.out.println("It was raining on " + dateStr); - else - System.out.println("It was NOT raining on " + dateStr); - } - catch (JAXBException e) { - e.printStackTrace(); - } - } // end of main() - -} // end of UnmarshallWeather class - -The output is: -It was NOT raining on 02/01/2017 - - - +Figure 20. The Weather Class Diagrams. + + +The top-level class is Current which stores the weather attributes as objects. + +My UnmarshallWeather.java example examines weather.xml to report if it was +raining on the report day: + +```java +// in UnmarshallWeather.java +public class UnmarshallWeather +{ + public static void main(String[] args) + { + try { + JAXBContext jaxbContext = + JAXBContext.newInstance(Current.class); + Unmarshaller jaxbUnmarshaller = + jaxbContext.createUnmarshaller(); + Current currWeather = (Current) jaxbUnmarshaller.unmarshal( + new File("weather.xml")); + + // get precipitation as a boolean + String rainingStatus = + currWeather.getPrecipitation().getValue(); + boolean isRaining = rainingStatus.equals("yes"); + + // get date in day/month/year format + XMLGregorianCalendar gCal = + currWeather.getLastupdate().getValueAttribute(); + Calendar cal = gCal.toGregorianCalendar(); + + SimpleDateFormat formatter = + new SimpleDateFormat("dd/MM/yyyy"); + formatter.setTimeZone(cal.getTimeZone()); + String dateStr = formatter.format(cal.getTime()); + + if (isRaining) + System.out.println("It was raining on " + dateStr); + else + System.out.println("It was NOT raining on " + dateStr); + } + catch (JAXBException e) { + e.printStackTrace(); + } + } // end of main() + +} // end of UnmarshallWeather class +``` + +The output is: + +``` +It was NOT raining on 02/01/2017 +``` diff --git a/docs/51-Simple_ODF.md b/docs/51-Simple_ODF.md index 3efa669..ee9dcab 100644 --- a/docs/51-Simple_ODF.md +++ b/docs/51-Simple_ODF.md @@ -1,853 +1,884 @@ -# Chapter 51. Simple ODF +# Chapter 51. Simple ODF !!! note "Topics" - The - OpenDocument Format; - Doc Information; - Unzipping an ODF Doc; - the Simple Java API for - ODF (Apache ODF - Toolkit): making docs - (text, sheet, and slides), - slide movement, doc - concatenation (text, - sheet, and slides) - - Example folders: - "ODFToolkit Tests" and - "Utils" - - -An OpenDocument Format (ODF) document is a zipped -folder containing an assortment of XML files, images, and -other resources. This makes it possible to manipulate using -zip/unzip and XML features, but that isn't a good idea due -to the complexity of the formats. This chapter looks at a -few of these lower-level techniques, but is mainly about the -Simple API for ODF, a sub-project of the Apache ODF -Toolkit (http://incubator.apache.org/odftoolkit/simple/). It's -a small Java API for creating, modifying and extracting -data from ODF documents, built on top of the ODFDOM -library (http://incubator.apache.org/odftoolkit/odfdom/). Rather surprisingly, its -support for the concatenation of documents is better than that in the Office API. - - - -## 1. The OpenDocument Format - -The contents of an ODF document (i.e. the zipped folder) depend on its type (e.g. - -does it contain a spreadsheet, presentation, or text?), but several files always appear -inside the folder: - content.xml: the document's textual contents, but not binary data such as -images; - meta.xml: the document's meta information, such as the author's name and the -last modification date. The data is most easily viewed through Office's File > -Properties menu item; - styles.xml: the styles used for the document's pages, paragraphs, text formats, -and others, which are usually set via the Styles and Formatting dialogs in -Office; - settings.xml: information specific to the application and document's display, -such as the window's size/position, printer settings, and whether headers and -footers are visible; - manifest.xml: this lists the content of the zipped folder, and is stored in the -META-INF/ subdirectory; - mimetype: a one-line file listing the document's MIME-type. - -The easiest way of viewing these files and folders is to unzip the ODF file using a -utility such as 7-Zip, as in Figure 1. - - - + The OpenDocument Format; + Doc Information; + Unzipping an ODF Doc; + the Simple Java API for + ODF (Apache ODF + Toolkit): making docs + (text, sheet, and slides), + slide movement, doc + concatenation (text, + sheet, and slides) + + Example folders: + "ODFToolkit Tests" and + "Utils" + + +An OpenDocument Format (ODF) document is a zipped +folder containing an assortment of XML files, images, and +other resources. This makes it possible to manipulate using +zip/unzip and XML features, but that isn't a good idea due +to the complexity of the formats. This chapter looks at a +few of these lower-level techniques, but is mainly about the +Simple API for ODF, a sub-project of the Apache ODF +Toolkit (http://incubator.apache.org/odftoolkit/simple/). It's +a small Java API for creating, modifying and extracting +data from ODF documents, built on top of the ODFDOM +library (http://incubator.apache.org/odftoolkit/odfdom/). Rather surprisingly, its +support for the concatenation of documents is better than that in the Office API. + + +## 1. The OpenDocument Format + +The contents of an ODF document (i.e. the zipped folder) depend on its type (e.g. + +does it contain a spreadsheet, presentation, or text?), but several files always appear +inside the folder: + +* content.xml: the document's textual contents, but not binary data such as +images; +* meta.xml: the document's meta information, such as the author's name and the +last modification date. The data is most easily viewed through Office's File > +Properties menu item; +* styles.xml: the styles used for the document's pages, paragraphs, text formats, +and others, which are usually set via the Styles and Formatting dialogs in +Office; +* settings.xml: information specific to the application and document's display, +such as the window's size/position, printer settings, and whether headers and +footers are visible; +* manifest.xml: this lists the content of the zipped folder, and is stored in the +META-INF/ subdirectory; +* mimetype: a one-line file listing the document's MIME-type. + +The easiest way of viewing these files and folders is to unzip the ODF file using a +utility such as 7-Zip, as in Figure 1. + ![](images/51-Simple_ODF-1.png) -Figure 1. An Unzipped View of algs.odp. +Figure 1. An Unzipped View of algs.odp. + - -Figure 1's Configurations2/ folder stores localization information for the Office GUI; -Pictures/ stores the images used in the document; Thumbnails/ holds a small 128x128 -picture of the document in thumbnail.png. +Figure 1's Configurations2/ folder stores localization information for the Office GUI; +Pictures/ stores the images used in the document; Thumbnails/ holds a small 128x128 +picture of the document in thumbnail.png. -The wikipedia page about ODF is quite informative -(https://en.wikipedia.org/wiki/OpenDocument), as is its entry for the OpenDocument -technical specification -(https://en.wikipedia.org/wiki/OpenDocument_technical_specification). There's a -quick summary of the OpenDocument Format (ODF) at -https://help.libreoffice.org/Common/XML_File_Formats. +The wikipedia page about ODF is quite informative +(https://en.wikipedia.org/wiki/OpenDocument), as is its entry for the OpenDocument +technical specification +(https://en.wikipedia.org/wiki/OpenDocument_technical_specification). There's a +quick summary of the OpenDocument Format (ODF) at +https://help.libreoffice.org/Common/XML_File_Formats. -The ODF standard was developed by the Organization for the Advancement of +The ODF standard was developed by the Organization for the Advancement of Structured Information Standards (OASIS) consortium (https://www.oasis- -open.org/), whose website hosts a lot of information. A related community site is -http://opendocument.xml.org/ -The most complete textbook on ODF is: -OASIS OpenDocument Essentials -J. David Eisenberg, 2005 -http:// books.evc-cit.info/ -The associated website has a free draft of the book, and all the examples and support -code. The site often seems to be offline, but the book can be found at other locations. +open.org/), whose website hosts a lot of information. A related community site is +http://opendocument.xml.org/ + +The most complete textbook on ODF is: + +OASIS OpenDocument Essentials +J. David Eisenberg, 2005 +http:// books.evc-cit.info/ +The associated website has a free draft of the book, and all the examples and support +code. The site often seems to be offline, but the book can be found at other locations. A recent version can be purchased at Lulu: http://www.lulu.com/shop/j-david- -eisenberg/oasis-opendocument-essentials/paperback/product-392512.html - - -## 2. Document Information - -There's little need to directly manipulate a document's XML files since the Office API -offers high-level techniques for accessing most of their information. - -For example, part of meta.xml for algs.odp is: - - - PowerPoint Presentation - 2017-01-06T16:53:58.639000000 - 4 - PT1M52S - LibreOffice/5.1.0.3$ - Windows_x86LibreOffice_project/ - 5e3e00a007d9b3b6efb6797a8b8e57b51ab1f737 - - Made in - Thailand - - -This data is displayed in the General tab of the File > Properties window in Figure 2. - - - +eisenberg/oasis-opendocument-essentials/paperback/product-392512.html + + +## 2. Document Information + +There's little need to directly manipulate a document's XML files since the Office API +offers high-level techniques for accessing most of their information. + +For example, part of meta.xml for algs.odp is: + +``` + + PowerPoint Presentation + 2017-01-06T16:53:58.639000000 + 4 + PT1M52S + LibreOffice/5.1.0.3$ + Windows_x86LibreOffice_project/ + 5e3e00a007d9b3b6efb6797a8b8e57b51ab1f737 + + Made in + Thailand + +``` + +This data is displayed in the General tab of the File, Properties window in Figure 2. + ![](images/51-Simple_ODF-2.png) -Figure 2. File > Properties Window. - - -Rather than attempting to extract and parse meta.xml, this information is reachable -through the XDocumentProperties interface which I described back in Chapter 3, -section 2. - -The following DocInfo.java example uses Info.printDocProperties() to print document -details: - -// in DocInfo.java -public class DocInfo -{ - public static void main(String args[]) - { - if (args.length < 1) { - System.out.println("Usage: run DocInfo "); - 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; - } - - System.out.println(); - Props.showObjProps("Document", doc); - Info.printDocProperties(doc); - - Lo.closeDoc(doc); - Lo.closeOffice(); - } // end of main() - -} // end of DocInfo class - -When DocInfo is passed algs.odp, some of the output is: - -Document Properties Info - Author: - Title: PowerPoint Presentation - Subject: - Description: - Generator: LibreOffice/5.1.0.3$Windows_x86 LibreOffice_project/ - 5e3e00a007d9b3b6efb6797a8b8e57b51ab1f737 - : - Modification Date: Jan 06, 2017 16:53 - : - Secret == Made in Thailand - -Info.printDocProperties() calls many XDocumentProperties.getXXX() methods to -obtain these details: - -// in the Info class -public static void printDocProperties(Object doc) -{ - XDocumentPropertiesSupplier docPropsSupp = - Lo.qi(XDocumentPropertiesSupplier.class, doc); - XDocumentProperties dps = docPropsSupp.getDocumentProperties(); - printDocProps(dps); - - XPropertyContainer udProps = dps.getUserDefinedProperties(); - Props.showObjProps("UserDefined Info", udProps); -} // end of printDocProperties() - - -public static void printDocProps(XDocumentProperties dps) -{ - System.out.println("Document Properties Info"); - - System.out.println(" Author: " + dps.getAuthor()); - System.out.println(" Title: " + dps.getTitle()); - System.out.println(" Subject: " + dps.getSubject()); - : // many more prints -} // end of printDocProps() - -XDocumentProperties replaces the deprecated XDocumentInfo interface, which has -been removed from LibreOffice (although it's still in OpenOffice). The two classes -manipulate almost the same information, but the older XDocumentInfo can retrieve -the document's MIME-type, which is absent from XDocumentProperties. One way of -obtaining this would be to access the mimetype file zipped inside the document, as -shown next. - - - -## 3. Unzipping an ODF Document - -My DocUnzip.java example shows how it's possible to list the zipped contents of a -document, access its MIME-type, and extract a zipped file: - -// in DocUnzip.java -public class DocUnzip -{ - public static void main(String args[]) - { - if ((args.length < 1) || (args.length > 2)) { - System.out.println("Usage: run DocUnzip []"); - return; - } - XComponentLoader loader = Lo.loadOffice(); - - FileIO.zipList(args[0]); - // FileIO.zipListUno(args[0]); // only names listed - - // get zip access to the document - XZipFileAccess zfa = FileIO.zipAccess(args[0]); - - String mimeType = FileIO.getMimeType(zfa); - System.out.println("MIME type: " + mimeType); - System.out.println("Other MIME type approach: " + - Info.getMIMEType(args[0])); - - // convert MIME-type too other forms - int docType = Info.mimeDocType(mimeType); - System.out.println("Doc Type: " + docType + "; " + - Lo.docTypeStr(docType)); - - if (args.length == 2) // extract the named file - FileIO.unzipFile(zfa, args[1]); - - Lo.closeOffice(); - } // end of main() -} // end of DocUnzip class - -The program can be called like so: -run DocUnzip algs.odp content.xml -content.xml will be unzipped from algs.odp and saved as contentCopy.xml in the -local directory. - -The rest of the output is: - -Listing of algs.odp: -Raw Size Size Date Time Name --------- ------- ------- ------- -------- -47 47 Jan 6, 2017 9:54:00 AM mimetype -0 0 Jan 6, 2017 9:54:00 AM Configurations2/popupmenu/ -0 0 Jan 6, 2017 9:54:00 AM Configurations2/floater/ -0 0 Jan 6, 2017 9:54:00 AM Configurations2/images/Bitmaps/ -0 2 Jan 6, 2017 9:54:00 AM -Configurations2/accelerator/current.xml -0 0 Jan 6, 2017 9:54:00 AM Configurations2/menubar/ -0 0 Jan 6, 2017 9:54:00 AM Configurations2/progressbar/ -0 0 Jan 6, 2017 9:54:00 AM Configurations2/toolbar/ -0 0 Jan 6, 2017 9:54:00 AM Configurations2/statusbar/ -0 0 Jan 6, 2017 9:54:00 AM Configurations2/toolpanel/ -83608 6501 Jan 6, 2017 9:54:00 AM styles.xml -4364 4364 Jan 6, 2017 9:54:00 AM -Pictures/100000000000004600000035E6F1CB2181A9ACF1.png -16977 16977 Jan 6, 2017 9:54:00 AM -Pictures/10000000000003080000002A0A348B9039C3652D.png -20480 20480 Jan 6, 2017 9:54:00 AM -Pictures/10000000000001F4000001F4E2E69E1D.jpg -45056 45056 Jan 6, 2017 9:54:00 AM -Pictures/10000000000000C8000000EEB0A3D2D2.jpg -8192 8192 Jan 6, 2017 9:54:00 AM -Pictures/10000000000000E1000000E1B343DD04.jpg -40960 40960 Jan 6, 2017 9:54:00 AM -Pictures/10000000000000E60000015E784CAA37.jpg -69632 69632 Jan 6, 2017 9:54:00 AM -Pictures/10000000000000DC000000FF9A43DBBF.jpg -28672 28672 Jan 6, 2017 9:54:00 AM -Pictures/100000000000011800000160792BAA16.jpg -12288 12288 Jan 6, 2017 9:54:00 AM -Pictures/10000000000000DC0000014B7CFE8C49.jpg -11901 11901 Jan 6, 2017 9:54:00 AM -Pictures/10000000000000BC0000010DD7ECB1F7.jpg -18238 18238 Jan 6, 2017 9:54:02 AM Thumbnails/thumbnail.png -86313 8040 Jan 6, 2017 9:54:02 AM content.xml -6037 913 Jan 6, 2017 9:54:02 AM settings.xml -1141 491 Jan 6, 2017 9:54:02 AM meta.xml -2248 452 Jan 6, 2017 9:54:02 AM META-INF/manifest.xml - - -MIME type: application/vnd.oasis.opendocument.presentation -Other MIME type approach: application/vnd.oasis.opendocument.presentation -Doc Type: 5; simpress - -Extracting content.xml -Saving to contentCopy.xml - -Most of the output is a detailed listing of the zipped contents of algs.odp. - - -### 3.1. Listing the Contents of a Zipped Folder - -There are two "zipList" functions in the FileIO utility class. FileIO.zipListUno() is -simpler since only file and folder names are printed: - -// in the FileIO class -public static void zipListUno(String fnm) -{ - XZipFileAccess zfa = zipAccess(fnm); - XNameAccess nmAccess = Lo.qi(XNameAccess.class, zfa); - String[] names = nmAccess.getElementNames(); - - System.out.println("\nZipped Contents of " + fnm); - Lo.printNames(names, 1); -} // end of zipListUno() - - -public static XZipFileAccess zipAccess(String fnm) -// get zip access to the document using Office API -{ - return Lo.createInstanceMCF(XZipFileAccess.class, - "com.sun.star.packages.zip.ZipFileAccess", - new Object[]{ fnmToURL(fnm) }); -} - -XZipFileAccess is created by instantiating the ZipFileAccess service with the -document's filename. The interface is cast to XNameAccess which allows the names -of the zipped files to be retrieved as an array of strings. Figure 3 shows the -relationships between the service and interfaces. - - - +Figure 2. File, Properties Window. + + +Rather than attempting to extract and parse meta.xml, this information is reachable +through the XDocumentProperties interface which I described back in Chapter 3, +section 2. + +The following DocInfo.java example uses Info.printDocProperties() to print document +details: + +```java +// in DocInfo.java +public class DocInfo +{ + public static void main(String args[]) + { + if (args.length < 1) { + System.out.println("Usage: run DocInfo "); + 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; + } + + System.out.println(); + Props.showObjProps("Document", doc); + Info.printDocProperties(doc); + + Lo.closeDoc(doc); + Lo.closeOffice(); + } // end of main() + +} // end of DocInfo class +``` + +When DocInfo is passed algs.odp, some of the output is: + +``` +Document Properties Info + Author: + Title: PowerPoint Presentation + Subject: + Description: + Generator: LibreOffice/5.1.0.3$Windows_x86 LibreOffice_project/ + 5e3e00a007d9b3b6efb6797a8b8e57b51ab1f737 + : + Modification Date: Jan 06, 2017 16:53 + : + Secret == Made in Thailand +``` + +Info.printDocProperties() calls many XDocumentProperties.getXXX() methods to +obtain these details: + +```java +// in the Info class +public static void printDocProperties(Object doc) +{ + XDocumentPropertiesSupplier docPropsSupp = + Lo.qi(XDocumentPropertiesSupplier.class, doc); + XDocumentProperties dps = docPropsSupp.getDocumentProperties(); + printDocProps(dps); + + XPropertyContainer udProps = dps.getUserDefinedProperties(); + Props.showObjProps("UserDefined Info", udProps); +} // end of printDocProperties() + + +public static void printDocProps(XDocumentProperties dps) +{ + System.out.println("Document Properties Info"); + + System.out.println(" Author: " + dps.getAuthor()); + System.out.println(" Title: " + dps.getTitle()); + System.out.println(" Subject: " + dps.getSubject()); + : // many more prints +} // end of printDocProps() +``` + +XDocumentProperties replaces the deprecated XDocumentInfo interface, which has +been removed from LibreOffice (although it's still in OpenOffice). The two classes +manipulate almost the same information, but the older XDocumentInfo can retrieve +the document's MIME-type, which is absent from XDocumentProperties. One way of +obtaining this would be to access the mimetype file zipped inside the document, as +shown next. + + +## 3. Unzipping an ODF Document + +My DocUnzip.java example shows how it's possible to list the zipped contents of a +document, access its MIME-type, and extract a zipped file: + +```java +// in DocUnzip.java +public class DocUnzip +{ + public static void main(String args[]) + { + if ((args.length < 1) || (args.length > 2)) { + System.out.println("Usage: run DocUnzip []"); + return; + } + XComponentLoader loader = Lo.loadOffice(); + + FileIO.zipList(args[0]); + // FileIO.zipListUno(args[0]); // only names listed + + // get zip access to the document + XZipFileAccess zfa = FileIO.zipAccess(args[0]); + + String mimeType = FileIO.getMimeType(zfa); + System.out.println("MIME type: " + mimeType); + System.out.println("Other MIME type approach: " + + Info.getMIMEType(args[0])); + + // convert MIME-type too other forms + int docType = Info.mimeDocType(mimeType); + System.out.println("Doc Type: " + docType + "; " + + Lo.docTypeStr(docType)); + + if (args.length == 2) // extract the named file + FileIO.unzipFile(zfa, args[1]); + + Lo.closeOffice(); + } // end of main() +} // end of DocUnzip class +``` + +The program can be called like so: + +``` +run DocUnzip algs.odp content.xml +``` + +content.xml will be unzipped from algs.odp and saved as contentCopy.xml in the +local directory. + +The rest of the output is: + +``` +Listing of algs.odp: +Raw Size Size Date Time Name +-------- ------- ------- ------- -------- +47 47 Jan 6, 2017 9:54:00 AM mimetype +0 0 Jan 6, 2017 9:54:00 AM Configurations2/popupmenu/ +0 0 Jan 6, 2017 9:54:00 AM Configurations2/floater/ +0 0 Jan 6, 2017 9:54:00 AM Configurations2/images/Bitmaps/ +0 2 Jan 6, 2017 9:54:00 AM +Configurations2/accelerator/current.xml +0 0 Jan 6, 2017 9:54:00 AM Configurations2/menubar/ +0 0 Jan 6, 2017 9:54:00 AM Configurations2/progressbar/ +0 0 Jan 6, 2017 9:54:00 AM Configurations2/toolbar/ +0 0 Jan 6, 2017 9:54:00 AM Configurations2/statusbar/ +0 0 Jan 6, 2017 9:54:00 AM Configurations2/toolpanel/ +83608 6501 Jan 6, 2017 9:54:00 AM styles.xml +4364 4364 Jan 6, 2017 9:54:00 AM +Pictures/100000000000004600000035E6F1CB2181A9ACF1.png +16977 16977 Jan 6, 2017 9:54:00 AM +Pictures/10000000000003080000002A0A348B9039C3652D.png +20480 20480 Jan 6, 2017 9:54:00 AM +Pictures/10000000000001F4000001F4E2E69E1D.jpg +45056 45056 Jan 6, 2017 9:54:00 AM +Pictures/10000000000000C8000000EEB0A3D2D2.jpg +8192 8192 Jan 6, 2017 9:54:00 AM +Pictures/10000000000000E1000000E1B343DD04.jpg +40960 40960 Jan 6, 2017 9:54:00 AM +Pictures/10000000000000E60000015E784CAA37.jpg +69632 69632 Jan 6, 2017 9:54:00 AM +Pictures/10000000000000DC000000FF9A43DBBF.jpg +28672 28672 Jan 6, 2017 9:54:00 AM +Pictures/100000000000011800000160792BAA16.jpg +12288 12288 Jan 6, 2017 9:54:00 AM +Pictures/10000000000000DC0000014B7CFE8C49.jpg +11901 11901 Jan 6, 2017 9:54:00 AM +Pictures/10000000000000BC0000010DD7ECB1F7.jpg +18238 18238 Jan 6, 2017 9:54:02 AM Thumbnails/thumbnail.png +86313 8040 Jan 6, 2017 9:54:02 AM content.xml +6037 913 Jan 6, 2017 9:54:02 AM settings.xml +1141 491 Jan 6, 2017 9:54:02 AM meta.xml +2248 452 Jan 6, 2017 9:54:02 AM META-INF/manifest.xml + + +MIME type: application/vnd.oasis.opendocument.presentation +Other MIME type approach: application/vnd.oasis.opendocument.presentation +Doc Type: 5; simpress + +Extracting content.xml +Saving to contentCopy.xml +``` + +Most of the output is a detailed listing of the zipped contents of algs.odp. + + +### 3.1. Listing the Contents of a Zipped Folder + +There are two "zipList" functions in the FileIO utility class. FileIO.zipListUno() is +simpler since only file and folder names are printed: + +```java +// in the FileIO class +public static void zipListUno(String fnm) +{ + XZipFileAccess zfa = zipAccess(fnm); + XNameAccess nmAccess = Lo.qi(XNameAccess.class, zfa); + String[] names = nmAccess.getElementNames(); + + System.out.println("\nZipped Contents of " + fnm); + Lo.printNames(names, 1); +} // end of zipListUno() + + +public static XZipFileAccess zipAccess(String fnm) +// get zip access to the document using Office API +{ + return Lo.createInstanceMCF(XZipFileAccess.class, + "com.sun.star.packages.zip.ZipFileAccess", + new Object[]{ fnmToURL(fnm) }); +} +``` + +XZipFileAccess is created by instantiating the ZipFileAccess service with the +document's filename. The interface is cast to XNameAccess which allows the names +of the zipped files to be retrieved as an array of strings. Figure 3 shows the +relationships between the service and interfaces. + ![](images/51-Simple_ODF-3.png) -Figure 3. The ZipFileAccess Service. - - -The ZipFileAccess service and interfaces are in the com.sun.star.packages.zip -module, which includes a ZipEntry class for holding information about each zipped -file (e.g. its compressed size). I was unable to find a way of creating ZipEntry objects, -but Java contains a complete zip API. By using Java rather than Office, I was able to -implement a more fancy "zipList": - -// in the FileIO class -public static void zipList(String fnm) -// using the Java API -{ - DateFormat df= DateFormat.getDateInstance(); // date format - DateFormat tf= DateFormat.getTimeInstance(); // time format - tf.setTimeZone( TimeZone.getDefault() ); - try { - ZipFile zfile = new ZipFile(fnm); - System.out.println("Listing of " + zfile.getName() + ":"); - System.out.println("Raw Size Size Date Time Name"); - System.out.println("--- ---- ---- ---- ---- ----"); - Enumeration zfs = - zfile.entries(); - while (zfs.hasMoreElements()) { - java.util.zip.ZipEntry entry = - (java.util.zip.ZipEntry) zfs.nextElement(); - Date d = new Date(entry.getTime()); - System.out.print( padSpaces(entry.getSize(), 9) + " "); - System.out.print( padSpaces(entry.getCompressedSize(),7)+ " "); - System.out.print(" " + df.format(d) + " "); - System.out.print(" " + tf.format(d) + " "); - System.out.println(" " + entry.getName()); - } - System.out.println(); - } - catch (java.io.IOException e) - { System.out.println(e); } -} // end of zipList() - -Another advantage of switching to Java are the large number of online examples of -zip manipulation; my zipList() function is closely based on one at -http://www.drdobbs.com/jvm/java-and-the-zip-file-format/184410339. - - -### 3.2. Extracting a MIME-type - -FileIO.getMimeType() employs XZipFileAccess.getStreamByPattern() to access the -zipped mimetype file as an input stream. - - -// in the FileIO class -public static String getMimeType(XZipFileAccess zfa) -{ - try { - XInputStream inStream = zfa.getStreamByPattern("mimetype"); - String[] lines = FileIO.readLines(inStream); - if (lines != null) - return lines[0].trim(); - } - catch (com.sun.star.uno.Exception e) - { System.out.println(e); } - - System.out.println("No mimetype found"); - return null; -} // end of getMimeType() - -There's an alternative approach which looks up the file's MIME-type using the Java -API class MimetypesFileTypeMap: - -// in the Info class -// global -private static final String MIME_FNM = "mime.types"; - - -public static String getMIMEType(String fnm) -{ - try { - MimetypesFileTypeMap mftMap = new MimetypesFileTypeMap( - FileIO.getUtilsFolder() + MIME_FNM); - return mftMap.getContentType(new File(fnm)); - } - catch(java.lang.Exception e) - { System.out.println("Could not find " + MIME_FNM); - return "application/octet-stream"; // better than nothing - } -} // end of getMIMEType() - -The MimetypesFileTypeMap() constructor examines a list of MIME-types loaded -from the utility classes folder. - -Most of my utility functions use document 'types' coded as integers rather than as -MIME-type strings; the values are defined at the start of the Lo utility class: - -// in the Lo class -public static final int UNKNOWN = 0; -public static final int WRITER = 1; -public static final int BASE = 2; -public static final int CALC = 3; -public static final int DRAW = 4; -public static final int IMPRESS = 5; -public static final int MATH = 6; - -Info.mimeDocType() maps ODF MIME-type strings to one of these integers: - -// in the Info class -public static int mimeDocType(String mimeType) -{ - if (mimeType == null) - return Lo.UNKNOWN; - - if (mimeType.contains("vnd.oasis.opendocument.text")) - return Lo.WRITER; - else if (mimeType.contains("vnd.oasis.opendocument.base")) - return Lo.BASE; - else if (mimeType.contains("vnd.oasis.opendocument.spreadsheet")) - return Lo.CALC; - else if (mimeType.contains("vnd.oasis.opendocument.graphics") || - mimeType.contains("vnd.oasis.opendocument.image") || - mimeType.contains("vnd.oasis.opendocument.chart")) - return Lo.DRAW; - else if (mimeType.contains("vnd.oasis.opendocument.presentation")) - return Lo.IMPRESS; - else if (mimeType.contains("vnd.oasis.opendocument.formula")) - return Lo.MATH; - else return Lo.UNKNOWN; -} // end of mimeDocType() - -Some of my functions also utilize short document type strings, which are also defined -in the Lo class: - -// in the Lo class -// docType strings -public static final String UNKNOWN_STR = "unknown"; -public static final String WRITER_STR = "swriter"; -public static final String BASE_STR = "sbase"; -public static final String CALC_STR = "scalc"; -public static final String DRAW_STR = "sdraw"; -public static final String IMPRESS_STR = "simpress"; -public static final String MATH_STR = "smath"; - -Lo.docTypeStr() maps document type integers to these strings. - - - -### 3.3. Extracting a Zipped File - -The XZipFileAccess.getStreamByPattern() method used in FileIO.getMimeType() is -also employed by FileIO.unzipFile() to extract a zipped file: - -// in FileIO class -public static void unzipFile(XZipFileAccess zfa, String fnm) -{ - String fileName = Info.getName(fnm); - String ext = Info.getExt(fnm); - try { - System.out.println("Extracting " + fnm); - XInputStream inStream = zfa.getStreamByPattern("*" + fnm); - - XSimpleFileAccess3 fileAcc = - Lo.createInstanceMCF(XSimpleFileAccess3.class, - "com.sun.star.ucb.SimpleFileAccess"); - - String copyFnm = (ext == null) ? (fileName + "Copy") : - (fileName + "Copy." + ext); - System.out.println("Saving to " + copyFnm); - fileAcc.writeFile(FileIO.fnmToURL(copyFnm), inStream); - } - catch (com.sun.star.uno.Exception e) - { System.out.println(e); } -} // end of unzipFile() - -The XZipFileAccess.getStreamByPattern() call includes the wildcard character "*" so -the filename will be found even if prefixed by a directory path, as in the case of an -ODF document's manifest.xml which is stored in "META-INF/manifest.xml". - -I utilize Office's IO interface, XSimpleFileAccess3, which supports writeFile() to -directly connect an XInputStream to a file. The filename of the extracted data is -constructed from the zipped filename with the addition of "Copy". - - - -## 4. Higher-level Manipulation of ODF - -Manipulating an ODF document as zipped files and XML is a recipe for highly -complex (and probably bug-ridden) code. And why bother when the Office API offers -all the necessary functionality in a higher-level framework? -"Maslow's hammer" maxim comes to mind; it states: "if all you have is a hammer, -then everything looks like a nail". In other words, it's useful to know about ODF APIs -other than Office's since they might offer better, easier ways of doing some tasks. - -As a consequence, I'm going to spend the rest of the chapter looking at the Simple -Java API for ODF, a sub-project of Apache ODF Toolkit -(http://incubator.apache.org/odftoolkit/simple/). It's a relatively small Java API for -creating, modifying and extracting data from ODF documents, built on top of the -ODFDOM library (http://incubator.apache.org/odftoolkit/odfdom/). - -Its best features are the cookbook pages at -http://incubator.apache.org/odftoolkit/simple/document/cookbook/, the Getting -Started Guide (http://incubator.apache.org/odftoolkit/simple/gettingstartguide.html), -and its demo pages (http://incubator.apache.org/odftoolkit/simple/demo/). - -The cookbook contains examples for manipulating text documents, presentations, -tables in text documents and spreadsheets, charts, style handling, text navigation, text -extraction, text fields, text forms, and document metadata. The API documentation is -at http://incubator.apache.org/odftoolkit/mvn-site/0.8-incubating/simple-odf/apidocs/, -and a mail archive at http://mail-archives.apache.org/mod_mbox/incubator-odf-users/ -By far the worst feature of the API is the large number of libraries that must be -installed before even a lowly "Hello World" example will work. Fortunately, I've -included copies of those libraries at this chapter's website at -http://fivedots.coe.psu.ac.th/~ad/jlop/. - -The Simple API doesn't have the depth of features of Office, so it's best suited for the -creation of simple text documents, spreadsheets, and presentations. However, it has -better support for combining documents (e.g. concatenating two slide decks into a -single one), and can move/copy slides in a presentation much more easily than Office; -look back at Chapter 17, sections 4 and 5 for my complaints about that. - - -### 4.1. Building Simple Documents - -This subsection looks at how the Simple API can be used to create text, spreadsheet, -and slide documents. - - -Make a Text Document -My MakeTextDoc.java example creates a text document containing an image, some -text, a list, and a table.; the result is shown in Figure 4. - - - +Figure 3. The ZipFileAccess Service. + + +The ZipFileAccess service and interfaces are in the com.sun.star.packages.zip +module, which includes a ZipEntry class for holding information about each zipped +file (e.g. its compressed size). I was unable to find a way of creating ZipEntry objects, +but Java contains a complete zip API. By using Java rather than Office, I was able to +implement a more fancy "zipList": + +```java +// in the FileIO class +public static void zipList(String fnm) +// using the Java API +{ + DateFormat df= DateFormat.getDateInstance(); // date format + DateFormat tf= DateFormat.getTimeInstance(); // time format + tf.setTimeZone( TimeZone.getDefault() ); + try { + ZipFile zfile = new ZipFile(fnm); + System.out.println("Listing of " + zfile.getName() + ":"); + System.out.println("Raw Size Size Date Time Name"); + System.out.println("--- ---- ---- ---- ---- ----"); + Enumeration zfs = + zfile.entries(); + while (zfs.hasMoreElements()) { + java.util.zip.ZipEntry entry = + (java.util.zip.ZipEntry) zfs.nextElement(); + Date d = new Date(entry.getTime()); + System.out.print( padSpaces(entry.getSize(), 9) + " "); + System.out.print( padSpaces(entry.getCompressedSize(),7)+ " "); + System.out.print(" " + df.format(d) + " "); + System.out.print(" " + tf.format(d) + " "); + System.out.println(" " + entry.getName()); + } + System.out.println(); + } + catch (java.io.IOException e) + { System.out.println(e); } +} // end of zipList() +``` + +Another advantage of switching to Java are the large number of online examples of +zip manipulation; my zipList() function is closely based on one at +http://www.drdobbs.com/jvm/java-and-the-zip-file-format/184410339. + + +### 3.2. Extracting a MIME-type + +FileIO.getMimeType() employs XZipFileAccess.getStreamByPattern() to access the +zipped mimetype file as an input stream. + + +```java +// in the FileIO class +public static String getMimeType(XZipFileAccess zfa) +{ + try { + XInputStream inStream = zfa.getStreamByPattern("mimetype"); + String[] lines = FileIO.readLines(inStream); + if (lines != null) + return lines[0].trim(); + } + catch (com.sun.star.uno.Exception e) + { System.out.println(e); } + + System.out.println("No mimetype found"); + return null; +} // end of getMimeType() + +There's an alternative approach which looks up the file's MIME-type using the Java +API class MimetypesFileTypeMap: + +// in the Info class +// global +private static final String MIME_FNM = "mime.types"; + + +public static String getMIMEType(String fnm) +{ + try { + MimetypesFileTypeMap mftMap = new MimetypesFileTypeMap( + FileIO.getUtilsFolder() + MIME_FNM); + return mftMap.getContentType(new File(fnm)); + } + catch(java.lang.Exception e) + { System.out.println("Could not find " + MIME_FNM); + return "application/octet-stream"; // better than nothing + } +} // end of getMIMEType() + +The MimetypesFileTypeMap() constructor examines a list of MIME-types loaded +from the utility classes folder. + +Most of my utility functions use document 'types' coded as integers rather than as +MIME-type strings; the values are defined at the start of the Lo utility class: + +// in the Lo class +public static final int UNKNOWN = 0; +public static final int WRITER = 1; +public static final int BASE = 2; +public static final int CALC = 3; +public static final int DRAW = 4; +public static final int IMPRESS = 5; +public static final int MATH = 6; + +Info.mimeDocType() maps ODF MIME-type strings to one of these integers: + +// in the Info class +public static int mimeDocType(String mimeType) +{ + if (mimeType == null) + return Lo.UNKNOWN; + + if (mimeType.contains("vnd.oasis.opendocument.text")) + return Lo.WRITER; + else if (mimeType.contains("vnd.oasis.opendocument.base")) + return Lo.BASE; + else if (mimeType.contains("vnd.oasis.opendocument.spreadsheet")) + return Lo.CALC; + else if (mimeType.contains("vnd.oasis.opendocument.graphics") || + mimeType.contains("vnd.oasis.opendocument.image") || + mimeType.contains("vnd.oasis.opendocument.chart")) + return Lo.DRAW; + else if (mimeType.contains("vnd.oasis.opendocument.presentation")) + return Lo.IMPRESS; + else if (mimeType.contains("vnd.oasis.opendocument.formula")) + return Lo.MATH; + else return Lo.UNKNOWN; +} // end of mimeDocType() +``` + +Some of my functions also utilize short document type strings, which are also defined +in the Lo class: + +```java +// in the Lo class +// docType strings +public static final String UNKNOWN_STR = "unknown"; +public static final String WRITER_STR = "swriter"; +public static final String BASE_STR = "sbase"; +public static final String CALC_STR = "scalc"; +public static final String DRAW_STR = "sdraw"; +public static final String IMPRESS_STR = "simpress"; +public static final String MATH_STR = "smath"; +``` + +Lo.docTypeStr() maps document type integers to these strings. + + +### 3.3. Extracting a Zipped File + +The XZipFileAccess.getStreamByPattern() method used in FileIO.getMimeType() is +also employed by FileIO.unzipFile() to extract a zipped file: + +```java +// in FileIO class +public static void unzipFile(XZipFileAccess zfa, String fnm) +{ + String fileName = Info.getName(fnm); + String ext = Info.getExt(fnm); + try { + System.out.println("Extracting " + fnm); + XInputStream inStream = zfa.getStreamByPattern("*" + fnm); + + XSimpleFileAccess3 fileAcc = + Lo.createInstanceMCF(XSimpleFileAccess3.class, + "com.sun.star.ucb.SimpleFileAccess"); + + String copyFnm = (ext == null) ? (fileName + "Copy") : + (fileName + "Copy." + ext); + System.out.println("Saving to " + copyFnm); + fileAcc.writeFile(FileIO.fnmToURL(copyFnm), inStream); + } + catch (com.sun.star.uno.Exception e) + { System.out.println(e); } +} // end of unzipFile() +``` + +The XZipFileAccess.getStreamByPattern() call includes the wildcard character "*" so +the filename will be found even if prefixed by a directory path, as in the case of an +ODF document's manifest.xml which is stored in "META-INF/manifest.xml". + +I utilize Office's IO interface, XSimpleFileAccess3, which supports writeFile() to +directly connect an XInputStream to a file. The filename of the extracted data is +constructed from the zipped filename with the addition of "Copy". + + +## 4. Higher-level Manipulation of ODF + +Manipulating an ODF document as zipped files and XML is a recipe for highly +complex (and probably bug-ridden) code. And why bother when the Office API offers +all the necessary functionality in a higher-level framework? + +"Maslow's hammer" maxim comes to mind; it states: "if all you have is a hammer, +then everything looks like a nail". In other words, it's useful to know about ODF APIs +other than Office's since they might offer better, easier ways of doing some tasks. + +As a consequence, I'm going to spend the rest of the chapter looking at the Simple +Java API for ODF, a sub-project of Apache ODF Toolkit +(http://incubator.apache.org/odftoolkit/simple/). It's a relatively small Java API for +creating, modifying and extracting data from ODF documents, built on top of the +ODFDOM library (http://incubator.apache.org/odftoolkit/odfdom/). + +Its best features are the cookbook pages at +http://incubator.apache.org/odftoolkit/simple/document/cookbook/, the Getting +Started Guide (http://incubator.apache.org/odftoolkit/simple/gettingstartguide.html), +and its demo pages (http://incubator.apache.org/odftoolkit/simple/demo/). + +The cookbook contains examples for manipulating text documents, presentations, +tables in text documents and spreadsheets, charts, style handling, text navigation, text +extraction, text fields, text forms, and document metadata. The API documentation is +at http://incubator.apache.org/odftoolkit/mvn-site/0.8-incubating/simple-odf/apidocs/, +and a mail archive at http://mail-archives.apache.org/mod_mbox/incubator-odf-users/ + +By far the worst feature of the API is the large number of libraries that must be +installed before even a lowly "Hello World" example will work. Fortunately, I've +included copies of those libraries at this chapter's website at +http://fivedots.coe.psu.ac.th/~ad/jlop/. + +The Simple API doesn't have the depth of features of Office, so it's best suited for the +creation of simple text documents, spreadsheets, and presentations. However, it has +better support for combining documents (e.g. concatenating two slide decks into a +single one), and can move/copy slides in a presentation much more easily than Office; +look back at Chapter 17, sections 4 and 5 for my complaints about that. + + +### 4.1. Building Simple Documents + +This subsection looks at how the Simple API can be used to create text, spreadsheet, +and slide documents. + + +Make a Text Document +My MakeTextDoc.java example creates a text document containing an image, some +text, a list, and a table.; the result is shown in Figure 4. + ![](images/51-Simple_ODF-4.png) -Figure 4. The Output of MakeTextDoc.java. - - -This isn't quite what the output looks like in the "Getting Started Guide", where the -program is called HelloWorld.java – in the guide the first paragraph is beneath the -image rather than to one side. - -The MakeTextDoc.java code: - -public class MakeTextDoc -{ - public static void main(String[] args) - { - try { - TextDocument doc = TextDocument.newTextDocument(); - doc.newImage(new URI("odf-logo.png")); - - // add paragraphs and list - doc.addParagraph("Hello World, Hello Simple ODF!"); - doc.addParagraph("The following is a list."); - List list = doc.addList(); - String[] items = {"item1", "item2", "item3"}; - list.addItems(items); - - // add table - Table table = doc.addTable(2, 2); - Cell cell = table.getCellByPosition(0, 0); - cell.setStringValue("Hello World!"); - - System.out.println("Creating MakeTextDoc.odt"); - doc.save("MakeTextDoc.odt"); - } - catch (Exception e) { - System.out.println(e); - } - } // end of main() - -} // end of MakeTextDoc class - -I was unable to find a way of changing the image's text wrap property to affect the -paragraph's position. It's probably do-able using the lower-level ODFDOM library, -but the programming information for that part of ODF Toolkit is mostly limited to its -API documentation. However, there's a good introductory tutorial at -http://www.langintro.com/odfdom_tutorials/. - - -Make a Spreadsheet -The MakeSheet.java example creates a spreadsheet with a single sheet, and a few -cells of data, as in Figure 5. - - - +Figure 4. The Output of MakeTextDoc.java. + + +This isn't quite what the output looks like in the "Getting Started Guide", where the +program is called HelloWorld.java – in the guide the first paragraph is beneath the +image rather than to one side. + +The MakeTextDoc.java code: + +```java +public class MakeTextDoc +{ + public static void main(String[] args) + { + try { + TextDocument doc = TextDocument.newTextDocument(); + doc.newImage(new URI("odf-logo.png")); + + // add paragraphs and list + doc.addParagraph("Hello World, Hello Simple ODF!"); + doc.addParagraph("The following is a list."); + List list = doc.addList(); + String[] items = {"item1", "item2", "item3"}; + list.addItems(items); + + // add table + Table table = doc.addTable(2, 2); + Cell cell = table.getCellByPosition(0, 0); + cell.setStringValue("Hello World!"); + + System.out.println("Creating MakeTextDoc.odt"); + doc.save("MakeTextDoc.odt"); + } + catch (Exception e) { + System.out.println(e); + } + } // end of main() + +} // end of MakeTextDoc class +``` + +I was unable to find a way of changing the image's text wrap property to affect the +paragraph's position. It's probably do-able using the lower-level ODFDOM library, +but the programming information for that part of ODF Toolkit is mostly limited to its +API documentation. However, there's a good introductory tutorial at +http://www.langintro.com/odfdom_tutorials/. + + +#### Make a Spreadsheet + +The MakeSheet.java example creates a spreadsheet with a single sheet, and a few +cells of data, as in Figure 5. + ![](images/51-Simple_ODF-5.png) -Figure 5. The Output of MakeSheet.java. - - -The program: - -public class MakeSheet -{ - public static void main(String[] args) - { - try { - SpreadsheetDocument doc = - SpreadsheetDocument.newSpreadsheetDocument(); - Table sheet = doc.getSheetByIndex(0); - sheet.getCellByPosition(0, 0).setStringValue("Hello"); - for (int row = 0; row < 5; row++) - sheet.getCellByPosition(1, row).setDoubleValue(row*2.0); - - System.out.println("Saving document to makeSheet.ods"); - doc.save("makeSheet.ods"); - } - catch (Exception e) - { System.out.println(e); } - } // end of main() - -} // end of MakeSheet class - - -Make a Slide Deck -The MakeSlides.java example creates a slide deck. The first slide contains a title, the -second some bulleted text and a picture, and the third slide is blank, as in Figure 6. - - - +Figure 5. The Output of MakeSheet.java. + + +The program: + +```java +public class MakeSheet +{ + public static void main(String[] args) + { + try { + SpreadsheetDocument doc = + SpreadsheetDocument.newSpreadsheetDocument(); + Table sheet = doc.getSheetByIndex(0); + sheet.getCellByPosition(0, 0).setStringValue("Hello"); + for (int row = 0; row < 5; row++) + sheet.getCellByPosition(1, row).setDoubleValue(row*2.0); + + System.out.println("Saving document to makeSheet.ods"); + doc.save("makeSheet.ods"); + } + catch (Exception e) + { System.out.println(e); } + } // end of main() + +} // end of MakeSheet class +``` + +#### Make a Slide Deck + +The MakeSlides.java example creates a slide deck. The first slide contains a title, the +second some bulleted text and a picture, and the third slide is blank, as in Figure 6. + ![](images/51-Simple_ODF-6.png) -Figure 6. The Output of MakeSlides.java. - - -The program: - -public class MakeSlides -{ - public static void main(String[] args) - { - try { - PresentationDocument doc = - PresentationDocument.newPresentationDocument(); - - // a title slide - Slide slide1 = doc.newSlide(0, "slide1", - SlideLayout.TITLE_ONLY); - Textbox titleBox = slide1.getTextboxByUsage( - PresentationClass.TITLE).get(0); - titleBox.setTextContent("Important Slide Presentation"); - - // a slide with text bullets and a picture - Slide slide2 = doc.newSlide(1, "slide2", - SlideLayout.TITLE_OUTLINE); - titleBox = slide2.getTextboxByUsage( - PresentationClass.TITLE).get(0); - titleBox.setTextContent("Overview"); - - Textbox outline = slide2.getTextboxByUsage( - PresentationClass.OUTLINE).get(0); - List txtList = outline.addList(); // two bullets - txtList.addItem("Item 1"); - txtList.addItem("Item 2"); - - Image image = Image.newImage(slide2, new URI("skinner.png")); - FrameRectangle rect = image.getRectangle(); - rect.setX(8); // position the image - rect.setY(4); - image.setRectangle(rect); - - System.out.println("Saving document to makeSlides.odp"); - doc.save("makeSlides.odp"); - } - catch (Exception e) - { System.out.println(e); } - } // end of main() - -} // end of MakeSlides class - -Programming with this part of the API is a littlie tricky since slide elements are -represented by different types of boxes. I also found it hard to determine the position -of the image, except by trial-and-error. - - -### 4.2. Moving a Slide - -Back in Chapter 17, section 4, I had to use copy and paste dispatch commands in the -slide-sorter view to rearrange a deck. Moving a slide in the Simple API is much -easier, as shown in MoveSlide.java, which moves the first slide of the deck to its end: - -public class MoveSlide -{ - public static void main(String[] args) - { - try { - PresentationDocument doc = - PresentationDocument.loadDocument("algs.odp"); - int numSlides = doc.getSlideCount(); - System.out.println("Moving first slide to the end"); - doc.moveSlide(0, numSlides); // why not numSlides-1? - - System.out.println("Saving document to algsMoved.odp"); - doc.save("algsMoved.odp"); - doc.close(); - } - catch (Exception e) - { System.out.println(e); } - } // end of main() - -} // end of MoveSlide class - -PresentationDocument.moveSlide() works without a hitch, although I'm confused -why I need to supply numSlides as the second numerical argument rather than -numSlides-1. - -There's also a PresentationDocument.copySlide() method for copying. - - -### 4.3. Combining Two Documents - -This subsection presents three short programs that combine two text documents, -appends the sheets of two spreadsheets, and concatenates two slide decks. These -examples are much simpler to program than equivalent ones using the Office API. - - -Combining Text Documents -CombineTexts.java adds the contents of doc2.odt to the end of the contents of -doc1.odt, separating them with a page break. The result is saved to combined.odt: - -public class CombineTexts -{ - public static void main(String[] args) - { - try { - TextDocument doc1 = TextDocument.loadDocument("doc1.odt"); - TextDocument doc2 = TextDocument.loadDocument("doc2.odt"); - - doc1.addPageBreak(); - Paragraph lastPara = doc1.getParagraphByReverseIndex(0, false); - - // insert contents at end and copy styles - doc1.insertContentFromDocumentAfter(doc2, lastPara, true); - - System.out.println("Saving combination to combined.odt"); - doc1.save("combined.odt"); - doc1.close(); - doc2.close(); - } - catch (Exception e) - { System.out.println(e); } - } // end of main() - -} // end of CombineTexts class - -The crucial method is TextDocument.insertContentFromDocumentAfter(). - - -Combining Spreadsheets -CombineSheets.java places the sheets in ss2.odt after the sheets in ss1.odt, and saves -the result in combined.ods: - -public class CombineSheets -{ - public static void main(String[] args) - { - try { - SpreadsheetDocument doc1 = - SpreadsheetDocument.loadDocument("ss1.ods"); - SpreadsheetDocument doc2 = - SpreadsheetDocument.loadDocument("ss2.ods"); - int numSheets2 = doc2.getSheetCount(); - - // add sheets of second document to end of first doc - for(int i=0; i < numSheets2; i++) { - Table t = doc2.getSheetByIndex(i); - doc1.appendSheet(t, t.getTableName()); - } - System.out.println("Saving combination to combined.ods"); - doc1.save("combined.ods"); - doc1.close(); - doc2.close(); - } - catch (Exception e) - { System.out.println(e); } - } // end of main() - -} // end of CombineSheets class - -Sadly there isn't a single method that appends tables. Instead I've used a loop to -append each sheet from ss2.ods after the sheets in ss1.ods. - - -Combining Slide Decks -CombineDecks.java adds the slides in deck2.odp after the slides in deck1.odp, saving -the result in combined.odp: - -public class CombineDecks -{ - public static void main(String[] args) - { - try { - PresentationDocument doc1 = - PresentationDocument.loadDocument("deck1.odp"); - PresentationDocument doc2 = - PresentationDocument.loadDocument("deck2.odp"); - doc1.appendPresentation(doc2); - System.out.println("Saving combination to combined.odp"); - doc1.save("combined.odp"); - doc1.close(); - doc2.close(); - } - catch (Exception e) - { System.out.println(e); } - } // end of main() - -} // end of CombineDecks class - -The PresentationDocument.appendPresentation() method does all the work, and -should be compared to my hacky Office solution in Chapter 17, section 5. +Figure 6. The Output of MakeSlides.java. + + +The program: + +```java +public class MakeSlides +{ + public static void main(String[] args) + { + try { + PresentationDocument doc = + PresentationDocument.newPresentationDocument(); + + // a title slide + Slide slide1 = doc.newSlide(0, "slide1", + SlideLayout.TITLE_ONLY); + Textbox titleBox = slide1.getTextboxByUsage( + PresentationClass.TITLE).get(0); + titleBox.setTextContent("Important Slide Presentation"); + + // a slide with text bullets and a picture + Slide slide2 = doc.newSlide(1, "slide2", + SlideLayout.TITLE_OUTLINE); + titleBox = slide2.getTextboxByUsage( + PresentationClass.TITLE).get(0); + titleBox.setTextContent("Overview"); + + Textbox outline = slide2.getTextboxByUsage( + PresentationClass.OUTLINE).get(0); + List txtList = outline.addList(); // two bullets + txtList.addItem("Item 1"); + txtList.addItem("Item 2"); + + Image image = Image.newImage(slide2, new URI("skinner.png")); + FrameRectangle rect = image.getRectangle(); + rect.setX(8); // position the image + rect.setY(4); + image.setRectangle(rect); + System.out.println("Saving document to makeSlides.odp"); + doc.save("makeSlides.odp"); + } + catch (Exception e) + { System.out.println(e); } + } // end of main() + +} // end of MakeSlides class +``` + +Programming with this part of the API is a littlie tricky since slide elements are +represented by different types of boxes. I also found it hard to determine the position +of the image, except by trial-and-error. + + +### 4.2. Moving a Slide + +Back in Chapter 17, section 4, I had to use copy and paste dispatch commands in the +slide-sorter view to rearrange a deck. Moving a slide in the Simple API is much +easier, as shown in MoveSlide.java, which moves the first slide of the deck to its end: + +```java +public class MoveSlide +{ + public static void main(String[] args) + { + try { + PresentationDocument doc = + PresentationDocument.loadDocument("algs.odp"); + int numSlides = doc.getSlideCount(); + System.out.println("Moving first slide to the end"); + doc.moveSlide(0, numSlides); // why not numSlides-1? + + System.out.println("Saving document to algsMoved.odp"); + doc.save("algsMoved.odp"); + doc.close(); + } + catch (Exception e) + { System.out.println(e); } + } // end of main() + +} // end of MoveSlide class +``` + +PresentationDocument.moveSlide() works without a hitch, although I'm confused +why I need to supply numSlides as the second numerical argument rather than +numSlides-1. + +There's also a PresentationDocument.copySlide() method for copying. + + +### 4.3. Combining Two Documents + +This subsection presents three short programs that combine two text documents, +appends the sheets of two spreadsheets, and concatenates two slide decks. These +examples are much simpler to program than equivalent ones using the Office API. + + +#### Combining Text Documents + +CombineTexts.java adds the contents of doc2.odt to the end of the contents of +doc1.odt, separating them with a page break. The result is saved to combined.odt: + +```java +public class CombineTexts +{ + public static void main(String[] args) + { + try { + TextDocument doc1 = TextDocument.loadDocument("doc1.odt"); + TextDocument doc2 = TextDocument.loadDocument("doc2.odt"); + + doc1.addPageBreak(); + Paragraph lastPara = doc1.getParagraphByReverseIndex(0, false); + + // insert contents at end and copy styles + doc1.insertContentFromDocumentAfter(doc2, lastPara, true); + + System.out.println("Saving combination to combined.odt"); + doc1.save("combined.odt"); + doc1.close(); + doc2.close(); + } + catch (Exception e) + { System.out.println(e); } + } // end of main() + +} // end of CombineTexts class +``` + +The crucial method is TextDocument.insertContentFromDocumentAfter(). + + +#### Combining Spreadsheets + +CombineSheets.java places the sheets in ss2.odt after the sheets in ss1.odt, and saves +the result in combined.ods: + +```java +public class CombineSheets +{ + public static void main(String[] args) + { + try { + SpreadsheetDocument doc1 = + SpreadsheetDocument.loadDocument("ss1.ods"); + SpreadsheetDocument doc2 = + SpreadsheetDocument.loadDocument("ss2.ods"); + int numSheets2 = doc2.getSheetCount(); + + // add sheets of second document to end of first doc + for(int i=0; i < numSheets2; i++) { + Table t = doc2.getSheetByIndex(i); + doc1.appendSheet(t, t.getTableName()); + } + System.out.println("Saving combination to combined.ods"); + doc1.save("combined.ods"); + doc1.close(); + doc2.close(); + } + catch (Exception e) + { System.out.println(e); } + } // end of main() + +} // end of CombineSheets class +``` + +Sadly there isn't a single method that appends tables. Instead I've used a loop to +append each sheet from ss2.ods after the sheets in ss1.ods. + + +#### Combining Slide Decks + +CombineDecks.java adds the slides in deck2.odp after the slides in deck1.odp, saving +the result in combined.odp: + +```java +public class CombineDecks +{ + public static void main(String[] args) + { + try { + PresentationDocument doc1 = + PresentationDocument.loadDocument("deck1.odp"); + PresentationDocument doc2 = + PresentationDocument.loadDocument("deck2.odp"); + doc1.appendPresentation(doc2); + System.out.println("Saving combination to combined.odp"); + doc1.save("combined.odp"); + doc1.close(); + doc2.close(); + } + catch (Exception e) + { System.out.println(e); } + } // end of main() + +} // end of CombineDecks class +``` + +The PresentationDocument.appendPresentation() method does all the work, and +should be compared to my hacky Office solution in Chapter 17, section 5. \ No newline at end of file