Preface
Goal: Transform plain row based data into cute form view.
-
Using LibreOffice UNO Macro,
-
Using Faktur Pajak (Indonesian Tax) as Example,
-
This article is the last part of two articles.
5: The Challenge
How to Automate?
Back to the Challenge This picture below is common in any business.
But how to speed up your workflow? Doing manually is extremely daunting task.
You need to spend your time to important task, rather than compiling data, right?
Of course you can do it with simpler script, so you can have the document compiled the worksheet, automatically for you.
All you need is just export the sheet to PDF. And you got the summary as below:
Provided Example Data
You can download the artefact for simple range copying here below. Each sheet, I provide two languages. You should choose only one over another.
- Template: [template.ods]
Then we can continue combine with sheet with long columns, along with dummy data.
- Data: [data.ods]
With these template and data, wen can finally get our final result, the completely filled form.
- Result: [result.ods]
Fields in Detail
Dual Language
Since I my original tutorial based on goverment tax field in Indonesia, I have to create the english version, so you understand, what the meaning of each example.
Consider have a look at all fields in detail. I provide two language for each: English and Indonesia.
- Fields: Screenshot One
- Fields: Screenshot Two
- Fields: Screenshot Three
Excuse my english, also apologize for my lack knowledge of tax terminology.
Prepare: English
Empty Template and Data Source
it is up to your choice whether you sue English or Indonesia. I provide both language.
- Empty Template
- Source Data
After each exection of each python code,
you will get additional Target
sheet:
Prepare: Indonesia
Empty Template and Data Source
- Empty Template
- Source Data
6: Simple Transformation Class
Just make sure you place each field correctly.
We can begin translating each fields, to predefined location.
Skeleton
class TransformRow01:
def __init__(self, eName, sName, tName):
def getFieldsMapping(self):
def copySheet(self):
def copyField(self, pair):
def run(self):
def main():
Class Initialization
We have three set of sheet names.
- Empty Template,
- Source sheet, and
- Target sheet (Result)
As shown in below code:
class TransformRow01:
def __init__(self, eName, sName, tName):
desktop = XSCRIPTCONTEXT.getDesktop()
model = desktop.getCurrentComponent()
self.sheets = model.Sheets
# save initial parameter
self.eName = eName
self.sName = sName
self.tName = tName
Main: Program Entry Point
Here comes all the parameter argument. This function responsible for class instantiation, is shown in below code:
def main():
sample = TransformRow01('Empty-en', 'Source-en', 'Target-en')
sample.run()
You can swith to other language,
en
for english, and id
for Indonesia.
just by modify the class initialization parameter, for example:
def main():
sample = TransformRow01('Empty-id', 'Source-id', 'Target-id')
sample.run()
Run: Execute
The run contain no parameter.
def run(self):
self.copySheet()
fields = self.getFieldsMapping()
for field, pair in fields.items():
self.copyField(pair)
Copy Sheet
In order to work in other sheet, we need to duplicate the Empty sheet to a new sheet. This new sheet would be our working sheet.
def copySheet(self):
sheets = self.sheets
sheets.copyByName(
self.eName, self.tName, len(sheets))
self.eSheet = sheets.getByName(self.eName)
self.sSheet = sheets.getByName(self.sName)
self.tSheet = sheets.getByName(self.tName)
After the new sheet created, we can freely make a reference to each sheet, to be used later.
This include these three sheet:
- Empty Template.
- Data Source: containing row based data.
- Target Result: working place to create form based result.
Mapping Definition
In the heart, it is just field position transformation
Again, just put source table, and form view, side by side. And manually defined the field one by one, into a script.
So you can have this one to one relationship as below:
For example in English:
def getFieldsMapping(self):
# declare all field coordinate
return {
'id' : { 'source': 'A7', 'target': 'B5' },
'name' : { 'source': 'B7', 'target': 'C5' },
'taxpayer_id' : { 'source': 'C7', 'target': 'F5' },
'tax_inv_id' : { 'source': 'D7', 'target': 'I5' },
'tax_inv_date' : { 'source': 'E7', 'target': 'D6' },
'month_period' : { 'source': 'F7', 'target': 'G7' },
'year' : { 'source': 'G7', 'target': 'G8' },
'tax_inv_status' : { 'source': 'H7', 'target': 'I10' },
'price' : { 'source': 'I7', 'target': 'J7' },
'vat' : { 'source': 'J7', 'target': 'J8' },
'vat_lux': { 'source': 'K7', 'target': 'J9' },
'approval_status' : { 'source': 'L7', 'target': 'I11' },
'approval_date' : { 'source': 'M7', 'target': 'D7' },
'description' : { 'source': 'N7', 'target': 'I12' },
'signatory_user' : { 'source': 'O7', 'target': 'D8' },
'reference' : { 'source': 'P7', 'target': 'I6' },
'record_user' : { 'source': 'Q7', 'target': 'D10' },
'record_date' : { 'source': 'R7', 'target': 'D9' },
'update_user' : { 'source': 'S7', 'target': 'D12' },
'update_date' : { 'source': 'T7', 'target': 'D11' },
}
You can ignore the name completly. I just put the field name as annotation.
Copy Field
Using Mapping
For first row, it is as simply as copy value from source to target.
The process of field translating field written in code below:
def copyField(self, pair):
addrSource = pair['source']
addrTarget = pair['target']
sourceCell = self.sSheet.getCellRangeByName(addrSource)
targetCell = self.tSheet.getCellRangeByName(addrTarget)
print("Cell %s: Type = %s, String = %s" \
% (addrSource, sourceCell.Type.value, sourceCell.String))
match sourceCell.Type.value:
case "VALUE" : targetCell.Value = sourceCell.Value
case "TEXT" : targetCell.String = sourceCell.String
I also add print
statement,
so you can happily debug in APSO console (python terminal).
Result
The result in APSO console would be similar as below:
Make sure there target-en
worksheet exist.
And the result in target-en
sheet would be as below:
Simple right?
We have successfully move one row into its form representative.
7: Handling Multiple Data
Offset Position?
The next step would focus on multiple row data. In order to do this we need to handle offset position, to get the right coordinate.
Since in this example,the height of the form is nine
.
For every one index increment,
we will have ten offset increment.
Skeleton
class TransformRow02:
def __init__(self, eName, sName, tName):
def getFieldsMapping(self):
def copySheet(self):
def copyField(self, pair, rowHeight, index):
def run(self, rowHeight, rowCount):
def main():
Class Initialization
Similar as previous class.
Copy Sheet
Similar as previous class.
Mapping Definition
Similar as previous class.
Main: Program Entry Point
This function responsible for class initialization.
def main():
sample = TransformRow02('Empty-en', 'Source-en', 'Target-en')
sample.run(9, 3)
Run: Execute
We pour our loop logic here.
There are two parameter argument:
- rowHeight : Height of the form
- rowCount : Number of data in
source
sheet to be processed.
def run(self, rowHeight, rowCount):
self.copySheet()
fields = self.getFieldsMapping()
masterIndices = range(1, rowCount + 1)
for index in masterIndices:
for field, pair in fields.items():
self.copyField(pair, rowHeight, index)
Calculate Cell Address
Mapping Multiple Data with Help of Offset.
There are to row positions to be considered:
- row in source: using index
- field in target: using offset
The relationship is as below:
Offset = (rowHeight+1) * (index-1)
First we need to save the position of base position
addrSource = pair['source']
addrTarget = pair['target']
sCellBase = self.sSheet.getCellRangeByName(addrSource)
tCellBase = self.tSheet.getCellRangeByName(addrTarget)
scAddress = sCellBase.CellAddress
tcAddress = tCellBase.CellAddress
Then we can continue to get ourdata position based on index:
Offset = (rowHeight+1) * (index-1)
sCell = self.sSheet.getCellByPosition(\
scAddress.Column, scAddress.Row + (index-1))
tCell = self.tSheet.getCellByPosition(\
tcAddress.Column, tcAddress.Row + Offset)
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-python" data-lang="python">And <span style="color:#66d9ef">finally</span> do the assignment<span style="color:#f92672">.</span></code></pre></div>
match sCell.Type.value:
case "VALUE" : tCell.Value = sCell.Value
case "TEXT" : tCell.String = sCell.String
Copy Field Method
The process of field translating field can be written in code below:
def copyField(self, pair, rowHeight, index):
addrSource = pair['source']
addrTarget = pair['target']
sCellBase = self.sSheet.getCellRangeByName(addrSource)
tCellBase = self.tSheet.getCellRangeByName(addrTarget)
scAddress = sCellBase.CellAddress
tcAddress = tCellBase.CellAddress
Offset = (rowHeight+1) * (index-1)
sCell = self.sSheet.getCellByPosition(\
scAddress.Column, scAddress.Row + (index-1))
tCell = self.tSheet.getCellByPosition(\
tcAddress.Column, tcAddress.Row + Offset)
match sCell.Type.value:
case "VALUE" : tCell.Value = sCell.Value
case "TEXT" : tCell.String = sCell.String
Result
We have succesfully migrate all the three rows. The result in our working sheet would be similar as below:
Take back you smile!
But hey, where is the formattingš¤?
8: Using Both Class
Make worksheet fun again.
Come out and play. This is our final form in this article.
Skeleton
Dual Class
Just puth both class here in one file.
class CopyRange03:
def __init__(self, sName, tName):
...
class TransformRow03:
def __init__(self, eName, sName, tName):
...
def main():
Class Initialization
Stay intact. No changes.
Copy Sheet
Stay intact. No changes.
Mapping Definition
Stay intact. No changes.
Copy Field
Stay intact. No changes.
Main: Program Entry Point
Here comes all the parameter argument.
def main():
sample = TransformRow03(\
'Empty-en', 'Source-en', 'Target-en')
sample.run('B4:K13', 'A1:L33', 3)
Now it is already containing properties of the copied range.
Run: Execute
This run method responsible for CopyRange
class instantiation.
Consider name the object as template
.
template = CopyRange03(self.eName, self.tName)
rowHeight = self.calculateHeight(stringRange)
Then we can duplicate the form for each row using loop.
masterIndices = range(1, rowCount + 1)
for index in masterIndices:
template.run(stringRange, index)
def run(self, stringRange, printArea, rowCount):
self.copySheet()
fields = self.getFieldsMapping()
template = CopyRange03(self.eName, self.tName)
rowHeight = self.calculateHeight(stringRange)
masterIndices = range(1, rowCount + 1)
for index in masterIndices:
template.run(stringRange, index)
for field, pair in fields.items():
self.copyField(pair, rowHeight, index)
template.setPrintArea(printArea)
Calculate Height
Form height calculation require a few lines.
template = CopyRange03(self.eName, self.tName)
rowHeight = self.calculateHeight(stringRange)
So I decide to put the calculation in its own method:
def calculateHeight(self, stringRange):
eCellRange = self.eSheet.getCellRangeByName(stringRange)
eRangeAddr = eCellRange.RangeAddress
rStart = eRangeAddr.StartRow
rEnd = eRangeAddr.EndRow
return rEnd - rStart
Result
Party Time
Get yourself more productive with script automation. I’m not thinking of salary raise, but rather more things to do in limited time. So I can think of in strategic area, instead of boring, cumbersome, tedious task.
Using Other Language
Just like previous example,
you can set the language in main()
,
for example Indonesia or English.
The result will be reflected in sheet tab name.
Working with worksheet is fun. I feels like, my accounting division looks even cooler now.
Conclusion
I think that’s all. Maybe another article, another time.
We are done. That is all. Thank you for reading.
What do you think?