<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Gokulakrishnan Shankar</title>
    <description>The latest articles on Forem by Gokulakrishnan Shankar (@gokullan).</description>
    <link>https://forem.com/gokullan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1067624%2F91b37b03-3c3f-420e-8287-c90a30983720.png</url>
      <title>Forem: Gokulakrishnan Shankar</title>
      <link>https://forem.com/gokullan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/gokullan"/>
    <language>en</language>
    <item>
      <title>Write custom formulae in Libreoffice Calc with Python</title>
      <dc:creator>Gokulakrishnan Shankar</dc:creator>
      <pubDate>Sun, 21 Jul 2024 13:12:45 +0000</pubDate>
      <link>https://forem.com/gokullan/write-custom-formulae-in-libreoffice-calc-with-python-2cm7</link>
      <guid>https://forem.com/gokullan/write-custom-formulae-in-libreoffice-calc-with-python-2cm7</guid>
      <description>&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;What if you wanted to generate values in Column B using values from Column A (on your Libreoffice spreadsheet), based on some complex logic? For instance, let's say that my Column A contains values in JSON format (why not 😅?) and I want to extract only values of a particular field into Column B. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ipvgvudidzfxxpvix4o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ipvgvudidzfxxpvix4o.png" alt="LibreofficePython_Task"&gt;&lt;/a&gt;&lt;br&gt;
This can be done in 3 ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use existing functions (In this case, that would be &lt;code&gt;REGEX&lt;/code&gt;, but what if want more control in deciding the logic?)&lt;/li&gt;
&lt;li&gt;Write custom functions

