Changing row-height and column-width in LibreOffice Calc using Python3 - python

I want to write a LibreOffice Calc document from within a Python3 program. Using pyoo I can do almost everything I want, including formatting and merging cells. But I cannot adjust row heights and column widths.
I found Change the column width and row height very helpful, and have been experimenting with it, but I can't seem to get quite the result I want. My present test file, based on the answer mentioned above, looks like this:
#! /usr/bin/python3
import os, pyoo, time, uno
s = '-'
while s != 'Y':
s = input("Have you remembered to start Calc? ").upper()
os.popen("soffice --accept=\"socket,host=localhost,port=2002;urp;\" --norestore --nologo --nodefault")
time.sleep(2)
desktop = pyoo.Desktop('localhost', 2002)
doc = desktop.create_spreadsheet()
class ofic:
sheet_idx = 0
row_num = 0
sheet = None
o = ofic()
uno_localContext = uno.getComponentContext()
uno_resolver = uno_localContext.ServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", uno_localContext )
uno_ctx = uno_resolver.resolve( "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext" )
uno_smgr = uno_ctx.ServiceManager
uno_desktop = uno_smgr.createInstanceWithContext( "com.sun.star.frame.Desktop", uno_ctx)
uno_model = uno_desktop.getCurrentComponent()
uno_controller = uno_model.getCurrentController()
uno_sheet_count = 0
doc.sheets.create("Page {}".format(1), index=o.sheet_idx)
o.sheet = doc.sheets[o.sheet_idx]
o.sheet[0, 0].value = "The quick brown fox jumps over the lazy dog"
o.sheet[1, 1].value = o.sheet_idx
uno_controller.setActiveSheet(uno_model.Sheets.getByIndex(uno_sheet_count))
uno_sheet_count += 1
uno_active_sheet = uno_model.CurrentController.ActiveSheet
uno_columns = uno_active_sheet.getColumns()
uno_column = uno_columns.getByName("B")
uno_column.Width = 1000
The main problem with the above is that I have 2 Calc documents on the screen, one of which is created before the Python program gets going; the other is created from Python with a pyoo function. The first document gets the column width change, and the second receives the text input etc. I want just the second document, and of course I want the column width change applied to it.
I am sure the answer must be fairly straightforward, but after hours of experimentation I still can't find it. Could someone point me in the right direction, please?

Your code alternates between pyoo and straight Python-UNO, so it's no wonder that it's giving messy results. Pick one or the other. Personally, I use straight Python-UNO and don't see the benefit of adding the extra pyoo library.
the other is created from Python with a pyoo function
Do you mean this line of code from your question, and is this the "second document" that you want the column change applied to?
doc = desktop.create_spreadsheet()
If so, then get objects from that document instead of whichever window the desktop happens to have selected.
controller = doc.getCurrentController()
sheets = doc.getSheets()
Or perhaps you want the other document, the one that didn't get created from Python. In that case, grab a reference to that document before creating the second one.
first_doc = uno_desktop.getCurrentComponent()
second_doc = desktop.create_spreadsheet()
controller = first_doc.getCurrentController()
sheets = first_doc.getSheets()
If you don't have a reference to the document, you can find it by iterating through the open windows.
oComponents = desktop.getComponents()
oDocs = oComponents.createEnumeration()
Finally, how to resize a column. The link in your question is for Excel and VBA (both from Microsoft), so I'm not sure why you think that would be relevant. Here is a Python-UNO example of resizing columns.
oColumns = oSheet.getColumns()
oColumn = oColumns.getByName("A")
oColumn.Width = 7000
oColumn = oColumns.getByName("B")
oColumn.OptimalWidth = True

Related

Using python-docx to iterate over a document and change text

