2019-06-16

15: Optimally Use LibreOffice as a Files Converter (the Concept)

<The previous article in this series | The table of contents of this series | The next article in this series>

'soffice --convert-to' from a program is inefficient and restrictive. No need for any 3rd-party tool. A better way. Applicable to also OpenOffice.

Topics


About: UNO (Universal Network Objects)
About: LibreOffice
About: Apache OpenOffice

The table of contents of this article


Starting Context



Target Context


  • The reader will know the concept of how to optimally use LibreOffice or Apache OpenOffice as a files converter.

Orientation


This part of this article is on the concept; there are following parts of this article for implementations in Java, in C++, in C#, in Python, and in LibreOffice or Apache OpenOffice Basic.

There are an article on how to create a LibreOffice or Apache OpenOffice daemon and an article on how to create a LibreOffice or Apache OpenOffice Microsoft Windows service.

There is an article on encrypting an Office file with any opening password with UNO.

There is an article on setting any editing password into an OpenDocument file with UNO.

There is an article on setting any editing password into a binary Word/Excel file with UNO.

There is an article on setting any editing password into an Office Open XML file with UNO.

There is an article on setting the size of any page of any word processor document with UNO.

There is an article on setting the size of any page of any spread sheets document with UNO.

There is an article on setting any page property of any word processor document with UNO.

There is an article on setting any page property of any spread sheets document with UNO.

There is an article on exporting any office document into a PDF file as per full specifications with UNO.

There is an article on writing any spread sheet to a CSV file in any format with UNO.

There is an article on writing all the spread sheets of any spread sheets document to CSV files with UNO.


Main Body

