Αφού γνωρίσαμε τη χρήση των μακροεντολών στο Writer του OpenOffice.org (ΟΟo), θα δούμε τώρα πως να τις αξιοποιήσουμε στο Calc, την εφαρμογή λογιστικού φύλλου του OOo.
Από τη “Διαφορική Μηχανή” του Charles Babbage μέχρι και το λογιστικό φύλλο Calc του ΟΟo, η επεξεργασία αριθμών γίνεται με αυτόματο τρόπο. Ένας από τους βασικότερους λόγους χρήσης ενός λογιστικού φύλλου είναι η αποφυγή της κούρασης και της μονοτονίας που συνεπάγεται η δουλειά με ..κατεβατά δεδομένων. Χάρη στο συνδυασμό της OOο Basic και του Calc, είναι δυνατό όχι μόνο να αυτοματοποιήσουμε τις πιο επίπονες δουλειές αλλά, όπως θα δούμε, να διαχειριστούμε αποδοτικά τα δεδομένα μας κατευθείαν από τη γραμμή εντολών!
Όπως και την προηγούμενη φορά, το πρώτο βήμα μας πρέπει να είναι η δημιουργία ενός νέου λογιστικού φύλλου. Ο κώδικας που χρησιμοποιήσαμε στο 1ο μέρος για ένα νέο, κενό έγγραφο του Writer ήταν:
sub main loadNewFile end sub sub loadNewFile dim doc as object, desk as object, myFile as string, Dummy() myFile = “private:factory/swriter” desk = CreateUnoService(“com.sun.star.frame.Desktop”) doc = desk.loadComponentFromUrl(myFile,”_blank”, 0,Dummy()) end sub
Κοιτάζοντας τον παραπάνω κώδικα βλέπουμε ότι το είδος του αρχείου που δημιουργεί ορίζεται στη γραμμή:
myFile = “private:factory/swriter”
Επομένως, πρέπει απλά να γνωρίζουμε τι να βάλουμε στη θέση του swriter. Για λογιστικό φύλλο πρέπει να το αλλάξουμε σε scalc:
myFile = “private:factory/scalc”
Η τεμπελιά είναι αρετή
Φυσικά, δεν θέλουμε μια διαφορετική υπορουτίνα για κάθε είδος αρχείου. Θα θέλαμε μια υπορουτίνα που να κάνει τα πάντα. Έτσι πρέπει να σκέφτεται ο καλός (τεμπέλης) προγραμματιστής, και έτσι θα το κάνουμε:
sub main loadNewFile(“scalc”) end sub sub loadNewFile (filetype as string) dim doc as object, desk as object, myFile as string, Dummy() myFile = “private:factory/” & filetype desk = CreateUnoService(“com.sun.star.frame.Desktop”) doc = desk.loadComponentfromurl(myFile,”_ blank”,0,Dummy()) end sub
Στον παραπάνω κώδικα “περνάμε” το είδος του αρχείου ως παράμετρο της υπορουτίνας, επιτυγχάνοντας ακριβώς αυτό που θέλουμε. Δηλαδή έχουμε μια υπορουτίνα ανεξάρτητα του αν θέλουμε να ανοίξουμε ένα έγγραφο του Writer ή ένα λογιστικό φύλλο του Calc. Αν θέλουμε μπορούμε να έχουμε και ένα εξορισμού είδος αρχείου, αξιοποιώντας την παράμετρο Optional και τη μέθοδο isMissing:
sub loadNewFile (optional filetype as string) if isMissing(filetype) then filetype = “scalc” end if
ΟΚ. Τώρα μπορούμε να ανοίξουμε ένα κενό λογιστικό φύλλο, αλλά πως θα γράψουμε σε κάποιο από τα κελιά του; Η επόμενη υπορουτίνα αναλαμβάνει αυτή τη δουλειά:
sub writeToCell dim sheet as object, cell as object sheet=thisComponent.sheets(0) cell=sheet.getCellByPosition(0,0) cell.string=”Hello World” end sub
Η υπορουτίνα writeToCell πρέπει να καλείται μέσα από την Main. Ας την δούμε πιο διεξοδικά. Το thisComponent το είχαμε ξαναδεί στα έγγραφα κειμένου και απλά αναφέρεται στο τρέχον έγγραφο (σε αυτήν την περίπτωση στο λογιστικό φύλλο). Κατόπιν, επιλέγουμε το φύλλο που θέλουμε, δηλαδή το sheet(0) που είναι το πρώτο φύλλο του Calc. Το sheet(1) θα αναφερόταν στο δεύτερο φύλλο κλπ. Τέλος επιλέγουμε ένα κελί με τη μέθοδο getCellByPosition η οποία χρειάζεται ως παραμέτρους τους αριθμούς στήλης και γραμμής. Έτσι το Position(0,0) αφορά στο κελί A1, το (1,0) στο B1, το (0,1) στο A2 κλπ.
Καλά αυτά, αλλά η σειρά των φύλλων μπορεί να αλλάξει. Τι κάνουμε αν θέλουμε να αναφερόμαστε στα φύλλα με το όνομά τους; Κανένα πρόβλημα. Αντί της μεθόδου sheets() θα χρησιμοποιήσουμε την getByName:
sheet=thisComponent.sheets.getByName(“Sheet1”)
Είδαμε πόσο εύκολο είναι να γράψουμε κείμενο σε ένα έγγραφο, γι' αυτό ας δούμε πως γίνεται τώρα η εισαγωγή και η επεξεργασία πληροφοριών στα κελιά:
sub simple_maths dim sheet as object, cell as object sheet=thisComponent.sheets.getByName(“Sheet1”) cell=sheet.getCellByPosition(0,0) cell.value=10 cell=sheet.getCellByPosition(0,1) cell.value=10 cell=sheet.getCellByPosition(0,2) cell.formula=”=A1+A2” end sub
Ο παραπάνω κώδικας δεν κάνει κάτι ιδιαίτερα χρήσιμο, αλλά δείχνει πόσο εύκολο είναι να φορτώνουμε δεδομένα στο φύλλο και να τα επεξεργαζόμαστε. Για να το κάνουμε πιο χρήσιμο, μπορούμε να επιτρέψουμε την εισαγωγή αριθμών στην υπορουτίνα:
sub simple_maths(numbA as double, numbB as double) dim sheet as object, cell as object sheet=thisComponent.sheets.getByName(“Sheet1”) cell=sheet.getCellByPosition(0,0) cell.value=numbA cell=sheet.getCellByPosition(0,1) cell.value=numbB cell=sheet.getCellByPosition(0,2) cell.formula=”=A1+A2” end sub
Τώρα αρκεί να τροποποιήσουμε την Main ως εξής:
simple_maths(12.5,35.7)
Πρόκειται για πολύ απλοϊκό παράδειγμα και, σίγουρα, θα ήταν ταχύτερο αν γράφαμε τα δεδομένα χειροκίνητα στο φύλλο. Ωστόσο, πρέπει να το δούμε ως ένα σημείο εκκίνησης από το οποίο μπορούμε να προσθέσουμε όσο πολύπλοκες λειτουργίες θέλουμε. Επίσης, δεν υπάρχει περιορισμός στο πλήθος των αριθμών που μπορούμε να περνάμε ως παραμέτρους στην υπορουτίνα. Μπορούμε να έχουμε 10,100 ή και 1000 παραμέτρους. Για να γλυτώσουμε τον κόπο, όμως, η παράμετρος μπορεί να είναι ένα κανονικό array:
sub main loadNewFile simple_maths_array(array(45,67,89,34)) end sub sub simple_maths_array(numbers) dim sheet as object, cell as object, r as integer, sum as double sheet = thisComponent.sheets.getByName(“Sheet1”) sum = 0 for r = 0 to ubound(numbers) sum = sum + numbers(r) cell = sheet.getCellByPosition(0,r) cell.value = numbers(r) next cell = sheet.getCellByPosition(0,r+1) cell.value = sum end sub
H υπορουτίνα simple_maths_array γεμίζει την πρώτη στήλη του Φύλλου 1 με τα περιεχόμενα ενός array αριθμών, και μετά γράφει το άθροισμα τους στο τέλος της στήλης. Φυσικά, μπορούμε να χρησιμοποιούμε δεδομένα που βρίσκονται ήδη σε ένα υπαρκτό λογιστικό φύλλο. Η επόμενη υπορουτίνα ανοίγει ένα υπαρκτό αρχείο (το ~/test.ods) και εμφανίζει τα περιεχόμενα του κελιού Α1 του πρώτου φύλλου του:
sub dataFromExistingFile dim doc as object, desk as object, sheet as object, cell as object dim url as string, contents as double, Dummy() desk = CreateUnoService(“com.sun.star.frame.Desktop”) url=”file://~/test.ods” doc=desk.loadComponentfromurl(url,”_blank”,0,Dummy()) sheet = thisComponent.sheets.getByName(“Sheet1”) cell = sheet.getCellByPosition(0,0) contents = cell.value msgbox(contents) end sub
Τι θα συμβεί όμως αν το κελί περιέχει κείμενο αντί για αριθμό; Ευτυχώς, η εντολή “contents = cell.value” δεν θα έχει ως αποτέλεσμα να κολλήσει η υπορουτίνα μας. Αντίθετα, αν το κελί περιέχει κείμενο, τότε η τιμή της παραμέτρου γίνεται μηδέν και έτσι γλυτώνουμε τα προβλήματα.
Περισσότερα μαθηματικά
Ό,τι είδαμε μέχρι τώρα είναι εντελώς απλοϊκό και αφορά στην απλή ανάγνωση και εγγραφή κελιών. Ας περάσουμε, λοιπόν, σε πιο προχωρημένα θέματα όπως τη αξιοποίηση των μαθηματικών συναρτήσεων που εμπεριέχει το ΟΟο Calc. Ας πούμε ότι αντί να γράψουμε ένα array αριθμών στο φύλλο, θέλουμε την μέση τιμή τους ή την τυπική απόκλιση. Μπορούμε να το κάνουμε αυτό χρησιμοποιώντας την υπηρεσία FunctionAccess:
sub usingOOoFunctions(iArray) dim service as object, sheet as object, cell as object service = createUnoService( “com.sun.star.sheet.FunctionAccess” ) sheet = thisComponent.sheets.getByName(“Sheet1”) cell = sheet.getCellByPosition(0,0) cell.value = service.callFunction( “STDEV”, iArray ) end sub
Και πάλι θα πρέπει να τροποποιήσουμε την Main προκειμένου να τρέχει την νέα υπορουτίνα:
usingOOoFunctions(array(45,67,89,34))
Υπάρχουν όμως μερικά προβλήματα στην υπορουτίνα μας. Προς το παρόν, μπορεί να υπολογίσει μόνο τυπική απόκλιση (STDEV) και μόνο στο 1ο φύλλο ενώ τα αποτελέσματα θα τα γράψει αποκλειστικά στο κελί Α1. Με κατάλληλη τροποποίηση όμως των παραμέτρων της, μπορούμε να την κάνουμε πολύ πιο προσαρμόσιμη:
sub usingOOoFunctions( fType as string, sName as string, _ c as integer, r as integer, iArray ) dim service as object, sheet as object, cell as object service = createUnoService( “com.sun.star.sheet. FunctionAccess” ) sheet = thisComponent.sheets.getByName(sName) cell = sheet.getCellByPosition(c,r) cell.value = service.callFunction( fType, iArray ) end sub
Τώρα πρέπει να αλλάξουμε την ρουτίνα Main ώστε να περιέχει:
usingOOoFunctions(“STDEV”,”Sheet1”, 1, 1,array(45,67,89,34))
Ένα καλό ερώτημα εδώ είναι πως χειριζόμαστε αποτελέσματα που θα μπορούσαν να κολλήσουν το πρόγραμμα. Για παράδειγμα, θα μπορούσε κάποιος να καλούσε την υπορουτίνα ως εξής:
usingOOoFunctions(“SQRT”,”Sheet1”, 1, 1, array(-1))
Εδώ το πρόβλημα είναι προφανές, αφού η υπορουτίνα θα ζητήσει την τετραγωνική ρίζα του -1, που δεν υπάρχει. Για να προλάβουμε τέτοιες καταστάσεις που οδηγούν σε σφάλματα, μπορούμε να προσθέσουμε συνθήκες όπως η:
if (fType <> “SQRT” and iArray(0) <> -1 ) then
αλλά θα έπρεπε τότε να γνωρίζουμε κάθε πιθανό συνδυασμό συνάρτησης και αριθμού που οδηγεί σε σφάλμα. Μια πολύ πιο αποδοτική λύση είναι να γράψουμε έναν χειριστή λαθών (error handler). Ας δούμε ένα παράδειγμα (που οδηγεί σε σφάλμα):
function dummy as double dim service as object service = createUnoService( “com.sun.star.sheet.FunctionAccess” ) dummy = service.callFunction( “SQRT”, array(-1) ) end function
Το τρέχουμε με:
msgbox (dummy)
Το πρόβλημα θα εμφανιστεί μόλις το πρόγραμμα φτάσει στην γραμμή επιστροφής, αλλά μπορούμε να το προλάβουμε εισάγοντας μια δήλωση ‘on error resume next’ στην αρχή της συνάρτησης. Έτσι αν υπάρξει σφάλμα, η συνάρτηση θα μεταβεί κατευθείαν στην επόμενη γραμμή κώδικα. Αν δεν θέλουμε το πρόγραμμά μας να συνεχίζει σε περίπτωση σφάλματος αλλά να τερματίζει, μπορούμε να το κάνουμε προσθέτοντας κώδικα για το σωστό χειρισμό του σφάλματος:
function dummy as double dim service as object on error goto errorFound service = createUnoService( “com.sun.star.sheet.FunctionAccess” ) dummy = service.callFunction( “SQRT”, array(-1) ) exit function errorFound: msgbox(“Invalid input. Result set to -1”) dummy=-1 end function
Αντί να συνεχίζει απτόητη, η παραπάνω συνάρτηση θα πηδήξει στο σημείο του κώδικα που έχει την ετικέτα errorFound:. H άνω-κάτω τελεία προσδιορίζει ό,τι πρόκειται για ετικέτα. Ο κώδικας μας περιέχει ακόμα μια γραμμή που λέει “exit function” ακριβώς πριν το τμήμα χειρισμού σφάλματος. Χωρίς αυτή τη γραμμή, το πρόγραμμα θα εκτελούσε πάντοτε τον κώδικα χειρισμού σφάλματος ακόμα κι αν δεν υπήρχε σφάλματα. Εμείς όμως θέλουμε να συμβαίνει αυτό μόνο όταν βρεθεί κάποιο σφάλμα.
Οι συναρτήσεις δεν είναι υπορουτίνες
Στα παραπάνω παραδείγματα, είδαμε συναρτήσεις και υπορουτίνες. Ας δούμε τις διαφορές τους. Μια συνάρτηση και μια υπορουτίνα είναι ουσιαστικά το ίδιο πράγμα εκτός του γεγονότος ότι η συνάρτηση επιστρέφει πάντα ένα αποτέλεσμα. Αυτό σημαίνει ό,τι όταν ορίζουμε μια συνάρτηση πρέπει να δηλώσουμε και το είδος των δεδομένων που θα επιστρέφει. Να ένα απλό παράδειγμα για να το καταλάβουμε αυτό. Ας δούμε πως δίνουμε μια τιμή σε ένα κελί μέσω μιας υπορουτίνας:
dim sheet as object, cell as object sub main loadNewFile sheet=thisComponent.sheets(0) cell=sheet.getCellByPosition(0,0) simple_sub end sub sub simple_sub cell.value = 1 end sub
Να πως γίνεται το ίδιο με μια συνάρτηση:
dim sheet as object, cell as object sub main loadNewFile sheet=thisComponent.sheets(0) cell=sheet.getCellByPosition(0,0) cell.value = simple_function end sub function simple_function as integer simple_function = 1 end function
Παρατηρούμε ότι η υπορουτίνα “γράφει” στο κελί απευθείας, ενώ η συνάρτηση δίνει ένα αποτέλεσμα που αντιστοιχίζουμε στο κελί. Αξίζει να σημειώσουμε επίσης ότι μερικές μεταβλητές έχουν γίνει καθολικές (global), πράγμα που σημαίνει ότι είναι διαθέσιμες σε όλες τις συναρτήσεις και τις υπορουτίνες. Εάν μια μεταβλητή ορίζεται μέσα σε μια συνάρτηση ή υπορουτίνα, τότε υπάρχει μόνο για όσο τρέχει ο κώδικας της συνάρτησης ή υπορουτίνας (αυτό λέγεται “scope” ή βεληνεκές της μεταβλητής). Αυτό είναι πολύ χρήσιμο αλλά σημαίνει ταυτόχρονα ότι πρέπει να είμαστε πολύ προσεκτικοί όταν ορίζουμε μεταβλητές:
dim sheet_number as integer dim sheet as object, cell as object sub main loadNewFile set_sheetnumber sheet= _ thisComponent.sheets(sheet_number) cell=sheet.getCellByPosition(0,0) cell.value = sheet_number end sub sub set_sheetnumber sheet_number = 1 end sub
Εδώ ο αριθμός 1 γράφεται στο κελί Α1 του 2ου φύλλου. Εάν βάζαμε την sheet_number ως ακέραιο μέσα στην υπορουτίνα set_sheetnumber του παραδείγματος, τότε θα δημιουργούνταν μία νέα μεταβλητή sheet_number. Αυτή θα ήταν προσβάσιμη μόνο μέσα από την ίδια την υπορουτίνα set_sheetnumber και, παρότι θα είχε το ίδιο όνομα με την μεταβλητή στην Main, οι δύο μεταβλητές θα ήταν διαφορετικές με ξεχωριστές τιμές.
Τώρα μπορούμε να διαβάζουμε και να γράφουμε άνετα όποιο κελί θέλουμε σε οποιοδήποτε φύλλου του αρχείου μας. Αυτό σημαίνει ότι μπορούμε να ρίξουμε μια ματιά στα ονόματα των φύλλων. Αυτά είναι λιγάκι βαρετά (Φύλλο1, Φύλλο2, κοκ) και δεν δίνουν πολλές πληροφορίες. Να μια υπορουτίνα που αλλάζει τα ονόματα και διαγράφει περιττά φύλλα:
sub changeSheetNames dim sheet as object sheet = thisComponent.createInstance(“com.sun.star.sheet.Spreadsheet”) thisComponent.Sheets.insertByName(“Equations”, Sheet) thisComponent.sheets.removebyname(“Sheet1”) thisComponent.sheets.removebyname(“Sheet2”) thisComponent.sheets.removebyname(“Sheet3”) end sub
Όμορφα και ωραία μέχρι εδώ, αν και πάλι δεν κάναμε πολλά. Ας την κάνουμε πιο χρήσιμη βάζοντας της ως παράμετρο ένα array που θα περιέχει τα ονόματα των νέων φύλλων που θέλουμε να δημιουργηθούν:
dim i as integer for i = 0 to ubound(sheetNames) sheet = thisComponent.createInstance(“com.sun.star.sheet.Spreadsheet”) thisComponent.Sheets.insertByName(sheetNames(i),Sheet) next
Σύνοψη
Για το τέλος, ας συνοψίσουμε όσα μάθαμε σε αυτό το τεύχος (μαζί με κόλπα από το 1ο μέρος). Ο παρακάτω κώδικας θα τρέξει μερικές εντολές κελύφους (συγκεκριμένα την df και την du), θα αποθηκεύσει τα αποτελέσματα σε ένα αρχείο και θα φορτώσει τα δεδομένα σε ένα λογιστικό φύλλο:
const tmpFile as string = “/tmp/myfile.tmp” const bshFile as string = “/tmp/runme.bsh” sub main theFullWorks end sub function buildCommand (ipCommand as string) as string buildCommand = “rm -f “ & tmpFile & “;” _ & ipCommand & “ | sed s/’\t’/’ ‘/g >” & tmpFile & “;” _ & “while [ “”$(grep ‘ ‘ “ & tmpFile & “)”” != “””” ];” _ & “do cat “ & tmpFile & “ | sed s/’ ‘/’ ‘/g > “ & tmpFile & “1;” _ & “mv “ & tmpFile & “1 “ & tmpFile & “;” & “done” end function sub theFullWorks dim command as string loadNewFile changeSheetNames (array(“Disk Space Usage”,”File Usage”)) command = buildCommand(“df|grep -v Filesystem”) reportSheet(command,”Disk Space Usage”) command = buildCommand(“du /| sort -nr”) reportSheet(command,”File Usage”) end sub sub reportSheet (command as string, sheetName as string) dim sheet as object, cell as object dim iNumber As Integer, oNumber As Integer, iLine As String dim i as integer, c as integer iNumber = Freefile oNumber = Freefile Open bshFile For output As #oNumber print #oNumber,command close #oNumber shell(“bash -c “”” & bshFile & “”””,,,true) i =1 sheet=thisComponent.sheets.getByName(sheetName) Open tmpFile For Input As #iNumber While not EOF(iNumber) dim cArray Line Input #iNumber, iLine cArray = split(iLine) for c=0 to ubound(cArray) cell=sheet.getCellByPosition(c,i) cell.string=cArray(c) next i = i +1 wend Close #iNumber end sub
Τα περισσότερα στον παραπάνω κώδικα είναι πολύ απλά, αλλά υπάρχουν μερικά πράγματα που ίσως ξενίσουν λίγο. Για παράδειγμα, το τμήμα με τα πολλά &.Εκεί απλά κατασκευάζουμε μια εντολή που θα δώσουμε στο κέλυφος του Linux. Εάν θέλουμε να δούμε ακριβώς τι θα σταλεί, μπορούμε να προσθέσουμε κι ένα διάλογο κειμένου (msgbox, δείτε την εικόνα στο πάνω μέρος), ως εξής:
Sub main dim command as string command = buildCommand(“df|grep -v Filesystem”) msgbox(command) end sub
Όσα είδαμε μέχρι τώρα είναι σε γενικές γραμμές απλά, αλλά θα βοηθήσουν σίγουρα να ξεκινήσουμε να κάνουμε μερικά πολύ καλά κόλπα με την OOo Basic!
ΓΡΗΓΟΡΕΣ ΣΥΜΒΟΥΛΕΣ
- Χρησιμοποιούμε το CreateUnoService για να αποκτήσουμε πρόσβαση στα διάφορα interfaces του ΟΟ.org (ή τα Universal Network Objects).
- Αν δεν μας αρέσει να χρησιμοποιούμε το thisComponent παντού για να αναφερόμαστε στο τρέχον έγγραφο, μπορούμε να το κάνουμε alias ως εξής:
dim doc as object
doc = thisComponent - Θυμόμαστε τις διαφορές μεταξύ των συναρτήσεων και των υπορουτινών. Μια συνάρτηση τρέχει κώδικα και επιστρέφει κάποια τιμή. Μια υπορουτίνα τρέχει κώδικα αλλά δεν επιστρέφει τίποτε.
- Όπου τυχαίνει να χρησιμοποιούμε το ίδιο κομμάτι κώδικα, θα πρέπει να γλυτώνουμε γράψιμο και χρόνο βάζοντας το μέσα σε μια υπορουτίνα ή μια συνάρτηση.
- Εάν φτιάχνουμε μια εντολή που θα στείλουμε στο κέλυφος, καλό είναι να την βλέπουμε πρώτα μέσα σε ένα διάλογο msgbox.
ΧΡΗΣΙΜΑ ΑΝΤΙΚΕΙΜΕΝΑ...
Μπορούμε να αποκτήσουμε πρόσβαση στα Universal Network Objects του OpenOffice.org μέσα από τη μέθοδο CreateUnoService. Τα αντικείμενα αυτά αναφέρονται ως “υπηρεσίες”.
Αναδημοσίευση από το τεύχος 11
- Συνδεθείτε ή εγγραφείτε για να σχολιάσετε
Σχόλια
Είσαι κορυφή :-)