The Problem:
I am supposed to create a word file with 250x5 placeholders.
The background is akin to a database export into a word document.
So I created the first placeholder
Name_1
Adress_1
City_1
occupation_1
zipcode_1
and copied the block x250.
Now I need the number to be different for each block; so the second block of 250 would read
Name_2
Adress_2
...etc.
I thought to myself, surely this is faster to automate than to individually type 1000+ numbers.
But here I am, stuck, because my Python expertise is very limited.
What I have so far:
from docx import Document
import os
document = Document("Mapping.docx")
paragraph = document.paragraphs[10]
print(len(document.paragraphs))
print(document.paragraphs[1].text)
def replaceNumbers(ReplaceNumber):
number = 0
for i in range(len(document.paragraphs)):
if number == 5:
replaceNumbers(ReplaceNumber + 1)
break
else:
y = document.paragraphs[i].text
if "1" in y:
document.paragraphs[i].text = document.paragraphs[i].text.replace("1", str(ReplaceNumber))
number = number + 1
return
if __name__ == '__main__':
replaceNumbers(2)
document.save("edited.docx")
now I am guessing there is a problem in my usage of the "while" loop, as the program gets stuck. but manually debugging seems impossible with so many imported libraries.
I hope someone can help me here

Python-pptx formatting datalabels after adding text

As I needed to add custom text to a chart data labels in python-pptx I used
for point in plot.series[1].points:
frame = point.data_label.text_frame
frame.text = "Test "
for run in frame.paragraphs[0].runs:
run.font.size = Pt(10)
run.font.color.rgb = RGBColor(0x0A, 0x42, 0x80)
This allowed me to change the font to the labels but I would need to rotate them.
I saw the solution from This other thread but it does not work. Any ideas?
I believe you need to change the txPr (as in the thread you mention) but on the respective element child:
for all_series in range(0, len(plot.series)):
for i in range(0, len(plot.series[all_series]._element)):
if re.sub("{.*}", '', plot.series[all_series]._element[i].tag) == >"dLbls":
txPr = plot.series[all_series]._element[i].get_or_add_txPr()
txPr.bodyPr.set('rot', '-5400000')
So you need to access the "dLbls" child within the correct element index, the "[i]" is the difference to the mentioned thread. I use regular expressions to derive the name from the ".tag"-method. There is probably a better way to find the correct index, but this is at least one ;)
This solution iterates over all series, if you do not need this you can skip the first loop (assuming you want only the 1-series):
for i in range(0, len(plot.series[1]._element)):
if re.sub("{.*}", '', plot.series[1]._element[i].tag) == "dLbls":
txPr = plot.series[1]._element[i].get_or_add_txPr()
txPr.bodyPr.set('rot', '-5400000')
Note, that this only applies to data labels which were set on the single point, so e.g. through
plot.series[1].points[0].data_label.text_frame.text = "Foo"
Hopefully this helps :)
You can try this custom function, feed in your plot variable, and other style properties as you needed. If we go at point level for data label then this wont work as we think, and below can help to achieve.
def apply_data_labels(plot,font,fontsize,numformat,rotate = False):
plot.has_data_labels = True
data_labels = plot.data_labels
data_labels.font.size = Pt(fontsize)
data_labels.font.name = font
if rotate == True:
data_labels._element.get_or_add_txPr().bodyPr.set('rot','-5400000')
data_labels.number_format = numformat
and call this function with your plot variable for example.
apply_data_labels(chart.plots[0],'Invention',10,'#,##0;[Red]#,##0',rotate = True)

Python GUI creating a list item with Text and checkboxes

I am trying to create a list like in outlook. With list items with an layout like this:
Don't get me wrong this isn't a "give me the full answer" question. I just have the problem of the right naming. I would appreciate it a lot if some could throw in the right words and I will look for them by my own.
I used tkinter at the moment but in that it seems like there isn't a solution for that.
Kind regards.
I think Tkinter can do this by using a bit of object oriented programming you could define how one list element should look like and then with static variables you could define a position of a new line relative to the position of the previous line. Something like:
from tkinter import *
class Line:
y = 0 # static variable for the y position
def __init__(self):
self.y_local = Line.y
Line.y = Line.y + 40 # increase the static variable for the next line
print(self.y_local)
def draw(self, tk, h_line="Headline", a_info="Addtional Information", date="01/01/1970"):
self.label_head = Label(text=h_line)
self.label_head.place(x=20, y=self.y)
self.label_info = Label(text=a_info)
self.label_info.place(x=20, y=self.y+20)
self.label_date = Label(text='Date')
self.label_date.place(x=200, y=self.y)
self.label_ndate = Label(text=date)
self.label_ndate.place(x=200, y=self.y+20)
self.chkbtn = Checkbutton(tk, text="Checkbox")
self.chkbtn.place(x=300, y=self.y+20)
tk = Tk()
data = [
["News", "WWIII has been avoided", "02/04/2018"],
["Python", "Python solves 42 riddles", "02/04/2018"]
]
for line in data:
temp = Line()
temp.draw(tk, line[0], line[1], line[2])
mainloop()
Hope I understood your question well. That you have a list with information and want to display that in an easy and scalable way. I have not looked to add the lines around the information as I've never done that before I know there are separators I've used vertical once before but I wouldn't be surprised if you can draw a box around each line either.