Stage Direction
Here are Special-Student-7, Lenard (a Python programmer), and Abby (a C# programmer) in front of a computer.


1: 'soffice --convert-to' from Any Serious Program (Especially Any Server Program) Is Not Recommended


Special-Student-7
Although many people seem to be fixated on using 'soffice --convert-to' for converting files from their programs, I really do not recommend using it for any serious program.

Lenard
. . . What do you mean by "serious"? Are you implying that my program is a joke?

Special-Student-7
Sir, on the contrary, I am supposing that your program is a serious one, and therefore am supposing that you may want a more appropriate way.

Lenard
. . . What kinds of programs are "serious", exactly?

Special-Student-7
A typical example is a server program used by many people, like a Web application.

But also a stand-alone program may be serious if high performance is required, or if there are some specific demands.

Lenard
"specific demands"?

Special-Student-7
For example, having a signed PDF file, having all the spread sheets exported into CSV files, having the document tweaked somehow (here and here, for example), etc..

Lenard
. . . Why is 'soffice --convert-to' bad for any "serious" program?

Special-Student-7
As is detailed in an article, that way is inefficient and restrictive.

Lenard
. . . You are saying that it is slow because it starts up and shuts down an office instance at each execution . . .

Special-Student-7
Among other things.

Abby
My multiple 'soffice --convert-to' executions can't run concurrently!

Special-Student-7
It is not only yours, madam, but anybody's.

Abby
What should I do?!

Special-Student-7
If you are fine with inefficiency, you should just synchronize the executions.

Abby
I'm not fine at all!

Special-Student-7
Then, using 'soffice --convert-to' itself is a bad idea in the first place, I have to say.

Note, I am not absolutely denying 'soffice --convert-to'; I am saying that it is for odd jobs, basically used on terminals as makeshift measures.


2: You Do Not Particularly Need Any 3rd-Party Tool


Abby
Being told "a bad idea", what should I do instead? . . . Ah, should I use a 3rd-party tool?

Special-Student-7
Well, if you find a 3rd-party tool handy, I do not absolutely deny your using it, but honestly, I do not see any necessity to use any 3rd-party tool.

Abby
Why?

Special-Student-7
As far as I know, any 3rd-party tool is a wrapper of 'soffice --convert-to' or a wrapper of the UNO API.

The former inevitably inherits the problems of 'soffice --convert-to' (although they might have been somewhat glossed over).

The unavoidable concern on the latter is the thickness of the wrapper: if the wrapper is too thin, it is not so labor-saving; if the wrapper is too thick, it means too much deprived leeway. Either way, any wrapper by someone entails the concern of possible bugs, the concern of possible future abandonment, the suffering of having to endure some disagreeable tastes, and I do not find any existing 3rd-party wrapper that is handy enough to compensate such disadvantages.

After all, you can do fine directly with the UNO API, without any 3rd-party wrapper.

Abby
How do you know I can do fine? What do you know about me, mister?

Special-Student-7
If you are a person who is tasked with programming a serious program, that is enough to know.


3: Directly Using the UNO API Is Not That Difficult


Special-Student-7
I assure you that directly using the UNO API is not that difficult.

Lenard
"that difficult"? Which difficult?

Special-Student-7
A reason why the UNO API may seem more difficult than really is the not-so-good official document. Once you understand the basic elements of UNO, the UNO API is rather straightforward, although it is somewhat cumbersome.

Lenard
So, it's cumbersome!

Special-Student-7
It is cumbersome in that the code tends to become lengthy.

Lenard
So, it becomes lengthy!

Special-Student-7
That is mainly because of the UNO proxy mechanism, in which you have to get a UNO proxy per UNO interface (while a typical UNO component has implemented many UNO interfaces), but the mechanism is not difficult in any way, although a little pain in the ass is that you have to investigate what UNO interfaces the UNO object has implemented.

Lenard
So, I'll suffer a pain in my ass! What a cruel world!

Special-Student-7
But as for the limited case of file conversion, the information will be shown in this article, so there will be no trouble for your ass.

Lenard
Oh? Will my ass be OK?

Special-Student-7
Your ass will be just fine.

And as I will show workable code in each programming language, you can just copy it if you will, and the length of the code to be copied should not affect the amount of your labor much.


4: The File Conversion Logic with the UNO API


Special-Student-7
Converting any file with the UNO API is nothing but opening the original file, storing the opened document in the target format, and closing the opened document.

Any decent programmer should not hesitate about writing such an elementary task.

Abby
"storing the opened document in the target format" seems to have been expressed fishily too simple. Doesn't that take about 100 lines, in fact?

Special-Student-7
It is really about calling a storing method with the storing-filter name specified in an argument.

Abby
Is it 1 line, then?

Special-Student-7
Well, whether it is 1 line or not depends on how you write the code: as the usual cumbersomeness of UNO, you have to get a UNO proxy that has the storing method, and the argument is really a sequence of properties, so, it is a matter of whether you push those preparations into the 1 line or not.

Abby
"sequence of properties" smells fishy; I seem to have to set up a ton of properties.

Special-Student-7
Not particularly so; it is for specifying some specific specifications for the target file; if you have no specific specification in your mind, the storing-filter name is the only thing you need to specify.

Abby
What storing-filter names can I specify?

Special-Student-7
These are typical storing-filters.

The nameThe description
writer8the filter to OpenDocument Text file
calc8the filter to OpenDocument Spreadsheet file
impress8the filter to OpenDocument Presentation file
MS Word 2007 XMLthe filter to Microsoft Word 2007 XML file
Calc MS Excel 2007 XMLthe filter to Microsoft Excel 2007 XML file
Impress MS PowerPoint 2007 XMLthe filter to Microsoft PowerPoint 2007 XML file
MS Word 97the filter to Microsoft Word 97 file
MS Excel 97the filter to Microsoft Excel 97 file
MS PowerPoint 97the filter to Microsoft PowerPoint 97 file
Text - txt - csv (StarCalc)the filter to CSV file
writer_pdf_Exportthe filter from Writer document to PDF file
calc_pdf_Exportthe filter from Calc document to PDF file
impress_pdf_Exportthe filter from Impress document to PDF file
draw_pdf_Exportthe filter from Draw document to PDF file
EPUBthe filter to EPUB file
XHTML Writer Filethe filter from Writer document to XHTML file
XHTML Calc Filethe filter from Calc document to XHTML file
XHTML Impress Filethe filter from Impress document to XHTML file
XHTML Draw Filethe filter from Draw document to XHTML file
writer_jpg_Exportthe filter from Writer document to JPEG file
calc_jpg_Exportthe filter from Calc document to JPEG file
impress_jpg_Exportthe filter from Impress document to JPEG file
draw_jpg_Exportthe filter from Draw document to JPEG file
writer_png_Exportthe filter from Writer document to PNG file
calc_png_Exportthe filter from Calc document to PNG file
impress_png_Exportthe filter from Impress document to PNG file
draw_png_Exportthe filter from Draw document to PNG file
writer_layout_dumpthe filter from Writer document to Writer layout file

Abby
When you say "sequence" . . .

Special-Student-7
'sequence' is a UNO datum type, which is mapped into 'array' in C#, for example.

Abby
So, is that it? Just, open, store, and close?

Special-Student-7
Yes, as a basic. If you want to tweak the document, you will need to know how for each tweaking, in addition, though.

Abby
I feel I'm being tricked.

Special-Student-7
You do not need to. I guess that the highest hurdle for starting UNO programming is not coding but preparing the development environment and knowing what are to be referred to for building your programs. You know, the official document does not elaborate on the Jar files, header files, libraries, etc. to be referred to.

Abby
So, I'm being tricked.

Special-Student-7
I hope not: you can clear the hurdle with the proper information contained in an article (for Linux or for Microsoft Windows).

Abby
But even then, I cannot just write only the file conversion logic in my program, can I?

Special-Student-7
You can connect your program to your office instance according to an article (for Java, for C++, for C#, or for Python).


5: The 4 Possible Ways, Including Non-Recommended Ones


Special-Student-7
While I have no hesitation about my way, you may still be persisting to consider some other options.

Let us think of 4 possible ways of converting files using LibreOffice or Apache OpenOffice, including ones that are not recommended.

The way 1 is to just make your program execute 'soffice --convert-to'.

The way 2 is to have an office instance (LibreOffice or Apache OpenOffice instance) started up beforehand and to make your program execute 'soffice --convert-to' by the office instance operating system user. You have to take care that the office instance is not shut down by any 'soffice --convert-to' execution.

The way 3 is to have an office instance started up beforehand as a TCP/IP socket UNO server and to implement the file conversion logic in your external UNO client.

The way 4 is to have an office instance started up beforehand as a TCP/IP socket UNO server, to implement the file conversion logic in the office instance as a UNO service or a macro, and to make your external UNO client invoke the UNO service or the macro.

Abby
Well, in the way 2, how can I "take care that the office instance is not shut down"?

Special-Student-7
For any office version I have tried, if you just start up the office instance in the headless mode or the invisible mode, any 'soffice --convert-to' execution will shut the office instance down. So, you should take a measure that is described in a later section or just start up the office instance in the non-headless, non-invisible mode.

Abby
Shouldn't I make the office instance a Windows service?

Special-Student-7
Not necessarily, but making the office instance a Linux daemon or a Windows service is certainly a reasonable option (how to make it so is explained in an article for daemon or for Windows service).

Abby
. . . How can I create a "UNO service"?

Special-Student-7
It is explained in an article for Java, for C++, or for Python.

Abby
C# and I are ignored!

Special-Student-7
The server part cannot be created in C#, although you can create the external UNO client in C#. So, your option is to use Java, C++, or Python for the server part, or to go on the way 3.


6: Some Non-Efficiency Implications of the 4 Ways


Special-Student-7
Before we see the efficiencies of the 4 ways in the next section, we will consider some non-efficiency implications of the 4 ways.

A grave problem of the way 1 and the way 2 is that concurrent executions of 'soffice --convert-to' will fail, at least for the office versions I have tried with.

Abby
That's an efficiency problem really, because I have to synchronize the requests after all, which is nothing but an efficiency problem.

Special-Student-7
Well, I think that you are right virtually; I said that before you turned to the solution of synchronization, it might look like a non-efficiency problem.

Abby
Anyway, only one execution at a time is not acceptable with a certain traffic.

Special-Student-7
Another problem of the way 1 and the way 2 is that executions of 'soffice --convert-to' have to be local to the office server.

Abby
So, my program has to be in the same computer with the office instance . . .

Special-Student-7
Well, it is possible for the program to send executions to the office instance computer via SSH or something, though.

Abby
Ah, it's possible, but with an overhead.

Special-Student-7
And executions have to be by the office instance operating system user.

Abby
Hmm, that is not critical, but is not favorable anyway; I want to run my Web application by a different user because it should not need the privilege to directly access the document files.

Special-Student-7
Another problem of the way 1 and the way 2 is that you do not have full control over the specifications of conversions.

Abby
Do the way 3 and the way 4 have full control?

Special-Student-7
Yes. In addition to specifying full properties for the document-storing method, they can tweak the document in any way.

Lenard
"tweak"? Do I want the document to be tweaked?

Special-Student-7
I do not know. . . . But if you can change some styles, add the page headers or something, etc., that may be handy.

Lenard
What are non-efficiency implications of the way 3 and the way 4?

Special-Student-7
They do not have the above problems.

Lenard
And what about other problems?

Special-Student-7
Some people may complain that the UNO API cannot be used in their programming languages, for example PHP.

Lenard
Ah, that's their problem.

Special-Student-7
Well, even if their programming language cannot use the UNO API directly, it should be able to use the UNO API indirectly via, for example, a Web application implemented in Java or something. Besides, PHP should be able to have a PHP extension written in C++.

Lenard
You shouldn't expect them to be able to write something in Java, or in C++.

Special-Student-7
I am not expecting all of them to be able to do so by themselves, but can they not ask someone to do so for them?

Lenard
I don't know; that's their problem.


7: Comparing the Efficiencies of the 4 Ways


Special-Student-7
Let us compare the times taken in converting some files in the 4 ways.

I have measured the time taken in converting 10 very small (each having 33 characters) ODF Text document files to 10 Microsoft Word 97-2003 files by each of the 4 ways (all the test programs are written in C++). Each office instance is in the same computer with the test program instances, in the headless mode. The way 1 and the way 2 are tested not via SSH or via SSH. The tests via SSH are done by executing a 'ssh' command per file conversion (SSH-1) or by establishing a SSH connection only once and sending the multiple file conversion commands through the single SSH connection (SSH-2).

Please have in view the fact that the test computer (a Linux machine) is very low-speed and has only a single core CPU when you judge the results.

These are the average times of the 5 tests of each way with the minimum and the maximum times excluded, in milliseconds.

@Output
+------------+------+------+------+------+------+------+-----+-----+
|The way     |1 not |1     |1     |2 not |2     |2     |3    |4    |
|            |via   |via   |via   |via   |via   |via   |     |     |
|            |SSH   |SSH-1 |SSH-2 |SSH   |SSH-1 |SSH-2 |     |     |
+------------+------+------+------+------+------+------+-----+-----+
|The time    |32,899|52,733|27,859| 3,448|20,281| 5,276|3,066|2,873|
+------------+------+------+------+------+------+------+-----+-----+
|The overhead|30,026|49,860|24,986|   575|17,408| 2,403|  193|    0|
+------------+------+------+------+------+------+------+-----+-----+

Lenard
Hmm, I understand that the way 1 and SSH-1 are unquestionably slow . . .

Special-Student-7
That is as expected, considering the mechanisms.

The reason why the way 1 is so slow is that it starts up and shuts down an office instance per conversion, which certainly takes unignorable time.

Abby
Why is the way 2 so different?

Special-Student-7
Please understand how any 'soffice' execution works. In the way 2, 'soffice --convert-to' does not start up any new office instance.

That is why I recommend you to take at least the way 2, if you insist to use 'soffice --convert-to'.

Lenard
The way 2 not via SSH and the way 2 via SSH-2 take about 1.2 times and 1.8 times times, respectively, than the way 4 . . .

Special-Student-7
Probably, considering the ratios is not much useful because they depend on the times purely taken in converting the files. That is the reason why I have shown the overhead times.

Lenard
"the overhead times"?

Special-Student-7
The times purely taken in converting the files should be the same for all the ways; the issue is the times taken otherwise.

Lenard
So, the overhead time per file conversion for the way 2 not via SSH is about 57 milliseconds, . . . not much, I would say, although 2.4 seconds for the way 2 via SSH-2 makes me think.

Special-Student-7
Of course, that will depend on your system requirements.

Abby
The way 2 seems good enough.

Special-Student-7
But you should remember the concurrency issue.


8: How to Prevent the Office Instance from Being Shut Down


Special-Student-7
Supposing that you are going on the way 2, if you have just started the office instance up in the headless mode or in the invisible mode, any 'soffice --convert-to' execution will shut the instance down, at least for the office versions I have tried with.

Lenard
How nosy! Why will it do such a thing?

Special-Student-7
After all, 'soffice --convert-to' is supposing the way 1, so, it thinks that it has to clean the instance up, which is supposed to have been started up by it.

Lenard
I see.

Special-Student-7
Anyway, your program can prevent the office instance from being shutdown, by vetoing the shutdown.

Lenard
Can it do that? "veto"? Like a president?

Special-Student-7
Yes. Your program registers itself as an office-instance-termination-attempts listener into the office instance, then, your program will be asked if it is OK when the office instance tries to shut itself down, and your program can answer "No!".

These are some code pieces for an office-instance-termination-attempts listener and its instance's being registered and unregistered in each programming language, where 'l_underlyingRemoteUnoObjectsContextInXComponentContext' is the UNO objects context to the office instance.

@Java Source Code
~

~
import com.sun.star.frame.TerminationVetoException;
import com.sun.star.frame.XDesktop;
import com.sun.star.frame.XTerminateListener;
import com.sun.star.lib.uno.helper.WeakBase;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.XInterface;
~

public class Test1Test {
	private static class OfficeTerminationQueryListener extends WeakBase implements XTerminateListener {
		public OfficeTerminationQueryListener () {
		}
		
		@Override
		public void queryTermination (com.sun.star.lang.EventObject a_event) throws TerminationVetoException {
			throw new TerminationVetoException ();
		}
		
		@Override
		public void notifyTermination (com.sun.star.lang.EventObject a_event) {
		}
		
		@Override
		public void disposing (com.sun.star.lang.EventObject a_event) {
		}
	}
	
	public static void main (String [] a_argumentsArray) {
		~
				XDesktop l_unoDesktopInXDesktop = UnoRuntime.queryInterface (XDesktop.class, (XInterface) l_underlyingRemoteUnoObjectsContextInXComponentContext.getValueByName ("/singletons/com.sun.star.frame.theDesktop"));
				OfficeTerminationQueryListener l_officeTerminationQueryListener = new OfficeTerminationQueryListener ();
				l_unoDesktopInXDesktop.addTerminateListener (l_officeTerminationQueryListener);
				~
				l_unoDesktopInXDesktop.removeTerminateListener (l_officeTerminationQueryListener);
				~
		~
	}
}

@C++ Source Code
// .hpp file
~
	~
	#include <com/sun/star/frame/XTerminateListener.hpp>
	#include <com/sun/star/lang/EventObject.hpp>
	#include <cppuhelper/compbase1.hxx>
	~
	
	using namespace ::std;
	using namespace ::com::sun::star::frame;
	using namespace ::com::sun::star::lang;
	using namespace ::cppu;
	~
	
	~
				class Test1Test {
					private:
						class OfficeTerminationQueryListener: public WeakImplHelper1 <XTerminateListener> {
							public:
								OfficeTerminationQueryListener ();
								virtual ~OfficeTerminationQueryListener ();
								virtual void SAL_CALL queryTermination (::com::sun::star::lang::EventObject const & a_event) override;
								virtual void SAL_CALL notifyTermination (::com::sun::star::lang::EventObject const & a_event) override;
								virtual void SAL_CALL disposing (::com::sun::star::lang::EventObject const & a_event) override;
						};
					public:
						static int main (int const & a_argumentsNumber, char const * const a_argumentsArray []);
				};
	~
~

// .cpp file
~
#include <com/sun/star/frame/TerminationVetoException.hpp>
#include <com/sun/star/frame/XDesktop.hpp>
#include <com/sun/star/uno/Any.hxx>
#include <com/sun/star/uno/Reference.hxx>
~
#include "theBiasPlanet/unoUtilities/stringsHandling/UnoExtendedStringHandler.hpp"
~

using namespace ::com::sun::star::frame;
using namespace ::com::sun::star::uno;
~
using namespace ::theBiasPlanet::unoUtilities::stringsHandling;
~

~
			Test1Test::OfficeTerminationQueryListener::OfficeTerminationQueryListener (): WeakImplHelper1 <XTerminateListener> () {
			}
			
			Test1Test::OfficeTerminationQueryListener::~OfficeTerminationQueryListener () {
			}
			
			void SAL_CALL Test1Test::OfficeTerminationQueryListener::queryTermination (::com::sun::star::lang::EventObject const & a_event) {
				throw TerminationVetoException ();
			}
			
			void SAL_CALL Test1Test::OfficeTerminationQueryListener::notifyTermination (::com::sun::star::lang::EventObject const & a_event) {
			}
			
			void SAL_CALL Test1Test::OfficeTerminationQueryListener::disposing (::com::sun::star::lang::EventObject const & a_event) {
			}
			
			int Test1Test::main (int const & a_argumentsNumber, char const * const a_argumentsArray []) {
				~
						Reference <XDesktop> l_unoDesktopInXDesktop ( * ( (Reference <XInterface> *) l_underlyingRemoteUnoObjectsContextInXComponentContext->getValueByName (UnoExtendedStringHandler::getOustring ("/singletons/com.sun.star.frame.theDesktop")).getValue ()), UNO_QUERY);
						Reference <XTerminateListener> l_officeTerminationQueryListener (new OfficeTerminationQueryListener ());
						l_unoDesktopInXDesktop->addTerminateListener (l_officeTerminationQueryListener);
						~
						l_unoDesktopInXDesktop->removeTerminateListener (l_officeTerminationQueryListener);
						~
				~
			}
~

"UnoExtendedStringHandler::getOustring" is my utility method that gets UNO string from standard string.

@C# Source Code
~
			~
			using uno;
			using uno.util;
			using unoidl.com.sun.star.frame;
			using unoidl.com.sun.star.lang;
			using unoidl.com.sun.star.uno;
			~
			
			public class Test1Test {
				private class OfficeTerminationQueryListener: WeakBase, XTerminateListener {
					public OfficeTerminationQueryListener () {
					}
					
					public void queryTermination (unoidl.com.sun.star.lang.EventObject a_event) {
						throw new TerminationVetoException ();
					}
					
					public void notifyTermination (unoidl.com.sun.star.lang.EventObject a_event) {
					}
					
					public void disposing (unoidl.com.sun.star.lang.EventObject a_event) {
					}
				}
				
				public void main (String [] a_argumentsArray) {
					~
								XDesktop l_unoDesktopInXDesktop = (XDesktop) (l_underlyingRemoteUnoObjectsContextInXComponentContext.getValueByName ("/singletons/com.sun.star.frame.theDesktop").Value);
								OfficeTerminationQueryListener l_officeTerminationQueryListener = new OfficeTerminationQueryListener ();
								l_unoDesktopInXDesktop.addTerminateListener (l_officeTerminationQueryListener);
								~
								l_unoDesktopInXDesktop.removeTerminateListener (l_officeTerminationQueryListener);
								~
					~
				}
			}
~

@Python Source Code
from typing import List
from typing import cast
~
import uno
from com.sun.star.frame import TerminationVetoException
from com.sun.star.frame import XDesktop
from com.sun.star.frame import XTerminateListener
from com.sun.star.lang import EventObject as com_sun_star_lang_EventObject
from com.sun.star.uno import XInterface
from unohelper import Base as UnoBase
~

class Test1Test:
	class OfficeTerminationQueryListener (UnoBase, XTerminateListener):
		def __init__ (a_this: "Test1Test.OfficeTerminationQueryListener") -> None:
			None
		
		def queryTermination (a_this: "Test1Test.OfficeTerminationQueryListener", a_event: com_sun_star_lang_EventObject) -> None:
			raise TerminationVetoException ()
		
		def notifyTermination (a_this: "Test1Test.OfficeTerminationQueryListener", a_event: com_sun_star_lang_EventObject) -> None:
			None
		
		def disposing (a_this: "Test1Test.OfficeTerminationQueryListener", a_event: com_sun_star_lang_EventObject) -> None:
			None
			
	@staticmethod
	def main (a_arguments: List [str]) -> None:
		~
				l_unoDesktopInXDesktop: XDesktop = cast (XDesktop, cast (XInterface, l_underlyingRemoteUnoObjectsContextInXComponentContext.getValueByName ("/singletons/com.sun.star.frame.theDesktop")))
				l_officeTerminationQueryListener: "Test1Test.OfficeTerminationQueryListener" = Test1Test.OfficeTerminationQueryListener ()
				l_unoDesktopInXDesktop.addTerminateListener (l_officeTerminationQueryListener)
				~
				l_unoDesktopInXDesktop.removeTerminateListener (l_officeTerminationQueryListener)
				~
		~


9: Implementations in Some Programming Languages Will Be Seen in the Subsequent Parts of This Article


Special-Student-7
Now, we have understood the concept of how to optimally convert files with LibreOffice or Apache OpenOffice.

We will see implementations in some programming languages (Java, C++, C#, Python, and LibreOffice Basic) in the subsequent parts of this article.


References


<The previous article in this series | The table of contents of this series | The next article in this series>