Where to Discuss?

Local Group

Preface

Goal: Convert LibreOffice python macro to UNO python script. Reading from CSV and writing two worksheets.

In the last article of our LibreOffice UNO series, we’re going to tackle a practical use case: reading a CSV file and populating both the Table sheet and the Pivot Sheet. just like our macro counterpart.

Let’s make it fast. The process involves two scripts:

  • 21-reader.py
  • 22-reader-writer.py

As mentioned earlier, the primary focus will be on adjusting the loading part. This article would be short. So, let’s dive in.


Reading The CSV

And also writing the table to worksheet.

In this part of our LibreOffice UNO series, we aim to read data from a previously generated CSV file, sample-data.csv, and write the resulting dataframe to the Table worksheet.

Unlike the macro, UNO allows us to create an instance of an empty sheet out of nowhere.

Class Diagram

Let’s begin with a visual representation of this simple yet essential operation:

Pivot: Calc UNO: CSV Reader: Table Writer: Class Diagram

Directory Structure

.
├── 21-reader.py
└── lib
    ├── helper.py
    ├── CSVReader.py
    └── TableWriter.py

The required libraries for this script are detailed below:

Script Skeleton

In this script, we leverage the CSVReader and the TableWriter classes:

# Local Library
from lib.helper import (
  get_desktop, create_calc_instance, get_file_path)
from lib.CSVReader import CSVReader
from lib.TableWriter import TableWriter

def main() -> int:
  ...

  return 0

if __name__ == "__main__":
  raise SystemExit(main())

Pivot: Calc UNO: CSV Reader: Table Writer: Script Skeleton

Main Script

We use the dataframe obtained from the CSV as input for the Table sheet:

def main() -> int:
  source_csv  = 'sample-data.csv'

  csv_reader = CSVReader(source_csv)
  csv_reader.process()
  dataframe = csv_reader.dataframe

  # Getting the source sheet
  desktop   = get_desktop()
  document  = create_calc_instance(desktop)

  if document:
    table_writer = TableWriter(
      document, 'Table', dataframe)
    table_writer.process()

  return 0

Pivot: Calc UNO: CSV Reader: Table Writer: Main Script

Note that we employ two distinct classes here:

  • The CSVReader(),
  • The TableWriter().

The CSVReader Class

This dataframe require a date conversion.

The CSVReader class mirrors its Macro counterpart, with the additional step of converting the date from the CSV to an integer type with an offset. This conversion ensures compatibility with spreadsheet date formats:

import pandas as pd
from datetime import datetime, timedelta

class CSVReader:
  def __init__(self, filename: str) -> None:
    ...

  def date_ordinal(self, value, format_source):
    ...

  def load_data(self):
    ...

  def process(self) -> None:
    self.load_data()

Pivot: Calc UNO: CSV Reader: Dataframe: Class Skeleton

While converting with an ordinal value is optional, it’s a safer approach than omit ordinal conversion, maintaining compatibility with Excel Epoch.

Class: TableWriter: Initialization

The only divergence lies in the initialization section:

class TableWriter:
  def __init__(self,
      document    : 'com.sun.star.frame.XModel',
      sheetName: str,
      dataframe: DataFrame) -> None:

    # save initial parameter
    self.document = document
    self.dataframe = dataframe
    self.sheetName = sheetName

Pivot: Calc UNO: CSV Reader: Table Writer: Initialization

Class: TableWriter: Preparation

And the subsequent initialization:

  def prepare_sheet(self):
    ...

    # activate sheet
    spreadsheetView = self.document.getCurrentController()
    spreadsheetView.setActiveSheet(self.sheet)

Pivot: Calc UNO: CSV Reader: Table Writer: Prepare

The remainder of the process remains consistent.

Result Preview

The outcome perfectly aligns, with the results obtained from the macro example:

Pivot: Calc UNO: CSV Reader: Table Writer: Result Preview


Writing Pivot Worksheets

Gather All Together

In this segment, we’ll consolidate our efforts by copying and pasting the previously crafted PivotWriter class to write the Pivot worksheet.

