The GUI looks as though you cannot, but actually, you can. So, you do not need to use Basic just in order to locate your macro in-document.
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: You Can Locate Your Python Macro in Any 'odt', 'ods', 'odp', 'odb', 'odf', or 'odg' File
- 2: How to Locate Your Python Macro in Any 'odt', 'ods', 'odp', 'odb', 'odf', or 'odg' File
- 3: Testing
Starting Context
- The reader has a basic knowledge on LibreOffice or Apache OpenOffice.
- The reader has a basic knowledge on the Python programming language.
Target Context
- The reader will know how to create his or her in-document Python macro for LibreOffice or Apache OpenOffice.
Orientation
Creating your user-owned or application-owned Python macro has been addressed in the previous article.
Executing any macro (user-owned, application-owned, or in-document) function from your UNO program will be addressed in a future article.
The details of Python macro programming will be dug into in some future articles.
Using an external full Python has been addressed in a previous article.
Stage Direction
Here are Hypothesizer 7, Objector 49A, and Objector 49B in front of a computer.
Main Body
1: You Can Locate Your Python Macro in Any 'odt', 'ods', 'odp', 'odb', 'odf', or 'odg' File
Hypothesizer 7
You can locate your Python macro in any 'odt', 'ods', 'odp', 'odb', 'odf', or 'odg' file.
Objector 49A
You aren't serious, right?
Hypothesizer 7
I am very serious, sir.
Objector 49A
But I've heard I can't.
Hypothesizer 7
Then, the one who said so did not know how.
Objector 49A
Everyone said so!
Stage Direction
Hypothesizer 7 shakes his head plaintively.
Hypothesizer 7
I did not, sir . . .
Objector 49A
But the GUI doesn't let me.
Hypothesizer 7
Well, there seems to be a prevalent misconception that the GUI shows what can be done; actually, the GUI shows only what can be done via the GUI.
Objector 49A
. . . Seeing the GUI, everybody will think that any in-document Python macro is not allowed!
Stage Direction
Hypothesizer 7 shakes his head plaintively.
Hypothesizer 7
I will not, sir. You know, the word, "everybody", should not be used carelessly.
Objector 49B
So, you tell me to execute a command to put my macro in-document?
Hypothesizer 7
Madam, in fact, it is more than a single command.
Objector 49B
That sounds difficult!
Hypothesizer 7
Any one who attempts to create a macro should be able to do it all right, in my opinion.
Objector 49A
You shouldn't use "Any one" carelessly!
Hypothesizer 7
I have used it carefully, sir. I literally mean it: you should learn, if you really cannot now.
2: How to Locate Your Python Macro in Any 'odt', 'ods', 'odp', 'odb', 'odf', or 'odg' File
Hypothesizer 7
Any 'odt', 'ods', 'odp', 'odb', 'odf', or 'odg' file is really a ZIP file.
Objector 49B
Is that so? Can I just expand the file?
Hypothesizer 7
Yes, you can.
For example, let us expand this 'odt' file, 'MacroTests.odt', into a directory, 'fileIngredients', like this, in this Linux machine.
@bash Source Code
l_targetFileName="MacroTests.odt"; l_expansionDirectoryName="fileIngredients"; mkdir "./${l_expansionDirectoryName}"; cp "./${l_targetFileName}" "./${l_expansionDirectoryName}/."; cd "./${l_expansionDirectoryName}"; unzip "./${l_targetFileName}"; rm "./${l_targetFileName}"
Objector 49B
. . . Well, I don't use Linux, but I can just expand the file in whatever way, right?
Hypothesizer 7
Right, madam.
Then, we copy the Python macro file under the 'Scripts/python' directory (which you have to create) in the expansion directory, like this, where 'PythonEnvironmentChecker.py' is the Python macro file.
@bash Source Code
l_macroBasePath=~/"myData/development/pythonEnvironmentCheckerUnoExtension/source/python"; l_macroPackagePath="theBiasPlanet/pythonEnvironmentChecker/macros"; l_macroFileName="PythonEnvironmentChecker.py"; mkdir -p "./Scripts/python/${l_macroPackagePath}"; cp "${l_macroBasePath}/${l_macroPackagePath}/${l_macroFileName}" "./Scripts/python/${l_macroPackagePath}/."
Objector 49B
"theBiasPlanet/pythonEnvironmentChecker/macros"?
Hypothesizer 7
That is just in order to demonstrate that you can put the Python module in any package; of course, you can put the Python module file directly in the 'Scripts/python' directory, if you will.
Objector 49B
Hmm.
Hypothesizer 7
Then, we edit the 'META-INF/manifest.xml' file to add this in the 'manifest:manifest' node, after the existing child nodes.
@xml Source Code
<manifest:file-entry manifest:full-path="Scripts" manifest:media-type="application/binary"></manifest:file-entry><manifest:file-entry manifest:full-path="Scripts/python" manifest:media-type="application/binary"></manifest:file-entry><manifest:file-entry manifest:full-path="Scripts/python/theBiasPlanet" manifest:media-type="application/binary"></manifest:file-entry><manifest:file-entry manifest:full-path="Scripts/python/theBiasPlanet/pythonEnvironmentChecker" manifest:media-type="application/binary"></manifest:file-entry><manifest:file-entry manifest:full-path="Scripts/python/theBiasPlanet/pythonEnvironmentChecker/macros" manifest:media-type="application/binary"></manifest:file-entry><manifest:file-entry manifest:full-path="Scripts/python/theBiasPlanet/pythonEnvironmentChecker/macros/PythonEnvironmentChecker.py" manifest:media-type=""></manifest:file-entry>
Objector 49B
It's bothersome . . .
Hypothesizer 7
But you can do it, right?
Objector 49B
I don't say I can't, but it's bothersome.
Hypothesizer 7
The last step is to re-archive the expansion directory, like this.
@bash Source Code
zip -r "../${l_targetFileName}" *
Objector 49B
They are bothersome steps.
Hypothesizer 7
On the contrary, you can just execute a shell script or batch file like this, although you have to edit the manifest file still.
@bash Source Code
#!/bin/bash# supposed to be executed at the target file directoryl_targetFileName="$1"l_modeName="$2" # 'add' or 'remove'l_macroBasePath="$3" # empty if the mode name is 'remove'l_macroPackagePath="$4" # can be empty if the mode name is 'remove' (the 'Scripts' path will be removed)l_macroFileName="$5" # can be empty if the mode name is 'remove'l_expansionDirectoryName="fileIngredients"set -xcp "./${l_targetFileName}" "./${l_targetFileName}.save"mkdir "./${l_expansionDirectoryName}"mv "./${l_targetFileName}" "./${l_expansionDirectoryName}/."cd "./${l_expansionDirectoryName}"unzip "./${l_targetFileName}"rm "./${l_targetFileName}"if [ "${l_modeName}" == "add" ]; thenif [ ! -d "./Scripts/python/${l_macroPackagePath}" ]; thenmkdir -p "./Scripts/python/${l_macroPackagePath}"ficp "${l_macroBasePath}/${l_macroPackagePath}/${l_macroFileName}" "./Scripts/python/${l_macroPackagePath}/."elseif [ "${l_macroPackagePath}" != "" ]; thenrm -r "./Scripts/python/${l_macroPackagePath}/${l_macroFileName}"elserm -r "./Scripts"fifivim "META-INF/manifest.xml"zip -r "../${l_targetFileName}" *cd ..rm -r "./${l_expansionDirectoryName}"
Objector 49B
The editing part is bothersome.
Hypothesizer 7
You can create a Python program that do the editing, like this.
theBiasPlanet/inDocumentPythonMacrosRegistrar/programs/InDocumentPythonMacrosRegistrarConsoleProgram.py
@Python Source Code
from typing import Listfrom typing import Optionalfrom typing import TextIOfrom typing import castfrom collections import OrderedDictimport osfrom pathlib import Pathimport shutilimport sysimport xml.saximport xml.sax.handlerfrom xml.sax.handler import ContentHandlerfrom xml.sax.xmlreader import AttributesImplfrom xml.sax.xmlreader import XMLReaderfrom theBiasPlanet.coreUtilities.constantsGroups.DefaultValuesConstantsGroup import DefaultValuesConstantsGroupfrom theBiasPlanet.coreUtilities.constantsGroups.FileNameSuffixesConstantsGroup import FileNameSuffixesConstantsGroupfrom theBiasPlanet.coreUtilities.constantsGroups.FileOpenModeNamesConstantsGroup import FileOpenModeNamesConstantsGroupfrom theBiasPlanet.coreUtilities.constantsGroups.GeneralConstantsConstantsGroup import GeneralConstantsConstantsGroupfrom theBiasPlanet.coreUtilities.constantsGroups.XmlExpressionsConstantsGroup import XmlExpressionsConstantsGroupfrom theBiasPlanet.coreUtilities.messagingHandling.Publisher import Publisherfrom theBiasPlanet.coreUtilities.xmlDataHandling.XmlDatumHandler import XmlDatumHandler"""mode name: "add" or "remove""""class InDocumentPythonMacrosRegistrarConsoleProgram:c_manifestFileRelativePath: Path = Path ("META-INF/manifest.xml")c_scriptsBaseDirectoryRelativePath: Path = Path ("Scripts")c_pythonBaseDirectoryRelativePath: Path = c_scriptsBaseDirectoryRelativePath.joinpath ("python")c_addModeName = "add"c_removeModeName = "remove"class ManifestDatumParseEventsHandler (ContentHandler):c_rootElementName: str = "manifest:manifest"c_filePathElementName: str = "manifest:file-entry"c_pathAttributeName: str = "manifest:full-path"c_mediaTypeAttributeName: str = "manifest:media-type"c_binaryMediaTypeName = "application/binary"c_emptyMediaTypeName = GeneralConstantsConstantsGroup.c_emptyStringc_pythonModuleFileGlobExpression = GeneralConstantsConstantsGroup.c_fileNameFormat.format (GeneralConstantsConstantsGroup.c_doubleAsterisks, FileNameSuffixesConstantsGroup.c_pythonModuleFileNameSuffix)def __init__ (a_this: "InDocumentPythonMacrosRegistrarConsoleProgram.ManifestDatumParseEventsHandler", a_modeName: str, a_concernedPathToIsFoundMap: "OrderedDict [Path, bool]", a_writer: TextIO) -> None:a_this.i_modeName: str = a_modeNamea_this.i_concernedPathToIsFoundMap: "OrderedDict [Path, bool]" = a_concernedPathToIsFoundMapa_this.i_writer: TextIO = a_writera_this.i_isForOneOfConcernedPaths: bool = Falsedef startDocument (a_this: "InDocumentPythonMacrosRegistrarConsoleProgram.ManifestDatumParseEventsHandler") -> None:a_this.i_writer.write (GeneralConstantsConstantsGroup.c_lineFormat.format (XmlExpressionsConstantsGroup.c_xml1_0Declaration))def endDocument (a_this: "InDocumentPythonMacrosRegistrarConsoleProgram.ManifestDatumParseEventsHandler") -> None:Nonedef startElement (a_this: "InDocumentPythonMacrosRegistrarConsoleProgram.ManifestDatumParseEventsHandler", a_elementName: str, a_attributes: AttributesImpl) -> None:try:l_pathString: Optional [str] = a_attributes.getValue (InDocumentPythonMacrosRegistrarConsoleProgram.ManifestDatumParseEventsHandler.c_pathAttributeName)l_path: Path = Path (l_pathString)if a_this.i_modeName == InDocumentPythonMacrosRegistrarConsoleProgram.c_addModeName:if a_this.i_concernedPathToIsFoundMap.get (l_path) is not None:a_this.i_isForOneOfConcernedPaths = Truea_this.i_concernedPathToIsFoundMap [l_path] = Trueelse:l_concernedPath: Optional [Path] = Nonel_isFound: Optional [bool] = None# There is only one path in it.for l_concernedPath, l_isFound in a_this.i_concernedPathToIsFoundMap.items ():Noneif l_path == l_concernedPath or l_path.match ("{0:s}{1:s}{2:s}".format (str (l_concernedPath), GeneralConstantsConstantsGroup.c_linuxDirectoriesDelimiter, GeneralConstantsConstantsGroup.c_doubleAsterisks)):a_this.i_isForOneOfConcernedPaths = Trueexcept (KeyError) as l_exception:Noneif not a_this.i_modeName == InDocumentPythonMacrosRegistrarConsoleProgram.c_removeModeName or not a_this.i_isForOneOfConcernedPaths:a_this.i_writer.write (XmlDatumHandler.getElementOpenString (a_elementName, a_attributes))def endElement (a_this: "InDocumentPythonMacrosRegistrarConsoleProgram.ManifestDatumParseEventsHandler", a_elementName: str) -> None:if a_elementName == InDocumentPythonMacrosRegistrarConsoleProgram.ManifestDatumParseEventsHandler.c_rootElementName and a_this.i_modeName == InDocumentPythonMacrosRegistrarConsoleProgram.c_addModeName:l_path: Optional [Path] = Nonel_isFound: Optional [bool] = Nonefor l_path, l_isFound in a_this.i_concernedPathToIsFoundMap.items ():if not l_isFound:l_mediaTypeName: Optional [str] = Noneif not l_path.match (InDocumentPythonMacrosRegistrarConsoleProgram.ManifestDatumParseEventsHandler.c_pythonModuleFileGlobExpression):l_mediaTypeName = InDocumentPythonMacrosRegistrarConsoleProgram.ManifestDatumParseEventsHandler.c_binaryMediaTypeNameelse:l_mediaTypeName = InDocumentPythonMacrosRegistrarConsoleProgram.ManifestDatumParseEventsHandler.c_emptyMediaTypeNamel_attributes: AttributesImpl = AttributesImpl ({InDocumentPythonMacrosRegistrarConsoleProgram.ManifestDatumParseEventsHandler.c_pathAttributeName: str (l_path), InDocumentPythonMacrosRegistrarConsoleProgram.ManifestDatumParseEventsHandler.c_mediaTypeAttributeName: l_mediaTypeName})a_this.i_writer.write (" {0:s}".format (GeneralConstantsConstantsGroup.c_lineFormat).format (XmlDatumHandler.getElementOpenString (InDocumentPythonMacrosRegistrarConsoleProgram.ManifestDatumParseEventsHandler.c_filePathElementName, l_attributes)))a_this.i_writer.write (" {0:s}".format (GeneralConstantsConstantsGroup.c_lineFormat).format (XmlDatumHandler.getElementCloseString (InDocumentPythonMacrosRegistrarConsoleProgram.ManifestDatumParseEventsHandler.c_filePathElementName)))if not a_this.i_modeName == InDocumentPythonMacrosRegistrarConsoleProgram.c_removeModeName or not a_this.i_isForOneOfConcernedPaths:a_this.i_writer.write (XmlDatumHandler.getElementCloseString (a_elementName))a_this.i_isForOneOfConcernedPaths = Falsedef characters (a_this: "InDocumentPythonMacrosRegistrarConsoleProgram.ManifestDatumParseEventsHandler", a_contents: str) -> None:if not a_this.i_modeName == InDocumentPythonMacrosRegistrarConsoleProgram.c_removeModeName or not a_this.i_isForOneOfConcernedPaths:a_this.i_writer.write (a_contents)"""arguments:1 -> the file ingredients base directory path string2 -> the mode name: 'add' or 'remove'3 -> the target path string: if the mode name is 'remove' and this is empty, the 'Scripts' directory will be removed."""@staticmethoddef main (a_arguments: List [str]) -> None:if len (a_arguments) != 4:Publisher.logErrorInformation ("The arguments have to be these:\n1) the file ingredients base directory path string\n2) the mode name: 'add' or 'remove'\n3) the target path string: if the mode name is 'remove' and this is empty, the 'Scripts' directory will be removed.\n")exit GeneralConstantsConstantsGroup.c_errorResultl_fileIngredientsBaseDirectoryPath: Path = Path (a_arguments [1])l_modeName: str = a_arguments [2]if l_modeName != InDocumentPythonMacrosRegistrarConsoleProgram.c_addModeName and l_modeName != InDocumentPythonMacrosRegistrarConsoleProgram.c_removeModeName:Publisher.logErrorInformation ("The mode name has to be 'add' or 'remove'\n")exit GeneralConstantsConstantsGroup.c_errorResultl_targetPath: Path = Path (a_arguments [3])if l_modeName == InDocumentPythonMacrosRegistrarConsoleProgram.c_addModeName and l_targetPath == GeneralConstantsConstantsGroup.c_emptyString:Publisher.logErrorInformation ("The target path string cannot be empty for the 'add' mode\n")exit GeneralConstantsConstantsGroup.c_errorResultl_manifestFilePath: Path = l_fileIngredientsBaseDirectoryPath.joinpath (InDocumentPythonMacrosRegistrarConsoleProgram.c_manifestFileRelativePath)l_manifestModifiedFilePath: Path = l_fileIngredientsBaseDirectoryPath.joinpath (Path (GeneralConstantsConstantsGroup.c_fileNameFormat.format (str (InDocumentPythonMacrosRegistrarConsoleProgram.c_manifestFileRelativePath), FileNameSuffixesConstantsGroup.c_modifiedFileNameSuffix)))l_concernedPathToIsFoundMap: "OrderedDict [Path, bool]" = OrderedDict ()if l_modeName == InDocumentPythonMacrosRegistrarConsoleProgram.c_addModeName:l_concernedPathToIsFoundMap.update ({InDocumentPythonMacrosRegistrarConsoleProgram.c_pythonBaseDirectoryRelativePath.parent: False, InDocumentPythonMacrosRegistrarConsoleProgram.c_pythonBaseDirectoryRelativePath: False})l_path: Optional [Path] = Nonefor l_path in reversed (l_targetPath.parents):if l_path != GeneralConstantsConstantsGroup.c_currentDirectoryPath:l_concernedPathToIsFoundMap.update ({InDocumentPythonMacrosRegistrarConsoleProgram.c_pythonBaseDirectoryRelativePath.joinpath (l_path): False})if l_targetPath != GeneralConstantsConstantsGroup.c_emptyString:l_concernedPathToIsFoundMap.update ({InDocumentPythonMacrosRegistrarConsoleProgram.c_pythonBaseDirectoryRelativePath.joinpath (l_targetPath): False})else:l_concernedPathToIsFoundMap.update ({InDocumentPythonMacrosRegistrarConsoleProgram.c_scriptsBaseDirectoryRelativePath: False})l_saxParser: XMLReader = xml.sax.make_parser ( [DefaultValuesConstantsGroup.c_saxParserModuleName])l_saxParser.setFeature (xml.sax.handler.feature_namespaces, False)l_manifestFileReader: Optional [TextIO] = Nonel_manifestFileWriter: Optional [TextIO] = Nonetry:l_manifestFileReader = cast (TextIO, open (l_manifestFilePath, FileOpenModeNamesConstantsGroup.c_readModeName))l_manifestFileWriter = cast (TextIO, open (l_manifestModifiedFilePath, FileOpenModeNamesConstantsGroup.c_eraseAndWriteModeName))l_manifestDatumParseEventsHandler: "InDocumentPythonMacrosRegistrarConsoleProgram.ManifestDatumParseEventsHandler" = InDocumentPythonMacrosRegistrarConsoleProgram.ManifestDatumParseEventsHandler (l_modeName, l_concernedPathToIsFoundMap, l_manifestFileWriter)l_saxParser.setContentHandler (l_manifestDatumParseEventsHandler)l_saxParser.parse (l_manifestFileReader)os.remove (l_manifestFilePath)shutil.move (str (l_manifestModifiedFilePath), str (l_manifestFilePath))except (Exception) as l_exception:Publisher.logErrorInformation (l_exception)if l_manifestFileWriter is not None:l_manifestFileWriter.close ()if l_manifestFileReader is not None:l_manifestFileReader.close ()if __name__ == GeneralConstantsConstantsGroup.c_pythonMainModuleName:InDocumentPythonMacrosRegistrarConsoleProgram.main (sys.argv)
theBiasPlanet/coreUtilities/constantsGroups/DefaultValuesConstantsGroup.py
@Python Source Code
class DefaultValuesConstantsGroup:~c_saxParserModuleName: str = "xml.sax.expatreader"
theBiasPlanet/coreUtilities/constantsGroups/FileNameSuffixesConstantsGroup.py
@Python Source Code
class FileNameSuffixesConstantsGroup:~c_pythonModuleFileNameSuffix: str = "py"~c_modifiedFileNameSuffix: str = "modified"
theBiasPlanet/coreUtilities/constantsGroups/FileOpenModeNamesConstantsGroup.py
@Python Source Code
class FileOpenModeNamesConstantsGroup:c_readModeName: str = "r"c_eraseAndWriteModeName: str = "w"~
theBiasPlanet/coreUtilities/constantsGroups/GeneralConstantsConstantsGroup.py
@Python Source Code
from pathlib import Pathimport sysfrom collections import OrderedDictfrom theBiasPlanet.coreUtilities.constantsGroups.FileNameSuffixesConstantsGroup import FileNameSuffixesConstantsGroupclass GeneralConstantsConstantsGroup:~c_errorResult: int = -1~c_emptyString: str = ""~c_doubleAsterisks: str = "{0:s}{0:s}".format (c_asteriskCharacter)~c_linuxDirectoriesDelimiter: str = '/' # char~c_currentDirectoryPath: Path = Path (".")~c_fileNameFormat: str = "{{:s}}{:s}{{:s}}".format (c_fileNameElementsDelimiter)c_lineFormat: str = "{{:s}}{:s}".format (c_newLineCharacter)~c_pythonMainModuleName: str = "__main__"~
theBiasPlanet/coreUtilities/constantsGroups/XmlExpressionsConstantsGroup.py
@Python Source Code
class XmlExpressionsConstantsGroup:c_xml1_0Declaration: str = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"~
'theBiasPlanet/coreUtilities/messagingHandling/Publisher.py' is totally omitted because the used method just writes logs.
theBiasPlanet/coreUtilities/xmlDataHandling/XmlDatumHandler.py
@Python Source Code
from typing import Listfrom typing import Optionalimport xml.sax.saxutilsfrom xml.sax.xmlreader import AttributesImplfrom theBiasPlanet.coreUtilities.constantsGroups.GeneralConstantsConstantsGroup import GeneralConstantsConstantsGroupclass XmlDatumHandler:@staticmethoddef getElementOpenString (a_elementName: str, a_attributes: AttributesImpl) -> str:l_attributeStrings: List [str] = []l_attributeName: Optional [str] = Nonefor l_attributeName in a_attributes.getNames ():l_attributeStrings.append ("{0:s}={1:s}".format (l_attributeName, xml.sax.saxutils.quoteattr (a_attributes.getValue (l_attributeName))))l_attributesString = " ".join (l_attributeStrings)return GeneralConstantsConstantsGroup.c_quotedByAngleBracketsFormat.format ("{0:s} {1:s}".format (a_elementName, l_attributesString))@staticmethoddef getElementCloseString (a_elementName: str) -> str:return GeneralConstantsConstantsGroup.c_quotedByAngleBracketsFormat.format ("/{0:s}".format (a_elementName))
Objector 49B
. . . Um? How can I use it?
Hypothesizer 7
In the above shell script, you can just replace 'vim "META-INF/manifest.xml"' with 'bash -c "export PYTHONPATH=\"${PYTHONPATH}:/home/%user name%/myData/development/inDocumentPythonMacrosRegistrar/target:/home/%user name%/myData/development/coreUtilities/target\"; python3 -m theBiasPlanet.inDocumentPythonMacrosRegistrar.programs.InDocumentPythonMacrosRegistrarConsoleProgram \".\" \"${l_modeName}\" \"./${l_macroPackagePath}/${l_macroFileName}\""', while, of course, the Python modules paths have to be configured appropriately.
3: Testing
Hypothesizer 7
Let us open the file and see that the Python macro is indeed recognized by LibreOffice or Apache OpenOffice, by selecting 'Tools' -> 'Macros' -> 'Run Macro...' -> '%the file name%' -> 'theBiasPlanet' -> 'pythonEnvironmentChecker' -> 'macros' -> 'PythonEnvironmentChecker' -> 'checkPythonEnvironment'.
Objector 49A
. . . Oh, it's there! Does it run?
Hypothesizer 7
If the macro is all right, of course.