<The previous article in this series | The table of contents of this series | The next article in this series>
How to Get Access to an Arbitrary Spread Sheet Cell from Java or Basic Macro Programs Is Explained
About: UNO (Universal Network Objects)
About: LibreOffice
About: Apache OpenOffice
About: Java Programming Language
A Note
This series is about developing UNO extensions that consist of Java UNO components, but most of its descriptions also apply to using UNO in external Java programs or Basic macros (handling spread sheets is inevitably using UNO because spread sheets are UNO objects). The only difference for using UNO in external Java programs is how to get the component context, and the difference is dealt with in the series, 'How to Use UNO (Handle LibreOffice or Apache OpenOffice Documents) in External Java Programs'. As for using UNO in Basic macros, in addition to how to get the component context, there are some other differences, and those differences are dealt with in the series, 'Notes About Using UNO (Handling LibreOffice or Apache OpenOffice Documents) in Basic Macros'.
Now, we know how to develop UNO extensions. However, we will probably want to interact with LibreOffice documents.
Or as we wanted to interact with LibreOffice documents, we began to learn how to develop UNO extensions.
. . . Yes. That's the correct order. Especially, we have wanted to read from and write to spread sheet cells.
Well, I understand that that is a typical concern when handling spread sheets is concerned.
As spread sheets documents are composed of instances of specific UNO components, we have to learn what those UNO components are and how to handle instances of those UNO components.
Although someone who isn't familiar with the term, UNO, might think, "UNO? What's UNO? I'm not interested in such a thing; I just want to read from and write to spread sheet cells.", . . .
As spread sheet cells are UNO objects and handling spread sheet cells is inevitably using UNO, he or she is certainly interested in UNO.
First, we will have to clarify our usage of terms.
What terms?
For example, 'spread sheet'.
Isn't what 'spread sheet' means obvious?
I sometimes don't know whether a 'spread sheet' means a spread sheet, a spread sheets document, or a spread sheets document file.
Ah, when we mention a 'spread sheet', it's definitely a spread sheet, not a spread sheets document nor a spread sheets document file. You know, our major principle is to state things as unambiguously as possible while using a term for multiple meanings is a major cause of ambiguity.
So, what is the spread sheets document?
The spread sheets document is a UNO object that bundles multiple spread sheets (although there are cases in which only one spread sheet is bundled).
We also distinguish 'spread sheets document file' from 'spread sheets document', don't we?
Yes, we do. The spread sheets document file is a file that is a bits array that is the result of the serialization of a spread sheets document.
So, the spread sheets document file is not a UNO object.
No, it isn't.
Anyway, in UNO programming, It's important for us to know what UNO interfaces a UNO component implements: unless we know them, we can't call methods of the UNO component. So, we will list the UNO interfaces that the spread sheets document component implements . . .
We don't have to know all the UNO interfaces just in order to read from and write to spread sheet cells, do we?
Certainly, not: we will use just a few UNO interfaces for that purpose. However, this list will be useful in the future when we want to do more things.
So, we don't need to peruse this list right now, but we can come back here when necessary.
Yes. The spread sheets document component implements these UNO interfaces.
- com.sun.star.container.XChild
- com.sun.star.document.XDocumentPropertiesSupplier
- com.sun.star.document.XCmisDocument
- com.sun.star.rdf.XDocumentMetadataAccess
- com.sun.star.document.XDocumentRecovery
- com.sun.star.document.XUndoManagerSupplier
- com.sun.star.document.XEventBroadcaster
- com.sun.star.document.XDocumentEventBroadcaster
- com.sun.star.lang.XEventListener
- com.sun.star.document.XEventsSupplier
- com.sun.star.document.XEmbeddedScripts
- com.sun.star.document.XScriptInvocationContext
- com.sun.star.frame.XModel2
- com.sun.star.util.XModifiable2
- com.sun.star.view.XPrintable
- com.sun.star.view.XPrintJobBroadcaster
- com.sun.star.frame.XStorable2
- com.sun.star.frame.XLoadable
- com.sun.star.script.XStarBasicAccess
- com.sun.star.document.XViewDataSupplier
- com.sun.star.util.XCloseable
- com.sun.star.datatransfer.XTransferable
- com.sun.star.document.XDocumentSubStorageSupplier
- com.sun.star.document.XStorageBasedDocument
- com.sun.star.script.provider.XScriptProviderSupplier
- com.sun.star.ui.XUIConfigurationManagerSupplier
- com.sun.star.embed.XVisualObject
- com.sun.star.lang.XUnoTunnel
- com.sun.star.frame.XModule
- com.sun.star.frame.XTitle
- com.sun.star.frame.XTitleChangeBroadcaster
- com.sun.star.frame.XUntitledNumbers
- com.sun.star.lang.XTypeProvider
- com.sun.star.uno.XWeak
- com.sun.star.sheet.XSpreadsheetDocument
- com.sun.star.document.XActionLockable
- com.sun.star.sheet.XCalculatable
- com.sun.star.util.XProtectable
- com.sun.star.drawing.XDrawPagesSupplier
- com.sun.star.sheet.XGoalSeek
- com.sun.star.sheet.XConsolidatable
- com.sun.star.sheet.XDocumentAuditing
- com.sun.star.style.XStyleFamiliesSupplier
- com.sun.star.view.XRenderable
- com.sun.star.document.XLinkTargetSupplier
- com.sun.star.beans.XPropertySet
- com.sun.star.lang.XMultiServiceFactory
- com.sun.star.lang.XServiceInfo
- com.sun.star.util.XChangesNotifier
- com.sun.star.sheet.opencl.XOpenCLSelection
- com.sun.star.util.XNumberFormatsSupplier
- com.sun.star.lang.XUnoTunnel
- com.sun.star.lang.XTypeProvider
- com.sun.star.uno.XWeak
- com.sun.star.uno.XAggregation
Well, it implements many UNO interfaces . . .
Then, what is the spread sheet?
The spread sheet is a UNO object that bundles spread sheet cells. As is well-known, on the display, a tab on an arbitrary spread sheets document frame represents a spread sheet.
The spread sheet component implements these UNO interfaces.
- com.sun.star.beans.XPropertySet
- com.sun.star.beans.XMultiPropertySet
- com.sun.star.beans.XPropertyState
- com.sun.star.sheet.XSheetOperation
- com.sun.star.chart.XChartDataArray
- com.sun.star.util.XIndent
- com.sun.star.sheet.XCellRangesQuery
- com.sun.star.sheet.XFormulaQuery
- com.sun.star.util.XReplaceable
- com.sun.star.util.XModifyBroadcaster
- com.sun.star.lang.XServiceInfo
- com.sun.star.lang.XUnoTunnel
- com.sun.star.lang.XTypeProvider
- com.sun.star.sheet.XCellRangeAddressable
- com.sun.star.sheet.XSheetCellRange
- com.sun.star.sheet.XArrayFormulaRange
- com.sun.star.sheet.XArrayFormulaTokens
- com.sun.star.sheet.XCellRangeData
- com.sun.star.sheet.XCellRangeFormula
- com.sun.star.sheet.XMultipleOperation
- com.sun.star.util.XMergeable
- com.sun.star.sheet.XCellSeries
- com.sun.star.table.XAutoFormattable
- com.sun.star.util.XSortable
- com.sun.star.sheet.XSheetFilterableEx
- com.sun.star.sheet.XSubTotalCalculatable
- com.sun.star.table.XColumnRowRange
- com.sun.star.util.XImportable
- com.sun.star.sheet.XCellFormatRangesSupplier
- com.sun.star.sheet.XUniqueCellFormatRangesSupplier
- com.sun.star.sheet.XSpreadsheet
- com.sun.star.container.XNamed
- com.sun.star.sheet.XSheetPageBreak
- com.sun.star.sheet.XCellRangeMovement
- com.sun.star.table.XTableChartsSupplier
- com.sun.star.sheet.XDataPilotTablesSupplier
- com.sun.star.sheet.XScenariosSupplier
- com.sun.star.sheet.XSheetAnnotationsSupplier
- com.sun.star.drawing.XDrawPageSupplier
- com.sun.star.sheet.XPrintAreas
- com.sun.star.sheet.XSheetAuditing
- com.sun.star.sheet.XSheetOutline
- com.sun.star.util.XProtectable
- com.sun.star.sheet.XScenario
- com.sun.star.sheet.XScenarioEnhanced
- com.sun.star.sheet.XSheetLinkable
- com.sun.star.sheet.XExternalSheetName
- com.sun.star.document.XEventsSupplier
What is the spread sheet cell?
The spread sheet cell is a UNO object that represents one of cells that are placed on the grid on a spread sheet.
Well, we can merge a rectangle of cells, can't we?
Yes, . . . and?
And do we call the rectangle a cell?
Well, I wonder. . . . From the viewpoint of UNO programming, the rectangle isn't a cell, in the meaning that the rectangle doesn't implement the UNO interface, 'com.sun.star.table.XCell'. However, from the viewpoint of us as users, the rectangle is a cell: we handle the rectangle as though it is a normal cell. . . . In our discussions, not calling the rectangle a cell would be inconvenient, for when we talk of cells, such rectangles of merged cells are included in our intention, in most cases.
However, we will sometimes have to talk specifically about cells in the UNO programming sense.
Yes. . . . We will call them 'atomic cells'.
Is a merged cell, meaning a cell in a rectangle of merged cells, an atomic cell?
Yes. Any atomic cell always keeps to be an atomic cell whether it has become a merged cell or remains an independent cell, as any atom doesn't cease to be an atom by being included in a molecule.
So, in our terminology, the 'atomic cell' is an indivisible cell whether it is an unmerged cell or a member of a rectangle of merged cells; the 'compound cell' is a rectangle of merged cells; the 'cell' is an atomic cell or a compound cell. . . . And we will call the top leftmost atomic cell of any compound cell the 'representative cell' of the compound cell, and the unmerged cell will be the representative cell of itself.
We need the last term because . . .
Because we handle the top leftmost atomic cell when we read from and write to any compound cell.
So, we can say, "When we read from or write to an arbitrary cell, we handle the representative cell of the cell".
Yes.
. . . Well, I can guess that such an attempt at exact distinctions are vexatious for many people, but that's necessary in order to be accurate . . .
Can you?
Certainly, but to be less accurate to be less vexatious isn't our option.
Of course.
Anyway, the spread sheet atomic cell component implements these UNO interfaces.
- com.sun.star.beans.XPropertySet
- com.sun.star.beans.XMultiPropertySet
- com.sun.star.beans.XPropertyState
- com.sun.star.sheet.XSheetOperation
- com.sun.star.chart.XChartDataArray
- com.sun.star.util.XIndent
- com.sun.star.sheet.XCellRangesQuery
- com.sun.star.sheet.XFormulaQuery
- com.sun.star.util.XReplaceable
- com.sun.star.util.XModifyBroadcaster
- com.sun.star.lang.XServiceInfo
- com.sun.star.lang.XUnoTunnel
- com.sun.star.lang.XTypeProvider
- com.sun.star.sheet.XCellRangeAddressable
- com.sun.star.sheet.XSheetCellRange
- com.sun.star.sheet.XArrayFormulaRange
- com.sun.star.sheet.XArrayFormulaTokens
- com.sun.star.sheet.XCellRangeData
- com.sun.star.sheet.XCellRangeFormula
- com.sun.star.sheet.XMultipleOperation
- com.sun.star.util.XMergeable
- com.sun.star.sheet.XCellSeries
- com.sun.star.table.XAutoFormattable
- com.sun.star.util.XSortable
- com.sun.star.sheet.XSheetFilterableEx
- com.sun.star.sheet.XSubTotalCalculatable
- com.sun.star.table.XColumnRowRange
- com.sun.star.util.XImportable
- com.sun.star.sheet.XCellFormatRangesSupplier
- com.sun.star.sheet.XUniqueCellFormatRangesSupplier
- com.sun.star.table.XCell
- com.sun.star.sheet.XCellAddressable
- com.sun.star.text.XText
- com.sun.star.container.XEnumerationAccess
- com.sun.star.sheet.XSheetAnnotationAnchor
- com.sun.star.text.XTextFieldsSupplier
- com.sun.star.document.XActionLockable
- com.sun.star.sheet.XFormulaTokens
- com.sun.star.table.XCell2
Although we now know what UNO interfaces those UNO components implement, first, we have to get references to UNO objects.
Of course.
A typical use case will be to get a reference to the current spread sheet.
By 'current', you mean the spread sheet that has the focus right now?
Yes. We can do that like this, where 'i_componentContextInXComponentContext' is the component context (how to get it in an arbitrary UNO extension is described here; how we get it in an arbitrary external UNO program is described here; how to get it in an arbitrary Basic macro is described here).
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.XComponentContext;
import com.sun.star.frame.XDesktop;
import com.sun.star.frame.XModel;
import com.sun.star.sheet.XSpreadsheetDocument;
import com.sun.star.sheet.XSpreadsheet;
import com.sun.star.sheet.XSpreadsheetView;
import thebiasplanet.coreutilities.messaging.Publisher;
XDesktop l_unoDesktopInXDesktop = null;
try {
l_unoDesktopInXDesktop = (XDesktop) UnoRuntime.queryInterface (XDesktop.class, i_componentContextInXComponentContext.getServiceManager ().createInstanceWithContext ("com.sun.star.frame.Desktop", i_componentContextInXComponentContext));
}
catch (com.sun.star.uno.Exception l_exception) {
Publisher.show (l_exception.toString ());
}
// Gets the current spread sheets document START
XSpreadsheetDocument l_currentSpreadSheetsDocumentInXSpreadsheetDocument = (XSpreadsheetDocument) UnoRuntime.queryInterface (XSpreadsheetDocument.class, l_unoDesktopInXDesktop.getCurrentComponent ());
if (l_currentSpreadSheetsDocumentInXSpreadsheetDocument != null) {
Publisher.show (l_currentSpreadSheetsDocumentInXSpreadsheetDocument.toString ());
}
else {
Publisher.show ("No spread sheets document is current.");
return "No spread sheets document is current.";
}
// Gets the current spread sheets document END
// Gets the current spread sheet START
XModel l_currentSpreadSheetsDocumentModelInXModel = (XModel) UnoRuntime.queryInterface (XModel.class, l_currentSpreadSheetsDocumentInXSpreadsheetDocument);
XSpreadsheetView l_currentSpreadSheetsDocumentViewInXSpreadsheetView = (XSpreadsheetView) UnoRuntime.queryInterface (XSpreadsheetView.class, l_currentSpreadSheetsDocumentModelInXModel.getCurrentController ());
XSpreadsheet l_currentSpreadSheetInXSpreadsheet = l_currentSpreadSheetsDocumentViewInXSpreadsheetView.getActiveSheet ();
Publisher.show (l_currentSpreadSheetInXSpreadsheet.toString ());
// Gets the current spread sheet END
If you don't understand what 'UnoRuntime.queryInterface' does, you will have to look back at here; 'i_componentContextInXComponentContext.getServiceManager ().createInstanceWithContext' creates a UNO object from the global UNO services manager.
I understand the code all right. 'l_currentSpreadSheetInXSpreadsheet' is the current spread sheet, and the current spread sheet document is already gotten as 'l_currentSpreadSheetsDocumentInXSpreadsheetDocument'.
Anyway, it's cumbersome just to get the current spread sheet.
Basically, handling UNO objects is cumbersome because a UNO object implements many UNO interfaces, and we have to get a UNO proxy for each UNO interface we intend to use. As it's cumbersome, we will later create utility classes and wrapper classes for our convenience.
All right.
By the way, it looks like we have created an instance of the service, 'com.sun.star.frame.Desktop'. Isn't that service a singleton? I mean, the desktop should be one and already exist. It seems odd to create multiple desktops.
Well, it seems a singleton. So, the 'createInstanceWithContext' method doesn't seem to necessarily create an instance, regardless of the name.
How can the 'createInstanceWithContext' method return a singleton? I mean, in the way we developed our first and second sample UNO extensions, we can't make the 'createInstanceWithContext' method return a singleton.
I don't know. I can't find any explanation about that in the reference documents. We created a singleton, but not in a way in which we can get the singleton instance from the global UNO service manager.
Well, we may have to investigate it in the future.
If we want to get a reference to another spread sheet of the spread sheets document, we can get it like this.
import com.sun.star.sheet.XSpreadsheets;
import com.sun.star.container.NoSuchElementException;
import com.sun.star.lang.WrappedTargetException;
// Gets another spread sheet START
XSpreadsheets l_currentSpreadSheetsInXSpreadsheets = l_currentSpreadSheetsDocumentInXSpreadsheetDocument.getSheets ();
XSpreadsheet l_anotherSpreadSheetInXSpreadsheet = null;
try {
l_anotherSpreadSheetInXSpreadsheet = (XSpreadsheet) UnoRuntime.queryInterface(XSpreadsheet.class, l_currentSpreadSheetsInXSpreadsheets.getByName ("Sheet2"));
Publisher.show (l_anotherSpreadSheetInXSpreadsheet.toString ());
}
catch (NoSuchElementException | WrappedTargetException l_exception) {
Publisher.show (l_exception.toString ());
}
// Gets another spread sheet END
We can get it by the spread sheet name.
Yes. Although we don't show here, we can also get it by the index of the spread sheet in the spread sheets contained in the spread sheets document.
Although we used the current spread sheets document here, the spread sheets document can be any spread sheets document if we get a reference to it?
Of course.
If we want to get a reference to an arbitrary spread sheet atomic cell on the spread sheet, we can get it like this, where 2 is the column index and 0 is the row index.
import com.sun.star.table.XCell;
// Gets a spread sheet cell START
XCell l_spreadSheetCellInXCell = null;
try {
l_spreadSheetCellInXCell = l_currentSpreadSheetInXSpreadsheet.getCellByPosition (2, 0);
Publisher.show (l_spreadSheetCellInXCell.toString ());
}
catch (com.sun.star.lang.IndexOutOfBoundsException l_exception) {
Publisher.show (l_exception.toString ());
}
// Gets a spread sheet cell END
Column indexes and row indexes start from 0.
Yes.
Again, the spread sheet can be any spread sheet?
Yes.
Postscript:
To be more accurate, what we get here is the first selected atomic cell (the first atomic cell among the selected cells), not necessarily the representative cell of the current cell. The difference between the current cell and the selected cells is described here. At the time of writing this article, we weren't concerned with the difference. In cases in which we use only single cell selections, the first selected atomic cell is identical with the representative cell of the current cell.
We can get the reference to any spread sheet atomic cell on the spread sheet, by the spread sheet atomic cell column index and row index, but sometimes we will want to get the reference to the representative cell of the current spread sheet cell.
That is a possibility.
We can get it like this.
import com.sun.star.uno.AnyConverter;
import com.sun.star.uno.XInterface;
import com.sun.star.sheet.XSheetCellRange;
import com.sun.star.sheet.XSheetCellRangeContainer;
// Gets the current spread sheet cell START
XInterface l_currentSelectionInXInterface = (XInterface) UnoRuntime.queryInterface (XInterface.class, l_currentSpreadSheetsDocumentInXModel.getCurrentSelection ());
XSheetCellRange l_currentCellsInXSheetCellRange = (XSheetCellRange) UnoRuntime.queryInterface (XSheetCellRange.class, l_currentSelectionInXInterface);
if (l_currentCellsInXSheetCellRange == null) {
XSheetCellRangeContainer l_currentCellsInXSheetCellRangeContainer = (XSheetCellRangeContainer) UnoRuntime.queryInterface (XSheetCellRangeContainer.class, l_currentSelectionInXInterface);
if (l_currentCellsInXSheetCellRangeContainer != null) {
try {
l_currentCellsInXSheetCellRange = (XSheetCellRange) AnyConverter.toObject (XSheetCellRange.class , l_currentCellsInXSheetCellRangeContainer.getByIndex (0));
}
catch (com.sun.star.lang.IndexOutOfBoundsException | WrappedTargetException l_exception) {
Publisher.show (l_exception.toString ());
}
}
else {
}
}
XCell l_currentSpreadSheetCellInXCell = null;
if (l_currentCellsInXSheetCellRange != null) {
try {
l_currentSpreadSheetCellInXCell = l_currentCellsInXSheetCellRange.getCellByPosition (0, 0);
Publisher.show (l_currentSpreadSheetCellInXCell.toString ());
}
catch (com.sun.star.lang.IndexOutOfBoundsException l_exception) {
Publisher.show (l_exception.toString ());
}
}
else {
Publisher.show ("No cell seems to be selected.");
}
// Gets the current spread sheet cell END
So far, we have talked on the precondition that the spread sheets document to be accessed is already loaded into LibreOffice and current. How can we open an arbitrary spread sheets document file?
We can open an arbitrary spread sheets document file like this.
import com.sun.star.util.XCloseable;
import com.sun.star.util.CloseVetoException;
import com.sun.star.frame.FrameSearchFlag;
import com.sun.star.frame.XComponentLoader;
import com.sun.star.frame.XStorable2;
import com.sun.star.beans.PropertyValue;
// Opens and closes a spread sheets document file START
XStorable2 l_currentSpreadSheetsDocumentInXStorable2 = (XStorable2) UnoRuntime.queryInterface (XStorable2.class, l_currentSpreadSheetsDocumentInXSpreadsheetDocument);
String l_currentDocumentUrl = l_currentSpreadSheetsDocumentInXStorable2.getLocation ();
String l_openedDocumentUrl = String.format ("%s%s", l_currentDocumentUrl.substring (0, l_currentDocumentUrl.lastIndexOf ("/") + 1), "TestSpreadSheetsDocument1.ods");
XComponentLoader l_unoDesktopInXComponentLoader = (XComponentLoader) UnoRuntime.queryInterface (XComponentLoader.class, l_unoDesktopInXDesktop);
PropertyValue [] l_loadPropertiesArray = new PropertyValue [2];
PropertyValue l_loadProperty = null;
l_loadProperty = new PropertyValue ();
l_loadProperty.Name = "ReadOnly";
l_loadProperty.Value = Boolean.valueOf (false);
l_loadPropertiesArray [0] = l_loadProperty;
l_loadProperty = new PropertyValue ();
l_loadProperty.Name = "Hidden";
l_loadProperty.Value = Boolean.valueOf (false);
l_loadPropertiesArray [1] = l_loadProperty;
XSpreadsheetDocument l_openedSpreadSheetsDocumentInXSpreadsheetDocument = null;
try {
l_openedSpreadSheetsDocumentInXSpreadsheetDocument = UnoRuntime.queryInterface (XSpreadsheetDocument.class, l_unoDesktopInXComponentLoader.loadComponentFromURL (l_openedDocumentUrl.replaceAll ("\\\\", "/"), "_blank", FrameSearchFlag.CREATE, l_loadPropertiesArray));
Publisher.show (l_openedSpreadSheetsDocumentInXSpreadsheetDocument.toString ());
XCloseable l_openedSpreadSheetsDocumentInXSpreadsheetDocumentInXCloseable = (XCloseable) UnoRuntime.queryInterface(XCloseable.class, l_openedSpreadSheetsDocumentInXSpreadsheetDocument);
l_openedSpreadSheetsDocumentInXSpreadsheetDocumentInXCloseable.close (false);
}
catch (com.sun.star.io.IOException | CloseVetoException l_exception) {
Publisher.show (l_exception.toString ());
}
// Opens and closes a spread sheets document file END
Um? What are we doing using 'XStorable2'?
We are on the premise that the spread sheets document file, 'TestSpreadSheetsDocument1.ods', is in the same directory with the file of the current spread sheets document, and are getting the URL of 'TestSpreadSheetsDocument1.ods' using the URL of the current spread sheets document file. If we want to directly specify the URL of the opened file, we don't have to do such a thing.
I see.
Our test code (in fact, it also includes the logics described in some following articles) is here. To test, we have to build the projects (how to build projects is described here); import a Basic library, 'UnoExtensionsTests', which is in the 'execution' directory of the 'testUnoExtensionToDisclose' project (how to import a Basic library is described here); open 'TestSpreadSheetsDocumentForExecutingTests.ods' in the 'execution' directory; push the 'Test1' button on the 'Tests' spread sheet.
Isn't it dangerous to import a Basic library and execute a Macro program as we are told?
Ah, of course, we should never do such a thing blindly. After we imported the library (just importing won't do any harm), peruse the contents of the library, and only if we happen to be satisfied that it's safe, we should push the button.
What if we are worried about just importing?
Then, open the files in the 'UnoExtensionsTests' directory, in a text editor: we can see the contents in the text editor.
<The previous article in this series | The table of contents of this series | The next article in this series>