Class Diagram

The entire diagram provides a comprehensive visualization:

Pivot: Calc UNO: CSV Reader: Pivot Writer: Class Diagram

Directory Structure

Since we also need to build pivot dataframe, we are going to use previous PivotSample example. This is an enhancement of CSVReader, equipped with pivot dataframe builder.

Since we also need to build a pivot dataframe, we will utilize the previous PivotSample example. This is an enhancement of CSVReader, equipped with a pivot dataframe builder.

.
├── 22-reader-writer.py
└── lib
    ├── helper.py
    ├── PivotSample.py
    ├── TableWriter.py
    └── PivotWriter.py

The required libraries for this script are outlined below:

Script Skeleton

Alongside PivotSample and TableWriter, we are going to use PivotWriter. With two sheets to be filled, namely the Table sheet and the Pivot sheet, the script structure is as follows:

# Local Library
from lib.helper import (
  get_desktop, create_calc_instance, get_file_path)
from lib.PivotSample import PivotSample
from lib.TableWriter import TableWriter
from lib.PivotWriter import PivotWriter

def main() -> int:
  ...

  return 0

if __name__ == "__main__":
  raise SystemExit(main())

Pivot: Calc UNO: CSV Reader: Pivot Writer: Script Skeleton

Main Script

From the dataframe result, we can also transform into the data into pivot dataframe, then fill the Pivot sheet with prepared values.

We start by filling the table sheet with the dataframe result. Following that, we transform the data into a pivot dataframe and proceed to populate the Pivot sheet with the prepared values.

The initiation of the script involves filling the table sheet:

def main() -> int:
  source_csv  = 'sample-data.csv'

  categories = [
    "Apple", "Banana", "Dragon Fruit",
    "Durian", "Grape", "Mango",
    "Orange", "Strawberry"]

  pivot_sample = PivotSample(source_csv, categories)
  pivot_sample.process()
  dataframe   = pivot_sample.get_dataframe()
  pivot_table = pivot_sample.get_pivot()

  # Print the newly created pivot table on console 
  print(pivot_table)
  print()  

  return 0

Pivot: Calc UNO: CSV Reader: Pivot Writer: Main Script

The conclusion of the script centers around filling the pivot sheet:

def main() -> int:
  ...

  # Getting the source sheet
  desktop   = get_desktop()
  document  = create_calc_instance(desktop)

  if document:
    table_writer = TableWriter(document, 
      'Table', dataframe)
    table_writer.process()

    writer = PivotWriter(document, 
      'Pivot', pivot_table, categories, 'B2')
    writer.process()

Pivot: Calc UNO: CSV Reader: Pivot Writer: Main Script

Now, with both sheets, Table and Pivot, our task is complete.

Class: PivotWriter: Initialization

The only discrepancy lies in the initialization part, along with the preparation to obtain the model object:

class PivotWriter:
  def __init__(self,
      document    : 'com.sun.star.sheet.SpreadsheetDocument',
      sheetName   : str,
      pivot_table : pd.DataFrame,
      categories  : List[str],
      start_cell  : str) -> None:

    # save initial parameter
    self.document = document
    self.sheetName   = sheetName
    self.pivot_table = pivot_table
    self.categories  = categories
    self.start_cell  = start_cell

    ...

Pivot: Calc UNO: CSV Reader: Pivot Writer: Initialization

There is absolutely nothing novel in this example. After all, it is merely a migration from a Python macro to a Python UNO script.

Activity Diagram

As a summary, the entire process can be depicted in this chronological order:

Pivot: Calc Macro: Reader Writer: Activity Diagram

Result Preview

The outcome mirrors precisely hat we achieved with the macro example:

Pivot: Calc UNO: CSV Reader: Pivot Writer: Result Preview


What’s the Next Exciting Step 🤔?

I’m going to take a brief pause and catch my breath before delving into the realm of Python’s easy-to-use OOO Dev library.

Consider continuing your exploration by reading [ Pivot - LO OOO - Open Instance ].