win32com moving between Styles in a Selectoin

I'm attempting to programmatically write a Word file and am struggling to alternate styles on a selection object. Here is the relevant bit of code:
objWord = win32com.client.Dispatch("Word.Application")
objSel = objWord.Selection
writestuff = "\r\nSome Heading"
objSel.Style = objWord.ActiveDocument.Styles("Heading 3")
objSel.TypeText(writestuff)
#This works so far, we have a heading and some text, now we want to write data below the heading
objSel.Style = objWord.ActiveDocument.Styles("Normal") #Setting style back to 'Normal' for next section
writestuff = "\r\nSome data about the heading we just wrote")
objSel.TypeText(writestuff)
#At this point the heading and new text both go to the 'Normal' style.
It appears that my 'selection' is affecting all of the document, however, when I make my initial objSel.Style assignment to 'Heading 3' previous lines aren't affected. When I switch back to normal everything else is.
It appears that what I needed to do (or at least one of the things which will solve this problem) is to add a TypeParagraph() method
Corrected code looks like this:
objWord = win32com.client.Dispatch("Word.Application")
objSel = objWord.Selection
writestuff = "\r\nSome Heading"
objSel.Style = objWord.ActiveDocument.Styles("Heading 3")
objSel.TypeText(writestuff)
objSel.TypeParagraph ##THIS IS WHAT I ADDED, NO NEED TO READJUST STYLE##
writestuff = "\r\nSome data about the heading we just wrote")
objSel.TypeText(writestuff)
This would present data with the correct heading and then normal-type text on the following line.

Using python win32com can't make two separate tables in MS Word 2007

I am trying to create multiple tables in a new Microsoft Word document using Python. I can create the first table okay. But I think I have the COM Range object configured wrong. It is not pointing to the end. The first table is put before "Hello I am a text!", the second table is put inside the first table's first cell. I thought that returning a Range from wordapp will return the full range, then collapse it using wdCollapseStart Enum which I think is 1. (I can't find the constants in Python win32com.). So adding a table to the end of the Range will add it to the end of the document but that is not happening.
Any ideas?
Thanks Tim
import win32com.client
wordapp = win32com.client.Dispatch("Word.Application")
wordapp.Visible = 1
worddoc = wordapp.Documents.Add()
worddoc.PageSetup.Orientation = 1
worddoc.PageSetup.BookFoldPrinting = 1
worddoc.Content.Font.Size = 11
worddoc.Content.Paragraphs.TabStops.Add (100)
worddoc.Content.Text = "Hello, I am a text!"
location = worddoc.Range()
location.Collapse(1)
location.Paragraphs.Add()
location.Collapse(1)
table = location.Tables.Add (location, 3, 4)
table.ApplyStyleHeadingRows = 1
table.AutoFormat(16)
table.Cell(1,1).Range.InsertAfter("Teacher")
location1 = worddoc.Range()
location1.Paragraphs.Add()
location1.Collapse(1)
table = location1.Tables.Add (location1, 3, 4)
table.ApplyStyleHeadingRows = 1
table.AutoFormat(16)
table.Cell(1,1).Range.InsertAfter("Teacher1")
worddoc.Content.MoveEnd
worddoc.Close() # Close the Word Document (a save-Dialog pops up)
wordapp.Quit() # Close the Word Application
The problem seems to be in the Range object that represents a part of the document. In my original code the Range object contains the first cell and starts at the first cell, where it will insert. Instead I want to insert at the end of the range. So I got the following code replacement to work. I moved the Collapse after the Add() call and gave it an argument of 0. Now there is only one Collapse call per Range object.
location = worddoc.Range()
location.Paragraphs.Add()
location.Collapse(0)
Now the code works, I can read from a database and populate new tables from each entry.
Tim

Categories

Resources