Tutorial OpenOffice.org Basic - #2 Μακροεντολές στο Calc

Ανώνυμος (χωρίς επαλήθευση) | Τρί, 04/01/2008 - 23:45 | 24' | 1

Αφού γνωρίσαμε τη χρήση των μακροεντολών στο 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

Δώσε αστέρια!

MO: (ψήφοι: 0)

Σχόλια