Title: 54: Import Any Module into Your LibreOffice Python Macro, Part 2
Also any in-document module can be imported into your macro or any module imported by your macro. Here is how.Topics
About: UNO (Universal Network Objects)
About: LibreOffice
About: Apache OpenOffice
About: The Python programming language
The table of contents of this article
- Starting Context
- Target Context
- Orientation
- Main Body
- 1: Any In-Document Module Is Another Story Because . . .
- 2: The Module Will Be Able to Be Imported, If the Module Contents Are Gotten
- 3: The Module Contents Can Be Gotten from the Opened Document, If the URL Is Known
- 4: Supposing That the Module Is Imported into a Macro in the Same Document
- 5: But If the Module Is Imported into a Module Imported into a Macro in the Same Document?
- 6: Supposing That the Module Is Imported into a Macro or Module Not in the Same Document
- 7: The Code of a Source Loader and a Module Importer
- 8: An Example Usage
Starting Context
- The reader has a basic knowledge on LibreOffice or Apache OpenOffice.
- The reader has a knowledge on what UNO is and how it is related to LibreOffice or Apache OpenOffice.
- The reader has a basic knowledge on the Python programming language.
Target Context
- The reader will know how to import any in-document module into his or her LibreOffice or Apache OpenOffice Python macro module.
Orientation
Using an external full Python has been addressed in a previous article.
Creating your user-owned or application-owned Python macro has been addressed in a previous article.
Creating your in-document Python macro has been addressed in a previous article.
Creating your in-extension Python macro has been addressed in a previous article.
Importing any non-in-document module has been addressed in the 1st part of this article.
The details of Python macro programming will be dug into in some future articles.
Main Body
Stage DirectionHere are Hypothesizer 7, Objector 54A, and Objector 54B in front of a computer.
1: Any In-Document Module Is Another Story Because . . .
Hypothesizer 7
If the module to be imported is not in-document, the 1st part of this article should be enough.
Any in-document module is another story because it is not in any operating system file.
Objector 54A
It IS in the document operating system file.
Hypothesizer 7
Certainly, sir, but what I mean is that the document operating system file is not the module file, but a ZIP file that contains the module file.
Objector 54A
So, I was right.
Hypothesizer 7
Well, to be exact, any in-document module is not DIRECTLY in any operating system file.
Anyway, the issue is that such a module location cannot be set in 'PYUNO_LOADER_PYTHONPATH' or 'sys.path'.
2: The Module Will Be Able to Be Imported, If the Module Contents Are Gotten
Hypothesizer 7
In fact, if the module contents are gotten, the module will be able to be imported all right.
Objector 54B
. . . So, I can just create the source loader?
Hypothesizer 7
If you can get the module contents, yes, madam.
Objector 54B
I can extract the module file from the ZIP file (the document file is a ZIP file, isn't it?), can't I?
Hypothesizer 7
That may be an option, if the document file is not encrypted.
Objector 54B
Well, what if?
Hypothesizer 7
The contents of the module file is encrypted.
Objector 54B
. . . I can decrypt the contents, can't I?
Hypothesizer 7
Can you?
Objector 54B
. . . Can't I?
Hypothesizer 7
Well, logically speaking, it should be possible if you learn the logic from the LibreOffice or Apache OpenOffice source files, and of course, you make the user provide the password.
Objector 54B
. . .
3: The Module Contents Can Be Gotten from the Opened Document, If the URL Is Known
Hypothesizer 7
In fact, the modules contents can be gotten from the opened document, like this, where 'l_remoteUnoObjectsContextInXComponentContext' is the UNO objects context to the LibreOffice or Apache OpenOffice instance, 'a_url' is the module URL, and the return is a 'bytes' datum.
@Python Source Code
from uno import ByteSequence
from com.sun.star.io import XInputStream
from com.sun.star.ucb import XSimpleFileAccess
l_simpleFilesAccessUnoService: XSimpleFileAccess = cast (XSimpleFileAccess, l_remoteUnoObjectsContextInXComponentContext.getServiceManager ().createInstanceWithContext ("com.sun.star.ucb.SimpleFileAccess", l_remoteUnoObjectsContextInXComponentContext))
try:
l_inputStream: "XInputStream" = l_simpleFilesAccessUnoService.openFileRead (a_url)
l_readLength: int = 0
l_inputDatum: ByteSequence = ByteSequence (b"")
l_inputBuffer: ByteSequence = None
while True:
l_readLength, l_inputBuffer = l_inputStream.readBytes (None, 1024)
l_inputDatum = l_inputDatum + l_inputBuffer
if l_readLength < 1024:
break
finally:
if l_inputStream is not None:
l_inputStream.closeInput
return l_inputDatum.value
Objector 54A
. . . Huh? "UNO objects context"? What is that?
Hypothesizer 7
. . . Please do not try to do without learning the basics.
Objector 54A
"basics"? Are you kidding me? They are beneath me.
Hypothesizer 7
. . . If your program is a macro, 'XSCRIPTCONTEXT.getComponentContext ()' returns one.
Objector 54A
So, what is the URL?
Hypothesizer 7
The URL is like 'vnd.sun.star.tdoc:/1/Scripts/python/theBiasPlanet/pythonEnvironmentChecker/InDocumentModuleTest.py'.
Objector 54A
So, I can just replace the part after 'Scripts/python'?
Hypothesizer 7
In fact, that number, '1', is the document loading number.
Objector 54A
What do you mean?
Hypothesizer 7
The 1st loaded document is given '1'; the second '2'; etc., since the instance was started.
Objector 54A
How, the hell, am I supposed to know the number of a specific document?
Hypothesizer 7
Well, there can be some ways.
4: Supposing That the Module Is Imported into a Macro in the Same Document
Hypothesizer 7
Let us suppose that the module is imported into a macro in the same document, which is. I suppose, usual.
Objector 54B
How else can it be?
Hypothesizer 7
The module might be imported into a macro in another document, or a user-owned, application-owned, or in-extension macro.
Objector 54B
That's unlikely, I suppose.
Hypothesizer 7
If so, it is very convenient for me, and you.
Objector 54B
So, supposing so . . .
Hypothesizer 7
In fact, in a section of the 1st part of this article, I have already implemented a logic that sets the macro module URL into a variable ('s_sourceFileUrl') in the macro module.
Objector 54B
. . . Hmm, you mean the URL of the importing macro, not the URL of the imported module, which I really need?
Hypothesizer 7
Yes, but the document loading number, which you need, is the same.
Objector 54B
Certainly.
5: But If the Module Is Imported into a Module Imported into a Macro in the Same Document?
Objector 54B
But if the module is imported into a module (of course, in the same document) imported into a macro in the same document?
Hypothesizer 7
So, let us make the macro set the URL of the imported module into the imported module.
Objector 54B
Ah, of course, so, URLs are set by turns.
Hypothesizer 7
Implementation-wise, the source loader can set the URL into the imported module.
6: Supposing That the Module Is Imported into a Macro or Module Not in the Same Document
Objector 54A
Then, do suppose that the module is imported into a macro or module NOT in the same document.
Hypothesizer 7
Well, do you really need that, sir?
Objector 54A
I don't really, at least, for the time being, but do so anyway.
Hypothesizer 7
Why, sir?
Objector 54A
Because I won't let you get away with not fulfilling your promise that any module can be imported.
Hypothesizer 7
Fair enough. There can be some ways, but I would prepare a macro in the document, that (the macro) returns the document Python modules base URL.
Objector 54A
. . . How can I call the macro?
Hypothesizer 7
The way introduced in a previous article should do.
Objector 54A
. . . Well, what other ways?
Hypothesizer 7
Well, I do not know any more recommendable way, but maybe, you could create a document opening events handler that records document openings, or just do brute force tries, for example.
7: The Code of a Source Loader and a Module Importer
Hypothesizer 7
This is the code of a source loader and a module importer.
theBiasPlanet/unoUtilities/pythonSourceLoader/UnoExtendedPythonSourceLoader.py
@Python Source Code
from typing import Union
from typing import cast
from importlib.abc import SourceLoader
import uno
from uno import ByteSequence
from com.sun.star.io import XInputStream
from com.sun.star.ucb import XSimpleFileAccess
from com.sun.star.uno import XComponentContext
class UnoExtendedPythonSourceLoader (SourceLoader):
c_readingBlockSize: int = 1024
def __init__ (a_this: "UnoExtendedPythonSourceLoader", a_remoteUnoObjectsContextInXComponentContext: "XComponentContext", a_uriPrefix: str) -> None:
a_this.i_simpleFilesAccessUnoService: XSimpleFileAccess = None
a_this.i_uriPrefix: str = a_uriPrefix
a_this.i_simpleFilesAccessUnoService = cast (XSimpleFileAccess, a_remoteUnoObjectsContextInXComponentContext.getServiceManager ().createInstanceWithContext ("com.sun.star.ucb.SimpleFileAccess", a_remoteUnoObjectsContextInXComponentContext))
def get_filename (a_this: "UnoExtendedPythonSourceLoader", a_moduleName: str) -> str:
return "{0:s}{1:s}.{2:s}".format (a_this.i_uriPrefix, a_moduleName.replace (".", "/"), "py")
def get_data (a_this: "UnoExtendedPythonSourceLoader", a_url: Union [bytes, str]) -> bytes:
try:
l_inputStream: "XInputStream" = a_this.i_simpleFilesAccessUnoService.openFileRead (a_url)
l_readLength: int = 0
l_inputDatum: ByteSequence = ByteSequence (b"")
l_inputBuffer: ByteSequence = None
while True:
l_readLength, l_inputBuffer = l_inputStream.readBytes (None, 1024)
l_inputDatum = l_inputDatum + l_inputBuffer
if l_readLength < 1024:
break
finally:
if l_inputStream is not None:
l_inputStream.closeInput
return l_inputDatum.value
theBiasPlanet/unoUtilities/pythonModuleImporter/UnoExtendedPythonModuleImporter.py
@Python Source Code
from typing import Optional
from collections import OrderedDict
import sys
from types import ModuleType
from theBiasPlanet.unoUtilities.pythonSourceLoader.UnoExtendedPythonSourceLoader import UnoExtendedPythonSourceLoader
class UnoExtendedPythonModuleImporter:
c_pythonModulesBaseDirectoryIndicator: str = "/Scripts/python/"
c_sourceFileUrlModulePropertyName: str = "s_sourceFileUrl"
@staticmethod
def getUriPrefix (a_sourceFileUrl: str) -> str:
l_uriPrefix: str = a_sourceFileUrl [0: a_sourceFileUrl.find (UnoExtendedPythonModuleImporter.c_pythonModulesBaseDirectoryIndicator) + len (UnoExtendedPythonModuleImporter.c_pythonModulesBaseDirectoryIndicator)]
return l_uriPrefix
@staticmethod
def importModule (a_unoExtendedPythonSourceLoader: "UnoExtendedPythonSourceLoader", a_moduleName: str, a_setModuleProperties: "Optional [OrderedDict [str, object]]") -> ModuleType:
l_pythonModule = ModuleType (a_moduleName)
l_pythonModule.__dict__ [UnoExtendedPythonModuleImporter.c_sourceFileUrlModulePropertyName] = a_unoExtendedPythonSourceLoader.get_filename (a_moduleName)
l_propertyName: str
l_propertyValue: object
for l_propertyName, l_propertyValue in a_setModuleProperties.items ():
l_pythonModule.__dict__ [l_propertyName] = l_propertyValue
a_unoExtendedPythonSourceLoader.exec_module (l_pythonModule)
sys.modules [a_moduleName] = l_pythonModule
return l_pythonModule
Objector 54B
Well . . .
Hypothesizer 7
The constructor of the source loader takes the UNO objects context to the LibreOffice or Apache OpenOffice instance and the URL prefix like 'vnd.sun.star.tdoc:/1/Scripts/python/'.
The importing method of the module importer takes the module name and the properties to be set into the imported module.
8: An Example Usage
Hypothesizer 7
This is an example usage that imports an in-the-same-document module, 'theBiasPlanet.pythonEnvironmentChecker.InDocumentModuleTest', into a module, which may be or not be a macro module.
@Python Source Code
from collections import OrderedDict
~
import sys
~
from types import ModuleType
~
from com.sun.star.script.provider import XScriptContext
~
from theBiasPlanet.unoUtilities.pythonModuleImporter.UnoExtendedPythonModuleImporter import UnoExtendedPythonModuleImporter
from theBiasPlanet.unoUtilities.pythonSourceLoader.UnoExtendedPythonSourceLoader import UnoExtendedPythonSourceLoader
XSCRIPTCONTEXT: XScriptContext
s_unoExtendedPythonSourceLoader: "UnoExtendedPythonSourceLoader" = UnoExtendedPythonSourceLoader (XSCRIPTCONTEXT.getComponentContext (), UnoExtendedPythonModuleImporter.getUriPrefix (s_sourceFileUrl))
s_pythonModule: ModuleType = UnoExtendedPythonModuleImporter.importModule (s_unoExtendedPythonSourceLoader, "theBiasPlanet.pythonEnvironmentChecker.InDocumentModuleTest", OrderedDict ( [ ("XSCRIPTCONTEXT", XSCRIPTCONTEXT)]))
InDocumentModuleTest = s_pythonModule.InDocumentModuleTest
~
def checkPythonEnvironment2 (a_message: str) -> str:
return "The Python environment: version -> {0:s}, paths -> {1:s}\n".format (sys.version, str (sys.path)) + ", " + InDocumentModuleTest.c_test + ", " + s_sourceFileUrl
Objector 54A
. . . That's somehow fussy.
Hypothesizer 7
Is it? Whatever it looks like, the only thing you have to change is the module name.
Objector 54A
Well . . .
Hypothesizer 7
Note that that 's_sourceFileUrl' is there only because 'pythonscript.py' (modified as in the 1st part of this article) or the module importer has set it there.
Objector 54A
How about a not-in-the-same-document module?
Hypothesizer 7
Well, the only issue is that you have to use an appropriate value instead of 's_sourceFileUrl'.
Objector 54A
How appropriate?
Hypothesizer 7
Very appropriate.
Objector 54A
. . . I don't understand what exactly "a macro in the document, that (the macro) returns the document Python modules base URL" should be like?
Hypothesizer 7
Don't you? The macro will just return 'UnoExtendedPythonModuleImporter.getUriPrefix (s_sourceFileUrl)', like this.
@Python Source Code
def getBaseUrlOfThisDocumentPythonModules () -> str:
return UnoExtendedPythonModuleImporter.getUriPrefix (s_sourceFileUrl)
As I said, how to call the macro is described in a previous article.