&lt;ul&gt;
&lt;li&gt;Define the function in Libreoffice Basic (You can think of Basic as Libreoffice's own programming-suite, but is it really worth learning a new language for such a simple use-case?)&lt;/li&gt;
&lt;li&gt;Define the function in Python and integrate it with Libreoffice (Yep, this is much more easier) &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pre-requisites
&lt;/h2&gt;

&lt;p&gt;To use Python functions in Libreoffice, you need to have the python script-provider for Libreoffice installed. To check if your version of Libreoffice already has this feature, go to &lt;code&gt;Tools&lt;/code&gt; → &lt;code&gt;Macros&lt;/code&gt; → &lt;code&gt;Organize Macros&lt;/code&gt; - you should see the option, &lt;code&gt;Python&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvmut0czvpzoz0kxwy7a0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvmut0czvpzoz0kxwy7a0.png" alt="LibreofficePython_CheckingMacro"&gt;&lt;/a&gt;&lt;br&gt;
If not, (i.e.), if you only see the option &lt;code&gt;Basic&lt;/code&gt;, you will need to install this script provider as follows:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;libreoffice-script-provider-python


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You should now be able to see &lt;code&gt;Python&lt;/code&gt; alongside &lt;code&gt;Basic&lt;/code&gt; under the &lt;code&gt;Organize Macros&lt;/code&gt; option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's get to it!
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Deciding the type of Macro
&lt;/h3&gt;

&lt;p&gt;Roughly put, a macro is any script that you use for automating tasks (via GUIs, functions, etc.) in Libreoffice. There are 3 types of Macros based on where the scripts reside.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Location (Linux)&lt;/th&gt;
&lt;th&gt;Accessibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;User Macros&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/home/USER/.config/libreoffice/4/user/Scripts/python&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Only current user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System Macros&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/usr/lib/libreoffice/share/Scripts/python/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;All users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Document Macros&lt;/td&gt;
&lt;td&gt;
&lt;em&gt;Inside&lt;/em&gt; the spreadsheet-file (Yes, you can extract spreadsheet files 🤯)&lt;/td&gt;
&lt;td&gt;Only this document&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For this post, I'm going to choose to create a user macro. If the path for the user script does not exist, you can go ahead and create it with &lt;code&gt;mkdir&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: To create document macros, some extra steps are needed. Have a look at the "Installing the Libreoffice python script provider" link in the References section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing the Python function
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create the file &lt;code&gt;Custom.py&lt;/code&gt; (it can be any name), inside &lt;code&gt;/home/USER/.config/libreoffice/4/user/Scripts/python&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Define your function - in this case, I would supply the stringified JSON and the field name as input and return the value of the specified field.
```python
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;import json&lt;/p&gt;

&lt;p&gt;def extract_from_json(stringified_json, key):&lt;br&gt;
    mapping = json.loads(stringified_json)&lt;br&gt;
    return mapping.get(key, "")&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
### Setting up Libreoffice to use the Python function
#### Check if your function is visible
- In Libreoffice Calc, go to `Tools` &amp;amp;rarr; `Macros` &amp;amp;rarr; `Python`. 
- In the now-opened "Python Macros" window, click on `My Macros` &amp;amp;rarr; [your python script filename]. You should see the list of functions present inside that script.

![LibreofficePython_CheckingFunction](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f4f2lfgqvtrwl3aryspm.png)
- If the options on the right side are greyed-out, don't worry about them. You wouldn't need to use them much anyway.

#### Setting up a formula to use the function
Remember when we chucked out using Libreoffice Basic to write custom formulae? Turns out we still need to use a small part of it :sweat_smile:.
- First we need a name for the formula that we are actually going to use in our spreadsheet (this can be different from our python function name). Let's say I choose the name `EXTRACTFROMJSON`.
- The definition for this formula (which will be written in Basic) should
  - Fetch the necessary function from the python script (this in itself will be a separate Basic function - let's call it `getPythonScript`)
  - Execute the function and return the results
- Go to `Tools` &amp;amp;rarr; `Macros` &amp;amp;rarr; `Edit Macros` and paste the below code to fetch the python script.
```visualbasic


Option Explicit
Option Compatible

Private scr As Object ' com.sun.star.script.provider.XScript

Public Function GetPythonScript(macro As String, _
        Optional location As String) As com.sun.star.script.provider.Xscript
    ''' Grab Python script object before execution
    ' Arguments:
    '    macro   : as "library/module.py$macro" or "module.py$macro"
    '    location: as "document", "share", "user" or ENUM(eration)
    ' Result:
    '    located com.sun.star.script.provider.XScript UNO service'''
    If IsMissing(location) Then location = "user"
    Dim mspf As Object ' com.sun.star.script.provider.MasterScriptProviderFactory
    Dim sp As Object ' com.sun.star.script.provider.XScriptProvider compatible
    Dim uri As String
    If location="document" Then
        sp = ThisComponent.getScriptProvider()
    Else
        mspf = CreateUNOService("com.sun.star.script.provider.MasterScriptProviderFactory")
        sp = mspf.createScriptProvider("")
    End If
    uri = "vnd.sun.star.script:"&amp;amp; macro &amp;amp;"?language=Python&amp;amp;location="&amp;amp; location
    GetPythonScript = sp.getScript(uri)
End Function ' GetPythonScript


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Next, we define the actual formula.
```visualbasic
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Public Function EXTRACTFROMJSON(stringifiedJson As String, key As String) As String&lt;br&gt;
    scr = GetPythonScript("Custom.py$extract_from_json", "user")&lt;br&gt;
    EXTRACTFROMJSON = scr.invoke(Array(stringifiedJson, key), Array(), Array())&lt;br&gt;
End Function&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- The first argument to `GetPythonScript` should be of the form `PythonFileName.py$PythonFunctionName`; the second argument is the type of macro ("user" in this case)
- We use `invoke` to execute the python function - the first argument of this function is an array of arguments to be passed to our actual python function.
- Finally, according to the rules of Libreoffice Basic, for a formula to return a value, the syntax is to be specified as `FORMULANAME = ReturnValue`

### Calling the function from the spreadsheet
This is a simple as doing `=EXTRACTFROMJSON(A2, "type")`

![LibreofficePython_ExecutingFormula](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y3do7bsapde615btwt62.png)
## References
- [Installing the Libreoffice python script provider](https://wiki.documentfoundation.org/Macros/Python_Guide/Introduction#Installation)
- [Calling Python scripts from Basic](https://help.libreoffice.org/7.3/en-US/text/sbasic/guide/basic_2_python.html?DbPAR=BASIC)
- [Sample JSON data](https://opensource.adobe.com/Spry/samples/data_region/JSONDataSetSample.html)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>libreoffice</category>
      <category>tutorial</category>
      <category>python</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
